-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebSocket JSR356 implementation not honoring javadoc of MessageHandler on Whole<Reader> #4475
Comments
Putting on my When you are using All of the frames that make up the The container implementation is now free to read the next frame and trigger the next The Putting my Jetty hat now ... If you are using Using Does Jetty have a bug here? Possibly, as the strict interpretation of TL;DR; It comes down to, how the final frame of data for the stream is handled. The reasons you see proper behavior with String vs Reader is ...
|
@joakime maybe we should at least block until |
@lachlan-roberts you cannot wait for the return of As this is a valid implementation ... @Override
public void onMessage(Reader reader)
{
JSON json = parseJson(reader);
processJson(json);
} (Yes, I realize that this could be done with a You cannot process more frames if you wait for the What if this message consists of 100 frames? you read the initial TEXT data frame (FIN==false), initiated the dispatch to |
Signed-off-by: Lachlan Roberts <[email protected]>
@lachlan-roberts now do it again with fragmented messages. |
Signed-off-by: Lachlan Roberts <[email protected]>
Signed-off-by: Lachlan Roberts <[email protected]>
Well, at least the Whole<Reader> seems to work as I hoped: It streams the bytes from the actual socket? So that if the JSON in question is large (which they seldom are, but can be), I do not needlessy allocate String objects, rather getting the bytes (chars) streamed directly from the socket into the deserializer. Is this correct? I obviously have not been that down into the details here as you guys, and might be wading completely to out of my depths, but I don't quite get the problem with the return of onMessage: If I do not read the entire message before returning, then could you not just dump the rest on the floor until you've read to the end of that message? I've obviously "had my fill", so to speak, so the rest is not of interest to me. If this was e.g. a megabyte of spaces after the final "}", the String (in Whole) would also contain that megabyte, and I would effectively not read that either. What I feel is rather obviously a non-compliance compared to what the JavaDoc there explicitly tells me, is that there is two threads running through that method at the same time. And what is rather baffling, is that they quite often come in the opposite order of how they were sent (that log line you see in my post is literally the first operation in the method). I also do not see why it would be good to be so eager to get rid of these messages instead of queuing them, or not read more from the TCP socket, or something: They come in over a TCP pipe, in a specific order. To be able to invoke my method on two different threads in the opposite order you must have parsed a chunk from the socket that contained two messages, made a Reader out of them both, and sent off two threads to my method concurrently? Anyway, you mentioned Whole<JSON>, which got me to think and leads me over to a question that is user support: Would the most efficient approach here be to register a TextStream-Decoder that decoded directly into my target type? Would I also then be assured that the onMessage invocations came in one by one, and in the correct order? Wrt to return (sending/encoding), is there a difference in efficiency between using a TextStream-Encoder, and just getting the getBasicRemote().getSendWriter() and piping it in there? (PS: It is evidently not possible to add a decoder with generics like this: |
Signed-off-by: Lachlan Roberts <[email protected]>
Signed-off-by: Lachlan Roberts <[email protected]>
Signed-off-by: Lachlan Roberts <[email protected]>
@stolsvik using the stream still copies the bytes from the WebSocket frame to accumulate in the Yes if you return from onMessage the correct behaviour should be to just dump the remaining bytes until the end of the message. There is a non-compliance compared to the JavaDoc here. Jetty is reading the messages in the right order, the problem is that it is not waiting for you to finish with the first one before reading/delivering the next one. So you can end up with multiple concurrent calls to Using the I am proposing a fix with PR #4486, so if you want you can test with that branch and let us know if it fixes the problem for you. |
…mMessageOrder Issue #4475 - fix WebSocket streaming message ordering
This will be fixed in the 9.4.27 release. This is not a problem for jetty-10 as we only receive the next frame once the callback has been succeeded, and there is logic to only succeed a |
Jetty version
jetty-9.4.25.v20191220
Java version
1.8.0_112
OS type/version
Ubuntu 18.04.3 LTS
Description
JSR 356 WebSocket's MessageHandler's JavaDoc states "Each web socket session uses no more than one thread at a time to call its MessageHandlers. This means that, provided each message handler instance is used to handle messages for one web socket session, at most one thread at a time can be calling any of its methods."
This has also held for everything I've tried as long as the registered listener is a Whole<String>. However, since I'm parsing JSON using Jackson, it hit me that I could as well use a Whole<Reader> and feed that directly into the Jackson ObjectReader. All of a sudden, I started to get protocol errors from my library. It depends on a HELLO message, containing auth, being sent before any other.
From the logs, it seems that with Whole<Reader>, two threads are delivering messages concurrently - and they are even sometimes coming in the wrong order, which is what triggers the failure.
This is a set of log lines showing concurrent processing of messages, but at least in the right order (HELLO before SEND):
00:42:07.573 [qtp833240229-150] INFO c.s.m.w.impl.MatsSocketSession - WebSocket received message [org.eclipse.jetty.websocket.common.message.MessageReader@106ebd8d] on MatsSocketSessionId [null], WebSocket SessionId:3, this:MatsSocketSession@3cdf2ae9 {} 00:42:07.573 [qtp833240229-151] INFO c.s.m.w.impl.MatsSocketSession - WebSocket received message [org.eclipse.jetty.websocket.common.message.MessageReader@2dd4f197] on MatsSocketSessionId [null], WebSocket SessionId:3, this:MatsSocketSession@3cdf2ae9 {} 00:42:07.573 [qtp833240229-150] INFO c.s.m.w.impl.MatsSocketSession - Messages: [[HELLO:NEW]->null,tid:MatsSocket_start_mJrl0b,cid:GImeQ9akra] {} 00:42:07.573 [qtp833240229-150] INFO c.s.m.w.MatsTestWebsocketServer - Resolving Authorization header to principal for header [DummyAuth:1578958947562]. {} 00:42:07.573 [qtp833240229-150] INFO c.s.m.w.impl.MatsSocketSession - MatsSocket HELLO! {matssocket.type=HELLO, matssocket.subType=NEW} 00:42:07.573 [qtp833240229-151] INFO c.s.m.w.impl.MatsSocketSession - Messages: [[SEND]->Test.single,tid:SEND_3J3yyp,cid:null] {} 00:42:07.573 [qtp833240229-151] INFO c.s.m.w.impl.MatsSocketSession - \- SEND to:[Test.single], reply:[null], msg:[{}]. {matssocket.type=SEND}
Here's a different set where they come both concurrent and in the wrong order (SEND before HELLO), thus the SEND is triggering close of socket, even while the concurrent HELLO is being processed.
00:42:07.619 [qtp349259569-144] INFO c.s.m.w.impl.MatsSocketSession - WebSocket received message [org.eclipse.jetty.websocket.common.message.MessageReader@13d88ad8] on MatsSocketSessionId [null], WebSocket SessionId:4, this:MatsSocketSession@f5142d7 {} 00:42:07.619 [qtp349259569-99] INFO c.s.m.w.impl.MatsSocketSession - WebSocket received message [org.eclipse.jetty.websocket.common.message.MessageReader@4ad307b0] on MatsSocketSessionId [null], WebSocket SessionId:4, this:MatsSocketSession@f5142d7 {} 00:42:07.620 [qtp349259569-99] INFO c.s.m.w.impl.MatsSocketSession - Messages: [[SEND]->Test.single,tid:SEND_3nj9E9,cid:null] {} 00:42:07.620 [qtp349259569-144] INFO c.s.m.w.impl.MatsSocketSession - Messages: [[HELLO:NEW]->null,tid:MatsSocket_start_OCjlh2,cid:MFSqZtWVXL] {} 00:42:07.620 [qtp349259569-99] ERROR c.s.m.w.impl.MatsSocketSession - We have not got Authorization header! {} 00:42:07.620 [qtp349259569-144] INFO c.s.m.w.MatsTestWebsocketServer - Resolving Authorization header to principal for header [DummyAuth:1578958947613]. {} 00:42:07.620 [qtp349259569-99] INFO c.s.m.w.impl.DefaultMatsSocketServer - Closing WebSocket SessionId [4]: code: [VIOLATED_POLICY], reason:[Missing Authorization header] {} 00:42:07.620 [qtp349259569-144] INFO c.s.m.w.impl.MatsSocketSession - MatsSocket HELLO! {matssocket.type=HELLO, matssocket.subType=NEW}
By literally only changing Whole<Reader> to Whole<String> and also doing Reader->String in the onMessage method, I get these lines for the same test - notice how it is a) in order, b) the first message is finished before the next comes in, and c) it is even the same thread that does the processing for those two messages.
01:01:57.205 [qtp1068945248-79] INFO c.s.m.w.impl.MatsSocketSession - WebSocket received message [[{"t":"HELLO","clv":"MatsSocket.js,v0.8.9; User-Agent: Unknown","ts":1578960117203,"an":"TestApp","av":"1.2.3","auth":"DummyAuth:1578960137196","cid":"WTvoxdiO8Y","tid":"MatsSocket_start_mKnhFA","st":"NEW"}]] on MatsSocketSessionId [null], WebSocket SessionId:4, this:MatsSocketSession@3185190d {} 01:01:57.205 [qtp1068945248-79] INFO c.s.m.w.impl.MatsSocketSession - Messages: [[HELLO:NEW]->null,tid:MatsSocket_start_mKnhFA,cid:WTvoxdiO8Y] {} 01:01:57.205 [qtp1068945248-79] INFO c.s.m.w.MatsTestWebsocketServer - Resolving Authorization header to principal for header [DummyAuth:1578960137196]. {} 01:01:57.205 [qtp1068945248-79] INFO c.s.m.w.impl.MatsSocketSession - MatsSocket HELLO! {matssocket.type=HELLO, matssocket.subType=NEW} 01:01:57.223 [qtp1068945248-79] INFO c.s.m.w.impl.MatsSocketSession - WebSocket received message [[{"eid":"Test.single","tid":"SEND_g1BBep","msg":{},"t":"SEND","cmcts":1578960117196,"cmseq":0}]] on MatsSocketSessionId [M5Q5oSpeow25hjY0], WebSocket SessionId:4, this:MatsSocketSession@3185190d {matssocket.principal=Mr. Dummy Auth, matssocket.sessionId=M5Q5oSpeow25hjY0} 01:01:57.223 [qtp1068945248-79] INFO c.s.m.w.impl.MatsSocketSession - Messages: [[SEND]->Test.single,tid:SEND_g1BBep,cid:null] {matssocket.principal=Mr. Dummy Auth, matssocket.sessionId=M5Q5oSpeow25hjY0} 01:01:57.223 [qtp1068945248-79] INFO c.s.m.w.impl.MatsSocketSession - \- SEND to:[Test.single], reply:[null], msg:[{}]. {matssocket.principal=Mr. Dummy Auth, matssocket.sessionId=M5Q5oSpeow25hjY0, matssocket.type=SEND}
The text was updated successfully, but these errors were encountered: