<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

Bad:

// WRONG
struct Bar {
  ctx context.Context
}

func NewBar(ctx context.Context) *Bar {
  return &Bar {
    ctx: ctx,
  }
}

func (s *Bar) Foo() {
  // Refering to s.ctx
}

Good:

func (s *Bar) Foo(ctx context.Context) {
	// use ctx
}

A full 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()
}

WIP: Better full example

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{}{}
}

Reference

<aside> 👉

Here’s a good article to read: https://zenhorace.dev/blog/context-control-go/

</aside>

https://go.dev/wiki/CodeReviewComments#contexts