google-code-export / gwt-comet

Automatically exported from code.google.com/p/gwt-comet
0 stars 1 forks source link

Recent Broken Pipe and Comet Timeout (a lot of information inside :) ) #20

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1. Write your own code using the chat example (as in issue 16)
2. Use the CometSessionsServlet to interact between users
3. Login in Chrome with some user
4. Open another tab and automatically login there
5 ...Or open another browser and login there, also try different tabs

What is the expected output? What do you see instead?

Very recently I see "... comet timeout has expired after 10000ms"
Also, very recently when I open new tab I see:

   [WARN] Error sending heartbeat
org.mortbay.jetty.EofException
    at org.mortbay.jetty.HttpGenerator.flush(HttpGenerator.java:760)
    at org.mortbay.jetty.AbstractGenerator$Output.flush(AbstractGenerator.java:566)
    at org.mortbay.jetty.HttpConnection$Output.flush(HttpConnection.java:911)
    at net.zschech.gwt.comet.server.deflate.DeflaterOutputStream.flush(DeflaterOutputStream.java:100)
    at net.zschech.gwt.comet.server.impl.CountOutputStream.flush(CountOutputStream.java:64)
       . . .
Caused by: java.io.IOException: Broken pipe
    at sun.nio.ch.FileDispatcher.write0(Native Method)
    at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:29)
    at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:100)
    at sun.nio.ch.IOUtil.write(IOUtil.java:56)
    at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:334)
    at org.mortbay.io.nio.ChannelEndPoint.flush(ChannelEndPoint.java:160)
        . . .

What version of the product are you using? On what operating system?

   Sun JDK 1.6 
   I use Jetty integrated into Eclipse Helios, GWT 2.3 and gwt-comet 1.2.3
   I run this on Ubuntu Linux 10.04 and test into Chrome 13.0.722.0 dev and Firefox 4.0.1. I do not think that OS must matter.

   I also ran it on my friend's Mac (Helios+Jetty), the issues were also reproduced

Please provide any additional information below.

   I want to use this library to demonstrate its possibilities (I do like the style of code and its simplicity) but I can't because, of this issue.

   Here is the source code: https://github.com/skavish/collaborative-paint (Main sessions servlet: https://github.com/skavish/collaborative-paint/blob/master/src/com/acme/collpaint/server/CometSessionsSupportServlet.java)

   Here is the video for this project where this issue is reproduced: http://vimeo.com/24335058 (but no console output, sorry)

   May it is my library mis-usage, but I have tried different ways to fix it and haven't found a one. 

   If you need more information on these issues, I can provide a lot -- I am interested in writing an article about this library and GWT+Canvas.

Original issue reported on code.google.com by shaman.sir on 5 Jun 2011 at 9:09

GoogleCodeExporter commented 9 years ago
I guess your problem is that you can't have two different sessions within one 
browser when using cookies.

I would suggest to try it with e.g. Chrome + FF or to disable cookies at all.

Original comment by mmm1...@gmx.net on 13 Aug 2011 at 7:23

GoogleCodeExporter commented 9 years ago
I started looking into this same issue. I have curiously found that when 
putting a breakpoint on EventSourceHandler.onOpen(...) that a second tab hits 
this breakpoint repeatedly. I have also seen the doComet repeatedly invoked. It 
seems that opening a second tab causes a spin of an unbounded number of 
connections.

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 12:23

GoogleCodeExporter commented 9 years ago
Actually, the client sees that expectingDisconnection is true and it 
reconnects.... over and over

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 12:26

GoogleCodeExporter commented 9 years ago
Docs state "When your application returns from the doComet the Comet response 
and HTTP response is suspended so that you can continue to write messages to it 
asynchronously from other threads.", however, this is not what happens, 
EventSourceCometServletResponse.initiate calls tryTerminate which causes the 
client to disconnect.

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 12:53

GoogleCodeExporter commented 9 years ago
When new connections come in from the same browser, the session is shared and 
that is incompatible with the assumption in this comment:

// This must be as the last step of initialise because after this
// response is set in the session
// it must be fully setup as it can be immediately terminated by the
// next response

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 4:57

GoogleCodeExporter commented 9 years ago
The best solution may be to use the UrlRewriteFilter and create a session per 
tab.

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 5:24

GoogleCodeExporter commented 9 years ago
Here's a path that gwteventservice uses to solve this problem 
http://code.google.com/p/gwteventservice/issues/detail?id=15

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 6:30

GoogleCodeExporter commented 9 years ago
This may have worked around the issue with the following in the servlet that 
uses a custom client ID and a comet session per client, even if the http 
session is shared.

private static class CustomSession extends SessionWrapper {
    public CustomSession (HttpSession delegate, String prefix) {
        super (delegate);
        this.prefix = prefix;
    }

    @Override
    public Object getAttribute (String name) {
        return super.getAttribute (this.prefix + "_" + name);
    }

    @Override
    public Enumeration<String> getAttributeNames () {
        List<String> names = new ArrayList<String> ();
        for (@SuppressWarnings("unchecked")
        Enumeration<String> e = super.getAttributeNames (); e.hasMoreElements (); ) {
            String name = e.nextElement ();
            if (name.startsWith (this.prefix)) {
                names.add (name);
            }
        }

        final Iterator<String> i = names.iterator ();
        return new Enumeration<String> () {
            @Override
            public boolean hasMoreElements () {
                return i.hasNext ();
            }

            @Override
            public String nextElement () {
                return i.next ();
            }
        };
    }

    @Override
    public void removeAttribute (String name) {
        super.removeAttribute (this.prefix + "_" + name);
    }

    private String prefix;
}

private class CustomSessionRequest extends HttpServletRequestWrapper {
    public CustomSessionRequest (HttpServletRequest delegate) {
        super (delegate);
    }

    @Override
    public HttpSession getSession (boolean create) {
        HttpSession session = super.getSession (create);
        if (null == session && !create) {
            return null;
        }
        return new CustomSession (session, this.getPathInfo ());
    }
}

@Override
protected void doGet (HttpServletRequest request, HttpServletResponse response) 
throws ServletException, IOException {
    super.doGet (new CustomSessionRequest (request), response);
}

The following in the client:

CometClient client = new CometClient (GWT.getModuleBaseURL() + "comet" + "/" + 
Math.random (), (CometSerializer) GWT.create (ServerEventSerializer.class), 
listener);
client.start ();

And the following path in web.xml:
<url-pattern>/comet/*</url-pattern>

Original comment by eric.woo...@gmail.com on 10 Feb 2012 at 11:09

GoogleCodeExporter commented 9 years ago
@Eric
private static class CustomSession extends SessionWrapper

I can't get which SessionWrapper class are you referring to
??

thanks for the code, anyway!

Original comment by ipliss...@gmail.com on 16 Feb 2012 at 1:21

GoogleCodeExporter commented 9 years ago
I wrote a SessionWrapper that implements javax.servlet.http.HttpSession

Original comment by eric.woo...@gmail.com on 16 Feb 2012 at 3:51

GoogleCodeExporter commented 9 years ago
I tried your code with the following SessionWrapper, but isn't working yet.
Same exceptions and same environment of the OP, but my problem arises when I 
REFRESH the host page.

public class SessionWrapper implements HttpSession {

    private ConcurrentHashMap<String, Object> attributes;
    private HttpSession wrapped;

    public SessionWrapper(HttpSession session){
        this.wrapped = session;
        this.attributes = new ConcurrentHashMap<String, Object>();
    }

    @Override
    public Object getAttribute(String arg0) {
        if (this.attributes.keySet().contains(arg0))
            return this.attributes.get(arg0);
        return null;
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return this.attributes.keys();
    }

    @Override
    public long getCreationTime() {
        return this.wrapped.getCreationTime();
    }

    @Override
    public String getId() {
        return this.wrapped.getId();
    }

    @Override
    public long getLastAccessedTime() {
        return this.wrapped.getLastAccessedTime();
    }

    @Override
    public int getMaxInactiveInterval() {
        return this.wrapped.getMaxInactiveInterval();
    }

    @Override
    public ServletContext getServletContext() {
        return this.wrapped.getServletContext();
    }

    @Override
    @Deprecated
    public HttpSessionContext getSessionContext() {
        return this.wrapped.getSessionContext();
    }

    @Override
    @Deprecated
    public Object getValue(String arg0) {
        return this.wrapped.getValue(arg0);
    }

    @Override
    @Deprecated
    public String[] getValueNames() {
        return this.wrapped.getValueNames();
    }

    @Override
    public void invalidate() {
        this.wrapped.invalidate();
        this.attributes.clear();
    }

    @Override
    public boolean isNew() {
        return this.wrapped.isNew();
    }

    @Override
    @Deprecated
    public void putValue(String arg0, Object arg1) {
        this.wrapped.putValue(arg0, arg1);
    }

    @Override
    public void removeAttribute(String arg0) {
        if (this.attributes.keySet().contains(arg0))
            this.attributes.remove(arg0);
    }

    @Override
    @Deprecated
    public void removeValue(String arg0) {
        this.wrapped.removeValue(arg0);
    }

    @Override
    public void setAttribute(String arg0, Object arg1) {
        this.attributes.put(arg0, arg1);
    }

    @Override
    public void setMaxInactiveInterval(int arg0) {
        this.wrapped.setMaxInactiveInterval(arg0);
    }
}

Original comment by ipliss...@gmail.com on 17 Feb 2012 at 3:04

GoogleCodeExporter commented 9 years ago
My session wrapper didn't augment the attribute list. It was simply a way to 
have CustomSession prefix each attribute with the client ID. You need to store 
the attributes in the original session. Otherwise, they are going to get 
lost/separated with each doComet.

public class SessionWrapper implements HttpSession {
    public SessionWrapper (HttpSession delegate) {
        this.delegate = delegate;
    }

    public Object getAttribute (String arg0) {
        return delegate.getAttribute (arg0);
    }

    public Enumeration getAttributeNames () {
        return delegate.getAttributeNames ();
    }

    public long getCreationTime () {
        return delegate.getCreationTime ();
    }

    public String getId () {
        return delegate.getId ();
    }

    public long getLastAccessedTime () {
        return delegate.getLastAccessedTime ();
    }

    public int getMaxInactiveInterval () {
        return delegate.getMaxInactiveInterval ();
    }

    public ServletContext getServletContext () {
        return delegate.getServletContext ();
    }

    public HttpSessionContext getSessionContext () {
        return delegate.getSessionContext ();
    }

    public Object getValue (String arg0) {
        return delegate.getValue (arg0);
    }

    public String[] getValueNames () {
        return delegate.getValueNames ();
    }

    public void invalidate () {
        delegate.invalidate ();
    }

    public boolean isNew () {
        return delegate.isNew ();
    }

    public void putValue (String arg0, Object arg1) {
        delegate.putValue (arg0, arg1);
    }

    public void removeAttribute (String arg0) {
        delegate.removeAttribute (arg0);
    }

    public void removeValue (String arg0) {
        delegate.removeValue (arg0);
    }

    public void setAttribute (String arg0, Object arg1) {
        delegate.setAttribute (arg0, arg1);
    }

    public void setMaxInactiveInterval (int arg0) {
        delegate.setMaxInactiveInterval (arg0);
    }

    private HttpSession delegate;
}

Original comment by eric.woo...@gmail.com on 17 Feb 2012 at 4:21

GoogleCodeExporter commented 9 years ago
ok tried and your method works very fine opening multiple tabs!
the evil IOException is still thrown when i REFRESH the page, but maybe is 
another kind of issue

Original comment by ipliss...@gmail.com on 17 Feb 2012 at 9:43

GoogleCodeExporter commented 9 years ago
The heartbeat failing is a typical condition, maybe not appropriate for dumping 
out an exception.

Original comment by eric.woo...@gmail.com on 17 Feb 2012 at 9:52

GoogleCodeExporter commented 9 years ago
The random client id is not a permanent solution. Ideally, it should be server 
issued.

Original comment by eric.woo...@gmail.com on 17 Feb 2012 at 9:58