Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP client request cancellation #714

Merged
merged 4 commits into from Mar 12, 2024

Conversation

DerGuteMoritz
Copy link
Collaborator

@DerGuteMoritz DerGuteMoritz commented Feb 12, 2024

This patch changes aleph.http/request so that setting the response deferred to an error status will terminate an in-flight request. This allows e.g. for d/timeout! to be used without potentially leaking connections.

For convenient explicit cancellation, we provide aleph.http/cancel-request!. It puts the given response deferred into error state with an instance of the new aleph.utils.RequestCancellationException.

Closes #712.

Review notes:

  • The patch uses the approach outlined in Cancellation API as a first-class citizen?聽manifold#167 (comment) for propagating cancellation. (not quite, it was merely an inspiration)
  • Diff best viewed without whitespace changes.
  • Still marked as draft because the patch only pertains to in-flight requests. Connection establishment is not yet cancellable. Combined with the default connection-timeout of 60s, this can make for an unpleasant surprise 馃槵 -- deferred to a follow-up patch once Netty has responded.

@DerGuteMoritz DerGuteMoritz marked this pull request as draft February 12, 2024 15:02
@DerGuteMoritz DerGuteMoritz changed the title Draft: HTTP client request cancellation HTTP client request cancellation Feb 12, 2024
@DerGuteMoritz
Copy link
Collaborator Author

Completion of this patch depends on how the Netty issue linked above will be resolved.

@KingMob
Copy link
Collaborator

KingMob commented Feb 14, 2024

Some impressive spelunking!

Completion of this patch depends on how the Netty issue linked above will be resolved.

Would we not still want to do this even if Netty doesn't want to change this for v4? After all, persistent conns are pretty common, so canceling the initial connect shouldn't be that relevant in those cases, no?

@DerGuteMoritz
Copy link
Collaborator Author

Some impressive spelunking!

You can say that again 馃檲

Would we not still want to do this even if Netty doesn't want to change this for v4? After all, persistent conns are pretty common, so canceling the initial connect shouldn't be that relevant in those cases, no?

Yeah, the in-flight cancellation on its own would be good to have already, indeed. We could of course already merge this as-is and defer the connection establishment cancellation to a follow-up.

I propose to include one more change, though - commit coming up!

@DerGuteMoritz
Copy link
Collaborator Author

@KingMob See 9e27cae 馃檹

@DerGuteMoritz DerGuteMoritz marked this pull request as ready for review February 14, 2024 16:52
Comment on lines 1446 to 1455
(deftest test-in-flight-request-cancellation
(let [conn-established (promise)
conn-closed (promise)]
(with-tcp-server (fn [s _]
(deliver conn-established true)
;; Required for the client close to be detected
(s/consume identity s)
(s/on-closed s (fn []
(deliver conn-closed true))))
(let [rsp (http-get "/")]
(is (= true (deref conn-established 1000 :timeout)))
(http/cancel-request! rsp)
(is (= true (deref conn-closed 1000 :timeout)))
(is (thrown? RequestCancellationException (deref rsp 1000 :timeout)))))))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see more testing, for both normal and error conditions. Maybe via overriding the conn-pool in some cases.

What's all the TCP handler/server/etc stuff get us? Why not the the HTTP code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see more testing, for both normal and error conditions.

You mean for the case where cancel-request! is invoked after the request had already completed successfully or due to some other error?

What's all the TCP handler/server/etc stuff get us? Why not the the HTTP code?

IMHO using a raw TCP server which only accepts and holds the connection is the cleanest way to reliably test whether cancellation closes the underlying TCP connection (via the s/on-closed handler). One could probably concoct something like it with a raw-stream? HTTP request handler but I don't think it adds much value, does it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give the latter idea a shot in any case!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, the test uses the HTTP server(s) now, works just as well and probably causes less confusion 馃憤

Copy link
Collaborator

@arnaudgeiser arnaudgeiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looks good to me, just a few comments.

src/aleph/http.clj Show resolved Hide resolved
src/aleph/http.clj Show resolved Hide resolved
This patch changes `aleph.http/request` so that setting the response deferred to an error status
will terminate an in-flight request. This allows e.g. for `d/timeout!` to be used without
potentially leaking connections.

For convenient explicit cancellation, we provide `aleph.http/cancel-request!`. It sets the given
response deferred to error with an instance of the new `aleph.utils.RequestCancellationException`.

Closes #712.
The generic "connection failed" catch handler is redundant with the one on the `result`
deferred. The same is the case for the logging in the "connection timeout" catch handler.
This works just as good as the raw TCP server but hopefully doesn't make future readers have to do a
double-take.
@DerGuteMoritz DerGuteMoritz force-pushed the http-client-request-cancellation branch from 75c4cbf to 810aad7 Compare March 12, 2024 11:16
@DerGuteMoritz DerGuteMoritz merged commit eef3c66 into master Mar 12, 2024
1 check passed
@DerGuteMoritz DerGuteMoritz deleted the http-client-request-cancellation branch March 12, 2024 11:21
DerGuteMoritz added a commit that referenced this pull request Apr 5, 2024
With #714 we added support for cancelling in-flight HTTP requests by putting the response deferred
into an error state. However, this only worked once the underlying TCP connection was
established. With this patch, it is now possible to cancel requests even while the connection is
still being established (possible since Netty 4.1.108.Final via
netty/netty#13849). This also works for `aleph.tcp/client`.
DerGuteMoritz added a commit that referenced this pull request Apr 5, 2024
With #714 we added support for cancelling in-flight HTTP requests by putting the response deferred
into an error state. However, this only worked once the underlying TCP connection was
established. With this patch, it is now possible to cancel requests even while the connection is
still being established (possible since Netty 4.1.108.Final via
netty/netty#13849). This also works for `aleph.tcp/client`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support cancellation of HTTP requests
3 participants