-
Notifications
You must be signed in to change notification settings - Fork 923
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
CancellationScheduler
finishes even if not started
#5212
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #5212 +/- ##
============================================
+ Coverage 73.67% 73.93% +0.25%
- Complexity 20105 20114 +9
============================================
Files 1741 1730 -11
Lines 74353 74199 -154
Branches 9481 9474 -7
============================================
+ Hits 54783 54858 +75
+ Misses 15033 14852 -181
+ Partials 4537 4489 -48 ☔ View full report in Codecov by Sentry. |
Motivation: While working on #5212, I realized that `noopResponseCancellationScheduler` is an already completed scheduler. When we create a `DefaultClientRequestContext` for the upgrade request, we use this scheduler. https://github.com/line/armeria/blob/8e139d972f5137f9d1805ddda4a7aeae60fecd40/core/src/main/java/com/linecorp/armeria/client/HttpClientPipelineConfigurator.java#L550 However, actually what we want is a scheduler which never completes instead of an already completed scheduler. For this reason, I propose that we separate a - `CancellationScheduler#noop`: signifies a scheduler which never completes - `CancellationScheduler#finished`: signifies a scheduler which already completed Note that the upgrade request is already bound by `connectTimeout`, so it is safe to use a `CancellationScheduler` which never completes. Modifications: - Rename `CancellationScheduler` to `DefaultCancellationScheduler`, and reduce visibility - Introduce an interface `CancellationScheduler` - Introduce factory methods `CancellationScheduler#of`, `CancellationScheduler#noop`, `CancellationScheduler#finished` Result: - It is easier to handle #5212. - Cleaner code overall. <!-- Visit this URL to learn more about how to write a pull request description: https://armeria.dev/community/developer-guide#how-to-write-pull-request-description -->
Motivation: While working on line#5212, I realized that `noopResponseCancellationScheduler` is an already completed scheduler. When we create a `DefaultClientRequestContext` for the upgrade request, we use this scheduler. https://github.com/line/armeria/blob/8e139d972f5137f9d1805ddda4a7aeae60fecd40/core/src/main/java/com/linecorp/armeria/client/HttpClientPipelineConfigurator.java#L550 However, actually what we want is a scheduler which never completes instead of an already completed scheduler. For this reason, I propose that we separate a - `CancellationScheduler#noop`: signifies a scheduler which never completes - `CancellationScheduler#finished`: signifies a scheduler which already completed Note that the upgrade request is already bound by `connectTimeout`, so it is safe to use a `CancellationScheduler` which never completes. Modifications: - Rename `CancellationScheduler` to `DefaultCancellationScheduler`, and reduce visibility - Introduce an interface `CancellationScheduler` - Introduce factory methods `CancellationScheduler#of`, `CancellationScheduler#noop`, `CancellationScheduler#finished` Result: - It is easier to handle line#5212. - Cleaner code overall. <!-- Visit this URL to learn more about how to write a pull request description: https://armeria.dev/community/developer-guide#how-to-write-pull-request-description -->
4923d45
to
57e1144
Compare
CancellationScheduler
finishes even if not initializedCancellationScheduler
finishes even if not started
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 overall direction of this PR looks good.
core/src/main/java/com/linecorp/armeria/internal/client/DefaultClientRequestContext.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/client/DefaultClientRequestContext.java
Show resolved
Hide resolved
} else { | ||
addPendingTask(() -> finishNow0(cause)); | ||
start(noopCancellationTask); | ||
finishNow0(cause); |
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.
ctx.cancel()
called in a decorator before invoking delegate.execute()
couldn't prevent the request headers from being written.
Would we abort the request in the following points if ctx.isCanceled()
is true?
final Throwable throwable = ClientPendingThrowableUtil.pendingThrowable(ctx); |
if (handleEarlyCancellation(ctx, req, res)) { |
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'm not decided on how we want to set up the dependency between log
, HttpResponse
, and ctx
yet but I think this is one possibility.
I think overall we want to
- Hook callbacks so that there is a dependency between the above three states
- Check whether a single state is completed or not
For now, I'm imagining that:
- If
ctx
is cancelled, then the correspondingHttpResponse
is cancelled - We check for
res.isDone
overall for early cancellation
But I will have to explore this further. For now, I think this PR doesn't change the previous behavior so it should probably be safe to merge.
Do you prefer this be done in this PR as well?
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 now, I'm imagining that:
Sounds good.
The case where ctx.cancel()
is called before a normal CancellationTask
is registered can be considered a separate issue.
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.
Thanks, @jrhee17! 🙇♂️🙇♂️
core/src/main/java/com/linecorp/armeria/internal/common/DefaultCancellationScheduler.java
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.
Changes look straightforward, thanks! 👍
(I wanted to try writing this sentence. 😆 )
Okay to merge this PR if CI builds pass. 😉 |
Thanks for the review all 🙇 |
Motivation:
Currently, a
CancellationScheduler
doesn't complete the callbackClientRequestContext#whenResponseCancelled
ifCancellationScheduler#init
is not called.This isn't a problem for server side since
CancellationScheduler#init
is called immediately. However, for client sideCancellationScheduler#init
is called when aHttpResponse
is subscribed to. For this reason, even if we callClientRequestContext#cancel
the callbackClientRequestContext#whenResponseCancelled
is never completed.We have recently decided that the semantics of "ClientRequestContext#cancel" means:
This means we should be able to finish a
CancellationScheduler
although it hasn't been initialized yet.In order to achieve this, I propose that the following changes are made.
CancellationScheduler
at an earlier timing so thatctx.cancel
is run from an event loop.CancellationScheduler#finish
can be called beforeCancellationScheduler#start
without worrying about race conditionsCancellationScheduler#init
method has been divided into two methods:CancellationScheduler#init
andCancellationScheduler#start
CancellationScheduler#init
initializes theCancellationScheduler
with anEventExecutor
CancellationScheduler#start
actually starts the cancellation timer if thetimeout > 0
DefaultClientRequestContext#init
is called as soon as anEventExecutor
is assignedctx#cancel
, I believe the request should be cancellable from more locations in the future. I propose that:CancellationScheduler#start
is called, then the task is executed immediately.CancellationScheduler#start
I imagine we can later introduce a
responseTimeStartTiming
parameter, which dictates where to callCancellationScheduler#start
from.Each location may also update the cancellation task since how to cancel from each step will be different.
e.g. Cancellation from
HttpClientDelegate#execute
This will be handled in a follow-up PR if it makes sense
Modifications:
CancellationScheduler#init
toCancellationScheduler#initAndStart
CancellationScheduler#init
initializes theCancellationScheduler
with anEventExecutor
CancellationScheduler#start
updates the task and schedules a timeout if not done alreadyCancellationScheduler#init
is now called immediately when an event loop is updated inDefaultClientRequestContext
CancellationScheduler
such thatfinishNow
executes from an event loopfinishNow
completes the state if even if it hasn't been started yettimeoutNanos
parameter fromCancellationScheduler#start
since the parameter wasn't adding any valueisInitialized
since it wasn't being used anywhereCancellationScheduler#of
toCancellationScheduler#ofClient
,CancellationScheduler#ofServer
for clarityResult:
ctx.whenCancelled
,ctx.whenCancelling
is now completed even if theCancellationScheduler
has not been initialized