{"id":15404530,"url":"https://github.com/k1low/donegroup","last_synced_at":"2025-06-20T14:09:17.261Z","repository":{"id":220940953,"uuid":"752986644","full_name":"k1LoW/donegroup","owner":"k1LoW","description":"donegroup is a package that provides a graceful cleanup transaction to context.Context when the context is canceled.","archived":false,"fork":false,"pushed_at":"2024-07-01T12:33:31.000Z","size":129,"stargazers_count":29,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T22:51:18.213Z","etag":null,"topics":["cleanup","context","errgroup","go","graceful"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k1LoW.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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},"funding":{"github":"k1LoW","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2024-02-05T08:47:15.000Z","updated_at":"2024-10-18T08:59:55.000Z","dependencies_parsed_at":"2024-10-19T10:21:02.768Z","dependency_job_id":null,"html_url":"https://github.com/k1LoW/donegroup","commit_stats":{"total_commits":136,"total_committers":3,"mean_commits":"45.333333333333336","dds":0.3088235294117647,"last_synced_commit":"21f315c3a0f6187aa6f2e09a1739832db27b4824"},"previous_names":["k1low/donegroup"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/k1LoW/donegroup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fdonegroup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fdonegroup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fdonegroup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fdonegroup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k1LoW","download_url":"https://codeload.github.com/k1LoW/donegroup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fdonegroup/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260958957,"owners_count":23088808,"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":["cleanup","context","errgroup","go","graceful"],"created_at":"2024-10-01T16:13:32.310Z","updated_at":"2025-06-20T14:09:12.247Z","avatar_url":"https://github.com/k1LoW.png","language":"Go","funding_links":["https://github.com/sponsors/k1LoW"],"categories":[],"sub_categories":[],"readme":"# donegroup [![Go Reference](https://pkg.go.dev/badge/github.com/k1LoW/donegroup.svg)](https://pkg.go.dev/github.com/k1LoW/donegroup) [![CI](https://github.com/k1LoW/donegroup/actions/workflows/ci.yml/badge.svg)](https://github.com/k1LoW/donegroup/actions/workflows/ci.yml) ![Coverage](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/donegroup/coverage.svg) ![Code to Test Ratio](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/donegroup/ratio.svg) ![Test Execution Time](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/donegroup/time.svg)\n\n`donegroup` is a package that provides a graceful cleanup transaction to context.Context when the context is canceled ( **done** ).\n\n\u003e sync.WaitGroup + \u003c-ctx.Done() = donegroup\n\n## Usage\n\nUse [donegroup.WithCancel](https://pkg.go.dev/github.com/k1LoW/donegroup#WithCancel) instead of [context.WithCancel](https://pkg.go.dev/context#WithCancel).\n\nThen, it can wait for the cleanup processes associated with the context using [donegroup.Wait](https://pkg.go.dev/github.com/k1LoW/donegroup#Wait).\n\n``` go\n// before\nctx, cancel := context.WithCancel(context.Background())\ndefer cancel()\n```\n\n↓\n\n``` go\n// after\nctx, cancel := donegroup.WithCancel(context.Background())\ndefer func() {\n\tcancel()\n\tif err := donegroup.Wait(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}()\n```\n\n### [donegroup.Cleanup](https://pkg.go.dev/github.com/k1LoW/donegroup#Cleanup) ( Basic usage )\n\n``` mermaid\ngantt\n    dateFormat  mm\n    axisFormat  \n\n    section main\n        donegroup.WithCancel :milestone, m1, 00, 0m\n        cancel context using cancel() : milestone, m2, 03, 0m\n        donegroup.Wait :milestone, m3, 04, 0m\n        waiting for all registered funcs to finish     :b, 04, 2m\n\n    section cleanup func1\n        register func1 using donegroup.Cleanup :milestone, m1, 01, 0m\n        waiting for context cancellation   :a, 01, 2m\n        executing func1  :active, b, 03, 3m\n\n    section cleanup func2\n        register func2 using donegroup.Cleanup :milestone, m3, 02, 0m\n        waiting for context cancellation  :a, 02, 1m\n        executing func2 :active, b, 03, 2m\n```\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/k1LoW/donegroup\"\n)\n\nfunc main() {\n\tctx, cancel := donegroup.WithCancel(context.Background())\n\n\t// Cleanup process func1 of some kind\n\tif err := donegroup.Cleanup(ctx, func() error {\n\t\tfmt.Println(\"cleanup func1\")\n\t\treturn nil\n\t}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Cleanup process func2 of some kind\n\tif err := donegroup.Cleanup(ctx, func() error {\n\t\ttime.Sleep(1 * time.Second)\n\t\tfmt.Println(\"cleanup func2\")\n\t\treturn nil\n\t}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer func() {\n\t\tcancel()\n\t\tif err := donegroup.Wait(ctx); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n\n\t// Main process of some kind\n\tfmt.Println(\"main\")\n\n\t// Output:\n\t// main finish\n\t// cleanup func1\n\t// cleanup func2\n}\n```\n\n[dongroup.Cleanup](https://pkg.go.dev/github.com/k1LoW/donegroup#Cleanup) is similar in usage to [testing.T.Cleanup](https://pkg.go.dev/testing#T.Cleanup), but the order of execution is not guaranteed.\n\n### [donegroup.WaitWithTimeout](https://pkg.go.dev/github.com/k1LoW/donegroup#WaitWithTimeout) ( Wait for a specified duration )\n\nUsing [donegroup.WaitWithTimeout](https://pkg.go.dev/github.com/k1LoW/donegroup#WaitWithTimeout), it is possible to set a timeout for the cleanup processes.\n\nNote that each cleanup process must handle its own context argument.\n\n``` mermaid\ngantt\n    dateFormat  mm\n    axisFormat  \n\n    section main\n        donegroup.WithCancel :milestone, m1, 00, 0m\n        cancel context using cancel() : milestone, m2, 03, 0m\n        donegroup.WaitWithTimeout :milestone, m3, 04, 0m\n        waiting for all registered funcs to finish     :b, 04, 1m\n        timeout :milestone, m4, 05, 0m\n\n    section cleanup func\n        register func using donegroup.Cleanup :milestone, m1, 01, 0m\n        waiting for context cancellation   :a, 01, 2m\n        executing func  :active, b, 03, 3m\n```\n\n```go\nctx, cancel := donegroup.WithCancel(context.Background())\n\n// Cleanup process of some kind\nif err := donegroup.Cleanup(ctx, func() error {\n\tfmt.Println(\"cleanup start\")\n\tfor i := 0; i \u003c 10; i++ {\n\t\ttime.Sleep(2 * time.Millisecond)\n\t}\n\tfmt.Println(\"cleanup finish\")\n\treturn nil\n}); err != nil {\n\tlog.Fatal(err)\n}\n\ndefer func() {\n\tcancel()\n\ttimeout := 5 * time.Millisecond\n\tif err := WaitWithTimeout(ctx, timeout); err != nil {\n\t\tfmt.Println(err)\n\t}\n}()\n\n// Main process of some kind\nfmt.Println(\"main start\")\n\nfmt.Println(\"main finish\")\n\n// Output:\n// main start\n// main finish\n// cleanup start\n// context deadline exceeded\n```\n\n### [donegroup.Awaiter](https://pkg.go.dev/github.com/k1LoW/donegroup#Awaiter)\n\nIn addition to using [donegroup.Cleanup](https://pkg.go.dev/github.com/k1LoW/donegroup#Cleanup) to register a cleanup function after context cancellation, it is possible to use [donegroup.Awaiter](https://pkg.go.dev/github.com/k1LoW/donegroup#Awaiter) to make the execution of an arbitrary process wait.\n\n``` mermaid\ngantt\n    dateFormat  mm\n    axisFormat  \n\n    section main\n        donegroup.WithCancel :milestone, m1, 00, 0m\n        cancel context using cancel() : milestone, m2, 03, 0m\n        donegroup.Wait :milestone, m3, 04, 0m\n        waiting for all registered funcs to finish     :b, 04, 1m\n\n    section func on goroutine\n        executing go func  :active, b, 01, 4m\n        donegroup.Awaiter :milestone, m3, 01, 0m\n        completed() :milestone, m3, 05, 0m\n```\n\n``` go\nctx, cancel := donegroup.WithCancel(context.Background())\n\ngo func() {\n\tcompleted, err := donegroup.Awaiter(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t\treturn\n\t}\n\ttime.Sleep(1000 * time.Millisecond)\n\tfmt.Println(\"do something\")\n\tcompleted()\n}()\n\n// Main process of some kind\nfmt.Println(\"main\")\ntime.Sleep(10 * time.Millisecond)\n\ncancel()\nif err := donegroup.Wait(ctx); err != nil {\n\tlog.Fatal(err)\n}\n\nfmt.Println(\"finish\")\n\n// Output:\n// main\n// do something\n// finish\n```\n\nIt is also possible to guarantee the execution of a function block using `defer donegroup.Awaitable(ctx)()`.\n\n``` go\ngo func() {\n\tdefer donegroup.Awaitable(ctx)()\n\ttime.Sleep(1000 * time.Millisecond)\n\tfmt.Println(\"do something\")\n}()\n```\n\n### [donegroup.Go](https://pkg.go.dev/github.com/k1LoW/donegroup#Go) ( Syntax sugar for `go func()` and donegroup.Awaiter )\n\n[donegroup.Go](https://pkg.go.dev/github.com/k1LoW/donegroup#Go) can execute arbitrary process asynchronously while still waiting for it to finish, similar to [donegroup.Awaiter](https://pkg.go.dev/github.com/k1LoW/donegroup#Awaiter).\n\n``` mermaid\ngantt\n    dateFormat  mm\n    axisFormat  \n\n    section main\n        donegroup.WithCancel :milestone, m1, 00, 0m\n        cancel context using cancel() : milestone, m2, 03, 0m\n        donegroup.Wait :milestone, m3, 04, 0m\n        waiting for all registered funcs to finish     :b, 04, 1m\n\n    section func on donegroup.Go\n        register and execute func using donegroup.Go :milestone, m3, 01, 0m\n        executing func  :active, b, 01, 4m\n```\n\n``` go\ndonegroup.Go(ctx, func() error {\n\ttime.Sleep(1000 * time.Millisecond)\n\tfmt.Println(\"do something\")\n\treturn nil\n}()\n```\n\nAlso, with [donegroup.Go](https://pkg.go.dev/github.com/k1LoW/donegroup#Go), the error can be received via [donegroup.Wait](https://pkg.go.dev/github.com/k1LoW/donegroup#Wait).\n\n### [donegroup.Cancel](https://pkg.go.dev/github.com/k1LoW/donegroup#Cancel)\n\n[donegroup.Cancel](https://pkg.go.dev/github.com/k1LoW/donegroup#Cancel) can cancel the context.\n\nCan be cancelled anywhere with the context.\n\n``` go\nctx, _ := donegroup.WithCancel(context.Background())\n\ndefer func() {\n\tif err := donegroup.Cancel(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := donegroup.Wait(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk1low%2Fdonegroup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk1low%2Fdonegroup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk1low%2Fdonegroup/lists"}