{"id":46260004,"url":"https://github.com/cinar/resile","last_synced_at":"2026-03-16T02:09:50.587Z","repository":{"id":341509988,"uuid":"1170330430","full_name":"cinar/resile","owner":"cinar","description":"Resile is an ergonomic, type-safe execution resilience and retry library for Go. Inspired by Python’s stamina, it features generic execution wrappers, AWS Full Jitter backoff, native Retry-After header support, and zero-dependency observability for distributed systems.","archived":false,"fork":false,"pushed_at":"2026-03-06T05:16:19.000Z","size":82,"stargazers_count":4,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-07T09:41:06.773Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/cinar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-02T02:11:10.000Z","updated_at":"2026-03-06T05:16:22.000Z","dependencies_parsed_at":"2026-03-06T03:02:45.784Z","dependency_job_id":null,"html_url":"https://github.com/cinar/resile","commit_stats":null,"previous_names":["cinar/resile"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/cinar/resile","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinar%2Fresile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinar%2Fresile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinar%2Fresile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinar%2Fresile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cinar","download_url":"https://codeload.github.com/cinar/resile/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinar%2Fresile/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30246626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T00:58:18.660Z","status":"online","status_checked_at":"2026-03-08T02:00:06.215Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2026-03-04T01:26:30.647Z","updated_at":"2026-03-16T02:09:50.578Z","avatar_url":"https://github.com/cinar.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Resile: Ergonomic Execution Resilience for Go\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/cinar/resile.svg)](https://pkg.go.dev/github.com/cinar/resile)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Build Status](https://github.com/cinar/resile/actions/workflows/ci.yml/badge.svg)](https://github.com/cinar/resile/actions/workflows/ci.yml)\n[![Codecov](https://codecov.io/gh/cinar/resile/branch/main/graph/badge.svg)](https://codecov.io/gh/cinar/resile)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cinar/resile)](https://goreportcard.com/report/github.com/cinar/resile)\n\n**Resile** is a production-grade execution resilience and retry library for Go, inspired by Python's [stamina](https://github.com/hynek/stamina). It provides a type-safe, ergonomic, and highly observable way to handle transient failures in distributed systems.\n\n---\n\n## Table of Contents\n- [Installation](#installation)\n- [Why Resile?](#why-resile)\n- [Articles \u0026 Tutorials](#articles--tutorials)\n- [Examples](#examples)\n- [Common Use Cases](#common-use-cases)\n  - [Simple Retries](#1-simple-retries)\n  - [Value-Yielding Retries (Generics)](#2-value-yielding-retries-generics)\n  - [Request Hedging (Speculative Retries)](#3-request-hedging-speculative-retries)\n  - [Stateful Retries \u0026 Endpoint Rotation](#4-stateful-retries--endpoint-rotation)\n  - [Handling Rate Limits (Retry-After)](#5-handling-rate-limits-retry-after)\n  - [Aborting Retries (Pushback Signal)](#6-aborting-retries-pushback-signal)\n  - [Fallback Strategies](#7-fallback-strategies)\n  - [Layered Defense with Circuit Breaker](#8-layered-defense-with-circuit-breaker)\n  - [Macro-Level Protection (Adaptive Retries)](#9-macro-level-protection-adaptive-retries)\n  - [Structured Logging \u0026 Telemetry](#10-structured-logging--telemetry)\n  - [Panic Recovery (\"Let It Crash\")](#11-panic-recovery-let-it-crash)\n  - [Fast Unit Testing](#12-fast-unit-testing)\n  - [Reusable Clients \u0026 Dependency Injection](#13-reusable-clients--dependency-injection)\n  - [Marking Errors as Fatal](#14-marking-errors-as-fatal)\n  - [Custom Error Filtering](#15-custom-error-filtering)\n- [Configuration Reference](#configuration-reference)\n- [Architecture \u0026 Design](#architecture--design)\n- [License](#license)\n\n---\n\n## Installation\n\n```bash\ngo get github.com/cinar/resile\n```\n\n---\n\n## Why Resile?\n\nIn distributed systems, transient failures are a mathematical certainty. Resile simplifies the \"Correct Way\" to retry:\n- **AWS Full Jitter**: Uses the industry-standard algorithm to prevent \"thundering herd\" synchronization.\n- **Adaptive Retries**: Built-in token bucket rate limiting to prevent \"retry storms\" across a cluster.\n- **Generic-First**: No `interface{}` or reflection. Full compile-time type safety.\n- **Context-Aware**: Strictly respects `context.Context` cancellation and deadlines.\n- **Zero-Dependency Core**: The core library only depends on the Go standard library.\n- **Opinionated Defaults**: Sensible production-ready defaults (5 attempts, exponential backoff).\n\n---\n\n## Articles \u0026 Tutorials\n\nWant to learn more about the philosophy behind Resile and advanced resilience patterns in Go? Check out these deep dives:\n\n* [Stop Writing Manual Retry Loops in Go: Why Your Current Logic is Probably Dangerous](https://dev.to/onurcinar/stop-writing-manual-retry-loops-in-go-why-your-current-logic-is-probably-dangerous-5bj5)\n* [Python's Stamina for Go: Bringing Ergonomic Resilience to Gophers](https://dev.to/onurcinar/pythons-stamina-for-go-bringing-ergonomic-resilience-to-gophers-1lf2)\n* [Beating Tail Latency: A Guide to Request Hedging in Go Microservices](https://dev.to/onurcinar/beating-tail-latency-a-guide-to-request-hedging-in-go-microservices-p81)\n* [Preventing Microservice Meltdowns: Adaptive Retries and Circuit Breakers in Go](https://dev.to/onurcinar/preventing-microservice-meltdowns-adaptive-retries-and-circuit-breakers-in-go-30ho)\n\n\n## Examples\n\nThe [examples/](examples/) directory contains standalone programs showing how to use Resile in various scenarios:\n\n- **[Basic Retry](examples/basic/main.go)**: Simple `Do` and `DoErr` calls.\n- **[Request Hedging](examples/hedging/main.go)**: Reducing tail latency with speculative retries.\n- **[HTTP with Rate Limits](examples/http/main.go)**: Respecting `Retry-After` headers and using `slog`.\n- **[Fallback Strategies](examples/fallback/main.go)**: Returning stale data when all attempts fail.\n- **[Stateful Rotation](examples/stateful/main.go)**: Rotating API endpoints using `RetryState`.\n- **[Circuit Breaker](examples/circuitbreaker/main.go)**: Layering defensive strategies.\n- **[Adaptive Retries](examples/adaptiveretry/main.go)**: Preventing retry storms with a token bucket.\n- **[Pushback Signal](examples/pushback/main.go)**: Aborting retries immediately using `CancelAllRetries`.\n- **[Panic Recovery](examples/panicrecovery/main.go)**: Implementing Erlang's \"Let It Crash\" philosophy.\n\n---\n\n## Common Use Cases\n\n### 1. Simple Retries\nRetry a simple operation that only returns an error.\n\n```go\nerr := resile.DoErr(ctx, func(ctx context.Context) error {\n    return db.PingContext(ctx)\n})\n```\n\n### 2. Value-Yielding Retries (Generics)\nFetch data with full type safety. The return type is inferred from your closure.\n\n```go\n// val is automatically inferred as *User\nuser, err := resile.Do(ctx, func(ctx context.Context) (*User, error) {\n    return apiClient.GetUser(ctx, userID)\n}, resile.WithMaxAttempts(3))\n```\n\n### 3. Request Hedging (Speculative Retries)\nSpeculative retries reduce tail latency by starting a second request if the first one doesn't finish within a configured `HedgingDelay`. The first successful result is used, and the other is cancelled.\n\n```go\n// For value-yielding operations\ndata, err := resile.DoHedged(ctx, action, \n    resile.WithMaxAttempts(3),\n    resile.WithHedgingDelay(100 * time.Millisecond),\n)\n\n// For error-only operations\nerr := resile.DoErrHedged(ctx, action,\n    resile.WithMaxAttempts(2),\n    resile.WithHedgingDelay(50 * time.Millisecond),\n)\n```\n\n### 4. Stateful Retries \u0026 Endpoint Rotation\nUse `DoState` (or `DoErrState`) to access the `RetryState`, allowing you to rotate endpoints or fallback logic based on the failure history.\n\n```go\nendpoints := []string{\"api-v1.example.com\", \"api-v2.example.com\"}\n\ndata, err := resile.DoState(ctx, func(ctx context.Context, state resile.RetryState) (string, error) {\n    // Rotate endpoint based on attempt number\n    url := endpoints[state.Attempt % uint(len(endpoints))]\n    return client.Get(ctx, url)\n})\n```\n\n### 5. Handling Rate Limits (Retry-After)\nResile automatically detects if an error implements `RetryAfterError`. It can override the jittered backoff with a server-dictated duration and can also signal immediate termination (pushback).\n\n```go\ntype RateLimitError struct {\n    WaitUntil time.Time\n}\n\nfunc (e *RateLimitError) Error() string { return \"too many requests\" }\nfunc (e *RateLimitError) RetryAfter() time.Duration {\n    return time.Until(e.WaitUntil)\n}\nfunc (e *RateLimitError) CancelAllRetries() bool {\n    // Return true to abort the entire retry loop immediately.\n    return false \n}\n\n// Resile will sleep exactly until WaitUntil when this error is encountered.\n```\n\n### 6. Aborting Retries (Pushback Signal)\nIf a downstream service returns a terminal error (like \"Quota Exceeded\") that shouldn't be retried, implement `CancelAllRetries() bool` to abort the entire retry loop immediately.\n\n```go\ntype QuotaExceededError struct{}\nfunc (e *QuotaExceededError) Error() string { return \"quota exhausted\" }\nfunc (e *QuotaExceededError) CancelAllRetries() bool { return true }\n\n// Resile will stop immediately if this error is encountered,\n// even if more attempts are remaining.\n_, err := resile.Do(ctx, action, resile.WithMaxAttempts(10))\n```\n\n### 7. Fallback Strategies\nProvide a fallback function to handle cases where all retries are exhausted or the circuit breaker is open. This is useful for returning stale data or default values.\n\n```go\ndata, err := resile.Do(ctx, fetchData,\n    resile.WithMaxAttempts(3),\n    resile.WithFallback(func(ctx context.Context, err error) (string, error) {\n        // Return stale data from cache if the primary fetch fails\n        return cache.Get(ctx, key), nil \n    }),\n)\n```\n\n### 8. Layered Defense with Circuit Breaker\nCombine retries (for transient blips) with a circuit breaker (for systemic outages).\n\n```go\nimport \"github.com/cinar/resile/circuit\"\n\ncb := circuit.New(circuit.Config{\n    FailureThreshold: 5,\n    ResetTimeout:     30 * time.Second,\n})\n\n// Returns circuit.ErrCircuitOpen immediately if the downstream is failing consistently.\nerr := resile.DoErr(ctx, action, resile.WithCircuitBreaker(cb))\n```\n\n### 9. Macro-Level Protection (Adaptive Retries)\nPrevent \"retry storms\" by using a token bucket that is shared across your entire cluster of clients. If the downstream service is degraded, the bucket will quickly deplete, causing clients to fail fast locally instead of hammering the service.\n\n```go\n// Share this bucket across multiple executions/goroutines\nbucket := resile.DefaultAdaptiveBucket()\n\nerr := resile.DoErr(ctx, action, resile.WithAdaptiveBucket(bucket))\n```\n\n### 10. Structured Logging \u0026 Telemetry\nIntegrate with `slog` or `OpenTelemetry` without bloating your core dependencies.\n\n```go\nimport \"github.com/cinar/resile/telemetry/resileslog\"\n\nlogger := slog.Default()\nresile.Do(ctx, action, \n    resile.WithName(\"get-inventory\"), // Name your operation for metrics/logs\n    resile.WithInstrumenter(resileslog.New(logger)),\n)\n```\n\n### 11. Panic Recovery (\"Let It Crash\")\nConvert unexpected Go panics into retryable errors, allowing your application to reset to a known good state without a hard crash.\n\n```go\n// val will succeed even if the first attempt panics\nval, err := resile.Do(ctx, riskyAction, \n    resile.WithPanicRecovery(),\n)\n```\n\n### 12. Fast Unit Testing\nNever let retry timers slow down your CI. Use `WithTestingBypass` to make all retries execute instantly.\n\n```go\nfunc TestMyService(t *testing.T) {\n    ctx := resile.WithTestingBypass(context.Background())\n    \n    // This will retry 10 times instantly without sleeping.\n    err := service.Handle(ctx)\n}\n```\n\n### 13. Reusable Clients \u0026 Dependency Injection\nUse `resile.New()` to create a `Retryer` interface for cleaner code architecture and easier testing.\n\n```go\n// Create a reusable resilience strategy\nretryer := resile.New(\n    resile.WithMaxAttempts(3),\n    resile.WithBaseDelay(200 * time.Millisecond),\n)\n\n// Use the interface to execute actions\nerr := retryer.DoErr(ctx, func(ctx context.Context) error {\n    return service.Call(ctx)\n})\n```\n\n### 14. Marking Errors as Fatal\nSometimes you know an error is terminal and shouldn't be retried (e.g., \"Invalid API Key\"). Use `resile.FatalError()` to abort the retry loop immediately.\n\n```go\nerr := resile.DoErr(ctx, func(ctx context.Context) error {\n    err := client.Do()\n    if errors.Is(err, ErrAuthFailed) {\n        return resile.FatalError(err) // Stops retries immediately\n    }\n    return err\n})\n```\n\n### 15. Custom Error Filtering\nControl which errors trigger a retry using `WithRetryIf` (for exact matches) or `WithRetryIfFunc` (for custom logic like checking status codes).\n\n```go\nerr := resile.DoErr(ctx, action,\n    // Only retry if the error is ErrConnReset\n    resile.WithRetryIf(ErrConnReset),\n    \n    // OR use a custom function for complex logic\n    resile.WithRetryIfFunc(func(err error) bool {\n        return errors.Is(err, ErrTransient) || isTimeout(err)\n    }),\n)\n```\n\n---\n\n## Configuration Reference\n\n| Option | Description | Default |\n| :--- | :--- | :--- |\n| `WithName(string)` | Identifies the operation in logs/metrics. | `\"\"` |\n| `WithMaxAttempts(uint)` | Total number of attempts (initial + retries). | `5` |\n| `WithBaseDelay(duration)` | Initial backoff duration. | `100ms` |\n| `WithMaxDelay(duration)` | Maximum possible backoff duration. | `30s` |\n| `WithBackoff(Backoff)` | Custom backoff algorithm (e.g. constant). | `Full Jitter` |\n| `WithHedgingDelay(duration)`| Delay before speculative retries. | `0` |\n| `WithRetryIf(error)` | Only retry if `errors.Is(err, target)`. | All non-fatal |\n| `WithRetryIfFunc(func)` | Custom logic to decide if an error is retriable. | `nil` |\n| `WithCircuitBreaker(cb)` | Attaches a circuit breaker state machine. | `nil` |\n| `WithAdaptiveBucket(b)` | Attaches a token bucket for adaptive retries. | `nil` |\n| `WithInstrumenter(inst)` | Attaches telemetry (slog/OTel/Prometheus). | `nil` |\n| `WithFallback(f)` | Sets a generic fallback function. | `nil` |\n| `WithFallbackErr(f)` | Sets a fallback function for error-only actions. | `nil` |\n| `WithPanicRecovery()` | Enables \"Let It Crash\" panic handling. | `false` |\n\n---\n\n## Architecture \u0026 Design\n\nResile is built for high-performance, concurrent applications:\n- **Memory Safety**: Uses `time.NewTimer` with proper cleanup to prevent memory leaks in long-running loops.\n- **Context Integrity**: Every internal sleep is a `select` between the timer and `ctx.Done()`.\n- **Zero Allocations**: Core execution loop is designed to be allocation-efficient.\n- **Errors are Values**: Leverage standard `errors.Is` and `errors.As` for all policy decisions.\n\n---\n\n## Acknowledgements\n\n- **AWS Architecture Blog**: For the definitive [Exponential Backoff and Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/) algorithm (Full Jitter).\n- **Stamina \u0026 Tenacity**: For pioneering ergonomic retry APIs in the Python ecosystem that inspired the design of Resile.\n\n## License\n\nResile is released under the [MIT License](LICENSE).\n\n```\nCopyright (c) 2026 Onur Cinar.\nThe source code is provided under MIT License.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcinar%2Fresile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcinar%2Fresile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcinar%2Fresile/lists"}