I have a HTTP server application which serves async jobs.
-> Request
--> Do async job with goroutine
<- Response
-------start goroutine------
-> Job1
-> Job1A
-> Job1B
-> Job2
-> Job3
User can request for the long running async job and the application responses to the request immediately after make goroutine.
I put Request ID, authenticated token, and user information in context.Context
of the request. And, I want to bring it under the goroutines. But, using the same context
with the request context
will cause unexpected cancel after the response, which is not my intended behavior.
How can I generate new context
with all values, independent from the parent request context
? Or, any other way to guarantee the context
bring into the goroutines is not dead after response?
One more extra question:
Job1
~ Job3
should be serialized, i.e., Job2
should wait for Job1
and Job3
wait for Job2
. And, Job1A
and Job1B
can be run simultaneously. If I want to propagate cancelation of given context
, how can I make cancel path(?) of them? Should I check with select statement of all functions?
I understood concepts of context
propagating cancelation and early exit without doing meaningless tasks. However, I didn't catch how to deal within the code yet. I'll be happy if someone can help understanding.
There is no way to discover the values in a context. They are not stored as a map, they are stored as layers of contexts, and each level may provide a different implementation of how values are stored.
However, if you know what values you need to propagate, you can query them and create a new context using those values.
That said, you can implement a new context type that uses the values in another context:
type newContext struct {
context.Context
values context.Context
}
func (c newContext) Value(key any) any {
return c.values.Value(key)
}
...
newCtx:=newContext{
Context: context.Background(),
values: ctx,
}
This uses an existing context for values and a new context for everything else.
Then, start a new goroutine to continue processing the request using that new context.
If you want to create multiple concurrent jobs, you can do that in that goroutine:
go func(ctx context.Context) {
withCancel, cancel:=context.WithCancel(ctx)
defer cancel()
wg:=sync.WaitGroup{}
wg.Add(2)
go job1(withCancel,&wg)
go job2(withCancel,&wg)
wg.Wait()
}(newCtx)
This way, when the context is canceled, both jobs will get the cancellation notification. If you want to control cancellation of job1 and job2 separately:
go func(ctx context.Context) {
withCancel1, cancel1:=context.WithCancel(ctx)
defer cancel1()
withCancel2, cancel2:=context.WithCancel(ctx)
defer cancel2()
wg:=sync.WaitGroup{}
wg.Add(2)
go job1(withCancel1,&wg)
go job2(withCancel2,&wg)
wg.Wait()
}(newCtx)
For sequential jobs (i.e. job3 finishes after job1), simply combine them so they appear like a single job.
To check if a context is canceled, you can do a select
on the Done
channel of the context, or simply check:
if ctx.Err()!=nil {
// Context canceled
}