func (g *GHR) UploadAssets(ctx context.Context, releaseID int, localAssets []string, parallel int) error { start := time.Now() defer func() { Debugf("UploadAssets: time: %d ms", int(time.Since(start).Seconds()*1000)) }() eg, ctx := errgroup.WithContext(ctx) semaphore := make(chan struct{}, parallel) for _, localAsset := range localAssets { localAsset := localAsset eg.Go(func() error { semaphore <- struct{}{} defer func() { <-semaphore }() fmt.Fprintf(g.outStream, "--> Uploading: %15s\n", filepath.Base(localAsset)) _, err := g.GitHub.UploadAsset(ctx, releaseID, localAsset) if err != nil { return errors.Wrapf(err, "failed to upload asset: %s", localAsset) } return nil }) } if err := eg.Wait(); err != nil { return errors.Wrap(err, "one of goroutines is failed") } return nil }
// Run triggers the manager loops func (m *Manager) Run() error { eg, _ := errgroup.WithContext(context.Background()) // start http server for servicing REST api endpoints. It feeds api/ux events. apiServingCh := make(chan struct{}, 1) eg.Go(func() error { return m.apiLoop(apiServingCh) }) // start monitor subsystem. It feeds node state monitoring events. // It needs to be started after api loop as monitor subsystem post events through API endpoints. // Additionally, we wait for api loop to signal that it has setup socket to receive requests <-apiServingCh eg.Go(m.monitorLoop) // start signal handler loop. // It needs to be started after api loop as signal handler posts events through API endpoints. eg.Go( func() error { m.signalLoop() return nil }) // start the event loop. It processes the events. eg.Go( func() error { m.eventLoop() return nil }) return eg.Wait() }
// parallelRevoke revokes the passed VaultAccessors in parallel. func (v *vaultClient) parallelRevoke(ctx context.Context, accessors []*structs.VaultAccessor) error { if !v.Enabled() { return fmt.Errorf("Vault integration disabled") } if !v.Active() { return fmt.Errorf("Vault client not active") } // Check if we have established a connection with Vault if !v.ConnectionEstablished() { return fmt.Errorf("Connection to Vault has not been established. Retry") } g, pCtx := errgroup.WithContext(ctx) // Cap the handlers handlers := len(accessors) if handlers > maxParallelRevokes { handlers = maxParallelRevokes } // Create the Vault Tokens input := make(chan *structs.VaultAccessor, handlers) for i := 0; i < handlers; i++ { g.Go(func() error { for { select { case va, ok := <-input: if !ok { return nil } if err := v.auth.RevokeAccessor(va.Accessor); err != nil { return fmt.Errorf("failed to revoke token (alloc: %q, node: %q, task: %q)", va.AllocID, va.NodeID, va.Task) } case <-pCtx.Done(): return nil } } }) } // Send the input go func() { defer close(input) for _, va := range accessors { select { case <-pCtx.Done(): return case input <- va: } } }() // Wait for everything to complete return g.Wait() }
func (service *CleanUpService) Run(ctx context.Context) error { service.log.Info("Initializing CleanUpService") g, _ := errgroup.WithContext(ctx) g.Go(func() error { return service.start(ctx) }) err := g.Wait() service.log.Info("Stopped CleanUpService", "reason", err) return err }
func (n *RootNotifier) sendNotifications(context *EvalContext, notifiers []Notifier) error { g, _ := errgroup.WithContext(context.Ctx) for _, notifier := range notifiers { n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType()) g.Go(func() error { return notifier.Notify(context) }) } return g.Wait() }
func (n *RootNotifier) sendNotifications(context *EvalContext, notifiers []Notifier) error { g, _ := errgroup.WithContext(context.Ctx) for _, notifier := range notifiers { not := notifier //avoid updating scope variable in go routine n.log.Info("Sending notification", "type", not.GetType(), "id", not.GetNotifierId(), "isDefault", not.GetIsDefault()) g.Go(func() error { return not.Notify(context) }) } return g.Wait() }
func NewGrafanaServer() models.GrafanaServer { rootCtx, shutdownFn := context.WithCancel(context.Background()) childRoutines, childCtx := errgroup.WithContext(rootCtx) return &GrafanaServerImpl{ context: childCtx, shutdownFn: shutdownFn, childRoutines: childRoutines, log: log.New("server"), } }
func (e *Engine) runJobDispatcher(grafanaCtx context.Context) error { dispatcherGroup, alertCtx := errgroup.WithContext(grafanaCtx) for { select { case <-grafanaCtx.Done(): return dispatcherGroup.Wait() case job := <-e.execQueue: dispatcherGroup.Go(func() error { return e.processJob(alertCtx, job) }) } } }
func (e *Engine) Run(ctx context.Context) error { e.log.Info("Initializing Alerting") alertGroup, ctx := errgroup.WithContext(ctx) alertGroup.Go(func() error { return e.alertingTicker(ctx) }) alertGroup.Go(func() error { return e.runJobDispatcher(ctx) }) err := alertGroup.Wait() e.log.Info("Stopped Alerting", "reason", err) return err }
func (g *GHR) DeleteAssets(ctx context.Context, releaseID int, localAssets []string, parallel int) error { start := time.Now() defer func() { Debugf("DeleteAssets: time: %d ms", int(time.Since(start).Seconds()*1000)) }() eg, ctx := errgroup.WithContext(ctx) assets, err := g.GitHub.ListAssets(ctx, releaseID) if err != nil { return errors.Wrap(err, "failed to list assets") } semaphore := make(chan struct{}, parallel) for _, localAsset := range localAssets { for _, asset := range assets { // https://golang.org/doc/faq#closures_and_goroutines localAsset, asset := localAsset, asset // Uploaded asset name is same as basename of local file if *asset.Name == filepath.Base(localAsset) { eg.Go(func() error { semaphore <- struct{}{} defer func() { <-semaphore }() fmt.Fprintf(g.outStream, "--> Deleting: %15s\n", *asset.Name) if err := g.GitHub.DeleteAsset(ctx, *asset.ID); err != nil { return errors.Wrapf(err, "failed to delete asset: %s", *asset.Name) } return nil }) } } } if err := eg.Wait(); err != nil { return errors.Wrap(err, "one of goroutines is failed") } return nil }
func TestWithContext(t *testing.T) { errDoom := errors.New("group_test: doomed") cases := []struct { errs []error want error }{ {want: nil}, {errs: []error{nil}, want: nil}, {errs: []error{errDoom}, want: errDoom}, {errs: []error{errDoom, nil}, want: errDoom}, } for _, tc := range cases { g, ctx := errgroup.WithContext(context.Background()) for _, err := range tc.errs { err := err g.Go(func() error { return err }) } if err := g.Wait(); err != tc.want { t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ "g.Wait() = %v; want %v", g, tc.errs, err, tc.want) } canceled := false select { case <-ctx.Done(): canceled = true default: } if !canceled { t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ "ctx.Done() was not closed", g, tc.errs) } } }
// Parallel illustrates the use of a Group for synchronizing a simple parallel // task: the "Google Search 2.0" function from // https://talks.golang.org/2012/concurrency.slide#46, augmented with a Context // and error-handling. func ExampleGroup_parallel() { Google := func(ctx context.Context, query string) ([]Result, error) { g, ctx := errgroup.WithContext(ctx) searches := []Search{Web, Image, Video} results := make([]Result, len(searches)) for i, search := range searches { i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { result, err := search(ctx, query) if err == nil { results[i] = result } return err }) } if err := g.Wait(); err != nil { return nil, err } return results, nil } results, err := Google(context.Background(), "golang") if err != nil { fmt.Fprintln(os.Stderr, err) return } for _, result := range results { fmt.Println(result) } // Output: // web result for "golang" // image result for "golang" // video result for "golang" }
// MD5All reads all the files in the file tree rooted at root and returns a map // from file path to the MD5 sum of the file's contents. If the directory walk // fails or any read operation fails, MD5All returns an error. func MD5All(ctx context.Context, root string) (map[string][md5.Size]byte, error) { // ctx is canceled when MD5All calls g.Wait(). When this version of MD5All // returns - even in case of error! - we know that all of the goroutines have // finished and the memory they were using can be garbage-collected. g, ctx := errgroup.WithContext(ctx) paths := make(chan string) g.Go(func() error { defer close(paths) return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.Mode().IsRegular() { return nil } select { case paths <- path: case <-ctx.Done(): return ctx.Err() } return nil }) }) // Start a fixed number of goroutines to read and digest files. c := make(chan result) const numDigesters = 20 for i := 0; i < numDigesters; i++ { g.Go(func() error { for path := range paths { data, err := ioutil.ReadFile(path) if err != nil { return err } select { case c <- result{path, md5.Sum(data)}: case <-ctx.Done(): return ctx.Err() } } return nil }) } go func() { g.Wait() close(c) }() m := make(map[string][md5.Size]byte) for r := range c { m[r.path] = r.sum } // Check whether any of the goroutines failed. Since g is accumulating the // errors, we don't need to send them (or check for them) in the individual // results sent on the channel. if err := g.Wait(); err != nil { return nil, err } return m, nil }
// DeriveVaultToken is used by the clients to request wrapped Vault tokens for // tasks func (n *Node) DeriveVaultToken(args *structs.DeriveVaultTokenRequest, reply *structs.DeriveVaultTokenResponse) error { // setErr is a helper for setting the recoverable error on the reply and // logging it setErr := func(e error, recoverable bool) { reply.Error = structs.NewRecoverableError(e, recoverable) n.srv.logger.Printf("[ERR] nomad.client: DeriveVaultToken failed (recoverable %v): %v", recoverable, e) } if done, err := n.srv.forward("Node.DeriveVaultToken", args, args, reply); done { setErr(err, err == structs.ErrNoLeader) return nil } defer metrics.MeasureSince([]string{"nomad", "client", "derive_vault_token"}, time.Now()) // Verify the arguments if args.NodeID == "" { setErr(fmt.Errorf("missing node ID"), false) return nil } if args.SecretID == "" { setErr(fmt.Errorf("missing node SecretID"), false) return nil } if args.AllocID == "" { setErr(fmt.Errorf("missing allocation ID"), false) return nil } if len(args.Tasks) == 0 { setErr(fmt.Errorf("no tasks specified"), false) return nil } // Verify the following: // * The Node exists and has the correct SecretID // * The Allocation exists on the specified node // * The allocation contains the given tasks and they each require Vault // tokens snap, err := n.srv.fsm.State().Snapshot() if err != nil { setErr(err, false) return nil } node, err := snap.NodeByID(args.NodeID) if err != nil { setErr(err, false) return nil } if node == nil { setErr(fmt.Errorf("Node %q does not exist", args.NodeID), false) return nil } if node.SecretID != args.SecretID { setErr(fmt.Errorf("SecretID mismatch"), false) return nil } alloc, err := snap.AllocByID(args.AllocID) if err != nil { setErr(err, false) return nil } if alloc == nil { setErr(fmt.Errorf("Allocation %q does not exist", args.AllocID), false) return nil } if alloc.NodeID != args.NodeID { setErr(fmt.Errorf("Allocation %q not running on Node %q", args.AllocID, args.NodeID), false) return nil } if alloc.TerminalStatus() { setErr(fmt.Errorf("Can't request Vault token for terminal allocation"), false) return nil } // Check the policies policies := alloc.Job.VaultPolicies() if policies == nil { setErr(fmt.Errorf("Job doesn't require Vault policies"), false) return nil } tg, ok := policies[alloc.TaskGroup] if !ok { setErr(fmt.Errorf("Task group does not require Vault policies"), false) return nil } var unneeded []string for _, task := range args.Tasks { taskVault := tg[task] if taskVault == nil || len(taskVault.Policies) == 0 { unneeded = append(unneeded, task) } } if len(unneeded) != 0 { e := fmt.Errorf("Requested Vault tokens for tasks without defined Vault policies: %s", strings.Join(unneeded, ", ")) setErr(e, false) return nil } // At this point the request is valid and we should contact Vault for // tokens. // Create an error group where we will spin up a fixed set of goroutines to // handle deriving tokens but where if any fails the whole group is // canceled. g, ctx := errgroup.WithContext(context.Background()) // Cap the handlers handlers := len(args.Tasks) if handlers > maxParallelRequestsPerDerive { handlers = maxParallelRequestsPerDerive } // Create the Vault Tokens input := make(chan string, handlers) results := make(map[string]*vapi.Secret, len(args.Tasks)) for i := 0; i < handlers; i++ { g.Go(func() error { for { select { case task, ok := <-input: if !ok { return nil } secret, err := n.srv.vault.CreateToken(ctx, alloc, task) if err != nil { wrapped := fmt.Errorf("failed to create token for task %q: %v", task, err) if rerr, ok := err.(*structs.RecoverableError); ok && rerr.Recoverable { // If the error is recoverable, propogate it return structs.NewRecoverableError(wrapped, true) } return wrapped } results[task] = secret case <-ctx.Done(): return nil } } }) } // Send the input go func() { defer close(input) for _, task := range args.Tasks { select { case <-ctx.Done(): return case input <- task: } } }() // Wait for everything to complete or for an error createErr := g.Wait() // Retrieve the results accessors := make([]*structs.VaultAccessor, 0, len(results)) tokens := make(map[string]string, len(results)) for task, secret := range results { w := secret.WrapInfo if w == nil { return fmt.Errorf("Vault returned Secret without WrapInfo") } tokens[task] = w.Token accessor := &structs.VaultAccessor{ Accessor: w.WrappedAccessor, Task: task, NodeID: alloc.NodeID, AllocID: alloc.ID, CreationTTL: w.TTL, } accessors = append(accessors, accessor) } // If there was an error revoke the created tokens if createErr != nil { n.srv.logger.Printf("[ERR] nomad.node: Vault token creation failed: %v", createErr) if revokeErr := n.srv.vault.RevokeTokens(context.Background(), accessors, false); revokeErr != nil { n.srv.logger.Printf("[ERR] nomad.node: Vault token revocation failed: %v", revokeErr) } if rerr, ok := createErr.(*structs.RecoverableError); ok { reply.Error = rerr } else { reply.Error = structs.NewRecoverableError(createErr, false) } return nil } // Commit to Raft before returning any of the tokens req := structs.VaultAccessorsRequest{Accessors: accessors} _, index, err := n.srv.raftApply(structs.VaultAccessorRegisterRequestType, &req) if err != nil { n.srv.logger.Printf("[ERR] nomad.client: Register Vault accessors failed: %v", err) // Determine if we can recover from the error retry := false switch err { case raft.ErrNotLeader, raft.ErrLeadershipLost, raft.ErrRaftShutdown, raft.ErrEnqueueTimeout: retry = true } setErr(err, retry) return nil } reply.Index = index reply.Tasks = tokens n.srv.setQueryMeta(&reply.QueryMeta) return nil }
func ExampleScrollService() { client, err := elastic.NewClient() if err != nil { panic(err) } // This example illustrates how to use two goroutines to iterate // through a result set via ScrollService. // // It uses the excellent golang.org/x/sync/errgroup package to do so. // // The first goroutine will Scroll through the result set and send // individual results to a channel. // // The second goroutine will receive results from the channel and // deserialize them. // // Feel free to add a third goroutine to do something with the // deserialized results from the 2nd goroutine. // // Let's go. // 1st goroutine sends individual hits to channel. hits := make(chan json.RawMessage) g, ctx := errgroup.WithContext(context.Background()) g.Go(func() error { defer close(hits) scroll := client.Scroll("twitter").Size(100) for { results, err := scroll.Do() if err == io.EOF { return nil // all results retrieved } if err != nil { return err // something went wrong } // Send the hits to the hits channel for _, hit := range results.Hits.Hits { hits <- *hit.Source } // Check if we need to terminate early select { default: case <-ctx.Done(): return ctx.Err() } } }) // 2nd goroutine receives hits and deserializes them. // // If you want, setup a number of goroutines handling deserialization in parallel. g.Go(func() error { for hit := range hits { // Deserialize var tw Tweet err := json.Unmarshal(hit, &tw) if err != nil { return err } // Do something with the tweet here, e.g. send it to another channel // for further processing. _ = tw // Terminate early? select { default: case <-ctx.Done(): return ctx.Err() } } return nil }) // Check whether any goroutines failed. if err := g.Wait(); err != nil { panic(err) } // Done. fmt.Print("Successfully processed tweets in parallel via ScrollService.\n") }