{"id":30174219,"url":"https://github.com/seuros/chrono_machines","last_synced_at":"2026-04-01T18:05:04.344Z","repository":{"id":304567601,"uuid":"1018728577","full_name":"seuros/chrono_machines","owner":"seuros","description":"A Ruby gem that implements exponential backoff retry patterns with full jitter for distributed systems. Simple, focused, and built for Ruby 3.2+ with fiber-aware operations.","archived":false,"fork":false,"pushed_at":"2026-03-19T03:06:13.000Z","size":297,"stargazers_count":25,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-19T17:49:11.434Z","etag":null,"topics":["async","backoff","configuration","distributed-systems","dsl","exponential-backoff","failure-handling","fault-tolerance","fiber","instrumentation","jitter","monitoring","patterns","reliability","resilience","retry","ruby","ruby-gem","temporal","timeout"],"latest_commit_sha":null,"homepage":null,"language":"Rust","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/seuros.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-12T22:51:47.000Z","updated_at":"2026-03-19T03:06:16.000Z","dependencies_parsed_at":"2025-07-14T00:19:57.179Z","dependency_job_id":"ab8e3f3e-71c8-4fcb-95e4-64f269a6c6d2","html_url":"https://github.com/seuros/chrono_machines","commit_stats":null,"previous_names":["seuros/chrono_machines"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/seuros/chrono_machines","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seuros%2Fchrono_machines","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seuros%2Fchrono_machines/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seuros%2Fchrono_machines/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seuros%2Fchrono_machines/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seuros","download_url":"https://codeload.github.com/seuros/chrono_machines/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seuros%2Fchrono_machines/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["async","backoff","configuration","distributed-systems","dsl","exponential-backoff","failure-handling","fault-tolerance","fiber","instrumentation","jitter","monitoring","patterns","reliability","resilience","retry","ruby","ruby-gem","temporal","timeout"],"created_at":"2025-08-12T00:44:32.879Z","updated_at":"2026-04-01T18:05:04.231Z","avatar_url":"https://github.com/seuros.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ChronoMachines\n\n\u003e The temporal manipulation engine that rewrites the rules of retry!\n\nA sophisticated Ruby implementation of exponential backoff and retry mechanisms, built for temporal precision in distributed systems where time itself is your greatest ally.\n\n## Quick Start\n\n```bash\ngem 'chrono_machines'\n```\n\n```ruby\nclass PaymentService\n  include ChronoMachines::DSL\n\n  chrono_policy :stripe_payment, max_attempts: 5, base_delay: 0.1, multiplier: 2\n\n  def charge(amount)\n    with_chrono_policy(:stripe_payment) do\n      Stripe::Charge.create(amount: amount)\n    end\n  end\nend\n\n# Or use it directly\nresult = ChronoMachines.retry(max_attempts: 3) do\n  perform_risky_operation\nend\n```\n\n## A Message from the Time Lords\n\nSo your microservices are failing faster than your deployment pipeline can recover, and you're stuck in an infinite loop of \"let's just add more retries\"?\n\nWelcome to the temporal wasteland—where every millisecond matters, exponential backoff is law, and jitter isn't just a feeling you get when watching your error rates spike.\n\nStill here? Excellent. Because in the fabric of spacetime, nobody can hear your servers screaming about cascading failures. It's all just timing and patience.\n\n### The Pattern Time Forgot\n\nBuilt for Ruby 3.2+ with fiber-aware sleep and full jitter implementation, because when you're manipulating time itself, precision matters.\n\n## Features\n\n- **Temporal Precision** - Full jitter exponential backoff with microsecond accuracy\n- **Advanced Retry Logic** - Configurable retryable exceptions and intelligent failure handling\n- **Rich Instrumentation** - Success, retry, and failure callbacks with contextual data\n- **Fallback Mechanisms** - Execute alternative logic when all retries are exhausted\n- **Declarative DSL** - Clean policy definitions with builder patterns\n- **Fiber-Safe Operations** - Async-aware sleep handling for modern Ruby\n- **Custom Exceptions** - MaxRetriesExceededError with original exception context\n- **Policy Management** - Named retry policies with inheritance and overrides\n- **Robust Error Handling** - Interrupt-preserving sleep with graceful degradation\n\n## The Temporal Manifesto\n\n### You Think: \"I'll just add `retry` and call it resilience!\"\n### Reality: You're creating a time paradox that crashes your entire fleet\n\nWhen your payment service fails, you don't want to hammer it into submission. You want to approach it like a time traveler—carefully, with exponential patience, and a healthy respect for the butterfly effect.\n\n## Core Usage Patterns\n\n### Direct Retry with Options\n\n```ruby\n# Simple retry with exponential backoff\nresult = ChronoMachines.retry(max_attempts: 3, base_delay: 0.1) do\n  fetch_external_data\nend\n\n# Advanced configuration\nresult = ChronoMachines.retry(\n  max_attempts: 5,\n  base_delay: 0.2,\n  multiplier: 3,\n  max_delay: 30,\n  retryable_exceptions: [Net::TimeoutError, HTTPError],\n  on_retry: -\u003e(exception:, attempt:, next_delay:) {\n    Rails.logger.warn \"Retry #{attempt}: #{exception.message}, waiting #{next_delay}s\"\n  },\n  on_failure: -\u003e(exception:, attempts:) {\n    Metrics.increment('api.retry.exhausted', tags: [\"attempts:#{attempts}\"])\n  }\n) do\n  external_api_call\nend\n```\n\n### Policy-Based Configuration\n\n```ruby\n# Configure global policies\nChronoMachines.configure do |config|\n  config.define_policy(:aggressive, max_attempts: 10, base_delay: 0.01, multiplier: 1.5)\n  config.define_policy(:conservative, max_attempts: 3, base_delay: 1.0, multiplier: 2)\n  config.define_policy(:database, max_attempts: 5, retryable_exceptions: [ActiveRecord::ConnectionError])\nend\n\n# Use named policies\nresult = ChronoMachines.retry(:database) do\n  User.find(user_id)\nend\n```\n\n### DSL Integration\n\n```ruby\nclass ApiClient\n  include ChronoMachines::DSL\n\n  # Define policies at class level\n  chrono_policy :standard_api, max_attempts: 5, base_delay: 0.1, multiplier: 2\n  chrono_policy :critical_api, max_attempts: 10, base_delay: 0.05, max_delay: 5\n\n  def fetch_user_data(id)\n    with_chrono_policy(:standard_api) do\n      api_request(\"/users/#{id}\")\n    end\n  end\n\n  def emergency_shutdown\n    # Use inline options for one-off scenarios\n    with_chrono_policy(max_attempts: 1, base_delay: 0) do\n      shutdown_api_call\n    end\n  end\nend\n```\n\n## Advanced Temporal Mechanics\n\n### Callback Instrumentation\n\n```ruby\n# Monitor retry patterns\npolicy_options = {\n  max_attempts: 5,\n  on_success: -\u003e(result:, attempts:) {\n    Metrics.histogram('operation.attempts', attempts)\n    Rails.logger.info \"Operation succeeded after #{attempts} attempts\"\n  },\n\n  on_retry: -\u003e(exception:, attempt:, next_delay:) {\n    Metrics.increment('operation.retry', tags: [\"attempt:#{attempt}\"])\n    Honeybadger.notify(exception, context: { attempt: attempt, next_delay: next_delay })\n  },\n\n  on_failure: -\u003e(exception:, attempts:) {\n    Metrics.increment('operation.failure', tags: [\"final_attempts:#{attempts}\"])\n    PagerDuty.trigger(\"Operation failed after #{attempts} attempts: #{exception.message}\")\n  }\n}\n\nChronoMachines.retry(policy_options) do\n  critical_operation\nend\n```\n\n### Exception Handling\n\n```ruby\nbegin\n  ChronoMachines.retry(max_attempts: 3) do\n    risky_operation\n  end\nrescue ChronoMachines::MaxRetriesExceededError =\u003e e\n  # Access the original exception and retry context\n  Rails.logger.error \"Failed after #{e.attempts} attempts: #{e.original_exception.message}\"\n\n  # The original exception is preserved\n  case e.original_exception\n  when Net::TimeoutError\n    handle_timeout_failure\n  when HTTPError\n    handle_http_failure\n  end\nend\n```\n\n### Fallback Mechanisms\n\n```ruby\n# Execute fallback logic when retries are exhausted\nChronoMachines.retry(\n  max_attempts: 3,\n  on_failure: -\u003e(exception:, attempts:) {\n    # Fallback doesn't throw - original exception is still raised\n    Rails.cache.write(\"fallback_data_#{user_id}\", cached_response, expires_in: 5.minutes)\n    SlackNotifier.notify(\"API down, serving cached data for user #{user_id}\")\n  }\n) do\n  fetch_fresh_user_data\nend\n```\n\n## The Science of Temporal Jitter\n\nChronoMachines implements **full jitter** exponential backoff:\n\n```ruby\n# Instead of predictable delays that create thundering herds:\n# Attempt 1: 100ms\n# Attempt 2: 200ms\n# Attempt 3: 400ms\n\n# ChronoMachines uses full jitter:\n# Attempt 1: random(0, 100ms)\n# Attempt 2: random(0, 200ms)\n# Attempt 3: random(0, 400ms)\n```\n\nThis prevents the \"thundering herd\" problem where multiple clients retry simultaneously, overwhelming recovering services.\n\n## Configuration Reference\n\n### Policy Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `max_attempts` | `3` | Maximum number of retry attempts |\n| `base_delay` | `0.1` | Initial delay in seconds |\n| `multiplier` | `2` | Exponential backoff multiplier |\n| `max_delay` | `10` | Maximum delay cap in seconds |\n| `retryable_exceptions` | `[StandardError]` | Array of exception classes to retry |\n| `on_success` | `nil` | Success callback: `(result:, attempts:)` |\n| `on_retry` | `nil` | Retry callback: `(exception:, attempt:, next_delay:)` |\n| `on_failure` | `nil` | Failure callback: `(exception:, attempts:)` |\n\n### DSL Methods\n\n| Method | Scope | Description |\n|--------|-------|-------------|\n| `chrono_policy(name, options)` | Class | Define a named retry policy |\n| `with_chrono_policy(policy_or_options, \u0026block)` | Instance | Execute block with retry policy |\n\n## Real-World Examples\n\n### Database Connection Resilience\n\n```ruby\nclass DatabaseService\n  include ChronoMachines::DSL\n\n  chrono_policy :db_connection,\n    max_attempts: 5,\n    base_delay: 0.1,\n    retryable_exceptions: [\n      ActiveRecord::ConnectionTimeoutError,\n      ActiveRecord::DisconnectedError,\n      PG::ConnectionBad\n    ],\n    on_retry: -\u003e(exception:, attempt:, next_delay:) {\n      Rails.logger.warn \"DB retry #{attempt}: #{exception.class}\"\n    }\n\n  def find_user(id)\n    with_chrono_policy(:db_connection) do\n      User.find(id)\n    end\n  end\nend\n```\n\n### HTTP API Integration\n\n```ruby\nclass WeatherService\n  include ChronoMachines::DSL\n\n  chrono_policy :weather_api,\n    max_attempts: 4,\n    base_delay: 0.2,\n    max_delay: 10,\n    retryable_exceptions: [Net::TimeoutError, Net::HTTPServerError],\n    on_failure: -\u003e(exception:, attempts:) {\n      # Serve stale data when API is completely down\n      Rails.cache.write(\"weather_service_down\", true, expires_in: 5.minutes)\n    }\n\n  def current_weather(location)\n    with_chrono_policy(:weather_api) do\n      response = HTTP.timeout(connect: 2, read: 5)\n                    .get(\"https://api.weather.com/#{location}\")\n      JSON.parse(response.body)\n    end\n  rescue ChronoMachines::MaxRetriesExceededError\n    # Return cached data if available\n    Rails.cache.fetch(\"weather_#{location}\", expires_in: 1.hour) do\n      { temperature: \"Unknown\", status: \"Service Unavailable\" }\n    end\n  end\nend\n```\n\n### Background Job Retry Logic\n\n```ruby\nclass EmailDeliveryJob\n  include ChronoMachines::DSL\n\n  chrono_policy :email_delivery,\n    max_attempts: 8,\n    base_delay: 1,\n    multiplier: 1.5,\n    max_delay: 300, # 5 minutes max\n    retryable_exceptions: [Net::SMTPServerBusy, Net::SMTPTemporaryError],\n    on_failure: -\u003e(exception:, attempts:) {\n      # Move to dead letter queue after all retries\n      DeadLetterQueue.push(job_data, reason: exception.message)\n    }\n\n  def perform(email_data)\n    with_chrono_policy(:email_delivery) do\n      EmailService.deliver(email_data)\n    end\n  end\nend\n```\n\n## Testing Strategies\n\n### Mocking Time and Retries\n\n```ruby\nrequire \"minitest/autorun\"\nrequire \"mocha/minitest\"\n\nclass PaymentServiceTest \u003c Minitest::Test\n  def setup\n    @service = PaymentService.new\n  end\n\n  def test_retries_payment_on_timeout\n    charge_response = { id: \"ch_123\", amount: 100 }\n\n    Stripe::Charge.expects(:create)\n      .raises(Net::TimeoutError).once\n      .then.returns(charge_response)\n\n    # Mock sleep to avoid test delays\n    ChronoMachines::Executor.any_instance.expects(:robust_sleep).at_least_once\n\n    result = @service.charge(100)\n    assert_equal charge_response, result\n  end\n\n  def test_respects_max_attempts\n    Stripe::Charge.expects(:create)\n      .raises(Net::TimeoutError).times(3)\n\n    assert_raises(ChronoMachines::MaxRetriesExceededError) do\n      @service.charge(100)\n    end\n  end\n\n  def test_preserves_original_exception\n    original_error = Net::TimeoutError.new(\"Connection timed out\")\n    Stripe::Charge.expects(:create).raises(original_error).times(3)\n\n    begin\n      @service.charge(100)\n      flunk \"Expected MaxRetriesExceededError to be raised\"\n    rescue ChronoMachines::MaxRetriesExceededError =\u003e e\n      assert_equal 3, e.attempts\n      assert_equal original_error, e.original_exception\n      assert_equal \"Connection timed out\", e.original_exception.message\n    end\n  end\nend\n```\n\n### Testing Callbacks\n\n```ruby\nclass CallbackTest \u003c Minitest::Test\n  def test_calls_retry_callback_with_correct_context\n    retry_calls = []\n    call_count = 0\n\n    result = ChronoMachines.retry(\n      max_attempts: 3,\n      base_delay: 0.001, # Short delay for tests\n      on_retry: -\u003e(exception:, attempt:, next_delay:) {\n        retry_calls \u003c\u003c {\n          attempt: attempt,\n          delay: next_delay,\n          exception_message: exception.message\n        }\n      }\n    ) do\n      call_count += 1\n      raise \"Fail\" if call_count \u003c 2\n      \"Success\"\n    end\n\n    assert_equal \"Success\", result\n    assert_equal 1, retry_calls.length\n    assert_equal 1, retry_calls.first[:attempt]\n    assert retry_calls.first[:delay] \u003e 0\n    assert_equal \"Fail\", retry_calls.first[:exception_message]\n  end\n\n  def test_calls_success_callback\n    success_called = false\n    result_captured = nil\n    attempts_captured = nil\n\n    result = ChronoMachines.retry(\n      on_success: -\u003e(result:, attempts:) {\n        success_called = true\n        result_captured = result\n        attempts_captured = attempts\n      }\n    ) do\n      \"Operation succeeded\"\n    end\n\n    assert success_called\n    assert_equal \"Operation succeeded\", result_captured\n    assert_equal 1, attempts_captured\n  end\n\n  def test_calls_failure_callback\n    failure_called = false\n    exception_captured = nil\n\n    assert_raises(ChronoMachines::MaxRetriesExceededError) do\n      ChronoMachines.retry(\n        max_attempts: 2,\n        on_failure: -\u003e(exception:, attempts:) {\n          failure_called = true\n          exception_captured = exception\n        }\n      ) do\n        raise \"Always fails\"\n      end\n    end\n\n    assert failure_called\n    assert_equal \"Always fails\", exception_captured.message\n  end\nend\n```\n\n### Testing DSL Integration\n\n```ruby\nclass DSLTestExample \u003c Minitest::Test\n  class TestService\n    include ChronoMachines::DSL\n\n    chrono_policy :test_policy, max_attempts: 2, base_delay: 0.001\n\n    def risky_operation\n      with_chrono_policy(:test_policy) do\n        # Simulated operation\n        yield if block_given?\n      end\n    end\n  end\n\n  def test_dsl_policy_definition\n    service = TestService.new\n\n    call_count = 0\n    result = service.risky_operation do\n      call_count += 1\n      raise \"Fail\" if call_count \u003c 2\n      \"Success\"\n    end\n\n    assert_equal \"Success\", result\n    assert_equal 2, call_count\n  end\n\n  def test_dsl_with_inline_options\n    service = TestService.new\n\n    assert_raises(ChronoMachines::MaxRetriesExceededError) do\n      service.with_chrono_policy(max_attempts: 1) do\n        raise \"Always fails\"\n      end\n    end\n  end\nend\n```\n\n## TestHelper for Library Authors\n\nChronoMachines provides a test helper module for library authors who want to integrate ChronoMachines testing utilities into their own test suites.\n\n### Setup\n\n```ruby\nrequire 'chrono_machines/test_helper'\n\nclass MyLibraryTest \u003c Minitest::Test\n  include ChronoMachines::TestHelper\n\n  def setup\n    super # Important: calls ChronoMachines config reset\n    # Your setup code here\n  end\nend\n```\n\n### Features\n\n**Configuration Reset**: The TestHelper automatically resets ChronoMachines configuration before each test, ensuring test isolation.\n\n**Custom Assertions**: Provides specialized assertions for testing delay ranges:\n\n```ruby\ndef test_delay_calculation\n  executor = ChronoMachines::Executor.new(base_delay: 0.1, multiplier: 2)\n  delay = executor.send(:calculate_delay, 1)\n\n  # Assert delay is within expected jitter range\n  assert_cm_delay_range(delay, 0.0, 0.1, \"First attempt delay out of range\")\nend\n```\n\n**Available Assertions**:\n- `assert_cm_delay_range(delay, min, max, message = nil)` - Assert delay falls within expected range\n\n### Integration Example\n\n```ruby\n# In your gem's test_helper.rb\nrequire 'minitest/autorun'\nrequire 'chrono_machines/test_helper'\n\nclass TestBase \u003c Minitest::Test\n  include ChronoMachines::TestHelper\n\n  def setup\n    super\n    # Reset any additional state\n  end\nend\n\n# In your specific tests\nclass RetryServiceTest \u003c TestBase\n  def test_retry_with_custom_policy\n    # ChronoMachines config is automatically reset\n    # You can safely define test-specific policies\n\n    ChronoMachines.configure do |config|\n      config.define_policy(:test_policy, max_attempts: 2)\n    end\n\n    result = ChronoMachines.retry(:test_policy) do\n      \"success\"\n    end\n\n    assert_equal \"success\", result\n  end\nend\n```\n\n## Why ChronoMachines?\n\n### Built for Modern Ruby\n- **Ruby 3.2+ Support**: Fiber-aware sleep handling\n- **Clean Architecture**: Separation of concerns with configurable policies\n- **Rich Instrumentation**: Comprehensive callback system for monitoring\n- **Battle-Tested**: Full jitter implementation prevents thundering herds\n\n### Time-Tested Patterns\n- **Exponential Backoff**: Industry-standard retry timing\n- **Circuit Breaking**: Fail-fast when upstream is down\n- **Fallback Support**: Graceful degradation strategies\n- **Exception Preservation**: Original errors aren't lost in retry logic\n\n## A Word from the Time Corps Engineering Division\n\n*The Temporal Commentary Engine activates:*\n\n\"Time isn't linear—especially when your payment processor is having 'a moment.'\n\nThe universe doesn't care about your startup's burn rate or your post on X about 'building in public.' It cares about one immutable law:\n\n**Does your system handle failure gracefully across the fourth dimension?**\n\nIf not, welcome to the Time Corps. We have exponential backoff.\n\nRemember: The goal isn't to prevent temporal anomalies—it's to fail fast, fail smart, and retry with mathematical precision.\n\nAs I always say when debugging production: 'Time heals all wounds, but jitter prevents thundering herds.'\"\n\n*— Temporal Commentary Engine, Log Entry ∞*\n\n## Contributing to the Timeline\n\n1. Fork it (like it's 2005, but with better temporal mechanics)\n2. Create your feature branch (`git checkout -b feature/quantum-retries`)\n3. Commit your changes (`git commit -am 'Add temporal stabilization'`)\n4. Push to the branch (`git push origin feature/quantum-retries`)\n5. Create a new Pull Request (and wait for the Time Lords to review)\n\n## License\n\nMIT License. See [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\n- The Ruby community - For building a language worth retrying for\n- Every timeout that ever taught us patience - You made us stronger\n- The Time Corps - For maintaining temporal stability\n- The universe - For being deterministically random\n\n## Author\n\nBuilt with time and coffee by temporal engineers fighting entropy one retry at a time.\n\n**Remember: In the fabric of spacetime, nobody can hear your API timeout. But they can feel your exponential backoff working as intended.**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseuros%2Fchrono_machines","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseuros%2Fchrono_machines","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseuros%2Fchrono_machines/lists"}