Skip to content

Remove eventLoop property from Task #82

Closed
@PopFlamingo

Description

@PopFlamingo

Currently, Task has an eventLoop stored property, and its channel must be on this same event loop.
In practice, eventLoop can be used by the http client delegate in the following ways:

  1. Return an immediately succeeded future in the appropriate methods when they don’t want to apply back pressure.
  2. Schedule backpressure related work on this event loop and return the backpressure control future.
  3. In case backpressure work is done on another event loop (which might be more often the case than Update readme with simple tutorial #2), hop the resulting backpressure future to eventLoop.

In these three cases, the goal to guarantee to the http client that it can assume all futures it gets from its delegate are already on the right event loop.

Potential shortcomings

Errors on user side

When applying back pressure, users getting futures from different event loops might forget to hop them back to the right event loop. On the other hand, if the client stopped relying on the assumption that futures returned from the delegate are already on the right event loop, and instead always took care of hopping them, this source of bugs would disappear entirely for a minimal performance cost (hopTo isn’t too heavy, and in many cases the user will need to use it either way, so why force them to do it when we could do this on the library side?).

Hard to compose with future evolutions

The eventLoop property might not compose very well with future evolutions of the client such as pooling (tldr: the associated event loop is hard to determine synchronously like Task needs, and Task might change of event loop during its lifetime):

  • It complicates the connection generation logic on the pool side. For instance a connection pool would likely contain a method such as getConnection(for request: HTTPClient.Request -> EventLoopFuture<Connection>, this is logical for it to return a future because a connection might not be instantly available, but it couldn’t be used as such with the current AsyncClient public API: execute() synchronously returns Task, but Task needs an eventLoop on initialization, which couldn’t be instantaneously determined due to getConnection being an async method. There would of be workarounds for this, but they would complicate the connection pool logic (maybe not a really good sign when two isolated components start to interact this way) and may also force creating new connections (instead of using released ones) just to satisfy the event loop identity constraint.
  • Pooling will also need a retry logic for when a connection dies too soon, this means associating the Task with a new channel that will probably not be on the same event loop as before except if we always made a new connection for retries, but it would be a ressource waste.

Proposed solution

Remove eventLoop property entirely, always hop futures provided by the delegate, additionally, update HTTPClientResponseDelegate backpressure controlling methods to make them return EventLoopFuture<Void>? instead of a non-optional EventLoopFuture<Void>.

This change would enable the following:

  • Reduces the risk of threading issue in delegate implementations
  • Makes Task less dependent on a specific event loop, making it easier to evolve in the future.
  • Delegate implementations that want to schedule work on the same event loop as channel can still make use of task.channel.eventLoop knowing it might evolve during the Task lifetime.
  • Delegate implementations that don't wish to exerce backpressure can express it more clearly/easily by returning nil instead of having to reference eventLoop just to return an immediately completed void future.

What do you think about this? Am I missing some important part?

Thank you

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions