Created
September 16, 2017 16:14
-
-
Save cstockton/77e38b16999f382fa0ef3060d785413a to your computer and use it in GitHub Desktop.
Example of cancellation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"log" | |
"os" | |
"os/signal" | |
"syscall" | |
"time" | |
"golang.org/x/sync/errgroup" | |
) | |
const ( | |
// Test unicode points I like to use for utf8, they all are a-z and do not | |
// have a step to alternative casing. Meaning they have the property that: | |
// | |
// (ucwN) 'A' + 32 = 'a' (lower case mapped letter) | |
uw1 = 'A' // 0x41 65 (1 byte) | |
uw2 = 'À' // 0xC0 192 (2 bytes) | |
uw3 = 'A' // 0xFF21 65313 (3 bytes) | |
uw4 = '𝐀' // 0x1D400 119808 (4 bytes) | |
) | |
func Run(ctx context.Context) error { | |
ctx, cancel := context.WithTimeout(ctx, timeout) | |
defer cancel() | |
g, ctx := errgroup.WithContext(ctx) | |
ch := make(chan rune) | |
g.Go(Worker(ctx, ch, uw1)) | |
g.Go(Worker(ctx, ch, uw1+32)) | |
g.Go(Worker(ctx, ch, uw4)) | |
g.Go(Worker(ctx, ch, uw4+32)) | |
errCh := make(chan error, 1) | |
go func() { | |
errCh <- g.Wait() | |
close(ch) | |
}() | |
var runes []rune | |
for r := range ch { | |
runes = append(runes, r) | |
} | |
err := <-errCh | |
fmt.Println(`Result err:`, err, `Runes:`, string(runes)) | |
return err | |
} | |
func Worker(ctx context.Context, ch chan<- rune, r rune) func() error { | |
return func() error { | |
for max := r + 27; r < max; r++ { | |
select { | |
case ch <- r: | |
time.Sleep(workerDuration / 27) | |
case <-ctx.Done(): | |
return ctx.Err() | |
} | |
} | |
return nil | |
} | |
} | |
const ( | |
// The most time you want to wait for the program to complete regardless of | |
// progress within the current task. | |
timeout = time.Second * 4 | |
// Each worker will take roughly workerDuration duration. | |
workerDuration = timeout / 2 // will complete | |
// slowWorker = timeout * 2 // will fail | |
) | |
func main() { | |
// Never trap signals without a very good reason, doing so you will find it | |
// quickly makes the debug cycle painful when you hit try to interrupt and | |
// your program hangs. I rarely need to trap signals and when I do I usually | |
// use a background context until development is done and I know the program | |
// is correct. | |
// | |
// If you do decide to trap signals it should always be your very top level | |
// context, or you are destined to have this as your tty "^C^C^M^CC^C^CAFJA". | |
ctx := WithSignals(context.Background(), os.Interrupt, syscall.SIGTERM) | |
if err := Run(ctx); err != nil { | |
log.Fatal(err) | |
} | |
} | |
// WithSignals returns a ctx that is cancelled on the given signals. | |
func WithSignals(ctx context.Context, sigs ...os.Signal) context.Context { | |
sigCh := make(chan os.Signal, 1) | |
signal.Notify(sigCh, sigs...) | |
ctx, cancel := context.WithCancel(ctx) | |
go func() { | |
defer cancel() | |
select { | |
case <-ctx.Done(): | |
return | |
case sig := <-sigCh: | |
for _, s := range sigs { | |
if s == sig { | |
fmt.Printf("caught %v, canceling context\n", s) | |
return | |
} | |
} | |
} | |
}() | |
return ctx | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment