-
Notifications
You must be signed in to change notification settings - Fork 594
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 stream failure on receive/send timeout (network lost detection) #4289
Websocket stream failure on receive/send timeout (network lost detection) #4289
Conversation
* Add receiveIdleTimeout and sendIdleTimeout config parameters to websocket client and server * Add idleTimeout stack layer * Add integration tests
Hi @gawronA, Thank you for your contribution! We really value the time you've taken to put this together. Before we proceed with reviewing this pull request, please sign the Lightbend Contributors License Agreement: |
CLA signed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a nice/useful feature, impl looks pretty good, I left some suggestions.
akka-http-core/src/main/scala/akka/http/impl/engine/ws/WebSocket.scala
Outdated
Show resolved
Hide resolved
akka-http-core/src/main/scala/akka/http/impl/engine/ws/WebSocket.scala
Outdated
Show resolved
Hide resolved
...ttp-core/src/test/scala/akka/http/impl/engine/ws/WebSocketServerReceiveIdleTimeoutSpec.scala
Outdated
Show resolved
Hide resolved
...ttp-core/src/test/scala/akka/http/impl/engine/ws/WebSocketServerReceiveIdleTimeoutSpec.scala
Outdated
Show resolved
Hide resolved
* Use BidiFlow.identity for default case instead of BidiFlow constructed from two identity Flows * Remove redundant maps * Add exception class matchers in tests
@johanandren thank you for the review. I applied your suggestions.
Also feel free to directly push whatever headers are needed to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do a sbt "headerCreateAll; compile"
to get the headers added and the code formatted and check in the result.
I think the TCP stage turning any stream idle timeout to TCP timeout is unfortunate but something that we could possibly fix in Akka core instead of doing anything about here.
Thread.sleep(200) | ||
}, Duration.apply(3, TimeUnit.SECONDS)) | ||
x | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace the in-stream-blocking map+sleep in the tests with .delay(200.millis)
In the tests I want to simulate constant flow of messages in one direction and ensure that we still get timeout when messages are not sent in opposite direction.
produces no output - expected. However, this piece of code (map replaced with delay):
produces:
which is quite unexpected as the I may be wrong but in my opinion the |
|
||
"fail the materialized future with akka.stream.StreamIdleTimeoutException if elements are not received within receive-idle-timeout" in { | ||
val bindingFuture = Http().newServerAt("localhost", 0).bindSync({ | ||
_.attribute(webSocketUpgrade).get.handleMessages(Flow.apply, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flow.apply == Flow.identity so this is an echo-server, the test expects it to be a server that does not emit anything, something like Flow.fromSinkAndSourceCoupled(Sink.ignore, Source.maybe[Message])
could work in a test (in real app you'd need to deal with text/binary, and also consume streaming but here we know it will be strict)
In general: With a Thread.sleep you are effectively blocking the entire thread and Akka stream that handles the tcp socket, while .delay just simulates async-busy-work by letting the stream do other things. Other would be collecting those TextMessages in a buffer for example. There is however no async boundary between source and delay, so there is no buffer introduced and Blocking a stream without async boundaries should also mean that the idle timeout operators are blocked from doing anything until that blocking stops, that interferes with what we want to test here. |
Ahh of course
I would like to leave |
Right, delay has an internal buffer, so it is not about the async boundary buffers, I didn't remember that from the top of my head. Fine to keep. |
val myPort = binding.localAddress.getPort | ||
|
||
Source(1 to 10).map(_ => { | ||
Await.result(Future(Thread.sleep(200)), Duration(3, TimeUnit.SECONDS)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A final blocking sleep to get rid of, then I think this PR is good to go
* replace stream-blocking Await with .delay operator in websocket server timeout tests * add outgoing message stream from server in receive-idle-timeout test
* replace printing source with ignoring source in receive-idle-timeout server test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks, good work!
Hello all,
I would like to contribute to Akka project by providing a feature that allows to catch websocket disconnections due to network unavailability.
I have introduced two configuration parameters for websocket client:
and server:
that can be configured together with
akka.http.{client, server}.periodic-keep-alive-max-idle
to detect lost network connection.The working principle is simple: if elements are not send or received (including ping/pong frames), the streams are failed with
akka.stream.StreamIdleTimeoutException
The default config values are
infinite
which means that this feature is disabled.I have provided two new test modules:
WebSocketServerReceiveIdleTimeoutSpec.scala
andWebSocketServerSendIdleTimeoutSpec.scala
because I couldn't find out how config overrides can be injected upon server creation (they are always taken from ActorSystem settings). If anyone have a better idea how this can be resolved, I'm open to proposals.Thanks!
References #2419