Skip to content

Advanced Examples

Group with Semaphore Limit and Fail-Fast

Limit concurrent execution and stop early on the first error.

go
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/foomo/gofuncy"
)

func main() {
	ctx := context.Background()

	g := gofuncy.NewGroup(ctx,
		gofuncy.WithLimit(5),
		gofuncy.WithFailFast(),
	)

	for i := range 20 {
		g.Add(func(ctx context.Context) error {
			// Check context before doing work -- fail-fast cancels the context
			if ctx.Err() != nil {
				return ctx.Err()
			}

			if i == 7 {
				return errors.New("task 7 failed")
			}

			fmt.Printf("completed task %d\n", i)
			return nil
		})
	}

	if err := g.Wait(); err != nil {
		fmt.Println("group error:", err)
	}
}

Shared Limiter Across Call Sites

Use a *semaphore.Weighted to limit concurrency across multiple independent Go calls.

go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/foomo/gofuncy"
	"golang.org/x/sync/semaphore"
)

func main() {
	ctx := context.Background()

	// At most 3 goroutines running at any time, across all call sites
	limiter := semaphore.NewWeighted(3)

	for i := range 10 {
		gofuncy.Go(ctx, func(ctx context.Context) error {
			fmt.Printf("worker %d started\n", i)
			time.Sleep(100 * time.Millisecond)
			fmt.Printf("worker %d done\n", i)
			return nil
		},
			gofuncy.WithLimiter(limiter),
		)
	}

	time.Sleep(500 * time.Millisecond)
}

Stall Detection

Detect goroutines that take longer than expected without cancelling them.

go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/foomo/gofuncy"
)

func main() {
	ctx := context.Background()

	gofuncy.Go(ctx, func(ctx context.Context) error {
		// This takes longer than the stall threshold
		time.Sleep(3 * time.Second)
		return nil
	},
		gofuncy.WithName("slow-task"),
		gofuncy.WithStallThreshold(1*time.Second),
		gofuncy.WithStallHandler(func(ctx context.Context, name string, elapsed time.Duration) {
			fmt.Printf("STALL: %s has been running for %v\n", name, elapsed)
		}),
	)

	time.Sleep(4 * time.Second)
	// Output: STALL: slow-task has been running for 1s
}

Custom Middleware

Add cross-cutting behavior like logging or retries using middleware.

go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/foomo/gofuncy"
)

// loggingMiddleware logs the start and end of each function execution.
func loggingMiddleware(next gofuncy.Func) gofuncy.Func {
	return func(ctx context.Context) error {
		name := gofuncy.NameFromContext(ctx)
		fmt.Printf("[%s] starting\n", name)
		start := time.Now()

		err := next(ctx)

		dur := time.Since(start)
		if err != nil {
			fmt.Printf("[%s] failed after %v: %v\n", name, dur, err)
		} else {
			fmt.Printf("[%s] completed in %v\n", name, dur)
		}
		return err
	}
}

func main() {
	ctx := context.Background()

	g := gofuncy.NewGroup(ctx,
		gofuncy.WithMiddleware(loggingMiddleware),
	)

	g.Add(func(ctx context.Context) error {
		time.Sleep(50 * time.Millisecond)
		return nil
	})

	g.Add(func(ctx context.Context) error {
		time.Sleep(100 * time.Millisecond)
		return nil
	})

	_ = g.Wait()
	// Output:
	// [task-a] starting
	// [task-b] starting
	// [task-a] completed in 50ms
	// [task-b] completed in 100ms
}

Map with Timeout

Transform items concurrently with a per-group timeout.

go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/foomo/gofuncy"
)

func main() {
	ctx := context.Background()

	ids := []int{1, 2, 3, 4, 5}

	results, err := gofuncy.Map(ctx, ids, func(ctx context.Context, id int) (string, error) {
		// Simulate variable latency
		time.Sleep(time.Duration(id*100) * time.Millisecond)

		// Respect context cancellation
		if ctx.Err() != nil {
			return "", ctx.Err()
		}

		return fmt.Sprintf("result-%d", id), nil
	},
		gofuncy.WithTimeout(250*time.Millisecond),
		gofuncy.WithLimit(3),
	)

	fmt.Println("results:", results)
	if err != nil {
		fmt.Println("errors:", err)
	}
}

Disabling Telemetry

Turn off all OpenTelemetry instrumentation for performance-critical paths.

go
package main

import (
	"context"

	"github.com/foomo/gofuncy"
)

func main() {
	ctx := context.Background()

	gofuncy.Go(ctx, func(ctx context.Context) error {
		// Hot path -- no telemetry overhead
		return nil
	},
		gofuncy.WithoutTracing(),
		gofuncy.WithoutStartedCounter(),
		gofuncy.WithoutErrorCounter(),
		gofuncy.WithoutActiveUpDownCounter(),
	)
}