FAQ
Hi,

context.Context is a great package, with die docs says:

*Do not store Contexts inside a struct type;*
*instead, pass a Context explicitly to each function that needs it.*

But why ? I often have situations, where storing a context is really useful.
Especially when dealing with derived io.Writer/Reader implementations.

Like NewReader(ctx, ...). The Read/Write function obviously do not allow
passing a ctx...
You might ask, why the hell does he need a ctx in a Read/Write method. But
there are useful cases ;).

If I'm not allowed to store a context in a struct, can I at least store the
.Done() channel in a struct ?


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Search Discussions

  • Shawn Milochik at Aug 13, 2015 at 2:22 pm
    It's more explicit. If you make it part of the function definition, you
    require *every* call to pass a context.

    It's easier; you can use structs from third-party packages naturally,
    without having to embed to add a context.

    I don't know your "done channel" answer.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Sameer Ajmani at Aug 13, 2015 at 4:00 pm
    Satisfying the interface io.Reader or io.Writer is a good reason to put a
    Context in a struct, but in general this should be avoided. The reason to
    avoid putting Contexts in structs is that Contexts should follow the
    synchronous (blocking) call graph of your program: this is what allows the
    cancelation behavior to be meaningful (canceling a Context should cancel
    the blocking calls running on behalf of that Context). Putting a Context
    in a struct (typically) means that the methods on that struct use that
    Context. This causes problems when those methods are called from functions
    in a *different* Context:

    type S struct {
       ctx Context
    }
    func (s *S) Foo() {
       bar(ctx)
    }

    func Baz(ctx context.Context, s *S) {
       s.Foo() // uses s.ctx, not ctx
    }
    This call to s.Foo won't be canceled when Baz's ctx is canceled. It also
    won't use any security credentials from Baz's ctx. It also won't propagate
    any trace IDs or loggers from Baz's ctx. The right way to do this is for
    Foo to take a context:
    func (s *S) Foo(ctx context.Context) {
       bar(ctx)
    }
    func Baz(ctx context.Context, s *S) {
       s.Foo(ctx)
    }

    However, in the specific case of satisfying an interface like io.Reader or
    io.Writer, storing the context in a struct is appropriate. But to avoid
    issues like the example above, this binding to ctx should be done locally
    to the use. One idiom we use in Google is to name the method "IO":
    func (s *S) IO(ctx context.Context) io.ReadWriter {
       // return a struct that implements Read and Write on s using ctx
    }

    Then code can use s.IO(ctx) as an io.Reader or io.Writer:
    func Baz(ctx context.Context, s *S) {
       fmt.Fprintln(s.IO(ctx), "hello, world")
    }

    We've tried and failed to document the rules around this, because it's
    really an API design judgement. The general rule is to avoid putting
    Contexts in structs and make them follow the blocking call graph.

    S

    On Thu, Aug 13, 2015 at 3:22 PM, Shawn Milochik wrote:

    It's more explicit. If you make it part of the function definition, you
    require *every* call to pass a context.

    It's easier; you can use structs from third-party packages naturally,
    without having to embed to add a context.

    I don't know your "done channel" answer.

    --
    You received this message because you are subscribed to the Google Groups
    "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an
    email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Foxnet Developer at Aug 14, 2015 at 9:53 am
    Alright, got it =).
    Thanks!

    Am Donnerstag, 13. August 2015 18:00:49 UTC+2 schrieb Sameer Ajmani:
    Satisfying the interface io.Reader or io.Writer is a good reason to put a
    Context in a struct, but in general this should be avoided. The reason to
    avoid putting Contexts in structs is that Contexts should follow the
    synchronous (blocking) call graph of your program: this is what allows the
    cancelation behavior to be meaningful (canceling a Context should cancel
    the blocking calls running on behalf of that Context). Putting a Context
    in a struct (typically) means that the methods on that struct use that
    Context. This causes problems when those methods are called from functions
    in a *different* Context:

    type S struct {
    ctx Context
    }
    func (s *S) Foo() {
    bar(ctx)
    }

    func Baz(ctx context.Context, s *S) {
    s.Foo() // uses s.ctx, not ctx
    }
    This call to s.Foo won't be canceled when Baz's ctx is canceled. It also
    won't use any security credentials from Baz's ctx. It also won't propagate
    any trace IDs or loggers from Baz's ctx. The right way to do this is for
    Foo to take a context:
    func (s *S) Foo(ctx context.Context) {
    bar(ctx)
    }
    func Baz(ctx context.Context, s *S) {
    s.Foo(ctx)
    }

    However, in the specific case of satisfying an interface like io.Reader or
    io.Writer, storing the context in a struct is appropriate. But to avoid
    issues like the example above, this binding to ctx should be done locally
    to the use. One idiom we use in Google is to name the method "IO":
    func (s *S) IO(ctx context.Context) io.ReadWriter {
    // return a struct that implements Read and Write on s using ctx
    }

    Then code can use s.IO(ctx) as an io.Reader or io.Writer:
    func Baz(ctx context.Context, s *S) {
    fmt.Fprintln(s.IO(ctx), "hello, world")
    }

    We've tried and failed to document the rules around this, because it's
    really an API design judgement. The general rule is to avoid putting
    Contexts in structs and make them follow the blocking call graph.

    S


    On Thu, Aug 13, 2015 at 3:22 PM, Shawn Milochik <shawn...@gmail.com
    <javascript:>> wrote:
    It's more explicit. If you make it part of the function definition, you
    require *every* call to pass a context.

    It's easier; you can use structs from third-party packages naturally,
    without having to embed to add a context.

    I don't know your "done channel" answer.

    --
    You received this message because you are subscribed to the Google Groups
    "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an
    email to golang-nuts...@googlegroups.com <javascript:>.
    For more options, visit https://groups.google.com/d/optout.
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Foxnet Developer at Aug 14, 2015 at 6:56 pm
    Hi, I just thought about an example, where storing a Context in a struct
    actually does make sense:

    Imagine a NewXXX function, which takes a ctx and returns some struct, lets
    call it TaskRunner.

    It also launches a goroutine in the background, which is necessary for the
    struct to operate (maybe a task poller or whatever).
    The struct also has a method, lets call it offerTask(localCtx, ... task),
    which blocks, while the task is processed.
    Due to blocking, it also takes a context.

    Now, a running and blocking offerTask should return, when
    - localCtx is cancelled
    - the ctx passed to NewXXX is cancelled (because the background goroutine
    is obviously not able to accept tasks after cancelling).
    - the task succeeds.

    The Select should look like this:

    select {
    case <-localCtx.Done():
    // clean up
    return
    case <-TaskRunner.ctx.Done():
    // clean up
    return
    case <-taskResultChan:
    fetch result...
    }

    Since the TaskRunner.ctx is local to NewXXX, it cannot be used in
    offerTask, without storing it in a struct, but this use case does not have
    any of your cons, since it also takes a localCtx.

    In my opinion, this is a perfectly valid and suitable use case for storing
    ctx in structs. (As long you make sure to select on all ctx's).

    What do you think about this ?

    Thanks in advance for this interesting discussion =).

    Regards,
    Chris

    PS:
    If you are interested:

    My use case is a token bucket, with a goroutine running and generating
    tokens.
    I want to shutdown the token bucket, when a specific ctx is cancelled
    NewTokenBucket(ctx...).
    There is a method called Take(ctx), which tries to take tokens and blocks
    if no tokens are available.

    Now, Take(...) should unblock, if the bucket is cancelled, because it would
    otherwise block forever.

    Right now, I do this with a manual done channel, but I think its wrong to
    duplicate exit strategies.


    Am Donnerstag, 13. August 2015 18:00:49 UTC+2 schrieb Sameer Ajmani:
    Satisfying the interface io.Reader or io.Writer is a good reason to put a
    Context in a struct, but in general this should be avoided. The reason to
    avoid putting Contexts in structs is that Contexts should follow the
    synchronous (blocking) call graph of your program: this is what allows the
    cancelation behavior to be meaningful (canceling a Context should cancel
    the blocking calls running on behalf of that Context). Putting a Context
    in a struct (typically) means that the methods on that struct use that
    Context. This causes problems when those methods are called from functions
    in a *different* Context:

    type S struct {
    ctx Context
    }
    func (s *S) Foo() {
    bar(ctx)
    }

    func Baz(ctx context.Context, s *S) {
    s.Foo() // uses s.ctx, not ctx
    }
    This call to s.Foo won't be canceled when Baz's ctx is canceled. It also
    won't use any security credentials from Baz's ctx. It also won't propagate
    any trace IDs or loggers from Baz's ctx. The right way to do this is for
    Foo to take a context:
    func (s *S) Foo(ctx context.Context) {
    bar(ctx)
    }
    func Baz(ctx context.Context, s *S) {
    s.Foo(ctx)
    }

    However, in the specific case of satisfying an interface like io.Reader or
    io.Writer, storing the context in a struct is appropriate. But to avoid
    issues like the example above, this binding to ctx should be done locally
    to the use. One idiom we use in Google is to name the method "IO":
    func (s *S) IO(ctx context.Context) io.ReadWriter {
    // return a struct that implements Read and Write on s using ctx
    }

    Then code can use s.IO(ctx) as an io.Reader or io.Writer:
    func Baz(ctx context.Context, s *S) {
    fmt.Fprintln(s.IO(ctx), "hello, world")
    }

    We've tried and failed to document the rules around this, because it's
    really an API design judgement. The general rule is to avoid putting
    Contexts in structs and make them follow the blocking call graph.

    S


    On Thu, Aug 13, 2015 at 3:22 PM, Shawn Milochik <shawn...@gmail.com
    <javascript:>> wrote:
    It's more explicit. If you make it part of the function definition, you
    require *every* call to pass a context.

    It's easier; you can use structs from third-party packages naturally,
    without having to embed to add a context.

    I don't know your "done channel" answer.

    --
    You received this message because you are subscribed to the Google Groups
    "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an
    email to golang-nuts...@googlegroups.com <javascript:>.
    For more options, visit https://groups.google.com/d/optout.
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Sameer Ajmani at Aug 15, 2015 at 10:16 pm
    Thanks for the example. In this case, the ctx passed to New is only being
    used for its Done channel; none of the request scoped values matter. So New
    doesn't need a ctx, it just needs a done channel. But even that is
    unusual: much more common is for the new object to have a Close method that
    shuts down the Background goroutine. Internally, this shut down mechanism
    can use a channel.
    On Aug 14, 2015 2:56 PM, wrote:

    Hi, I just thought about an example, where storing a Context in a struct
    actually does make sense:

    Imagine a NewXXX function, which takes a ctx and returns some struct, lets
    call it TaskRunner.

    It also launches a goroutine in the background, which is necessary for the
    struct to operate (maybe a task poller or whatever).
    The struct also has a method, lets call it offerTask(localCtx, ... task),
    which blocks, while the task is processed.
    Due to blocking, it also takes a context.

    Now, a running and blocking offerTask should return, when
    - localCtx is cancelled
    - the ctx passed to NewXXX is cancelled (because the background goroutine
    is obviously not able to accept tasks after cancelling).
    - the task succeeds.

    The Select should look like this:

    select {
    case <-localCtx.Done():
    // clean up
    return
    case <-TaskRunner.ctx.Done():
    // clean up
    return
    case <-taskResultChan:
    fetch result...
    }

    Since the TaskRunner.ctx is local to NewXXX, it cannot be used in
    offerTask, without storing it in a struct, but this use case does not have
    any of your cons, since it also takes a localCtx.

    In my opinion, this is a perfectly valid and suitable use case for storing
    ctx in structs. (As long you make sure to select on all ctx's).

    What do you think about this ?

    Thanks in advance for this interesting discussion =).

    Regards,
    Chris

    PS:
    If you are interested:

    My use case is a token bucket, with a goroutine running and generating
    tokens.
    I want to shutdown the token bucket, when a specific ctx is cancelled
    NewTokenBucket(ctx...).
    There is a method called Take(ctx), which tries to take tokens and blocks
    if no tokens are available.

    Now, Take(...) should unblock, if the bucket is cancelled, because it
    would otherwise block forever.

    Right now, I do this with a manual done channel, but I think its wrong to
    duplicate exit strategies.


    Am Donnerstag, 13. August 2015 18:00:49 UTC+2 schrieb Sameer Ajmani:
    Satisfying the interface io.Reader or io.Writer is a good reason to put a
    Context in a struct, but in general this should be avoided. The reason to
    avoid putting Contexts in structs is that Contexts should follow the
    synchronous (blocking) call graph of your program: this is what allows the
    cancelation behavior to be meaningful (canceling a Context should cancel
    the blocking calls running on behalf of that Context). Putting a Context
    in a struct (typically) means that the methods on that struct use that
    Context. This causes problems when those methods are called from functions
    in a *different* Context:

    type S struct {
    ctx Context
    }
    func (s *S) Foo() {
    bar(ctx)
    }

    func Baz(ctx context.Context, s *S) {
    s.Foo() // uses s.ctx, not ctx
    }
    This call to s.Foo won't be canceled when Baz's ctx is canceled. It also
    won't use any security credentials from Baz's ctx. It also won't propagate
    any trace IDs or loggers from Baz's ctx. The right way to do this is for
    Foo to take a context:
    func (s *S) Foo(ctx context.Context) {
    bar(ctx)
    }
    func Baz(ctx context.Context, s *S) {
    s.Foo(ctx)
    }

    However, in the specific case of satisfying an interface like io.Reader
    or io.Writer, storing the context in a struct is appropriate. But to avoid
    issues like the example above, this binding to ctx should be done locally
    to the use. One idiom we use in Google is to name the method "IO":
    func (s *S) IO(ctx context.Context) io.ReadWriter {
    // return a struct that implements Read and Write on s using ctx
    }

    Then code can use s.IO(ctx) as an io.Reader or io.Writer:
    func Baz(ctx context.Context, s *S) {
    fmt.Fprintln(s.IO(ctx), "hello, world")
    }

    We've tried and failed to document the rules around this, because it's
    really an API design judgement. The general rule is to avoid putting
    Contexts in structs and make them follow the blocking call graph.

    S


    On Thu, Aug 13, 2015 at 3:22 PM, Shawn Milochik <shawn...@gmail.com>
    wrote:
    It's more explicit. If you make it part of the function definition, you
    require *every* call to pass a context.

    It's easier; you can use structs from third-party packages naturally,
    without having to embed to add a context.

    I don't know your "done channel" answer.

    --
    You received this message because you are subscribed to the Google
    Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send
    an email to golang-nuts...@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
    --
    You received this message because you are subscribed to the Google Groups
    "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an
    email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupgolang-nuts @
categoriesgo
postedAug 13, '15 at 1:48p
activeAug 15, '15 at 10:16p
posts6
users3
websitegolang.org

People

Translate

site design / logo © 2021 Grokbase