{"id":13614365,"url":"https://github.com/alecthomas/waitgroup","last_synced_at":"2025-07-14T18:39:49.308Z","repository":{"id":61627391,"uuid":"546372906","full_name":"alecthomas/waitgroup","owner":"alecthomas","description":"Like sync.WaitGroup and ergroup.Group had a baby.","archived":false,"fork":false,"pushed_at":"2025-07-12T05:46:43.000Z","size":13,"stargazers_count":7,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-12T07:20:41.755Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alecthomas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["alecthomas"]}},"created_at":"2022-10-06T01:27:08.000Z","updated_at":"2025-05-30T22:25:25.000Z","dependencies_parsed_at":"2024-03-06T06:39:40.515Z","dependency_job_id":"0d4d285f-86fd-44b5-8eeb-d857b2b70935","html_url":"https://github.com/alecthomas/waitgroup","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/alecthomas/waitgroup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fwaitgroup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fwaitgroup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fwaitgroup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fwaitgroup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alecthomas","download_url":"https://codeload.github.com/alecthomas/waitgroup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fwaitgroup/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265333900,"owners_count":23748895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T20:01:00.676Z","updated_at":"2025-07-14T18:39:49.278Z","avatar_url":"https://github.com/alecthomas.png","language":"Go","funding_links":["https://github.com/sponsors/alecthomas"],"categories":["Go"],"sub_categories":[],"readme":"# Like sync.WaitGroup and errgroup.Group had a baby\n\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/alecthomas/waitgroup)](https://pkg.go.dev/github.com/alecthomas/waitgroup) [![CI](https://github.com/alecthomas/assert/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/assert/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/waitgroup)](https://goreportcard.com/report/github.com/alecthomas/waitgroup) [![Slack chat](https://img.shields.io/static/v1?logo=slack\u0026style=flat\u0026label=slack\u0026color=green\u0026message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3)\n\n## Motivation\n\nA pattern I encounter fairly frequently with long-running services is the\nfollowing lifecycle:\n\n```go\n// Start the service in the background, or return any startup errors.\nStart() error\n// Wait for the service to complete and return any errors.\nWait() error\n// Close stops the service and returns any errors.\nClose() error\n```\n\nThe `Start()` function usually creates some number of background tasks,\nlisteners, etc. Then at shutdown it will need to wait for them, in addition to\nperforming other synchronous shutdown tasks such as closing connections or\ncleaning up resources. A combination of `sync.WaitGroup` and `errgroup.Group`\ncan solve this, but there are a couple of problems:\n\n1. There is no safe way to balance `Add()` and `Done()` calls when they are in\n    separate methods, `Start()` and `Close()` in this case.\n2. `errgroup.Group` does not support explicit `Add()` and `Done()` calls, so a\n   separate and unrelated `sync.WaitGroup` is required.\n\n## Usage\n\nHere's a full example illustrating how this is typically used (not tested):\n\n```go\n\n// ShutdownDeadline is the maximum time to wait for the service to shutdown.\nconst ShutdownDeadline = time.Second * 60\n\ntype Service struct {\n    wg    *waitgroup.Group // Wait for all aspects of the service to stop.\n    conns *waitgroup.Group // Connection draining group.\n\n    cancel func()          // Call to forcibly terminate background tasks.\n\n    l net.Listener\n    listenTask waitgroup.ID // waitgroup ID associated with stopping the listener.\n}\n\nfunc (s *Service) Start(addr string) error {\n    // Create a context for the waitgroups, along with a cancellation function\n    // that we will use to tell the server to shut down.\n    ctx, cancel := context.WithCancel(context.Background())\n    s.cancel = cancel\n\n    s.wg = waitgroup.WithContext(ctx)\n    s.conns = waitgroup.WithContext(ctx)\n\n    s.wg.Link(s.conns) // Outer group waits for connection draining group.\n\n    l, err := net.Listen(\"tcp\", addr)\n    if err != nil {\n        return err\n    }\n    s.l = l\n    // Record a task ID for the listener, decremented when the listener is stopped.\n    s.listenTask = s.wg.Add()\n\n    // Start a task for the accept loop (not implemented here). The accept loop\n    // tracks each new connection in the conns waitgroup.\n    s.wg.Go(s.accept)\n\n    return nil\n}\n\nfunc (s *Service) Close() error {\n    ctx, cancel := context.WithTimeout(context.Background(), ShutdownDeadline)\n    defer cancel()\n\n    // Tell the waitgroups to shutdown.\n    s.cancel()\n\n    merr := multierror.Errors{}\n\n    // First, wait for connection drain to complete.\n    err := s.conns.Wait(ctx)\n    if err != nil {\n        merr.Add(err)\n    }\n\n    // Close the listener and mark the task as done.\n    err = s.l.Close()\n    if err != nil {\n        merr.Add(err)\n    }\n    s.listenTask.Done(s.listenTask)\n\n    // Finally, wait for the outer group to complete, at which point everything will be cleaned up.\n    err = s.wg.Wait(ctx)\n    if err != nil {\n        merr.Add(err)\n    }\n\n    return merr\n}\n\nfunc (s *Service) Wait() error {\n    return s.wg.Wait()\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecthomas%2Fwaitgroup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falecthomas%2Fwaitgroup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecthomas%2Fwaitgroup/lists"}