Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "Ubuntu",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers-extra/features/mise:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"postCreateCommand": "bash .devcontainer/post_create.sh",
"customizations": {
"vscode": {
"extensions": [
"golang.go"
]
}
}
}
13 changes: 13 additions & 0 deletions .devcontainer/post_create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

# note that bash will read from ~/.profile or ~/.bash_profile if the latter exists
# ergo, you may want to check to see which is defined on your system and only append to the existing file
echo 'eval "$(mise activate bash --shims)"' >>~/.bash_profile # this sets up non-interactive sessions
echo 'eval "$(mise activate bash)"' >>~/.bashrc # this sets up interactive sessions
Comment thread
min0625 marked this conversation as resolved.

mise trust .

mise install

mise exec -- go install -v golang.org/x/tools/gopls@latest
mise exec -- go install -v github.com/go-delve/delve/cmd/dlv@latest
Comment thread
min0625 marked this conversation as resolved.
41 changes: 0 additions & 41 deletions .github/workflows/go.yml

This file was deleted.

25 changes: 25 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: PR Check

on:
pull_request:
branches:
- main

jobs:
check:
name: Lint & Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Install mise
uses: jdx/mise-action@v4

- name: Download Go dependencies
run: go mod download

- name: Run check (lint + test)
run: make check
2 changes: 0 additions & 2 deletions .tool-versions

This file was deleted.

27 changes: 27 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch test function",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}",
"args": [
"-test.run",
"MyTestFunction"
]
},
{
"name": "Launch Workspace",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
}
]
}
17 changes: 17 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"go.useLanguageServer": true,
"go.toolsManagement.autoUpdate": true,
"go.lintTool": "golangci-lint-v2",
"go.lintFlags": [
"--path-mode=abs",
"--fast-only"
],
"go.alternateTools": {
"customFormatter": "golangci-lint-v2"
},
"go.formatTool": "custom",
"go.formatFlags": [
"fmt",
"--stdin"
]
}
96 changes: 96 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# AGENTS.md

This file provides instructions and conventions for AI coding agents working in this repository.

## Project Overview

`github.com/min0625/errgroup` is a Go library that extends [`golang.org/x/sync/errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup) with panic recovery. Panics occurring in goroutines started by `Go` or `TryGo` are caught and re-panicked inside `Wait`, wrapped as `PanicError` or `PanicValue`.

### Packages

| Path | Module path | Description |
|------|-------------|-------------|
| `/` (root) | `github.com/min0625/errgroup` | Drop-in replacement for `golang.org/x/sync/errgroup` |
| `x/errgroup/` | `github.com/min0625/errgroup/x/errgroup` | Context-aware variant — passes `context.Context` into each goroutine function |

## Repository Structure

```
errgroup.go # Core Group type and methods
panic.go # PanicError, PanicValue types and exception() helper
errgroup_test.go # Tests for the root package
example_test.go # Runnable examples for the root package
go.mod # Module definition
Makefile # Developer commands
mise.toml # Pinned tool versions (Go, golangci-lint)
x/errgroup/
errgroup.go # Context-aware Group type
panic.go # Re-exports panic types from the root package (if any)
errgroup_test.go # Tests for the x/errgroup package
example_test.go # Runnable examples for the x/errgroup package
```

## Tool Versions

Tool versions are pinned in `mise.toml`. Install them with:

```sh
mise install
```

| Tool | Version |
|------|---------|
| Go | 1.24.x |
| golangci-lint | 2.x |

## Common Commands

All commands should be run from the repository root.

| Command | Description |
|---------|-------------|
| `make fmt` | Format all Go source files via `golangci-lint fmt` |
| `make lint` | Run linter (`golangci-lint run`) |
| `make fix` | Run linter with auto-fix (`golangci-lint run --fix`) |
| `make test` | Run all tests with race detector (`go test -v -race -failfast ./...`) |
| `make check` | Run `lint` then `test` (full CI gate) |

Always run `make check` before considering a change complete.

## Development Guidelines

### Code Style

- Follow standard Go conventions (`gofmt`, `goimports`).
- Use `make fmt` to format code; do not manually reformat.
- Exported symbols must have GoDoc comments. Keep them concise and consistent with the existing style.
- Internal helpers (unexported) should be commented only when non-obvious.

### Testing

- All new behaviour must have corresponding tests.
- Tests must be parallel where possible (`t.Parallel()`).
- Use [`github.com/stretchr/testify`](https://pkg.go.dev/github.com/stretchr/testify) (`assert`, `require`) — consistent with the existing test suite.
- Run tests with the race detector: `make test` (uses `-race` flag).
- Place tests in `_test` package (external test package), e.g. `package errgroup_test`.

### Panic Handling

- `PanicError` wraps recovered values that implement `error`.
- `PanicValue` wraps all other recovered values.
- Both expose a `Stack []byte` field with the captured stack trace.
- When multiple goroutines panic concurrently, only the first panic is propagated; the rest are silently discarded. Preserve this invariant.

### Dual-Package Consistency

Changes to the root package (`errgroup.go`, `panic.go`) that affect the public API or panic-recovery behaviour must be reflected in `x/errgroup/` where applicable, and vice versa.

### Dependencies

- Keep dependencies minimal. The only non-test runtime dependency is `golang.org/x/sync`.
- Run `go mod tidy` after adding or removing imports.

### Linting

- The project uses `golangci-lint`. Lint rules are configured in the repository (check for `.golangci.yml` or inline `//nolint` directives).
- Suppress a lint warning with `//nolint:<linter> // <reason>` only when genuinely necessary, not to silence legitimate issues.
24 changes: 5 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
MODULE_DIRS = .

gowork:
go work init .

tidy:
go mod tidy

install-asdf:
-asdf install

install: install-asdf tidy
# curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v2.5.0

fmt: install
fmt:
golangci-lint fmt -v ./...

fix: install
fix:
golangci-lint run -v --fix ./...

lint: install
lint:
golangci-lint run -v ./...

test: install
test:
go test -v -race -failfast ./...

check: fix lint test
check: lint test
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
# errgroup
[![Go Reference](https://pkg.go.dev/badge/github.com/min0625/errgroup.svg)](https://pkg.go.dev/github.com/min0625/errgroup)

A recoverable errgroup based on `golang.org/x/sync/errgroup` that can recover from panics. Panics are caught and re-panicked in the Wait function.
A recoverable errgroup based on `golang.org/x/sync/errgroup` that can recover from panics. Panics are caught and re-panicked in the `Wait` call.

Ref: https://github.com/golang/go/issues/53757

## Packages

| Package | Description |
|---|---|
| `github.com/min0625/errgroup` | Drop-in replacement for `golang.org/x/sync/errgroup` with panic recovery |
| [`github.com/min0625/errgroup/x/errgroup`](x/errgroup/README.md) | Context-aware variant — passes `context.Context` directly into each goroutine function |

## Installation
```sh
go get github.com/min0625/errgroup
```

## Panic behaviour

Panics in goroutines started by `Go` or `TryGo` are caught and re-panicked inside `Wait`, wrapped as:

- `PanicError` — when the panicked value implements `error`
- `PanicValue` — for all other values

Both types expose a `Stack` field containing the stack trace captured at the point of the panic.

If multiple goroutines panic concurrently, only the first panic is propagated; the rest are silently discarded.

## Example
```go

Expand Down Expand Up @@ -42,6 +60,8 @@ func Example() {

if err := g.Wait(); err != nil {
// Handle error
fmt.Println(err)
return
}

// Output: oops
Expand Down
26 changes: 25 additions & 1 deletion errgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
//
// A zero Group is valid, has no limit on the number of active goroutines,
// and does not cancel on error.
//
// Unhandled panics from goroutines started by Go or TryGo are caught and
// re-panicked in the Wait call, wrapped as PanicError or PanicValue.
type Group struct {
once sync.Once
panicValue chan any
Expand Down Expand Up @@ -51,6 +54,9 @@ func (g *Group) SetLimit(n int) {
//
// The first call to return a non-nil error cancels the group's context, if the
// group was created by calling WithContext. The error will be returned by Wait.
//
// If f panics, the panic is caught and re-panicked in Wait, wrapped as a
// PanicError or PanicValue.
func (g *Group) Go(f func() error) {
g.eg.Go(g.do(f))
}
Expand All @@ -59,12 +65,26 @@ func (g *Group) Go(f func() error) {
// active goroutines in the group is currently below the configured limit.
//
// The return value reports whether the goroutine was started.
//
// If f panics, the panic is caught and re-panicked in Wait, wrapped as a
// PanicError or PanicValue.
func (g *Group) TryGo(f func() error) bool {
return g.eg.TryGo(g.do(f))
}

// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
//
// If any goroutine panicked, Wait re-panics with the recovered value wrapped
// as a PanicError (if the panic value implements error) or PanicValue.
// If multiple goroutines panic, only the first panic is re-panicked; the rest
// are silently discarded.
//
// Implementation note: Wait runs g.eg.Wait() in a separate goroutine so that
// a panic inside eg.Wait itself (e.g. from the underlying errgroup) can also be
// caught and forwarded through the same panicValue channel. The inner goroutine
// writes to a buffered channel of size 1, so it always completes and is never
// leaked, even when Wait returns early via a panic branch.
func (g *Group) Wait() error {
g.init()

Expand All @@ -82,9 +102,13 @@ func (g *Group) Wait() error {

select {
case panicValue := <-g.panicValue:
// A goroutine panicked before eg.Wait returned. The inner goroutine
// will eventually write to waitError (buffered) and exit on its own.
panic(panicValue)
case err := <-waitError:
// Double check panic occurred
// eg.Wait returned normally; do a non-blocking check in case a panic
// was also sent (e.g. the goroutine panicked and the sentinel error
// arrived first).
select {
case panicValue := <-g.panicValue:
panic(panicValue)
Expand Down
Loading