Index: core/src/com/icesoft/faces/webapp/http/core/SendUpdatedViews.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/core/SendUpdatedViews.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/core/SendUpdatedViews.java Mon Feb 25 15:51:48 MST 2008 @@ -1,10 +1,13 @@ package com.icesoft.faces.webapp.http.core; +import com.icesoft.faces.webapp.command.NOOP; +import com.icesoft.faces.webapp.http.common.Configuration; import com.icesoft.faces.webapp.http.common.Request; import com.icesoft.faces.webapp.http.common.Response; import com.icesoft.faces.webapp.http.common.ResponseHandler; import com.icesoft.faces.webapp.http.common.Server; import com.icesoft.faces.webapp.http.common.standard.FixedXMLContentHandler; +import com.icesoft.util.MonitorRunner; import edu.emory.mathcs.backport.java.util.concurrent.BlockingQueue; import edu.emory.mathcs.backport.java.util.concurrent.LinkedBlockingQueue; @@ -15,8 +18,10 @@ import java.util.Set; public class SendUpdatedViews implements Server { - private static final Runnable Noop = new Runnable() { - public void run() { + private static final NOOP NOOPCommand = new NOOP(); + private static final ResponseHandler NOOPResponseHandler = new FixedXMLContentHandler() { + public void writeTo(Writer writer) throws IOException { + NOOPCommand.serializeTo(writer); } }; private static final ResponseHandler EmptyResponseHandler = new ResponseHandler() { @@ -24,10 +29,21 @@ response.setHeader("Content-Length", 0); } }; - private BlockingQueue pendingRequest = new LinkedBlockingQueue(1); - private ViewQueue allUpdatedViews; + private static final Runnable Noop = new Runnable() { + public void run() { + } + }; - public SendUpdatedViews(final Collection synchronouslyUpdatedViews, final ViewQueue allUpdatedViews) { + private final BlockingQueue pendingRequest = new LinkedBlockingQueue(1); + private final ViewQueue allUpdatedViews; + private final long timeoutInterval; + private final Runnable inactivityMonitor; + private final MonitorRunner monitorRunner; + private long responseTimeoutTime; + + public SendUpdatedViews(final Collection synchronouslyUpdatedViews, final ViewQueue allUpdatedViews, MonitorRunner monitorRunner, Configuration configuration) { + this.timeoutInterval = configuration.getAttributeAsLong("blockingConnectionTimeout", 30000); + this.monitorRunner = monitorRunner; this.allUpdatedViews = allUpdatedViews; this.allUpdatedViews.onPut(new Runnable() { public void run() { @@ -46,9 +62,27 @@ } } }); + + this.inactivityMonitor = new Runnable() { + public void run() { + try { + if ((System.currentTimeMillis() > responseTimeoutTime) && (!pendingRequest.isEmpty())) { + Request request = (Request) pendingRequest.poll(); + if (request != null) { + request.respondWith(NOOPResponseHandler); - } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + //add monitor + this.monitorRunner.registerMonitor(inactivityMonitor); + } public void service(final Request request) throws Exception { + responseTimeoutTime = System.currentTimeMillis() + timeoutInterval; respondToPreviousRequest(); pendingRequest.put(request); } @@ -65,6 +99,8 @@ } public void shutdown() { + //remove monitor + monitorRunner.unregisterMonitor(inactivityMonitor); allUpdatedViews.onPut(Noop); respondToPreviousRequest(); } Index: core/src/com/icesoft/faces/webapp/http/core/AsyncServerDetector.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/core/AsyncServerDetector.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/core/AsyncServerDetector.java Mon Feb 25 15:55:58 MST 2008 @@ -3,6 +3,7 @@ import com.icesoft.faces.webapp.http.common.Configuration; import com.icesoft.faces.webapp.http.common.Request; import com.icesoft.faces.webapp.http.common.Server; +import com.icesoft.util.MonitorRunner; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -13,7 +14,7 @@ private static final Log LOG = LogFactory.getLog(AsyncServerDetector.class); private Server server; - public AsyncServerDetector(String icefacesID, Collection synchronouslyUpdatedViews, ViewQueue allUpdatedViews, ServletContext servletContext, Configuration configuration) { + public AsyncServerDetector(String icefacesID, Collection synchronouslyUpdatedViews, ViewQueue allUpdatedViews, ServletContext servletContext, MonitorRunner monitorRunner, Configuration configuration) { boolean useAsyncHttpServerByDefault; try { getClass().getClassLoader().loadClass("com.icesoft.faces.async.server.AsyncHttpServerAdaptingServlet"); @@ -54,7 +55,7 @@ } } } else { - server = new SendUpdatedViews(synchronouslyUpdatedViews, allUpdatedViews); + server = new SendUpdatedViews(synchronouslyUpdatedViews, allUpdatedViews, monitorRunner, configuration); } } Index: core/src/com/icesoft/faces/webapp/http/core/SessionExpiredException.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/core/SessionExpiredException.java Tue Feb 19 12:29:46 MST 2008 +++ core/src/com/icesoft/faces/webapp/http/core/SessionExpiredException.java Tue Feb 19 12:29:46 MST 2008 @@ -0,0 +1,7 @@ +package com.icesoft.faces.webapp.http.core; + +public class SessionExpiredException extends RuntimeException { + public SessionExpiredException(Throwable throwable) { + super(throwable); + } +} Index: bridge/src/application.js =================================================================== --- bridge/src/application.js (revision 15810) +++ bridge/src/application.js Mon Feb 25 16:02:13 MST 2008 @@ -73,6 +73,7 @@ commandDispatcher.register('session-expired', function() { logger.warn('Session has expired'); statusManager.sessionExpired.on(); + this.connection.cancelDisposeViews(); this.dispose(); }.bind(this)); Index: core/src/com/icesoft/faces/webapp/http/portlet/ProxyPortletSession.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/portlet/ProxyPortletSession.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/portlet/ProxyPortletSession.java Tue Feb 19 12:30:56 MST 2008 @@ -1,5 +1,7 @@ package com.icesoft.faces.webapp.http.portlet; +import com.icesoft.faces.webapp.http.core.SessionExpiredException; + import javax.portlet.PortletContext; import javax.portlet.PortletSession; import java.util.Enumeration; @@ -12,66 +14,130 @@ } public Object getAttribute(String string) { + try { - return session.getAttribute(string); + return session.getAttribute(string); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public Object getAttribute(String string, int i) { + try { - return session.getAttribute(string, i); + return session.getAttribute(string, i); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public Enumeration getAttributeNames() { + try { - return session.getAttributeNames(); + return session.getAttributeNames(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public Enumeration getAttributeNames(int i) { + try { - return session.getAttributeNames(); + return session.getAttributeNames(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public long getCreationTime() { + try { - return session.getCreationTime(); + return session.getCreationTime(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public String getId() { + try { - return session.getId(); + return session.getId(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public long getLastAccessedTime() { + try { - return session.getLastAccessedTime(); + return session.getLastAccessedTime(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public int getMaxInactiveInterval() { + try { - return session.getMaxInactiveInterval(); + return session.getMaxInactiveInterval(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void invalidate() { + try { - session.invalidate(); + session.invalidate(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public boolean isNew() { + try { - return session.isNew(); + return session.isNew(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void removeAttribute(String string) { + try { - session.removeAttribute(string); + session.removeAttribute(string); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void removeAttribute(String string, int i) { + try { - session.removeAttribute(string, i); + session.removeAttribute(string, i); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void setAttribute(String string, Object object) { + try { - session.setAttribute(string, object); + session.setAttribute(string, object); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void setAttribute(String string, Object object, int i) { + try { - session.setAttribute(string, object, i); + session.setAttribute(string, object, i); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void setMaxInactiveInterval(int i) { + try { - session.setMaxInactiveInterval(i); + session.setMaxInactiveInterval(i); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public PortletContext getPortletContext() { + try { - return session.getPortletContext(); + return session.getPortletContext(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } -} + } + } +} Index: core/src/com/icesoft/faces/webapp/http/servlet/MainServlet.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/servlet/MainServlet.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/servlet/MainServlet.java Mon Feb 25 15:55:00 MST 2008 @@ -6,6 +6,7 @@ import com.icesoft.faces.webapp.http.core.ResourceServer; import com.icesoft.faces.webapp.xmlhttp.PersistentFacesCommonlet; import com.icesoft.util.IdGenerator; +import com.icesoft.util.MonitorRunner; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,6 +30,7 @@ private PathDispatcher dispatcher = new PathDispatcher(); private static final String AWT_HEADLESS = "java.awt.headless"; private String contextPath; + private MonitorRunner monitorRunner; public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); @@ -54,11 +56,12 @@ return new File(fileLocation); } }; + monitorRunner = new MonitorRunner(configuration.getAttributeAsLong("monitorRunnerInterval", 10000)); PseudoServlet resourceServer = new BasicAdaptingServlet(new ResourceServer(configuration, mimeTypeMatcher, localFileLocator)); PseudoServlet sessionServer = new SessionDispatcher() { protected PseudoServlet newServlet(HttpSession session, Listener.Monitor sessionMonitor) { - return new MainSessionBoundServlet(session, sessionMonitor, idGenerator, configuration); + return new MainSessionBoundServlet(session, sessionMonitor, idGenerator, monitorRunner, configuration); } }; @@ -104,6 +107,7 @@ } public void destroy() { + monitorRunner.stop(); dispatcher.shutdown(); } } Index: core/src/com/icesoft/faces/webapp/http/servlet/SessionDispatcher.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/servlet/SessionDispatcher.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/servlet/SessionDispatcher.java Tue Feb 19 12:06:57 MST 2008 @@ -60,14 +60,10 @@ } private void sessionShutdown(HttpSession session) { - PseudoServlet server = (PseudoServlet) sessionBoundServers.get(session.getId()); + PseudoServlet server = (PseudoServlet) sessionBoundServers.remove(session.getId()); server.shutdown(); } - private void sessionDestroyed(HttpSession session) { - sessionBoundServers.remove(session.getId()); - } - //Exposing MainSessionBoundServlet for Tomcat 6 Ajax Push public static PseudoServlet getSingletonSessionServlet(HttpSession session) { return ((SessionDispatcher) SessionDispatchers.get(0)) @@ -113,18 +109,7 @@ } public void sessionDestroyed(HttpSessionEvent event) { - HttpSession session = event.getSession(); - - Iterator i = SessionDispatchers.iterator(); - while (i.hasNext()) { - try { - SessionDispatcher sessionDispatcher = (SessionDispatcher) i.next(); - sessionDispatcher.sessionDestroyed(session); - } catch (Exception e) { - new RuntimeException(e); - } + } - } - } public void contextInitialized(ServletContextEvent servletContextEvent) { Thread monitor = new Thread("Session Monitor") { Index: core/src/com/icesoft/util/MonitorRunner.java =================================================================== --- core/src/com/icesoft/util/MonitorRunner.java Mon Feb 25 15:54:32 MST 2008 +++ core/src/com/icesoft/util/MonitorRunner.java Mon Feb 25 15:54:32 MST 2008 @@ -0,0 +1,51 @@ +package com.icesoft.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +public class MonitorRunner { + private static final Log log = LogFactory.getLog(MonitorRunner.class); + private Collection monitors = new ArrayList(); + private boolean run = true; + + public MonitorRunner(final long interval) { + Thread thread = new Thread("Monitor Runner") { + public void run() { + while (run) { + try { + Thread.sleep(interval); + Iterator i = new ArrayList(monitors).iterator(); + while (i.hasNext()) { + Runnable monitor = (Runnable) i.next(); + try { + monitor.run(); + } catch (Throwable t) { + log.warn("Failed to run monitor: " + monitor); + } + } + } catch (InterruptedException e) { + //do nothing + } + } + } + }; + thread.setDaemon(true); + thread.start(); + } + + public void registerMonitor(Runnable monitor) { + monitors.add(monitor); + } + + public void unregisterMonitor(Runnable monitor) { + monitors.remove(monitor); + } + + public void stop() { + run = false; + } +} Index: core/src/com/icesoft/faces/webapp/http/core/ViewBoundServer.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/core/ViewBoundServer.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/core/ViewBoundServer.java Tue Feb 19 12:33:34 MST 2008 @@ -16,7 +16,18 @@ import java.util.Map; public class ViewBoundServer implements Server { - private final static Command SessionExpired = new SessionExpired(); + private static final Command SessionExpired = new SessionExpired(); + private static final FixedXMLContentHandler SessionExpiredHandler = new FixedXMLContentHandler() { + public void writeTo(Writer writer) throws IOException { + SessionExpired.serializeTo(writer); + } + }; + private static final ResponseHandler MissingParameterHandler = new ResponseHandler() { + public void respond(Response response) throws Exception { + response.setStatus(500); + response.writeBody().write("Cannot match view instance. 'ice.view.active' parameter is missing.".getBytes()); + } + }; private Map views; private SessionDispatcher.Listener.Monitor sessionMonitor; private Server server; @@ -30,21 +41,12 @@ public synchronized void service(Request request) throws Exception { String viewNumber = request.getParameter("ice.view.active"); if (viewNumber == null) { - request.respondWith(new ResponseHandler() { - public void respond(Response response) throws Exception { - response.setStatus(500); - response.writeBody().write("Cannot match view instance. 'ice.view.active' parameter is missing.".getBytes()); - } - }); + request.respondWith(MissingParameterHandler); } else { View view = (View) views.get(viewNumber); if (view == null) { //todo: revisit this -- maybe the session was not created yet - request.respondWith(new FixedXMLContentHandler() { - public void writeTo(Writer writer) throws IOException { - SessionExpired.serializeTo(writer); - } - }); + request.respondWith(SessionExpiredHandler); } else { try { view.updateOnXMLHttpRequest(request); @@ -58,6 +60,11 @@ } else { throw (Exception) nestedException; } + } catch (SessionExpiredException e) { + //exception thrown in the middle of JSF lifecycle + //respond immediately with session-expired message to avoid any new connections + //being initiated by the bridge. + request.respondWith(SessionExpiredHandler); } finally { view.release(); } Index: core/src/com/icesoft/faces/webapp/http/servlet/ProxyHttpSession.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/servlet/ProxyHttpSession.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/servlet/ProxyHttpSession.java Tue Feb 19 12:30:29 MST 2008 @@ -1,5 +1,7 @@ package com.icesoft.faces.webapp.http.servlet; +import com.icesoft.faces.webapp.http.core.SessionExpiredException; + import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; @@ -13,70 +15,138 @@ } public long getCreationTime() { + try { - return session.getCreationTime(); + return session.getCreationTime(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public String getId() { + try { - return session.getId(); + return session.getId(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public long getLastAccessedTime() { - return getLastAccessedTime(); + try { + return session.getLastAccessedTime(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public ServletContext getServletContext() { + try { - return session.getServletContext(); + return session.getServletContext(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void setMaxInactiveInterval(int i) { + try { - session.setMaxInactiveInterval(i); + session.setMaxInactiveInterval(i); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public int getMaxInactiveInterval() { + try { - return session.getMaxInactiveInterval(); + return session.getMaxInactiveInterval(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public HttpSessionContext getSessionContext() { + try { - return session.getSessionContext(); + return session.getSessionContext(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public Object getAttribute(String string) { + try { - return session.getAttribute(string); + return session.getAttribute(string); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public Object getValue(String string) { + try { - return session.getValue(string); + return session.getValue(string); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public Enumeration getAttributeNames() { + try { - return session.getAttributeNames(); + return session.getAttributeNames(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public String[] getValueNames() { + try { - return session.getValueNames(); + return session.getValueNames(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void setAttribute(String string, Object object) { + try { - session.setAttribute(string, object); + session.setAttribute(string, object); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void putValue(String string, Object object) { + try { - session.putValue(string, object); + session.putValue(string, object); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void removeAttribute(String string) { + try { - session.removeAttribute(string); + session.removeAttribute(string); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void removeValue(String string) { + try { - session.removeValue(string); + session.removeValue(string); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public void invalidate() { + try { - session.invalidate(); + session.invalidate(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } + } + } public boolean isNew() { + try { - return session.isNew(); + return session.isNew(); + } catch (IllegalStateException e) { + throw new SessionExpiredException(e); - } -} + } + } +} Index: core/src/com/icesoft/faces/webapp/http/servlet/MainSessionBoundServlet.java =================================================================== --- core/src/com/icesoft/faces/webapp/http/servlet/MainSessionBoundServlet.java (revision 15810) +++ core/src/com/icesoft/faces/webapp/http/servlet/MainSessionBoundServlet.java Mon Feb 25 15:55:58 MST 2008 @@ -9,18 +9,9 @@ import com.icesoft.faces.webapp.http.common.Server; import com.icesoft.faces.webapp.http.common.standard.OKHandler; import com.icesoft.faces.webapp.http.common.standard.PathDispatcherServer; -import com.icesoft.faces.webapp.http.core.AsyncServerDetector; -import com.icesoft.faces.webapp.http.core.DisposeViews; -import com.icesoft.faces.webapp.http.core.IDVerifier; -import com.icesoft.faces.webapp.http.core.MultiViewServer; -import com.icesoft.faces.webapp.http.core.ReceivePing; -import com.icesoft.faces.webapp.http.core.ReceiveSendUpdates; -import com.icesoft.faces.webapp.http.core.SendUpdates; -import com.icesoft.faces.webapp.http.core.SingleViewServer; -import com.icesoft.faces.webapp.http.core.UploadServer; -import com.icesoft.faces.webapp.http.core.ViewBoundServer; -import com.icesoft.faces.webapp.http.core.ViewQueue; +import com.icesoft.faces.webapp.http.core.*; import com.icesoft.util.IdGenerator; +import com.icesoft.util.MonitorRunner; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -60,7 +51,7 @@ private PseudoServlet servlet; private HttpSession session; - public MainSessionBoundServlet(HttpSession session, SessionDispatcher.Listener.Monitor sessionMonitor, IdGenerator idGenerator, Configuration configuration) { + public MainSessionBoundServlet(HttpSession session, SessionDispatcher.Listener.Monitor sessionMonitor, IdGenerator idGenerator, MonitorRunner monitorRunner, Configuration configuration) { this.session = session; sessionID = idGenerator.newIdentifier(); ContextEventRepeater.iceFacesIdRetrieved(session, sessionID); @@ -86,7 +77,7 @@ receivePing = NOOPServer; } else { //setup blocking connection server - sendUpdatedViews = new IDVerifier(sessionID, new AsyncServerDetector(sessionID, synchronouslyUpdatedViews, allUpdatedViews, session.getServletContext(), configuration)); + sendUpdatedViews = new IDVerifier(sessionID, new AsyncServerDetector(sessionID, synchronouslyUpdatedViews, allUpdatedViews, session.getServletContext(), monitorRunner, configuration)); sendUpdates = new IDVerifier(sessionID, new SendUpdates(views)); receivePing = new IDVerifier(sessionID, new ReceivePing(views)); } @@ -116,15 +107,7 @@ commandQueue.put(SessionExpired); } ContextEventRepeater.iceFacesIdDisposed(session, sessionID); - try { - //wait for the for the bridge to receive the 'session-expire' command - Thread.sleep(1000); - } catch (InterruptedException e) { - //do nothing - } finally { - servlet.shutdown(); + servlet.shutdown(); - } - Iterator viewIterator = views.values().iterator(); while (viewIterator.hasNext()) { View view = (View) viewIterator.next();