-
Notifications
You must be signed in to change notification settings - Fork 9.2k
Add timeout to SSE #8565
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
base: master
Are you sure you want to change the base?
Add timeout to SSE #8565
Conversation
| /** | ||
| * Seconds elapsed between 2 events until connection failed. Doesn't timeout if null | ||
| */ | ||
| open var timeout: Long? = null |
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.
From Call, I think there is a convention on timeout names. Such as readTimeoutMillis. So perhaps receiveTimeoutMillis?
I wonder if we should prefer Kotlin Duration here also?
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.
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.
Internally we use callTimeout in RealEventSource to track establishment, and it's obviously an existing top level think in OkHttp which extends to the end of a request.
read feel to I/O socket focused.
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.
perhaps idleTimeoutMillis?
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.
Sounds good. Do you want me to update the PR or are you fine doing it ?
okhttp-sse/src/main/kotlin/okhttp3/sse/internal/RealEventSource.kt
Outdated
Show resolved
Hide resolved
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.
result: ### 改动目的
此次PR的主要目的是为EventSourceListener类添加一个超时机制,允许用户设置两个事件之间的最大时间间隔。如果超过这个时间间隔没有收到新的事件,连接将被视为失败。此外,PR还对代码中的一些注释进行了完善,增加了对onEvent和onClosed方法的描述,使其更清晰易懂。
发现问题
- 超时机制的实现可能不够完善:在
RealEventSource类中,超时机制的实现依赖于AsyncTimeout,并且只在onResponse和processNextEvent中更新超时时间。如果事件处理逻辑复杂或耗时较长,可能会导致超时机制失效。 - 超时时间的单位不明确:在
EventSourceListener中,timeout属性的单位是Long,但没有明确说明是秒还是毫秒,可能会导致使用时的混淆。 - 超时机制的取消逻辑可能存在问题:在
onResponse中,如果listener.timeout为null,则会调用call?.timeout()?.cancel()取消超时。然而,如果timeout在后续操作中被设置为非null值,可能会导致超时机制无法正常工作。
优化建议
- 明确超时时间的单位:建议在
EventSourceListener的timeout属性的注释中明确说明单位是秒,或者在代码中使用Duration类型来避免混淆。 - 优化超时机制的实现:建议在
RealEventSource类中增加对超时机制的全面检查,确保在所有可能的事件处理路径中都正确更新超时时间。可以考虑在processNextEvent中增加超时检查的逻辑,确保即使事件处理耗时较长,超时机制也能正常工作。 - 改进超时取消逻辑:建议在
onResponse中增加对timeout属性的检查,确保在timeout为null时正确取消超时,并在后续操作中正确处理timeout的变化。可以考虑在timeout属性发生变化时重新设置超时时间。
通过这些优化,可以确保超时机制的可靠性和代码的可维护性。
|
Translation: Purpose of the Changes Issues Found Ambiguous timeout unit: The timeout property in EventSourceListener is a Long type, but the unit (seconds or milliseconds) is not explicitly stated. This could lead to confusion when using it. Potential issue with timeout cancellation logic: In onResponse, if listener.timeout is null, call?.timeout()?.cancel() is called to cancel the timeout. However, if timeout is later set to a non-null value, it might prevent the timeout mechanism from working correctly. Optimization Suggestions Improve the timeout mechanism implementation: It's suggested to add a comprehensive check of the timeout mechanism within the RealEventSource class. This would ensure the timeout duration is correctly updated in all possible event-handling paths. Consider adding timeout check logic to processNextEvent to ensure the mechanism works correctly even when event processing takes a long time. Improve timeout cancellation logic: It's recommended to add a check for the timeout property in onResponse to ensure that the timeout is correctly canceled when timeout is null and that changes to the timeout property are handled correctly in subsequent operations. You might consider resetting the timeout duration whenever the timeout property changes. These optimizations will ensure the reliability of the timeout mechanism and the maintainability of the code. |
|
I've renamed |
|
This looks wrong as |
|
I'm a bit torn on whether this should or needs to be built in. I'm curious if a listener externally can implement a timeout. I'll try this locally. It doesn't seem like a common pattern for python or js clients. So I'm not sure how common this need is. Overall, it would need good tests to land, but also would like @swankjesse to bless the API |
My bad, I'm pushing a fix |
|
For comparison, this is this function in the rust sse_client crate: https://github.com/viniciusgerevini/sse-client/blob/master/src/tls.rs#L23 It makes sense to have this feature with SSE:
Then, it may be possible to rely on an external solution, let me know if you find something - I'll continue using my fork for now |
|
Thanks, that's useful context in the rust api |
|
The difference is that the client don't have to restart the connection when the server sends the keepalive value. Depending on the application, this may be the first thing the server sends to the client. In this case, with the readTimeout, all clients would have to restart at least once, during the first connection. But this timeout is not supposed to change very often. So maybe that's fine having a less-efficient solution, where readTimeout is set during client build. Example with the timeout set by the server: |
| /** | ||
| * Milliseconds elapsed between 2 events until connection failed. Doesn't timeout if null | ||
| */ | ||
| open var idleTimeoutMillis: Long? = null |
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.
For consistency with the rest of the library, could you make this non-nullable, using a value of 0 for no timeout?
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.
I don’t think the listener is the right place for this property.
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.
I think instead it aught to be something like this
interface EventSource {
...
fun timeout(): Timeout = Timeout.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.
Do you mean with a setter? We need to be able to change the EventSource timeout
interface EventSource {
...
fun timeout(timeout: Timeout)
}
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.
The Timeout object is mutable.
This PR adds the possibility to timeout the SSE connection. It is now possible to fail as soon as an event-free timeout is not reached, making it easier to detect a lost connection.
For a real world example: I've an app connected to a server. It sends a ping at a keepalive interval. I've a job checking from time to time that everything works as expected (using a worker, so the period is 15min, the minimum). It happens that the connection is lost at the beginning of this interval, therefore the users are disconnected for several minutes.
With this PR we can directly catch a lost connection: https://codeberg.org/NextPush/nextpush-android/commit/f6edb178ddee8255e9de6e548ea75f5d3eb6b32a
PS: I've also added some comments, but I can remove this commit from the branch