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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: spec: make context.Context a predefined type like error #20282

Closed
dsnet opened this issue May 8, 2017 · 11 comments
Closed

proposal: spec: make context.Context a predefined type like error #20282

dsnet opened this issue May 8, 2017 · 11 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@dsnet
Copy link
Member

dsnet commented May 8, 2017

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.

@gopherbot gopherbot added this to the Proposal milestone May 8, 2017
@dsnet dsnet added LanguageChange v2 A language change or incompatible library change labels May 8, 2017
@bradfitz
Copy link
Contributor

bradfitz commented May 8, 2017

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.

@dsnet
Copy link
Member Author

dsnet commented May 8, 2017

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.

@rasky
Copy link
Member

rasky commented May 8, 2017

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.

@davecheney
Copy link
Contributor

davecheney commented May 8, 2017 via email

@rsc rsc changed the title proposal: should Context be part of the language? proposal: spec: make context.Context a predefined type like error Jun 16, 2017
@ghasemloo
Copy link

ghasemloo commented Jul 11, 2017

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/

@cstockton
Copy link

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.

@lwc
Copy link

lwc commented Aug 8, 2017

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

@vektah
Copy link

vektah commented Aug 8, 2017

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:

  • conceptually simple
  • covers the 99% of context proliferation
  • requires adding only the close channel to the goroutine storage
  • probably not a bc break with 1.0

Drawbacks:

  • you need goroutines at every cancellation boundary. There is probably a distinction to be made between things like a timeout on an individual io.Reader.Read call and the deadline / external cancellation.
  • cancellation is no longer nested and if you start a goroutine you are responsible for propegating the cancellation, but thats probably not a huge deal.

@ghasemloo
Copy link

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.

@tv42
Copy link

tv42 commented Aug 9, 2017

Riffing off of #20282 (comment) , 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!

@ianlancetaylor
Copy link
Contributor

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.

@golang golang locked and limited conversation to collaborators Feb 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests