You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Done returns a channel that's closed when work done on behalf of this// context should be canceled. Done may return nil if this context can// never be canceled. ...Done() <-chanstruct{}
// If Done is not yet closed, Err returns nil.// If Done is closed, Err returns a non-nil error explaining why:// DeadlineExceeded if the context's deadline passed,// or Canceled if the context was canceled for some other reason.// After Err returns a non-nil error, successive calls to Err return the same error.Err() error
One could read that Done() being closed or Err() returning non-nil constitutes a cancelled context.
This interpretation doesn't jive with the current implementation of context.Cause:
Though this has other subtler issues, like: which of the errors should be preferred, if the child was "cancelled" earlier than the parent. In the proposal, the parents one wins. I'm not sure it is resolveable.
The core problem is that Cause assumes that if a context is canceled, the first ancestor of that context created by the context package contains the cause. This is wrong: The first ancestor might not be canceled at all, and if it is canceled it might not contain the cause.
If we cancel1(someCause), then the existing algorithm works.
If we cancel2(), then Cause(ctx2) returns nil, which is obviously wrong.
If we cancel2(); cancel1(someCause), then Cause(ctx2) returns someCause. This is actually wrong: ctx2 was canceled before ctx1 and should not inherit ctx1's cause.
I don't see any way to fix this last case. Cause has no way to ask a third-party context for its cause. We could define a new optional method for returning causes, but that doesn't do anything for existing third-party contexts.
I think the best we might be able to do is say that if Cause is called on a canceled context where no cause can be identified, it defaults to returning Canceled.
dmitshur
added
NeedsFix
The path to resolution is known, but the work has not been done.
and removed
NeedsInvestigation
Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
labels
Apr 8, 2025
Go version
go1.25-dev (HEAD)
Output of
go env
in your module/workspace:What did you do?
When looking at what constitutes cancellation:
One could read that
Done()
being closed orErr()
returning non-nil constitutes a cancelled context.This interpretation doesn't jive with the current implementation of
context.Cause
:go/src/context/context.go
Lines 282 to 300 in af278bf
Using a custom
context.Context
implementation, cancellation can be implemented as returning a non-nil Errhttps://go.dev/play/p/bmnq9qOZuac?v=gotip
This causes
context.Cause
to returnnil
, because it looks up thecause
in the parent (throughValue()
).A (partial) solution may be to change
context.Cause
to not returncause
if it isnil
, which I think complies with the contract:Though this has other subtler issues, like: which of the errors should be preferred, if the child was "cancelled" earlier than the parent. In the proposal, the parents one wins. I'm not sure it is resolveable.
Side note #1:
The context package internally resolves this for
withoutCancelCtx
(used forcontext.WithoutCancel
) with a special-case invalue
:go/src/context/context.go
Lines 780 to 785 in af278bf
It's possible this special case may not be necessary anymore if
context.Cause
is fixed.Side note #2:
I see this in the docs:
go/src/context/context.go
Lines 27 to 31 in af278bf
But
WithCancelCause
is:go/src/context/context.go
Lines 699 to 704 in af278bf
Which is an inconsistency in the docs.
What did you see happen?
https://go.dev/play/p/bmnq9qOZuac?v=gotip
Prints:
What did you expect to see?
If
ctx.Err() != nil
, I would have expectedcontext.Cause(ctx) != nil
.The text was updated successfully, but these errors were encountered: