<aside> 🤷
You may find many violations of this rule in the status-go
codebase.
While we’re trying to fix this, please write new code with this rule in mind, even if it ties to the old code with this anti-pattern.
</aside>
Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it, - documentation for context
// WRONG
struct Bar {
ctx context.Context
}
func NewBar(ctx context.Context) *Bar {
return &Bar {
ctx: ctx,
}
}
func (s *Bar) Foo() {
// Refering to s.ctx
}
func (s *Bar) Foo(ctx context.Context) {
// use ctx
}
Worker
example:type Worker struct {
cancel context.CancelFunc
wg *sync.WaitGroup
}
// Pass ctx to Start, and do not store it in the Worker
func (w *Worker) Start(ctx context.Context) {
// Derive the given context, save the CancelFunc
ctx, w.cancel = context.WithCancel(ctx)
// Use internal WaitGroup for graceful stop
w.wg.Add(1)
// Start the goroutine
go func() {
// Notfiy the WaitGroup when the goroutine is finished
defer w.wg.Done()
select {
// The derived `ctx` will be canceled in both cases:
// parent context is done, or Stop is called.
case <-ctx.Done():
return
}
}()
}
// Stop allows you to stop this specific worker, while allowing
// other workirs with the same `ctx` to continue running.
func (w *Worker) Stop() {
if w.cancel != nil {
w.cancel()
}
w.wg.Wait()
}
type Service struct {
// Not storing the context
}
// This is a RUN method, not START.
// This method is SYNCRONOUS. The client may call it `go service.Run(ctx)`.
// And there will be no STOP method.
// To stop the service, just Cancel the provided context.
func (s *Service) Run(ctx context.Context) {
wg *sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case <-time.After(1*time.Second):
// do work
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
// do work with respect to `ctx`
// If spawning more goroutines, they MUST use the WaitGroup as well.
wg.Add(1)
go func() {
defer wg.Done()
}()
}()
// This methid is syncronous.
// Wait for all goroutines to finish
wg.Wait()
}
// When need other methods to access service's resources,
// the work MUST be done in the Service's context.
// Therefore, the task must be passed to the Service with channels.
// e.g. Waku.ConnectionChange
func (s *Service) ConnectionChange() {
s.connectionChange <- struct{}{}
}
<aside> 👉
Here’s a good article to read: https://zenhorace.dev/blog/context-control-go/
</aside>