{"id":18841635,"url":"https://github.com/alphasights/sneakers_handlers","last_synced_at":"2025-04-14T07:31:03.729Z","repository":{"id":7058438,"uuid":"54650755","full_name":"alphasights/sneakers_handlers","owner":"alphasights","description":"Retries with exponential backoff for sneakers","archived":false,"fork":false,"pushed_at":"2023-10-23T17:53:53.000Z","size":99,"stargazers_count":33,"open_issues_count":6,"forks_count":11,"subscribers_count":38,"default_branch":"master","last_synced_at":"2025-04-12T03:48:59.421Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/alphasights.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2016-03-24T15:02:44.000Z","updated_at":"2024-06-06T07:53:07.000Z","dependencies_parsed_at":"2024-06-21T13:07:59.547Z","dependency_job_id":"a488c7a1-c9c2-4b2f-a238-d3cb506772b3","html_url":"https://github.com/alphasights/sneakers_handlers","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphasights%2Fsneakers_handlers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphasights%2Fsneakers_handlers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphasights%2Fsneakers_handlers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphasights%2Fsneakers_handlers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alphasights","download_url":"https://codeload.github.com/alphasights/sneakers_handlers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248839317,"owners_count":21169791,"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":[],"created_at":"2024-11-08T02:52:02.984Z","updated_at":"2025-04-14T07:31:03.402Z","avatar_url":"https://github.com/alphasights.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SneakersHandlers\n\nThe gem introduces three handlers you can use as part of your [`Sneakers`](https://github.com/jondot/sneakers) workers: \n\n* `SneakersHandlers::DeadLetterHandler`\n* `SneakersHandlers::RetryHandler` \n* `SneakersHandlers::ExponentialBackoffHandler`.\n\n`Sneakers` handlers are used to define custom behaviours to different scenarios (e.g. a success, error, timeout, etc.). \n\nBy default `Sneakers` uses a handler called [`OneShot`](https://github.com/jondot/sneakers/blob/41883dd0df8b360c8d6e2f29101c960d5650f711/lib/sneakers/handlers/oneshot.rb) that,\nas the name indicates, will try to execute the message only once, and `reject` it if something goes wrong. That can be fine for some workers, but we usually need something that will be able\nto handle failed messages in a better way, either by sending them to a [dead-letter exchange](https://www.rabbitmq.com/dlx.html) or by trying to execute them again.\n\n## Using the `DeadLetterHandler`\n\nThe `DeadLetterHandler` is an extension of the default `OneShot` handler. It will try to process the message only once, and when something goes wrong it will publish this message to the dead letter exchange.\n\nWhen defining your worker, you have to define these extra arguments:\n\n`x-dead-letter-exchange`: The name of the dead-letter exchange where failed messages will be published to.\n\n`x-dead-letter-routing-key`: The routing key that will be used when dead-lettering a failed message. This value needs to be unique to your\napplication to avoid having the same message delivered to multiple queues. The recommendation is to use the queue name, although that's not mandatory.\n\nHere's an example:\n\n```diff\nclass DeadLetterWorker\n  include Sneakers::Worker\n\n  from_queue \"sneakers_handlers.my_queue\",\n    ack: true,\n    exchange: \"sneakers_handlers\",\n    exchange_type: :topic,\n    routing_key: \"sneakers_handlers.dead_letter_test\",\n+   handler: SneakersHandlers::DeadLetterHandler,\n+   arguments: { \"x-dead-letter-exchange\" =\u003e \"sneakers_handlers.dlx\",\n+                \"x-dead-letter-routing-key\" =\u003e \"sneakers_handlers.my_queue\" }\n\n  def work(*args)\n    ack!\n  end\nend\n```\n\n## Using the `RetryHandler`\n\nThe `RetryHandler` will try to execute the message `max_retry` times before dead-lettering it. The setup is very similar to the `DeadLetterHandler`, the only difference if that you can\nalso provide a `max_retry` argument, that will specify how many times the handler should try to execute this message.\n\n```diff\nclass RetryWorker\n  include Sneakers::Worker\n\n  from_queue \"sneakers_handlers.my_queue\",\n      ack: true,\n      exchange: \"sneakers_handlers\",\n      exchange_type: :topic,\n      routing_key: \"sneakers_handlers.retry_test\",\n+     handler: SneakersHandlers::RetryHandler,\n+     max_retry: 50,\n+     arguments: { \"x-dead-letter-exchange\" =\u003e \"sneakers_handlers.dlx\",\n+                  \"x-dead-letter-routing-key\" =\u003e \"sneakers_handlers.my_queue\" }\n\n  def work(*args)\n    ack!\n  end\nend\n```\n\nWhen a message fails, it will be published back to the end of the queue, so, assuming the queue is empty, there will be no delay (other than the network latency) between these retries.\n\n## Using the `ExponentialBackoffHandler`\n\nWith this handler every retry is delayed by a power of 2 on the attempt number. The retry attempt is inserted into a new queue with a naming convention of `\u003cqueue name\u003e.retry.\u003cdelay\u003e`.\nAfter exhausting the maximum number of retries (`max_retries`), the message will be moved into the dead letter exchange.\n\n![backoff](https://github.com/alphasights/sneakers_handlers/blob/master/docs/backoff.png)\n\nThe setup is also very similar to the other handlers:\n\n```diff\nclass ExponentialBackoffWorker\n  include Sneakers::Worker\n\n  from_queue \"sneakers_handlers.my_queue\",\n      ack: true,\n      exchange: \"sneakers_handlers\",\n      exchange_type: :topic,\n      routing_key: \"sneakers_handlers.backoff_test\",\n+     handler: SneakersHandlers::ExponentialBackoffHandler,\n+     max_retries: 50,\n+     arguments: { \"x-dead-letter-exchange\" =\u003e \"sneakers_handlers.dlx\",\n+                  \"x-dead-letter-routing-key\" =\u003e \"sneakers_handlers.my_queue\" }\n\n  def work(*args)\n    ack!\n  end\nend\n```\n\nYou can also customize the backoff function defining the `backoff_function` option, that can be any `call`able object (a lambda, a method, a class that responds to `call`, etc.)\nthat will receive the current attempt count and should return in how many seconds the message will be retried. \n\n```diff\nclass ExponentialBackoffWorker\n  include Sneakers::Worker\n\n  from_queue \"sneakers_handlers.my_queue\",\n      ack: true,\n      exchange: \"sneakers_handlers\",\n      exchange_type: :topic,\n      routing_key: \"sneakers_handlers.backoff_test\",\n      handler: SneakersHandlers::ExponentialBackoffHandler,\n+     backoff_function: -\u003e(attempt_number) { attempt_number ** 3 },\n      max_retries: 50,\n      arguments: { \"x-dead-letter-exchange\" =\u003e \"sneakers_handlers.dlx\",\n                   \"x-dead-letter-routing-key\" =\u003e \"sneakers_handlers.my_queue\" }\n\n  def work(*args)\n    ack!\n  end\nend\n```\n\nFor a more detailed explanation of how the backoff handler works, check out the [blog post](https://m.alphasights.com/exponential-backoff-with-rabbitmq-78386b9bec81) we wrote about it.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'sneakers_handlers'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install sneakers_handlers\n\n## Development\n\nAfter checking out the repository, run `bin/setup` to install dependencies. Then, run `rake` to run the tests (you will need to have a real `RabbitMQ` instance running). You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falphasights%2Fsneakers_handlers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falphasights%2Fsneakers_handlers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falphasights%2Fsneakers_handlers/lists"}