Split from #20280, @rasky @zombiezen
Should context.Context
be part of the language? Currently, error
is an interface type defined in the language and is a pervasive type that is commonly returned from functions. The context.Context
type can be seen as sort of the reverse of error
, where it is a pervasive type that is commonly passed to functions.
However, one challenge with the context.Context
interface is that the Context.Deadline
method returns a time.Time
, which probably should not be promoted to the language itself.
Personally, I can see the opposite being more likely: that error
be pulled out of the language specification and instead be defined in the errors
package. (In fact, it used to be a library defined type as os.Error
).
It would be consistent if context.Context
and error
were given equal treatment from the perspective of the language in Go2.
Go ahead and discuss.
If Context does become more official, it's worth also considering whether it should be more magic.
We don't want to do goroutine-local storage, but you could imagine some sort of scheme whereby contexts arguments are always present or at least declared but passed implicitly by default, unless explicitly passed separately. Everything I think of is pretty gross, but there might be something there.
I should note that more magic handling around errors have been proposed many times in the past as well. Magic handling of context should be considered along with more magic handling of errors.
One simple idea would be to have something like context.This() return the "implicit context" for the current goroutine, and then everything would work like now but without having to explicitly thread a context through all function calls and all libraries.
I would love something even more integrated (as you say, if you propagate errors, you can also propagate context errors and at that point it gets even more magic) but this would be a start.
I am very strongly opposed to removing error from the universe block, I
remember os.Error and I don't want to go back to that.
On Tue, 9 May 2017, 05:41 Giovanni Bajo notifications@github.com wrote:
One simple idea would be to have something like context.This() return the
"implicit context" for the current goroutine, and then everything would
work like now but without having to explicitly thread a context through all
function calls and all libraries.I would love something even more integrated (as you say, if you propagate
errors, you can also propagate context errors and at that point it gets
even more magic) but this would be a start.—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/20282#issuecomment-299969465, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAAcA4OGmcqCDh-4BZ-APNmpBHcTd2spks5r32_SgaJpZM4NURSd
.
I personally feel that error is really a very special case of interface in go. In no other case I really need to return a nil interface, it is the only interface that where a nil interface value has a meaning. So comparison with error is generally not a close comparison.
ps: it turns out they are indeed related:
https://arxiv.org/abs/1112.2394
https://cstheory.stackexchange.com/a/38603/
If context.Context became a builtin and remained a interface, maybe it's worth considering changing the interface to be:
Deadline() (time.Time, bool)
Done() <-chan struct{}
Err() error
Parent() context.Context
Now you have opened up a opportunity for user space implementations of not just Context values, but actual context. Part of the reason no specialized context libraries have emerged is due to the fact the context package is tightly coupled to itself to find parent and call cancellation functions (parentCancelCtx). So one can not implement a user context from top down that plays well at all with the std lib or optimizes away any inefficiencies. You can't make a map to use for context.Value for example to store just your app state to prevent the 6-8 allocs that come with stdlib context.WithValue because if a context is injected between yours you can't reliably traverse up to your object.
With a Parent method you could, and all existing instances of (context.Context).Value(...) simply get changed to context.Value(...) a top level function call implemented using context.Parent() to walk up the tree until it asserts it's own private valuecontext type. Tooling could do this task very easily.. and then we can finally implement our own context and perhaps a set of context packages outside of std library can emerge for special purpose cases.
If such a change is off the table at least we could consider a refactor of stdlib to define a optional interface in that package which would allow an efficient implementation of cancellation without having to fire new goroutines and the allocations along with it.
// Canceler is the interface that wraps the basic Cancel method.
//
// If a context implements Cancel then it is expected that once a call to Cancel
// completes the context is done.
type Canceler interface { Cancel() }
The only issue with this is it possible outside of a per-package basis for child contexts to signal cancellation to parents via a library feature, such as ctxcancel.Cancel(context.Context), which certainly goes against the spirit of context.
One simple idea would be to have something like context.This() return the "implicit context" for the current goroutine, and then everything would work like now but without having to explicitly thread a context through all function calls and all libraries.
This seems less-worse than context
spreading like a plague over all our sacred interfaces
If we ignore context values why not reduce it all the way down to just the cancel channel stored internally per gouroutine and let go func()...
return a canceller:
cancel := go func() {
<- runtime.Done() // fetches this goroutines cancel channel
}()
cancel()
timeout:
cancel := go func() {
<- runtime.Done()
}()
time.AfterFunc(10 * time.Millisecond, cancel)
Advantages:
Drawbacks:
Context is not just for cancellation, it can also contain security or tracing or deadline information for a call.
My understanding is that the decision to make context explicit in go in place of implicit as in say C++ was an intentional thoroughly discussed one.
Riffing off of https://github.com/golang/go/issues/20282#issuecomment-299943692 , here's an incomplete and not fully thought out, but reasonably concrete proposal for the value-storing aspects of context, with plenty of room for bikeshedding. (No mention of cancellation here, those can be two separate problems/mechanisms.)
type T struct {
color string
}
func parent() {
// compare to context.WithValue(ctx, (*T)(nil), ...)
with &T{color: "blue"} {
// ...
intermediate()
}
}
func intermediate() {
// compare to context.WithValue(ctx, (*T)(nil), ...)
// stacks just like context.WithValue
with &T{color: "green"} {
child()
}
with &T{color: "yellow"} {
child()
}
child()
}
func child() {
var t *T
// compare to ctx.Value((*T)(nil)).(*T)
// looks upward in implicit context, just like ctx.Value
context(&t)
// also ok := context(&t)
fmt.Printf("bikeshed is %v", t.color)
}
// bikeshed is green
// bikeshed is yellow
// bikeshed is blue
In this idea, context is implicit, scoped separately from functions or goroutines so you can call multiple helpers with slightly different contexts, and stored likely in the callstack with parent pointers across goroutines. And yes, as-is it adds keywords; I considered that cleaner than runtime.Foo
. And with
is a completely new construct, but as you can see from func intermediate
it seems useful, instead of just having a per-function implicit context variable. And this proposal is type-safe!
One of the reasons to make error
a predeclared type was that it used to be defined in the package os
, and that meant that any package that os
itself imported could not use the standard error type. We experimented with defining it in the errors
package, but that constant use of errors.Error
seemed to be a bit much.
error
is and always will be much more widely used than context.Context
. So I don't think the comparison with error
holds up.
Any magical handling of Context
would make it hard for a goroutine to use multiple contexts. And it would destroy the clear handling that is one of the advantages of the current system. And in any case this proposal was not originally about any magical handling, and doesn't have any specific proposals for it.
Closing.
Most helpful comment
If Context does become more official, it's worth also considering whether it should be more magic.
We don't want to do goroutine-local storage, but you could imagine some sort of scheme whereby contexts arguments are always present or at least declared but passed implicitly by default, unless explicitly passed separately. Everything I think of is pretty gross, but there might be something there.