Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang context.Context propagation

Tags:

go

I would like to ask how we should approach the issue with context propagation in Golang.

My application is an HTTP JSON API server.

I would use the context as a container of informative data (e.g. the request id, some things I unpack from requests or from the process).

One of the dumbest advantages is to vehicle data and tags useful for statistics and logging. E.g. Be able to add at each log line the transaction id in all the packages I own

The problem I'm facing is the following:

func handleActivityY(w http.ResponseWriter, r *http.Request) {
    info, err := decodeRequest(r)
    ...
    stepOne, err := stepOne(r.Context(), info)
    ...
    stepTwo, err := stepTwo(r.Context(), stepOne)
    ...
}

The problem with this design is the fact the context is an immutable entity (each time we add something or we set a new timeout, we have a new context).

The context cannot be propagated except returning the context at each function call (together with the return value, if any and the error).

The only way to make this work would be:

func handleActivityY(w http.ResponseWriter, r *http.Request) {
    ctx, info, err := decodeRequest(r)
    ...
    ctx, stepOne, err := stepOne(ctx, info)
    ...
    ctx, stepTwo, err := stepTwo(ctx, stepOne)
    ...
}

I've already polluted almost any function in my packages with the context.Context parameter. Returning it in addition to other parameters seems to me overkill.

Is there really no other more elegant way to do so?

I am currently using the gin framework, which has its own context and it is mutable. I don't want to add the dependency to Gin for that.

like image 470
lucabelluccini Avatar asked Oct 16 '25 04:10

lucabelluccini


2 Answers

Early in your context pipeline, add a mutable pointer to a data struct:

type MyData struct {
    // whatever you need
}

var MyDataKey = /* something */

ctx, cancel := context.WithValue(context.Background(), MyDataKey, &MyData{})

Then in your methods that need to modify your data structure, just do so:

data := ctx.Value(MyDataKey)
data.Foo = /* something */

All normal rules about concurrent access safety apply, so you may need to use mutexes or other protection mechanisms if multiple goroutines can read/set your data value simultaneously.

like image 64
Flimzy Avatar answered Oct 17 '25 19:10

Flimzy


Is there really no other more elegant way to do so?

stepOne could return it's own data independent of context and isolated from how the caller may use its information (ie put it in a databag/context and pass it to other functions)

func handleActivityY(w http.ResponseWriter, r *http.Request) {
    ctx, info, err := decodeRequest(r)
    ...
    stepOne, err := stepOne(ctx, info)
    ...
    ctx = context.WithValue(ctx, "someContextStepTwoNeeds", stepOne.Something())
    stepTwo, err := stepTwo(ctx, stepOne)
    ...
}

IMO data being passed request scoped should be extremely minimal contextual information

like image 27
dm03514 Avatar answered Oct 17 '25 17:10

dm03514



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!