ctxgroup

package
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2023 License: Apache-2.0 Imports: 2 Imported by: 0

README

This has been vendored from CockroachDB to avoid large dependencies. It is made available under the Apache 2.0 License. https://github.com/cockroachdb/cockroach/blob/126ebfda589553ecb26e0c0bf5b43c4332581465/pkg/util/ctxgroup/ctxgroup.go

When Go modules make it possible to avoid pulling in the entirety of a project for a submodule, this can be removed.

Documentation

Overview

Package ctxgroup wraps golang.org/x/sync/errgroup with a context func.

This package extends and modifies the errgroup API slightly to make context variables more explicit. WithContext no longer returns a context. Instead, the GoCtx method explicitly passes one to the invoked func. The goal is to make misuse of context vars with errgroups more difficult. Example usage:

ctx := context.Background()
g := ctxgroup.WithContext(ctx)
ch := make(chan bool)
g.GoCtx(func(ctx context.Context) error {
	defer close(ch)
	for _, val := range []bool{true, false} {
		select {
		case ch <- val:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
	return nil
})
g.GoCtx(func(ctx context.Context) error {
	for val := range ch {
		if err := api.Call(ctx, val); err != nil {
			return err
		}
	}
	return nil
})
if err := g.Wait(); err != nil {
	return err
}
api.Call(ctx, "done")

Problems with errgroup

The bugs this package attempts to prevent are: misuse of shadowed ctx variables after errgroup closure and confusion in the face of multiple ctx variables when trying to prevent shadowing. The following are all example bugs that Cockroach has had during its use of errgroup:

ctx := context.Background()
g, ctx := errgroup.WithContext(ctx)
ch := make(chan bool)
g.Go(func() error {
	defer close(ch)
	for _, val := range []bool{true, false} {
		select {
		case ch <- val:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
	return nil
})
g.Go(func() error {
	for val := range ch {
		if err := api.Call(ctx, val); err != nil {
			return err
		}
	}
	return nil
})
if err := g.Wait(); err != nil {
	return err
}
api.Call(ctx, "done")

The ctx used by the final api.Call is already closed because the errgroup has returned. This happened because of the desire to not create another ctx variable, and so we shadowed the original ctx var, but then incorrectly continued to use it after the errgroup had closed its context. So we make a modification and create new gCtx variable that doesn't shadow the original ctx:

ctx := context.Background()
g, gCtx := errgroup.WithContext(ctx)
ch := make(chan bool)
g.Go(func() error {
	defer close(ch)
	for _, val := range []bool{true, false} {
		select {
		case ch <- val:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
	return nil
})
g.Go(func() error {
	for val := range ch {
		if err := api.Call(ctx, val); err != nil {
			return err
		}
	}
	return nil
})
if err := g.Wait(); err != nil {
	return err
}
api.Call(ctx, "done")

Now the final api.Call is correct. But the other api.Call is incorrect and the ctx.Done receive is incorrect because they are using the wrong context and thus won't correctly exit early if the errgroup needs to exit early.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GroupWorkers

func GroupWorkers(ctx context.Context, num int, f func(context.Context) error) error

GroupWorkers runs num worker go routines in an errgroup.

Types

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group wraps errgroup.

func WithContext

func WithContext(ctx context.Context) Group

WithContext returns a new Group and an associated Context derived from ctx.

func (Group) Go

func (g Group) Go(f func() error)

Go calls the given function in a new goroutine.

func (Group) GoCtx

func (g Group) GoCtx(f func(ctx context.Context) error)

GoCtx calls the given function in a new goroutine.

func (Group) Wait

func (g Group) Wait() error

Wait blocks until all function calls from the Go method have returned, then returns the first non-nil error (if any) from them. If Wait() is invoked after the context (originally supplied to WithContext) is canceled, Wait returns an error, even if no Go invocation did. In particular, calling Wait() after Done has been closed is guaranteed to return an error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL