{"id":13805952,"url":"https://github.com/yammer/tenacity","last_synced_at":"2025-05-13T21:31:55.665Z","repository":{"id":12330974,"uuid":"14969928","full_name":"yammer/tenacity","owner":"yammer","description":"Dropwizard + Hystrix Module.","archived":false,"fork":false,"pushed_at":"2023-12-15T04:43:57.000Z","size":7651,"stargazers_count":203,"open_issues_count":1,"forks_count":38,"subscribers_count":43,"default_branch":"master","last_synced_at":"2024-08-04T01:05:41.321Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yammer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","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":"2013-12-06T00:43:18.000Z","updated_at":"2024-03-31T14:14:19.000Z","dependencies_parsed_at":"2024-06-21T16:48:13.317Z","dependency_job_id":"0cc1937e-43c2-49d4-81cb-01808610f129","html_url":"https://github.com/yammer/tenacity","commit_stats":null,"previous_names":[],"tags_count":99,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yammer%2Ftenacity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yammer%2Ftenacity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yammer%2Ftenacity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yammer%2Ftenacity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yammer","download_url":"https://codeload.github.com/yammer/tenacity/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225260365,"owners_count":17446085,"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-08-04T01:01:06.560Z","updated_at":"2024-11-18T22:31:02.162Z","avatar_url":"https://github.com/yammer.png","language":"Java","readme":"Tenacity [![Build Status](https://travis-ci.org/yammer/tenacity.svg?style=flat-square)](https://travis-ci.org/yammer/tenacity) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.yammer.tenacity/tenacity-core/badge.svg?style=flat-square)](https://maven-badges.herokuapp.com/maven-central/com.yammer.tenacity/tenacity-core)\n========\n\nTenacity is [Dropwizard](http://www.dropwizard.io)+[Hystrix](https://github.com/Netflix/Hystrix).\n\n[Dropwizard](http://www.dropwizard.io) is a framework for building REST services. [Hystrix](https://github.com/Netflix/Hystrix) is a resiliency library from [Netflix](https://github.com/Netflix) and [Ben Christensen](https://github.com/benjchristensen).\n\n[Hystrix's](https://github.com/Netflix/Hystrix) goals are to:\n\n1. Stop cascading failures.\n2. Fail-fast and rapidly recover.\n3. Reduce mean-time-to-discovery (with dashboards)\n4. Reduce mean-time-to-recovery (with dynamic configuration)\n\nTenacity makes [Hystrix](https://github.com/Netflix/Hystrix) dropwizard-friendly and for dropwizard-developers to quickly leverage the benefits of Hystrix.\n\n1. Uses dropwizard-bundles for bootstrapping: property strategies, metrics, dynamic configuration, and some resource endpoints (e.g. for dashboards).\n2. Dropwizard-configuration style (YAML) for dependencies.\n3. Abstractions to clearly configure a dependency operation (`TenacityCommand\u003cReturnType\u003e`).\n4. Ability to unit-test Hystrix: Resets static state held by Hystrix (metrics, counters, etc.). Increases rate at which a concurrent thread updates metrics.\n5. Publishes measurements via [Metrics](https://github.com/dropwizard/metrics).\n\n*Tenacity is meant to be used with [Breakerbox](https://github.com/yammer/breakerbox) which adds real-time visualization of metrics and dynamic configuration.*\n\nModules\n-------\n\n-   `tenacity-core`:            The building blocks to quickly use Hystrix within the context of Dropwizard.\n-   `tenacity-client`:          Client for consuming the resources that `tenacity-core` adds.\n-   `tenacity-testing`:         `TenacityTestRule` allows for easier unit testing. Resets internal state of Hystrix.\n-   `tenacity-jdbi`:            Pulls in dropwizard-jdbi and provides a DBIExceptionLogger and SQLExceptionLogger to be used with the ExceptionLoggingCommandHook.\n\nHow To Use\n==========\n\nHere is a sample `TenacityCommand` that always succeeds:\n\n```java\npublic class AlwaysSucceed extends TenacityCommand\u003cString\u003e {\n    public AlwaysSucceed() {\n        super(DependencyKey.ALWAYS_SUCCEED);\n    }\n\n    @Override\n    protected String run() throws Exception {\n        return \"value\";\n    }\n}\n```\n\nA quick primer on the way to use a `TenacityCommand` if you are not familiar with [Hystrix's execution model](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Synchronous-Execution):\n\nSynchronous Execution\n---------------------\n```java\nAlwaysSucceed command = new AlwaysSucceed();\nString result = command.execute();\n```\n\nThis executes the command synchronously but through the protection of a `Future.get(configurableTimeout)`.\n\nAsynchronous Execution\n----------------------\n\n```java\nFuture\u003cString\u003e futureResult = command.queue();\n```\n\nReactive Execution\n------------------------------\n\n```java\nObservable\u003cString\u003e observable = new AlwaysSucceed().observe();\n```\n\nFallbacks\n---------\n\nWhen execution fails, it is possible to gracefully degrade with the use of [fallbacks](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Fallback).\n\nExecution Flow\n--------------\n\n![Alt text](https://raw.githubusercontent.com/wiki/Netflix/Hystrix/images/hystrix-command-flow-chart.png)\n\n\nTenacityCommand Constructor Arguments\n-------------------------------------\n\nEarlier we saw:\n\n```java\npublic class AlwaysSucceed extends TenacityCommand\u003cString\u003e {\n    public AlwaysSucceed() {\n        super(DependencyKey.ALWAYS_SUCCEED);\n    }\n    ...\n}\n```\n\nThe arguments are:\n\n1. `commandKey`: This creates a circuit-breaker, threadpool, and also the identifier that will be used in dashboards.\nThis should be your implementation of the `TenacityPropertyKey` interface.\n\n*It is possible to create multiple circuit-breakers that leverage a single threadpool, but for simplicity we are not allowing that type of configuration.*\n\n\nHow to add Tenacity to your Dropwizard Service\n----------\n\n1. To leverage within dropwizard first add the following to your `pom.xml`:\n\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.yammer.tenacity\u003c/groupId\u003e\n        \u003cartifactId\u003etenacity-core\u003c/artifactId\u003e\n        \u003cversion\u003e1.1.2\u003c/version\u003e\n    \u003c/dependency\u003e\n    ```\n    \n    Or you can leverage the tenacity-bom:\n    \n    ```xml\n    \u003cdependencyManagement\u003e\n        \u003cdependencies\u003e\n            \u003cdependency\u003e\n                \u003cgroupId\u003ecom.yammer.tenacity\u003c/groupId\u003e\n                \u003cartifactId\u003etenacity-bom\u003c/artifactId\u003e\n                \u003cversion\u003e1.1.2\u003c/version\u003e\n                \u003ctype\u003epom\u003c/type\u003e\n                \u003cscope\u003eimport\u003c/scope\u003e\n            \u003c/dependency\u003e\n        \u003c/dependencies\u003e\n    \u003c/dependencyManagement\u003e\n    \n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.yammer.tenacity\u003c/groupId\u003e\n        \u003cartifactId\u003etenacity-core\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n    ```\n\n2. Enumerate your dependencies. These will eventually be used as global identifiers in dashboards. We have found that it works best for us when you include the service and the external dependency at a minimum. Here is an example of `completie`'s dependencies. Note we also shave down some characters to save on space, again for UI purposes. In addition, you'll need to have an implementation of a `TenacityPropertyKeyFactory` which you can see an example of below.\n\n    ```java\n    public enum CompletieDependencyKeys implements TenacityPropertyKey {\n        CMPLT_PRNK_USER, CMPLT_PRNK_GROUP, CMPLT_PRNK_SCND_ORDER, CMPLT_PRNK_NETWORK,\n        CMPLT_TOKIE_AUTH,\n        CMPLT_TYRANT_AUTH,\n        CMPLT_WHVLL_PRESENCE\n    }\n    ```\n    ```java\n    public class CompletieDependencyKeyFactory implements TenacityPropertyKeyFactory {\n        @Override\n        public TenacityPropertyKey from(String value) {\n            return CompletieDependencyKeys.valueOf(value.toUpperCase());\n        }\n    }\n    ```\n    \n3. Create a `TenacityBundleConfigurationFactory` implementation - you can use the `BaseTenacityBundleConfigurationFactory` as your starting point. This will be used to register your custom tenacity dependencies and custom configurations.\n\n    ```java\n    public class CompletieTenacityBundleConfigurationFactory extends BaseTenacityBundleConfigurationFactory\u003cCompletieConfiguration\u003e {\n    \n      @Override\n      public Map\u003cTenacityPropertyKey, TenacityConfiguration\u003e getTenacityConfigurations(CompletieConfiguration configuration) {\n            final ImmutableMap.Builder\u003cTenacityPropertyKey, TenacityConfiguration\u003e builder = ImmutableMap.builder();\n    \n            builder.put(CompletieDependencyKeys.CMPLT_PRNK_USER, configuration.getRanking().getHystrixUserConfig());\n            builder.put(CompletieDependencyKeys.CMPLT_PRNK_GROUP, configuration.getRanking().getHystrixGroupConfig());\n            builder.put(CompletieDependencyKeys.CMPLT_PRNK_SCND_ORDER, configuration.getRanking().getHystrixSecondOrderConfig());\n            builder.put(CompletieDependencyKeys.CMPLT_PRNK_NETWORK, configuration.getRanking().getHystrixNetworkConfig());\n            builder.put(CompletieDependencyKeys.CMPLT_TOKIE_AUTH, configuration.getAuthentication().getHystrixConfig());\n            builder.put(CompletieDependencyKeys.CMPLT_WHVLL_PRESENCE, configuration.getPresence().getHystrixConfig())\n    \n            return builder.build();\n      }\n    }\n    ```\n    \n4. Then make sure you add the bundle in your `Application`.\n\n    `Map\u003cTenacityPropertyKey, TenacityConfiguration\u003e` type.\n\n    ```java\n    @Override\n    public void initialize(Bootstrap\u003cMyConfiguration\u003e bootstrap) {\n        ...\n        bootstrap.addBundle(TenacityBundleBuilder\n                                            .\u003cMyConfiguration\u003e newBuilder()\n                                            .configurationFactory(new CompletieTenacityBundleConfigurationFactory())\n                                            .build());\n        ...\n    }\n    ```\n\n5. Use `TenacityCommand` to select which custom tenacity configuration you want to use.\n\n    ```java\n    public class CompletieDependencyOnTokie extends TenacityCommand\u003cString\u003e {\n        public CompletieDependencyOnTokie() {\n            super(CompletieDependencyKeys.CMPLT_TOKIE_AUTH);\n        }\n        ...\n    }\n    ```\n    \n6. When testing use the `tenacity-testing` module. This registers appropriate custom publishers/strategies, clears global `Archaius` configuration state (Hystrix uses internally to manage configuration), and tweaks threads that calculate metrics which influence circuit breakers to update a more frequent interval. Simply use the `TenacityTestRule`.\n\n    ```java\n    @Rule\n    public final TenacityTestRule tenacityTestRule = new TenacityTestRule();\n    ```\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.yammer.tenacity\u003c/groupId\u003e\n        \u003cartifactId\u003etenacity-testing\u003c/artifactId\u003e\n        \u003cversion\u003e1.1.2\u003c/version\u003e\n        \u003cscope\u003etest\u003c/scope\u003e\n    \u003c/dependency\u003e\n    ```\n\n7. Last is to actually configure your dependencies once they are wrapped with `TenacityCommand`.\n\n\nConfiguration\n=============\n\nOnce you have identified your dependencies you need to configure them appropriately. Here is the basic structure of a single\n`TenacityConfiguration` that may be leverage multiple times through your service configuration:\n\nDefaults\n--------\n```yaml\nexecutionIsolationThreadTimeoutInMillis: 1000\nexecutionIsolationStrategy: THREAD\nthreadpool:\n    threadPoolCoreSize: 10\n    keepAliveTimeMinutes: 1\n    maxQueueSize: -1\n    queueSizeRejectionThreshold: 5\n    metricsRollingStatisticalWindowInMilliseconds: 10000\n    metricsRollingStatisticalWindowBuckets: 10\ncircuitBreaker:\n    requestVolumeThreshold: 20\n    errorThresholdPercentage: 50\n    sleepWindowInMillis: 5000\n    metricsRollingStatisticalWindowInMilliseconds: 10000\n    metricsRollingStatisticalWindowBuckets: 10\nsemaphore:\n    maxConcurrentRequests: 10\n    fallbackMaxConcurrentRequests: 10\n```\n\nThe following two are the most important and you can probably get by just fine by defining just these two and leveraging the\ndefaults.\n\n-   `executionIsolationStrategy`: Which to use THREAD or SEMAPHORE. Defaults to THREAD. [Execution Isolation Strategy](https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy).\n-   `executionIsolationThreadTimeoutInMillis`: How long the entire dependency command should take.\n-   `threadPoolCoreSize`: Self explanatory.\n\nHere are the rest of the descriptions:\n\n-   `keepAliveTimeMinutes`: Thread keepAlive time in the thread pool.\n-   `maxQueueSize`: -1 uses a `SynchronousQueue`. Anything \u003e0 leverages a `BlockingQueue` and enables the `queueSizeRejectionThreshold` variable.\n-   `queueSizeRejectionThreshold`: Disabled when using -1 for `maxQueueSize` otherwise self explanatory.\n-   `requestVolumeThreshold`: The minimum number of requests that need to be received within the `metricsRollingStatisticalWindowInMilliseconds` in order to open a circuit breaker.\n-   `errorThresholdPercentage`: The percentage of errors needed to trip a circuit breaker. In order for this to take effect the `requestVolumeThreshold` must first be satisfied.\n-   `sleepWindowInMillis`: How long to keep the circuit breaker open, before trying again.\n\nHere are the semaphore related items:\n\n-   `maxConcurrentRequests`: The number of concurrent requests for a given key at any given time.\n-   `fallbackMaxConcurrentRequests`: The number of concurrent requests for the fallback at any given time.\n\nThese are recommended to be left alone unless you know what you're doing:\n\n-   `metricsRollingStatisticalWindowInMilliseconds`: How long to keep around metrics for calculating rates.\n-   `metricsRollingStatisticalWindowBuckets`: How many different metric windows to keep in memory.\n\nOnce you are done configuring your Tenacity dependencies. Don't forget to tweak the necessary connect/read timeouts on HTTP clients.\nWe have some suggestions for how you go about this in the Equations section.\n\nBreakerbox\n----------\nOne of the great things about Tenacity is the ability to aid in the reduction of mean-time-to-discovery and mean-time-to-recovery for issues. This is available through a separate service [Breakerbox](https://github.com/yammer/breakerbox).\n\n[Breakerbox](https://github.com/yammer/breakerbox) is a central dashboard and an on-the-fly configuration tool for Tenacity. In addition to the per-tenacity-command configurations shown above this configuration piece let's you define where and how often\nto check for newer configurations.\n\n```yaml\nbreakerbox:\n  urls: http://breakerbox.yourcompany.com:8080/archaius/{service}\n  initialDelay: 0s\n  delay: 60s\n  waitForInitialLoad: 0s\n```\n\n-   `urls` is a list of comma-deliminated list of urls for where to pull tenacity configurations. This will pull override configurations for all dependency keys for requested service.\n-   `initialDelay` how long before the first poll for newer configuration executes.\n-   `delay` the ongoing schedule to poll for newer configurations.\n-   `waitForInitialLoad` is the amount of time to block Dropwizard from starting while waiting for `Breakerbox` configurations.\n\n![Breakerbox Dashboard](http://yammer.github.io/tenacity/breakerbox_latest.png)\n![Breakerbox Configure](http://yammer.github.io/tenacity/breakerbox_configure.png)\n\nIn order to add integration with Breakerbox you need to implement the following method in your `TenacityBundleConfigurationFactory` implementation:\n\n```\n@Override\npublic BreakerboxConfiguration getBreakerboxConfiguration(CompletieConfiguration configuration) {\n   return configuration.getBreakerbox();\n}\n```\n\nConfiguration Hierarchy Order\n-----------------------------\n\nConfigurations can happen in a lot of different spots so it's good to just spell it out clearly. The order in this list matters, the earlier items override those that come later.\n\n1. [Breakerbox](https://github.com/yammer/breakerbox)\n2. Local service configuration YAML\n3. [Defaults](https://github.com/yammer/tenacity#defaults)\n\n\nConfiguration Equations\n---------\n\nHow to configure your dependent services can be confusing. A good place to start if don't have a predefined SLA is to just look\nat actual measurements. At Yammer, we set our max operational time for our actions somewhere between p99 and p999 for response times. We\ndo this because we have found it to actually be faster to fail those requests, retry, and optimistically get a p50 response time.\n\n1. Tenacity\n  -   executionIsolationThreadTimeoutInMillis = `p99 + median + extra`\n  -   p99 \u003c executionIsolationThreadTimeoutInMillis \u003c p999\n  -   threadpool\n      *   size = `(p99 in seconds) * (m1 rate req/sec) + extra`\n      *   10 is usually fine for most operations. Anything with a large pool should be understood why that is necessary (e.g. long response times)\n      *   Extra: this number only meets the current traffic needs. Make sure to add some extra for bursts as well as growth.\n\n2. HTTP client\n  -   connectTimeout = `33% of executionIsolationThreadTimeoutInMillis`\n  -   timeout (readTimeout) = `110% of executionIsolationThreadTimeoutInMillis`\n      *   We put this timeout higher so that it doesn't raise a TimeoutException from the HTTP client, but instead from Hystrix.\n\n*Note: These are just suggestions, feel free to look at Hystrix's configuration [documentation](https://github.com/Netflix/Hystrix/wiki/Configuration), or implement your own.*\n\nResources\n=========\n\nTenacity adds resources under `/tenacity`:\n\n1. `GET /tenacity/propertykeys`:  List of strings which are all the registered propertykeys with Tenacity.\n2. `GET /tenacity/configuration/{key}`:         JSON representation of a `TenacityConfiguration` for the supplied {key}.\n3. `GET /tenacity/circuitbreakers`:             Simple JSON representation of all circuitbreakers and their circuitbreaker status.\n   `GET /tenacity/circuitbreakers/{key}`:       Single circuitbreaker status\n   `PUT /tenacity/circuitbreakers/{key}`:       Expected \"FORCED_CLOSED, FORCED_OPEN, or FORCED_RESET\" as the body.\n4. `GET /tenacity/metrics.stream`:              text/event-stream of Hystrix metrics.\n\nBy default these are put onto the main application port. If you want to place these instead on the admin port, \nyou can configure this when building the `Tenacity` bundle.\n\n```java\nTenacityBundleBuilder\n                .\u003cMyConfiguration\u003e newBuilder()\n                ...\n                .usingAdminPort()\n                .build();\n```\n\nTenacityExceptionMapper\n=======================\n\nAn exception mapper exists to serve as an aid for unhandled `HystrixRuntimeException`s. It is used to convert all the types of unhandled exceptions to be converted to a simple\nHTTP status code. A common pattern here is to convert the unhandled `HystrixRuntimeException`s to [429 Too Many Requests](http://tools.ietf.org/html/rfc6585#section-4):\n\n```java\nTenacityBundleBuilder\n                .\u003cMyConfiguration\u003e newBuilder()\n                .configurationFactory(configurationFactory)\n                .mapAllHystrixRuntimeExceptionsTo(429)\n                .build();\n```\n\nExceptionLoggingCommandHook\n===========================\n\nIf you don't handle logging exceptions explicitly within each `TenacityCommand`, you can easily miss problems or at-least find them very hard to debug.\nInstead you can add the `ExceptionLoggingCommandHook` to the `TenacityBundle` and register `ExceptionLogger`s to handle the logging of different kinds of Exceptions.\nThe `ExecutionLoggingCommandHook` acts as a `HystrixCommandExecutionHook` and intercepts all Exceptions that occur during the `run()` method of your `TenacityCommand`s.\nBy sequencing `ExceptionLogger`s from most specific to most general, the `ExceptionLoggingCommandHook` will be able to find the best `ExceptionLogger` for the type of Exception.\n\n```java\nTenacityBundleBuilder\n                .\u003cMyConfiguration\u003e newBuilder()\n                .configurationFactory(configurationFactory)\n                .mapAllHystrixRuntimeExceptionsTo(429)\n                .commandExecutionHook(new ExceptionLoggingCommandHook(\n                    new DBIExceptionLogger(registry),\n                    new SQLExceptionLogger(registry),\n                    new DefaultExceptionLogger()\n                ))\n                .build();\n```\n\nTenacityJerseyClientBuilder\n===========================\n`TenacityJerseyClient` and `TenacityJerseyClientBuilder` to reduce configuration complexity when using Tenacity\nand `JerseyClient`. At the moment these two have competing timeout configurations that can end up looking like application exceptions\nwhen they are simply `TimeoutException`s being thrown by JerseyClient. `TenacityJerseyClient` aims to fix this by adjusting the socket read timeout\non a per-request basis on the currently set execution timeout value for resources built from `TenacityJerseyClient` and its associated\n`TenacityPropertyKey`. Note: Metrics associated with the `TenacityPropertyKey` are *NOT* updated whenever the underlying `Client` is used. The `TenacityPropertyKey` metrics are only\never updated when using `TenacityCommand` or `TenacityObservableCommand` at the moment.\n\n```java\nClient client = new JerseyClientBuilder(environment).build(\"some-external-dependency\");\nClient tenacityClient = TenacityJerseyClientBuilder\n    .builder(YOUR_TENACITY_PROPERTY_KEY)\n    .usingTimeoutPadding(Duration.milliseconds(50))\n        //Padding to add in addition to the Tenacity set time. Default is 50ms.\n        //Result: TenacityTimeout + TimeoutPadding = SocketReadTimeout\n    .build(client);\n\n//Then use tenacityClient the same way as you'd use client. TenacityClient overrides resource/asyncResource and those in turn are Tenacity*Resources.\n//They adjust timeouts on every use or on a per-request basis.\n```\n\nTenacityCircuitBreakerHealthCheck\n=================================\nThere is now the ability to add a `HealthCheck` which returns `unhealthy` when any circuit is open. This could be because a circuit is\nforced open or because of environment circumstances. The default is for this not to be turned on. You can enable this `HealthCheck`\nby configuring it on the bundle that is added to your application.\n\n```java\nTenacityBundleBuilder\n                .newBuilder()\n                ...\n                .withCircuitBreakerHealthCheck()\n                .build();\n```\n\nTenacityCommand.Builder\n=======================\nJava8 brings functional interfaces and `TenacityCommand`/`TenacityObservableCommand` offers support. \n\n```java\nTenacityCommand\n      .builder(DependencyKey.GENERAL)\n      .run(() -\u003e 1)\n      .fallback(() -\u003e 2)\n      .execute()\n```\n```java\nTenacityObservableCommand\n      .builder(DependencyKey.GENERAL)\n      .run(() -\u003e Observable.just(1))\n      .fallback(() -\u003e Observable.just(2))\n      .observe()\n```\n","funding_links":[],"categories":["Open Source"],"sub_categories":["Eclipse"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyammer%2Ftenacity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyammer%2Ftenacity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyammer%2Ftenacity/lists"}