pax_global_header00006660000000000000000000000064144456105070014520gustar00rootroot0000000000000052 comment=8ae344fda21eb00c32a34bbd9517babe5b6fadc5 failsafe-failsafe-parent-3.3.2/000077500000000000000000000000001444561050700163365ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/.github/000077500000000000000000000000001444561050700176765ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/.github/workflows/000077500000000000000000000000001444561050700217335ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/.github/workflows/maven.yml000066400000000000000000000013661444561050700235720ustar00rootroot00000000000000# This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: build on: [push, pull_request] jobs: compile: runs-on: ubuntu-latest strategy: matrix: java: [ 8, 11, 17 ] jdk: ['temurin', 'zulu'] name: Java ${{ matrix.java }} ${{ matrix.jdk }} steps: - name: Checkout Source Code uses: actions/checkout@v2 - name: Setup Java uses: actions/setup-java@v2 with: distribution: ${{ matrix.jdk }} java-package: jdk java-version: ${{ matrix.java }} cache: 'maven' - name: Build with maven run: mvn -B test failsafe-failsafe-parent-3.3.2/.gitignore000066400000000000000000000001341444561050700203240ustar00rootroot00000000000000target/ *~ .project .classpath .settings/ test-output/ docs/ .idea *.iml _site .java-versionfailsafe-failsafe-parent-3.3.2/CHANGELOG.md000066400000000000000000000606021444561050700201530ustar00rootroot00000000000000# 3.3.2 ### Bug Fixes - Issue #365 - Bulkhead policy may drop requests when maxWaitTime is specified. # 3.3.1 ### Improevments - Issue #358 - Added full java module descriptors to Failsafe jars. - Issue #361 - Released execution references inside Failsafe provided CompletableFutures. # 3.3.0 ### API Changes - `ExecutionContext.getStartTime` now returns a `Instant` rather than `Duration`, and `ExecutionEvent.getStartTime` now returns `Optional`. - `getFailure`, `getLastFailure`, `recordFailure` and similar methods for recording Exceptions, which were previously deprecated, were removed. Use `getException`, `getLastException`, `recordException`, etc. instead. # 3.2.4 ### Improvements - Added additional thread safety checks. # 3.2.3 ### Bug Fixes - Fixed an issue where Timeouts would not fire under certain conditions when used outside a RetryPolicy. # 3.2.2 ### Improvements - Released [OkHttp](https://square.github.io/okhttp/) module. - Released [Retrofit](https://square.github.io/retrofit/) module. - Added `Call` support to `FailsafeExecutor`, which can cancel synchrnous calls. - Added `onCancel` callback to `ExecutionContext`, which can propagate cancellations. ### SPI Changes - `SyncExecutionInternal.isInterruptable()` and `.setInterrupted` were removed and `.interrupt()` was added instead to simplify performing an interruption. # 3.2.1 ### Improvements - Issue #326 - Added support for reserving a `RateLimiter` permit with a wait time. ### API Changes - Deprecated `ExecutionContext.getLastFailure`, `Execution.recordFailure` and similar methods throughout that API that refer to exceptions as failures. In their place, new methods have been added, such as `getLastException`, `recordException` and so on. This clarifies the difference between an exception and a failure, since an exception may or may not be a failure, depending on the policy configuration. - Changed the policy builders to use `CheckedPredicate` and `CheckedBiPredicate` instead of `Predicate` and `BiPredicate`, allowing exceptions to be thrown which are ignored. # 3.2.0 ### Improvements - Issue #309 - Introduced a `Bulkhead` policy. - Issue #318 - Add non-blocking async waiting for rate limiters. ### SPI Changes - `PolicyExecutor.preExecuteAsync` was introduced to support async pre-execution. This is backwards compatible with `preExecute`. # 3.1.0 ### Improvements - Issue #308 - Introduced a `RateLimiter` policy. # 3.0.2 ### Bug Fixes - Issue #311 - `with(Executor)` not working as expected in some cases. # 3.0.1 ### Improvements - Issue #310 - Added `.builder(PolicyConfig)` methods to each of the policy interfaces, to allow new policies to be built from existing config. - Issue #251 - Relaxed the illegal state validation in `RetryPolicyBuilder` to allow different types of delays to be configured, replacing previous configuration. Also removed the requirement that a jitter duration be configured after a delay. ### Bug Fixes - Issue #215 - Added overflow checking for large user-provided `Duration` values. # 3.0 ### API Changes This release introduces some breaking changes to the API: #### General - The maven group id for Failsafe has changed to `dev.failsafe`. Be sure to update your build config. - All files have been moved to the `dev.failsafe` package. Be sure to update your imports. #### Policies - All policies now use a builder API. Using the builder API mostly requires inserting `builder()` and `build()` methods into the call chain for constructing a policy since the actual `with` configuration methods are mostly the same as in 2.x policies, with a few changes described below. Some notes: - A policy builder can be created via `builder()`, ex: `RetryPolicy.builder()`. - `RetryPolicy` and `CircuitBreaker` can also be constructed with default values using `ofDefaults()`. - `Fallback` and `Timeout` offer additional factory methods for creating a a policy with only their required arguments, without using a builder, ex: `Timeout.of(Duration.ofSeconds(10))`. Optional arguments must be specified through a builder, ex: `Timeout.builder(duration).withInterrupt().build()`. - Policy configuration is now accessible via a `policy.getConfig()`. #### RetryPolicy and CircuitBreaker - In `RetryPolicyBuilder` and `CircuitBreakerBuilder`: - `withDelay` has been renamed to `withDelayFn`. - `withDelayOn` has been renamed to `withDelayFnOn`. - `withDelayWhen` has been renamed to `withDelayFnWhen`. - The above method signatures have also been changed to accept a `ContextualSupplier` instead of a `DelayFunction`, since it provides access to the same information. #### CircuitBreaker - `onOpen`, `onClose`, and `onHalfOpen` methods now accept a `CircuitBreakerStateChangedEvent` argument. - `allowsExecution()` was removed in favor of `acquirePermit()` and `tryAcquirePermit()`, which are meant for standalone CircuitBreaker usage. #### Fallback - The `Fallback` async factory methods have been removed in favor of a `FallbackBuilder.withAsync()` option. #### Timeout - `Timeout.withInterrupt(boolean)` is now `TimeoutBuilder.withInterrupt()`. #### Execution and AsyncExecution - The standalone `Execution` API, and the `AsyncExecution` API created via the `FailsafeExecutor.runAsyncExecution` and `getAsyncExecution` methods, have been unified to include: - `record(R, Throwable)` - `recordResult(R)` - `recordException(Throwable)` - `complete()` - The previously supported `Execution` and `AsyncExecution` methods for recording a result have been removed. The methods for performing a retry have also been removed. For `Execution`, `isComplete` will indicate whether the execution is complete else if retries can be performed. For `AsyncExecution` retries will automatically be performed, if possible, immediately after a result or failure is recorded. - The `Execution` constructor is no longer visible. `Execution` instances must now be constructed via `Execution.of(policies)`. - `Execution.getWaitTime()` was renamed to `getDelay()`. #### Failsafe class - `Failsafe.with(P[] policies)` was removed in favor of `Failsafe.with(P, P...)`. This should only affect users who were explicitly passing an array to `Failsafe.with`. ### SPI Changes The following changes effect the SPI classes, for users who are extending Failsafe with custom schedulers or policies: - `Scheduler` and `DefauledScheduledFuture` were moved to the `spi` package. - `Policy` and `PolicyExecutor` were moved to the `spi` package and some method signatures changed. - `ExecutionResult` was moved to the `spi` package and made generic. - Several new classes were added to the `spi` package to contain internal execution APIs including `ExecutionInternal`, `SyncExecutionInternal`, and `AsyncExecutionInternal`. - `FailsafeFuture` was moved to the SPI package and some method signatures changed. ### Bug Fixes - Improved the reliability of async executions, cancellations, and Timeouts. ### Improvements - Issue #47 - All policies and policy config classes are now threadsafe. Policy builders are not threadsafe. - Issue #201 - Thread safety is clearly documented in policy, policy config, and policy builder classes. - Issue #292 - Created an extensible Policy SPI. - Issue #254 - Added an explicit `compose` method to `FailsafeExecutor`. - Issue #293 - Added `RetryPolicyBuilder.withBackoff(Duration, Duration)` and `.withDelay(Duration, Duration)`. - Issue #221 - `Executor` instances configured via `FailsafeExecutor.with(Executor)` are now used on all executions, including sync executions, and can be used in conjunction with a separately configured `ExecutorService` or `Scheduler` for async executions. - Added `FailsafeExecutor.getPolicies()`. - Added `isFirstAttempt()` and `isRetry()` to `ExecutionAttempt`, which is available via a few event listeners. # 2.4.4 ### Bug Fixes - Fixed #298 - `Fallback.onFailedAttempt` not being called correctly ### Improvements - Fixed #296 - Add Automatic-Module-Name entry to the generated manifest file ### API Changes - Added a generic result type `R` to `ExecutionContext`, `Execution`, `AsyncExecution`, and `AsyncRunnable`. This ensures that result types are unified across the API. It does mean that there are a few minor breaking changes to the API: - `ContextualSupplier` now has an additional result type parameter `R`. Normally this type is used as lambda parameters where the type is inferred, so most users should not be impacted. But any explicit generic declaration of this type will not compile until the new parameter is added. - `PolicyExecutor`, which is part of the SPI, now accepts an additional result type parameter `R`. This is only relevant for SPI users who are implementing their own Policies. - Changed `FailsafeExecutor.getAsyncExecution` to accept `AsyncRunnable` instead of `AsyncSupplier`. This is a breaking change for any `getAsyncExecution` calls, but the fix is to simply remove any `return` statement. The reason for this change is that the provided object does not need to return a result since the result will already be passed asynchronously to one of the `AsyncExecution` `complete` or `retry` methods. # 2.4.3 ### Bug Fixes - Fixed #289 - Binary imcompatibility with code that was compiled against previous Failsafe versions. # 2.4.2 ### Improvements - Added `RetryPolicy.onRetryScheduled` event handler. - Added `ExecutionEvent.getExecutionCount()` and `ExecutionContext.getExecutionCount()`, which distinguishes between attempts which may have been rejected and completed executions. - Added `Failsafe.none` to create a no-op `FailsafeExecutor`. - Improved support for outer Timeouts with retries. - Fixed #221 - Added support for `FailsafeExecutor.with(Executor)`. - Fixed #277 - Changed `Timeout` to use Failsafe's internal scheduler, so that user provided `ExecutorService` shutdowns do not interfere with timeouts. - Fixed #266 - Propagate `Future` cancellation to supplied `CompletionStage` when using `getStageAsync`. ### Bug Fixes - Fixed #267 - Allow null fallback values to be passed through when using nested fallbacks. # 2.4.1 ### Improvements - Fixed #234 - An outer `Timeout` should cancel any inner retries. ### API Changes - Deprecated `Timeout.withCancel(boolean)` and `Timeout.canCancel()`. Timeouts always cancel any executions and inner retries. - Added `Timeout.withInterrupt(boolean)` to take the place of `withCancel`. - Added `ExecutionEvent.getElapsedAttemptTime()`. # 2.4.0 ### Improvements - Added time based thresholding support to `CircuitBreaker` via: - `withFailureThreshold(int failureThreshold, Duration failureThresholdingPeriod)` - `withFailureThreshold(int failureThreshold, int failureExecutionThreshold, Duration failureThresholdingPeriod)` - `withFailureRateThreshold(int failureRateThreshold, int failureExecutionThreshold, Duration failureThresholdingPeriod)` - Added getters to `CircuitBreaker` for existing count based thresholding settings: - `getFailureThresholdingCapacity()` - `getSuccessThresholdingCapacity()` - And added getters to `CircuitBreaker` for new time based thresholding settings: - `getFailureRateThreshold()` - `getFailureExecutionThreshold()` - `getFailureThresholdingPeriod()` - Added some new metrics to `CircuitBreaker`: - `getSuccessRate()` - `getFailureRate()` - `getExecutionCount()` ### API Changes - Changed the return type of `CircuitBreaker`'s `getFailureThreshold()` and `getSuccessThreshold()` from `Ratio` to `int`. `getFailureThresholdingCapacity`, `getFailureRateThreshold`, `getFailureExecutionThreshold`, and `getSuccessThresholdingCapacity` provide additional detail about thresholding configuration. - Removed support for the previously deprecated `CircuitBreaker.withTimeout`. The `Timeout` policy should be used instead. # 2.3.5 ### Bug Fixes - Fixed #242 - Delays not occurring between manually triggered async execution retries. # 2.3.4 ### Improvements - Re-worked internal threading to only create async threads immediately prior to supplier execution. See #230. ### Bug Fixes - Fixed #240 - `handleResult(null)` always triggering when an exception is thrown. # 2.3.3 ### Improvements Added support for `CompletionStage` to the `Fallback` policy. ### Bug Fixes - Fixed #224 - Allow combining random delay and jitter. ### API Changes - `Fallback.apply` was made package private. - `DelayablePolicy.computeDelay` was made package private. # 2.3.2 ### Improvements - Added `CircuitBreaker.getRemainingDelay()`. - Added support for `Fallback.VOID`. ### Bug Fixes - Fixed #216 - Incorrect computation of randomDelay. # 2.3.1 ### Improvements - Set `setRemoveOnCancelPolicy(true)` for the internal delay scheduler. - Added `Scheduler.DEFAULT` to return the default scheduler Failsafe uses. ### Bug Fixes - Fixed #206 - Problem with Fallback converting from failure to success. # 2.3.0 ### Behavior Changes - `FailsafeExecutor.get` and `FailsafeExecutor.run` will no longer wrap `Error` instances in `FailsafeException` before throwing. ### Bug Fixes - Fixed potential race between `Timeout` interrupts and execution completion. # 2.2.0 ### Improvements - Added a new `Timeout` policy that fails with `TimeoutExceededException`. - Added `ExecutionContext.isCancelled()`. - Added `ExecutionContext.getElapsedAttemptTime()`. - Made the internal delay scheduler more adaptive. ### API Changes - Deprecated `CircuitBreaker.withTimeout` in favor of using a separate `Timeout` policy. ### Bug Fixes - Reset interrupt flag when a synchronous execution is interrupted. - Improved handling around externally completing a Failsafe `CompletableFuture`. # 2.1.1 ### Improvements - Added support for `CircuitBreaker.withDelay(DelayFunction)` - Added `Fallback.ofException` for returning custom exceptions. - Added `ExecutionContext.getLastResult` and `.getLastFailure` to support retries that depend on previous executions - Added `CircuitBreakerOpenException.getCircuitBreaker` ### API Changes - `RetryPolicy.DelayedFunction` was moved to the `net.jodah.failsafe.function` package. - Removed `RetryPolicy.canApplyDelayFn` # 2.1.0 ### Improvements - Added support for `Failsafe.with(List>)`. - Allow `null` `Fallback` values. ### Behavior Changes - A [standalone](https://github.com/jhalterman/failsafe#execution-tracking) or [async execution](https://github.com/jhalterman/failsafe#asynchronous-api-integration) will only be marked as complete when all policies are complete. `Execution.isComplete` reflects this. ### Bug Fixes - Issue #190 - Failure listener called on success for async executions. - Issue #191 - Add missing listeners to RetryPolicy copy constructor. - Issue #192 - Problem with detecting completion when performing async execution. # 2.0.1 ### Improvements - Added support for using `ExecutorService` via `FailsafeExecutor.with(ExecutorService)`. - Added interruptable cancellation for executions ran on `ForkJoinPool` via `CompletableFuture.cancel(true)`. ### Bug Fixes - Issue #171 - Handle completed futures when using `getStageAsync`. # 2.0 ### Improvements * [Policy composition](README.md#policy-composition) is now supported. * [A Policy SPI](README.md#policy-spi) is now available. * Async execution is now supported without requiring that a `ScheduledExecutorService` or `Scheduler` be configured. When no scheduler is configured, the `ForkJoinPool`'s common pool will be used by default. * `Fallback` now support async execution via `ofAsync`. * `CircuitBreaker` supports execution metrics (see below). * Strong typing based on result types is supported throughout the API. ### Behavior Changes - `RetryPolicy` now has 3 max attempts by default. - `CircuitBreaker` now has a 1 minute delay by default. ### JRE Changes - Java 8+ is now required ### API Changes Failsafe 2.0 includes a few API changes from 1.x that were meant to consolidate behavior such as the execution APIs, which are now based on common `Policy` implementations, while adding some new features such as `Policy` composition. - Policies - Policy implementations now take a type parameter `R` that represents the expected result type. - Some of the time related policy configurations have been changed to use `Duration` instead of `long` + `TimeUnit`. - Policy configuration - Multiple policies can no longer be configured by chaining multiple `Failsafe.with` calls. Instead they must be supplied in a single `Failsafe.with` call. This is was intentional to require users to consider the ordering of composed policies. See the README section on [policy composition](README.md#policy-composition) for more details. - RetryPoilicy - The `retryOn`, `retryIf`, and `retryWhen` methods have been replace with `handleOn`, etc. - CircuitBreaker - The `failOn`, `failIf`, and `failWhen` methods have been replace with `handleOn`, etc. - Fallbacks - Fallbacks must be wrapped in a `Fallback` instance via `Fallback.of` - Failsafe APIs - `Supplier`s are now used instead of `Callable`s. - `java.util.function.Predicate` is used instead of Failsafe's internal Predicate. - `withFallback` is no longer supported. Instead, `Failsafe.with(fallback...)` should be used. - Async execution - Async execution is now performed with the `getAsync`, `runAsync`, `getStageAsync`, etc. methods. - Async API integration is now supported via the `getAsyncExecution`, `runAsyncExecution`, etc. methods. - Event listeners - Event listeners now all consume a single `ExecutionEvent` object, which includes references to the result, failure, and other information. - Event listeners that are specific to policies, such as `onRetry` for `RetryPolicy`, must now be configured through the policy instance. The top level `Failsafe` API only supports `onComplete`, `onSuccess`, and `onFailure`. Individual `Policy` implementations still support `onSuccess` and `onFailure` in addition to policy specific events. - The top level `Failsafe.onSuccess` event listener will only be called if *all* configured policies consider an execution to be successful, otherwise `onFailure` will be called. - The `Listeners` class was removed, since it was mostly intended for Java 6/7 users. - The async event listener APIs were removed. Events will always be delivered in the same thread as the execution that they follow or preceed, including for async executions. - Java 8 - `java.time.Duration` is used instead of Failsafe's own `Duration` impl. - `ChronoUnit` is used instead of `TimeUnit` in policies. - `ExecutionContext.getExecutions` is now `getAttemptCount`. - `Schedulers.of(ScheduledExecutorService)` was moved to the `Scheduler` interface. ### API Additions - `CircuitBreaker` - `preExecute` is now exposed to support standalone usage. - Execution metrics are available via `getFailureCount`, `getFailureRatio`, `getSuccessCount`, and `getSuccessRatio`. ### Bug Fixes * Issue #152 - Min/max delay was not being computed correctly # 1.1.0 ### Bug Fixes * Issue #115 - Jitter bigger than Delay causes a (random) failure at runtime * Issue #116 - Setting jitter without a delay works fine bug * Issue #123 - Ability to reset the jitterFactor ### Improvements * Issue #110 - Added support for computed delays: `RetryPolicy.withDelay(DelayFunction)` * Issue #126 - Added support for random delays: `RetryPolicy.withDelay(1, 10, TimeUnit.MILLISECONDS)` # 1.0.5 ### Bug Fixes * Issue #97 - Should not increment exponential backoff time on first attempt * Issue #92 - `handleRetriesExceeded` called incorrectly. # 1.0.4 ### API Changes * Asynchronous execution attempts no longer throw `CircuitBreakerOpenException` if a configured `CircuitBreaker` is open when an execution is first attempted. Instead, the resulting `Future` is completed exceptionally with `CircuitBreakerOpenException`. See [issue #84](https://github.com/jhalterman/failsafe/issues/84). ### Improvements * Issue #81 - Added single argument failure configuration to avoid varargs related warnings. # 1.0.3 ### Bug Fixes * Fixed #76 - Make sure AsyncExecution.completeOrRetry is called when Error is thrown. # 1.0.2 ### Bug Fixes * Fixed #75 - Incorrect future completion when a fallback is present. # 1.0.1 ### Changes * `FailsafeException` now has public constructors, for easier mocking and testing. # 1.0.0 ### API Changes * Failsafe will now only throw `FailsafeException` when an execution fails with a checked `Exception`. See [issue #66](https://github.com/jhalterman/failsafe/issues/66) for details. # 0.9.5 ### Bug Fixes * Fixed #59 - Classloading issue on Java 6/7. # 0.9.4 ### Bug Fixes * Fixed #63 - Proper handling of thread interrupts during synchronous execution delays. * Fixed #54 - Added hashCode and equals implementations to Duration. # 0.9.3 ### New Features * Added OSGi support. * `FailsafeFutuer.cancel` calls completion handlers. `.get` after cancel throws `CancellationException`. ### Bug Fixes * Fixed #52 - FailsafeFuture.cancel not working as expected. * Fixed #55 - Fallback always called for asynchronous executions. ### API Changes * `CircuitBreakerOpenException` now extends `FailsafeException`. # 0.9.2 ### New Features * Various fallback and listener API additions and improvements # 0.9.1 ### New Features * Added support for retry delay [jitter](https://github.com/jhalterman/failsafe#retry-policies). # 0.9.0 ### New Features * Added support for [fallbacks](https://github.com/jhalterman/failsafe#fallbacks). ### Bug Fixes * Fixed issue #36 - Failed attempt listener not always called on completion. * Fixed issue #34 - CircuitBreaker should default to closed state. # 0.8.3 ### Bug Fixes * Fixed #33 - `CircuitBreaker` not decrementing currentExections when under load # 0.8.2 ### New Features * Added support for `onRetriesExceeded` listeners. * `RetryPolicy` can be extended (it's no longer marked as final) ### Bug Fixes * Abort should not call failure listeners. # 0.8.1 ### New Features * Simplified listeners API. * Added support for failure listeners via `Failsafe.with(...).onFailure(e -> {})`. * Added `onAbort` listeners. * Added additional async listeners. * `RetryPolicy` and `CircuitBreaker` now support multiple configuration rules. Ex: `new RetryPolicy().retryWhen(null).retryWhen("")`. If any rule matches then the policy is matched. ### API Changes * Added top level support for listener registration via `Failsafe.with(...).onXxx`. The `Listeners` class is now only meant for Java 6 and 7 usage via method overrides. * Removed listener registration from `Listeners` class. * Removed `AsyncListeners` class. * Removed listener registration from `FailsafeFuture` class. # 0.8.0 ### New Features * Added support for circuit breakers ### API Changes * Project renamed from Recurrent to Failsafe # 0.7.1 ### Bug Fixes * Added better support for scheduling failure handling * Fixed RetryPolicy failure assignability checking ### API Changes * Invocation APIs were renamed to Execution to better align with the `java.util.concurrent` naming. * `InvocationStats.getAttemptCount()` was renamed to `ExecutionStats.getExecutions()` # 0.7.0 ### New Features * Added additional contextual callable and runnable support ### API Changes * Changed to a new API entry point: `Recurrent.with`. * Added `.with` for configuring listeners. # 0.6.0 ### New Features * Added `RetryPolicy.abortOn`, `abortWhen` and `abortIf` methods to abort retries when matched. ### API Changes * `RetryPolicy.retryWhen` was renamed to `retryIf` for retrying if a `Predicate` is matched. * `RetryPolicy.retryFor` was renamed to `retryWhen` for retrying when a result is matched. * `Scheduler` and `Schedulers` were moved to `net.jodah.recurrent.util.concurrent`. # 0.5.0 ### New Features * Added support for synchronous and asynchronous event listeners * Added support for `CheckedRunnable` ### API Changes * The `Recurrent.run` methods now require a `CheckedRunnable` rather than `Runnable`. This allows Recurrent to be used on code that throws checked exceptions without having to wrap the code in try/catch blocks. * The synchronous `Recurrent.run` and `Recurrent.get` methods will throw a `RecurrentException` if a failure occurs and the retry policy is exceeded. # 0.4.0 ### New Features * Added better support for invocation tracking ### API Changes * New Invocation and `AsyncInvocation` APIs # 0.3.3 ### New Features * Add `Scheduler` API * Make `RetryPolicy` copyable ### Behavior Changes * Require `ContextualCallable` and `ContextualRunnable` to be manually retried * Add support for checking multiple retry policy conditions ### API Changes * Make ContextualRunnable throw Exception # 0.3.2 ### New Features * Add support for retrying when an invocation result matches a policy # 0.3.1 ### New Features * Added support for seprate retry tracking. # 0.3.0 * Initial Releasefailsafe-failsafe-parent-3.3.2/CONTRIBUTING.md000066400000000000000000000027171444561050700205760ustar00rootroot00000000000000### Reporting Bugs Bug reports are welcome and appreciated. When filing an issue, please include a small code snippet that demonstrates the bug if you can, else include a good description of how to reproduce the bug. ### Contributing Bug Fixes Pull requests for bugs related to existing features are always welcome. ### Requesting Features Feature requests are welcome by filing an issue. In general we try to make sure that new features fit well with the existing ones and that they're broadly useful. If your feature will require new APIs or API changes, feel free to share an example of how you think the API should look. ### Contributing Features If you have an idea for a new feature, the best place to start is not with a pull request but rather by opening an issue describing how the feature or API change should work and why you think it is necessary. The reason we suggest starting with an issue rather than a pull request is that we like to make sure every feature and API change is widely useful and a good fit for the library, and would hate to reject a PR that someone puts a lot of time into if it's not a good fit. If your feature idea sounds good, you can then submit a PR, else we'll schedule the feature for implementation. ### Contributing Documentation or Website Fixes Fixes to the Failsafe documentation or website are welcome. Just clone the [website repo](https://github.com/failsafe-lib/failsafe-lib.github.io) and feel free to submit a pull request. failsafe-failsafe-parent-3.3.2/LICENSE000066400000000000000000000236751444561050700173600ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONSfailsafe-failsafe-parent-3.3.2/README.md000066400000000000000000000034601444561050700176200ustar00rootroot00000000000000# Failsafe [![Build Status](https://github.com/failsafe-lib/failsafe/workflows/build/badge.svg)](https://github.com/failsafe-lib/failsafe/actions) [![Maven Central](https://img.shields.io/maven-central/v/dev.failsafe/failsafe.svg?maxAge=60&colorB=53C92E)](https://maven-badges.herokuapp.com/maven-central/dev.failsafe/failsafe) [![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) [![JavaDoc](https://img.shields.io/maven-central/v/dev.failsafe/failsafe.svg?maxAge=60&label=javadoc)](https://failsafe.dev/javadoc/core) [![Join the chat at https://gitter.im/jhalterman/failsafe](https://badges.gitter.im/jhalterman/failsafe.svg)](https://gitter.im/jhalterman/failsafe) Failsafe is a lightweight, zero-dependency library for handling failures in Java 8+, with a concise API for handling everyday use cases and the flexibility to handle everything else. It works by wrapping executable logic with one or more resilience policies, which can be combined and composed as needed. Policies include [Retry](https://failsafe.dev/retry/), [CircuitBreaker](https://failsafe.dev/circuit-breaker/), [RateLimiter](https://failsafe.dev/rate-limiter/), [Timeout](https://failsafe.dev/timeout/), [Bulkhead](https://failsafe.dev/bulkhead/), and [Fallback](https://failsafe.dev/fallback/). Additional modules include [OkHttp](https://failsafe.dev/okhttp/) and [Retrofit](https://failsafe.dev/retrofit/). ## Usage Visit [failsafe.dev](https://failsafe.dev) for usage info, docs, and additional resources. ## Contributing Check out the [contributing guidelines](https://github.com/failsafe-lib/failsafe/blob/master/CONTRIBUTING.md). ## License Copyright Jonathan Halterman and friends. Released under the [Apache 2.0 license](https://github.com/failsafe-lib/failsafe/blob/master/LICENSE).failsafe-failsafe-parent-3.3.2/VERSIONING.md000066400000000000000000000007031444561050700203430ustar00rootroot00000000000000### Versioning Failsafe follows MAJOR.MINOR.PATCH versioning where: - MAJOR versions contain significant new features and potentially significant incompatible API changes. - MINOR versions contain new features and potentially minor yet incompatible API changes. - PATCH versions contain bug fixes and minor new features that are fully backwards compatible. All versions, new features, and API changes are described in the [CHANGELOG](CHANGELOG.md).failsafe-failsafe-parent-3.3.2/bin/000077500000000000000000000000001444561050700171065ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/bin/push-javadoc.sh000077500000000000000000000031761444561050700220400ustar00rootroot00000000000000#!/bin/sh # run from top level dir ORG=failsafe-lib REPO=failsafe.dev pwd=`pwd` build () { echo "Building Javadocs for $1" cd $pwd if [ "$1" != "core" ]; then cd modules fi cd $1 mvn javadoc:javadoc -Djv=$apiVersion rm -rf target/docs git clone git@github.com:$ORG/$REPO.git target/docs cd target/docs git rm -rf javadoc/$1 mkdir -p javadoc/$1 mv -v ../site/apidocs/* javadoc/$1 patchFavIcon "javadoc" "../assets/images/favicon.png" commit && echo "Published Javadocs for $1" } patchFavIcon () { echo "Patching favicons" for f in $1/*.html ; do if [ -f "$f" ]; then # if no .html files exist, f is literal "*.html" tmpfile=`mktemp patch_favicon_XXXXX` # This creates tmpfile, with the same permissions as $f. # The next command will overwrite it but preserve the permissions. # Hat tip to http://superuser.com/questions/170226/standard-way-to-duplicate-a-files-permissions for this trick. \cp -p $f $tmpfile sed -e " s%\$%%" $f > $tmpfile DIFF=$(diff $f $tmpfile) if [ "$DIFF" != "" ] then echo "$f modified with favicon" fi mv -f $tmpfile $f fi; done ; for d in $1/* ; do if [ -d $d ]; then echo "descending to "$d ; patchFavIcon $d ../$2 ; fi ; done } commit() { echo "Committing javadocs" git add -A -f javadoc git commit -m "Updated JavaDocs" git push -fq > /dev/null } # Install parent and core echo "Installing parent and core artifacts" mvn install -N cd core mvn install -DskipTests=true cd ../ build "core" build "okhttp" build "retrofit"failsafe-failsafe-parent-3.3.2/core/000077500000000000000000000000001444561050700172665ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/pom.xml000066400000000000000000000017571444561050700206150ustar00rootroot00000000000000 4.0.0 dev.failsafe failsafe-parent 3.3.2 failsafe Failsafe ${project.groupId}.core org.moditect moditect-maven-plugin maven-jar-plugin test-jar failsafe-failsafe-parent-3.3.2/core/src/000077500000000000000000000000001444561050700200555ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/000077500000000000000000000000001444561050700210015ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/000077500000000000000000000000001444561050700217225ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/000077500000000000000000000000001444561050700225005ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/000077500000000000000000000000001444561050700242525ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/AsyncExecution.java000066400000000000000000000047431444561050700300660ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import java.util.concurrent.CompletableFuture; /** * Allows asynchronous executions to record their results or complete an execution. * * @param result type * @author Jonathan Halterman */ public interface AsyncExecution extends ExecutionContext { /** * Completes the execution and the associated {@code CompletableFuture}. * * @throws IllegalStateException if the execution is already recorded or complete */ void complete(); /** * Returns whether the execution is complete or if it can be retried. An execution is considered complete only when * all configured policies consider the execution complete. */ boolean isComplete(); /** * Records an execution {@code result} or {@code exception} which triggers failure handling, if needed, by the * configured policies. If policy handling is not possible or already complete, the resulting {@link * CompletableFuture} is completed. * * @throws IllegalStateException if the most recent execution was already recorded or the execution is complete */ void record(R result, Throwable exception); /** * Records an execution {@code result} which triggers failure handling, if needed, by the configured policies. If * policy handling is not possible or already complete, the resulting {@link CompletableFuture} is completed. * * @throws IllegalStateException if the most recent execution was already recorded or the execution is complete */ void recordResult(R result); /** * Records an {@code exception} which triggers failure handling, if needed, by the configured policies. If policy * handling is not possible or already complete, the resulting {@link CompletableFuture} is completed. * * @throws IllegalStateException if the most recent execution was already recorded or the execution is complete */ void recordException(Throwable exception); }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/AsyncExecutionImpl.java000066400000000000000000000110611444561050700306770ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.internal.util.Assert; import dev.failsafe.spi.*; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.function.Function; /** * AsyncExecution and AsyncExecutionInternal implementation. * * @param result type * @author Jonathan Halterman */ final class AsyncExecutionImpl extends ExecutionImpl implements AsyncExecutionInternal { // -- Cross-attempt state -- private final FailsafeFuture future; private final boolean asyncExecution; // The outermost function that executions begin with private Function, CompletableFuture>> outerFn; // -- Per-attempt state -- // Whether a policy executor completed post execution private final boolean[] policyPostExecuted = new boolean[policyExecutors.size()]; // Whether a result has been recorded private volatile boolean recorded; AsyncExecutionImpl(List> policies, Scheduler scheduler, FailsafeFuture future, boolean asyncExecution, Function, CompletableFuture>> innerFn) { super(policies); this.future = future; this.asyncExecution = asyncExecution; outerFn = asyncExecution ? Functions.toExecutionAware(innerFn) : innerFn; outerFn = Functions.toAsync(outerFn, scheduler, future); for (PolicyExecutor policyExecutor : policyExecutors) outerFn = policyExecutor.applyAsync(outerFn, scheduler, future); } /** * Create an async execution for a new attempt. */ private AsyncExecutionImpl(AsyncExecutionImpl execution) { super(execution); outerFn = execution.outerFn; future = execution.future; asyncExecution = execution.asyncExecution; } @Override public void complete() { Assert.state(!recorded, "The most recent execution has already been recorded or completed"); recorded = true; // Guard against race with a timeout being set synchronized (future) { ExecutionResult result = this.result != null ? this.result : ExecutionResult.none(); complete(postExecute(result), null); } } @Override public boolean isComplete() { return completed; } @Override public void record(R result, Throwable exception) { Assert.state(!recorded, "The most recent execution has already been recorded or completed"); recorded = true; // Guard against race with a timeout being set synchronized (future) { if (!attemptRecorded) { Assert.state(!completed, "Execution has already been completed"); record(new ExecutionResult<>(result, exception)); } // Proceed with handling the recorded result executeAsync(); } } @Override public void recordResult(R result) { record(result, null); } @Override public void recordException(Throwable exception) { record(null, exception); } @Override public boolean isAsyncExecution() { return asyncExecution; } @Override public boolean isRecorded() { return recorded; } @Override public synchronized void setPostExecuted(int policyIndex) { policyPostExecuted[policyIndex] = true; } @Override public synchronized boolean isPostExecuted(int policyIndex) { return policyPostExecuted[policyIndex]; } @Override public AsyncExecutionInternal copy() { return new AsyncExecutionImpl<>(this); } /** * Performs an asynchronous execution. */ void executeAsync() { outerFn.apply(this).whenComplete(this::complete); } private void complete(ExecutionResult result, Throwable error) { if (result == null && error == null) return; completed = true; if (!future.isDone()) { if (result != null) future.completeResult(result); else { if (error instanceof CompletionException) error = error.getCause(); future.completeResult(ExecutionResult.exception(error)); } } } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Bulkhead.java000066400000000000000000000110101444561050700266250ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.internal.BulkheadImpl; import java.time.Duration; /** * A bulkhead allows you to restrict concurrent executions as a way of preventing system overload. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see BulkheadConfig * @see BulkheadBuilder * @see BulkheadFullException */ public interface Bulkhead extends Policy { /** * Returns a Bulkhead for the {@code maxConcurrency} that has {@link BulkheadBuilder#withMaxWaitTime(Duration) zero * wait}. * * @param maxConcurrency controls the max concurrent executions that are permitted within the bulkhead */ static BulkheadBuilder builder(int maxConcurrency) { return new BulkheadBuilder<>(maxConcurrency); } /** * Creates a new BulkheadBuilder that will be based on the {@code config}. */ static BulkheadBuilder builder(BulkheadConfig config) { return new BulkheadBuilder<>(config); } /** * Returns a Bulkhead for the {@code maxConcurrency} that has {@link BulkheadBuilder#withMaxWaitTime(Duration) zero * wait}. Alias for {@code Bulkhead.builder(maxConcurrency).build()}. To configure additional options on a Bulkhead, * use {@link #builder(int)} instead. * * @param maxConcurrency controls the max concurrent executions that are permitted within the bulkhead * @see #builder(int) */ static Bulkhead of(int maxConcurrency) { return new BulkheadImpl<>(new BulkheadConfig<>(maxConcurrency)); } /** * Returns the {@link BulkheadConfig} that the Bulkhead was built with. */ @Override BulkheadConfig getConfig(); /** * Attempts to acquire a permit to perform an execution against within the bulkhead, waiting until one is available or * the thread is interrupted. After execution is complete, the permit should be {@link #releasePermit() released} back * to the bulkhead. * * @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit * @see #tryAcquirePermit() */ void acquirePermit() throws InterruptedException; /** * Attempts to acquire a permit to perform an execution within the bulkhead, waiting up to the {@code maxWaitTime} * until one is available, else throwing {@link BulkheadFullException} if a permit will not be available in time. * After execution is complete, the permit should be {@link #releasePermit() released} back to the bulkhead. * * @throws NullPointerException if {@code maxWaitTime} is null * @throws BulkheadFullException if the bulkhead cannot acquire a permit within the {@code maxWaitTime} * @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit * @see #tryAcquirePermit(Duration) */ default void acquirePermit(Duration maxWaitTime) throws InterruptedException { if (!tryAcquirePermit(maxWaitTime)) throw new BulkheadFullException(this); } /** * Tries to acquire a permit to perform an execution within the bulkhead, returning immediately without waiting. After * execution is complete, the permit should be {@link #releasePermit() released} back to the bulkhead. * * @return whether the requested {@code permits} are successfully acquired or not */ boolean tryAcquirePermit(); /** * Tries to acquire a permit to perform an execution within the bulkhead, waiting up to the {@code maxWaitTime} until * they are available. After execution is complete, the permit should be {@link #releasePermit() released} back to the * bulkhead. * * @return whether a permit is successfully acquired * @throws NullPointerException if {@code maxWaitTime} is null * @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit */ boolean tryAcquirePermit(Duration maxWaitTime) throws InterruptedException; /** * Releases a permit to execute. */ void releasePermit(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/BulkheadBuilder.java000066400000000000000000000040261444561050700301450ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.internal.BulkheadImpl; import dev.failsafe.internal.util.Assert; import java.time.Duration; /** * Builds {@link Bulkhead} instances. *

* This class is not threadsafe. *

* * @param result type * @author Jonathan Halterman * @see BulkheadConfig * @see BulkheadFullException */ public class BulkheadBuilder extends PolicyBuilder, BulkheadConfig, R> { BulkheadBuilder(int maxConcurrency) { super(new BulkheadConfig<>(maxConcurrency)); } BulkheadBuilder(BulkheadConfig config) { super(new BulkheadConfig<>(config)); } /** * Builds a new {@link Bulkhead} using the builder's configuration. */ public Bulkhead build() { return new BulkheadImpl<>(new BulkheadConfig<>(config)); } /** * Configures the {@code maxWaitTime} to wait for permits to be available. If permits cannot be acquired before the * {@code maxWaitTime} is exceeded, then the bulkhead will throw {@link BulkheadFullException}. *

* This setting only applies when the resulting Bulkhead is used with the {@link Failsafe} class. It does not apply * when the Bulkhead is used in a standalone way. *

* * @throws NullPointerException if {@code maxWaitTime} is null */ public BulkheadBuilder withMaxWaitTime(Duration maxWaitTime) { config.maxWaitTime = Assert.notNull(maxWaitTime, "maxWaitTime"); return this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/BulkheadConfig.java000066400000000000000000000034761444561050700277740ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import java.time.Duration; /** * Configuration for a {@link Bulkhead}. * * @param result type * @author Jonathan Halterman */ public class BulkheadConfig extends PolicyConfig { int maxConcurrency; Duration maxWaitTime; BulkheadConfig(int maxConcurrency) { this.maxConcurrency = maxConcurrency; maxWaitTime = Duration.ZERO; } BulkheadConfig(BulkheadConfig config) { super(config); maxConcurrency = config.maxConcurrency; maxWaitTime = config.maxWaitTime; } /** * Returns that max concurrent executions that are permitted within the bulkhead. * * @see Bulkhead#builder(int) */ public int getMaxConcurrency() { return maxConcurrency; } /** * Returns the max time to wait for permits to be available. If permits cannot be acquired before the max wait time is * exceeded, then the bulkhead will throw {@link BulkheadFullException}. *

* This setting only applies when the Bulkhead is used with the {@link Failsafe} class. It does not apply when the * Bulkhead is used in a standalone way. *

* * @see BulkheadBuilder#withMaxWaitTime(Duration) */ public Duration getMaxWaitTime() { return maxWaitTime; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/BulkheadFullException.java000066400000000000000000000021511444561050700313350ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * Thrown when an execution is attempted against a {@link Bulkhead} that is full. * * @author Jonathan Halterman */ public class BulkheadFullException extends FailsafeException { private static final long serialVersionUID = 1L; private final Bulkhead bulkhead; public BulkheadFullException(Bulkhead bulkhead) { this.bulkhead = bulkhead; } /** Returns the {@link Bulkhead} that caused the exception. */ public Bulkhead getBulkhead() { return bulkhead; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Call.java000066400000000000000000000043001444561050700257650ustar00rootroot00000000000000/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedRunnable; /** * A call that can perform Failsafe executions and can be cancelled. Cancellations are propagated to any {@link * ExecutionContext#onCancel(CheckedRunnable) cancelCallback} that is registered. Useful for integrating with libraries * that support cancellation. *

* To perform cancellable async executions, use the {@link FailsafeExecutor} async methods. *

* * @param result type * @author Jonathan Halterman */ public interface Call { /** * Executes the call until a successful result is returned or the configured policies are exceeded. * * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ R execute(); /** * Cancels a synchronous execution and calls the most recent {@link ExecutionContext#onCancel(CheckedRunnable) * cancelCallback} that was registered. The execution is still allowed to complete and return a result. In addition to * using a {@link ExecutionContext#onCancel(CheckedRunnable) cancelCallback}, executions can cooperate with * cancellation by checking {@link ExecutionContext#isCancelled()}. * * @param mayInterruptIfRunning whether the execution should be interrupted * @return whether cancellation was successful or not. Returns {@code false} if the execution was already cancelled or * completed. */ boolean cancel(boolean mayInterruptIfRunning); /** * Returns whether the call has been cancelled. */ boolean isCancelled(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/CallImpl.java000066400000000000000000000024201444561050700266100ustar00rootroot00000000000000/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * A call implementation that delegates to an execution. * * @param result type * @author Jonathan Halterman */ class CallImpl implements Call { private volatile SyncExecutionImpl execution; void setExecution(SyncExecutionImpl execution) { this.execution = execution; } @Override public R execute() { return execution.executeSync(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { boolean result = execution.cancel(); if (mayInterruptIfRunning) execution.interrupt(); return result; } @Override public boolean isCancelled() { return execution.isCancelled(); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/CircuitBreaker.java000066400000000000000000000215701444561050700300200ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import java.time.Duration; /** * A circuit breaker temporarily blocks execution when a configured number of failures are exceeded. *

* Circuit breakers have three states: closed, open, and half-open. When a circuit breaker is in * the closed (initial) state, executions are allowed. If a {@link CircuitBreakerBuilder#withFailureThreshold(int) * configurable number} of failures occur, optionally over some {@link CircuitBreakerBuilder#withFailureThreshold(int, * Duration) time period}, the circuit breaker transitions to the open state. In the open state a circuit * breaker will fail executions with {@link CircuitBreakerOpenException}. After a {@link * CircuitBreakerBuilder#withDelay(Duration) configurable delay}, the circuit breaker will transition to a * half-open state. In the * half-open state a {@link CircuitBreakerBuilder#withSuccessThreshold(int) configurable number} of trial * executions will be allowed, after which the circuit breaker will transition back to closed or open * depending on how many were successful. *

*

* A circuit breaker can be count based or time based: *

    *
  • Count based circuit breakers will transition between states when recent execution results exceed a threshold.
  • *
  • Time based circuit breakers will transition between states when recent execution results exceed a threshold * within a time period.
  • *
*

*

A minimum number of executions must be performed in order for a state transition to occur. Time based circuit * breakers use a sliding window to aggregate execution results. The window is divided into {@code 10} time slices, * each representing 1/10th of the {@link CircuitBreakerConfig#getFailureThresholdingPeriod() failureThresholdingPeriod}. * As time progresses, statistics for old time slices are gradually discarded, which smoothes the calculation of * success and failure rates.

*

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see CircuitBreakerConfig * @see CircuitBreakerBuilder * @see CircuitBreakerOpenException */ public interface CircuitBreaker extends Policy { /** * Creates a CircuitBreakerBuilder that by default will build a count based circuit breaker that opens after a {@link * CircuitBreakerBuilder#withFailureThreshold(int) single failure}, closes after a {@link * CircuitBreakerBuilder#withSuccessThreshold(int) single success}, and has a 1 minute {@link * CircuitBreakerBuilder#withDelay(Duration) delay}, unless configured otherwise. * * @see #ofDefaults() */ static CircuitBreakerBuilder builder() { return new CircuitBreakerBuilder<>(); } /** * Creates a new CircuitBreakerBuilder that will be based on the {@code config}. */ static CircuitBreakerBuilder builder(CircuitBreakerConfig config) { return new CircuitBreakerBuilder<>(config); } /** * Creates a count based CircuitBreaker that opens after one {@link CircuitBreakerBuilder#withFailureThreshold(int) * failure}, half-opens after a one minute {@link CircuitBreakerBuilder#withDelay(Duration) delay}, and closes after one * {@link CircuitBreakerBuilder#withSuccessThreshold(int) success}. To configure additional options on a * CircuitBreaker, use {@link #builder()} instead. * * @see #builder() */ static CircuitBreaker ofDefaults() { return CircuitBreaker.builder().build(); } /** * The state of the circuit. */ enum State { /** The circuit is closed and fully functional, allowing executions to occur. */ CLOSED, /** The circuit is opened and not allowing executions to occur. */ OPEN, /** The circuit is temporarily allowing executions to occur. */ HALF_OPEN } /** * Returns the {@link CircuitBreakerConfig} that the CircuitBreaker was built with. */ @Override CircuitBreakerConfig getConfig(); /** * Attempts to acquire a permit for the circuit breaker and throws {@link CircuitBreakerOpenException} if a permit * could not be acquired. Permission will be automatically released when a result or failure is recorded. * * @throws CircuitBreakerOpenException if the circuit breaker is in a half-open state and no permits remain according * to the configured success or failure thresholding capacity. * @see #tryAcquirePermit() * @see #recordResult(Object) * @see #recordException(Throwable) * @see #recordSuccess() * @see #recordFailure() */ default void acquirePermit() { if (!tryAcquirePermit()) throw new CircuitBreakerOpenException(this); } /** * Tries to acquire a permit to use the circuit breaker and returns whether a permit was acquired. Permission will be * automatically released when a result or failure is recorded. * * @see #recordResult(Object) * @see #recordException(Throwable) * @see #recordSuccess() * @see #recordFailure() */ boolean tryAcquirePermit(); /** * Opens the circuit. */ void open(); /** * Closes the circuit. */ void close(); /** * Half-opens the circuit. */ void halfOpen(); /** * Gets the state of the circuit. */ State getState(); /** * Returns the number of executions recorded in the current state when the state is CLOSED or HALF_OPEN. When the * state is OPEN, returns the executions recorded during the previous CLOSED state. *

* For count based thresholding, the max number of executions is limited to the execution threshold. For time based * thresholds, the number of executions may vary within the thresholding period. *

*/ int getExecutionCount(); /** * When in the OPEN state, returns the remaining delay until the circuit is half-opened and allows another execution, * else returns {@code Duration.ZERO}. */ Duration getRemainingDelay(); /** * Returns the number of failures recorded in the current state when the state is CLOSED or HALF_OPEN. When the state * is OPEN, returns the failures recorded during the previous CLOSED state. *

* For count based thresholds, the max number of failures is based on the {@link * CircuitBreakerConfig#getFailureThreshold() failure threshold}. For time based thresholds, the number of failures * may vary within the {@link CircuitBreakerConfig#getFailureThresholdingPeriod() failure thresholding period}. *

*/ long getFailureCount(); /** * The percentage rate of failed executions, from 0 to 100, in the current state when the state is CLOSED or * HALF_OPEN. When the state is OPEN, returns the rate recorded during the previous CLOSED state. *

* The rate is based on the configured {@link CircuitBreakerConfig#getFailureThresholdingCapacity() failure * thresholding capacity}. *

*/ int getFailureRate(); /** * Returns the number of successes recorded in the current state when the state is CLOSED or HALF_OPEN. When the state * is OPEN, returns the successes recorded during the previous CLOSED state. *

* The max number of successes is based on the {@link CircuitBreakerConfig#getSuccessThreshold() success threshold}. *

*/ int getSuccessCount(); /** * The percentage rate of successful executions, from 0 to 100, in the current state when the state is CLOSED or * HALF_OPEN. When the state is OPEN, returns the rate recorded during the previous CLOSED state. *

* The rate is based on the configured {@link CircuitBreakerConfig#getSuccessThresholdingCapacity() success * thresholding capacity}. *

*/ int getSuccessRate(); /** * Returns whether the circuit is closed. */ boolean isClosed(); /** * Returns whether the circuit is half open. */ boolean isHalfOpen(); /** * Returns whether the circuit is open. */ boolean isOpen(); /** * Records an execution failure. */ void recordFailure(); /** * Records an {@code exception} as a success or failure based on the failure configuration. */ void recordException(Throwable exception); /** * Records an execution {@code result} as a success or failure based on the failure configuration. */ void recordResult(R result); /** * Records an execution success. */ void recordSuccess(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/CircuitBreakerBuilder.java000066400000000000000000000362071444561050700313320ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.CircuitBreakerStateChangedEvent; import dev.failsafe.event.EventListener; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import dev.failsafe.internal.CircuitBreakerImpl; import dev.failsafe.internal.util.Assert; import dev.failsafe.internal.util.Durations; import java.time.Duration; /** * Builds {@link CircuitBreaker} instances. *
    *
  • By default, any exception is considered a failure and will be handled by the policy. You can override this by * specifying your own {@code handle} conditions. The default exception handling condition will only be overridden by * another condition that handles exceptions such as {@link #handle(Class)} or {@link #handleIf(CheckedBiPredicate)}. * Specifying a condition that only handles results, such as {@link #handleResult(Object)} or * {@link #handleResultIf(CheckedPredicate)} will not replace the default exception handling condition.
  • *
  • If multiple {@code handle} conditions are specified, any condition that matches an execution result or exception * will trigger policy handling.
  • *
*

* Note: *

    *
  • This class extends {@link DelayablePolicyBuilder} and {@link FailurePolicyBuilder} which offer additional configuration.
  • *
  • This class is not threadsafe.
  • *
*

* * @param result type * @author Jonathan Halterman * @see CircuitBreaker * @see CircuitBreakerConfig * @see CircuitBreakerOpenException */ public class CircuitBreakerBuilder extends DelayablePolicyBuilder, CircuitBreakerConfig, R> implements PolicyListeners, R> { CircuitBreakerBuilder() { super(new CircuitBreakerConfig<>()); config.delay = Duration.ofMinutes(1); config.failureThreshold = 1; config.failureThresholdingCapacity = 1; } CircuitBreakerBuilder(CircuitBreakerConfig config) { super(new CircuitBreakerConfig<>(config)); } /** * Builds a new {@link CircuitBreaker} using the builder's configuration. */ public CircuitBreaker build() { return new CircuitBreakerImpl<>(new CircuitBreakerConfig<>(config)); } /** * Calls the {@code listener} when the circuit is closed. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored.

* * @throws NullPointerException if {@code listener} is null */ public CircuitBreakerBuilder onClose(EventListener listener) { config.closeListener = Assert.notNull(listener, "runnable"); return this; } /** * Calls the {@code listener} when the circuit is half-opened. *

Note: Any exceptions that are thrown within the {@code listener} are ignored.

* * @throws NullPointerException if {@code listener} is null */ public CircuitBreakerBuilder onHalfOpen(EventListener listener) { config.halfOpenListener = Assert.notNull(listener, "runnable"); return this; } /** * Calls the {@code listener} when the circuit is opened. *

Note: Any exceptions that are thrown within the {@code listener} are ignored.

* * @throws NullPointerException if {@code listener} is null */ public CircuitBreakerBuilder onOpen(EventListener listener) { config.openListener = Assert.notNull(listener, "listener"); return this; } /** * Sets the {@code delay} to wait in OPEN state before transitioning to half-open. * * @throws NullPointerException if {@code delay} is null * @throws IllegalArgumentException if {@code delay} < 0 */ public CircuitBreakerBuilder withDelay(Duration delay) { Assert.notNull(delay, "delay"); delay = Durations.ofSafeNanos(delay); Assert.isTrue(delay.toNanos() >= 0, "delay must be >= 0"); config.delay = delay; return this; } /** * Configures count based failure thresholding by setting the number of consecutive failures that must occur when in a * CLOSED state in order to open the circuit. *

* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} will also * be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to OPEN or * CLOSED. *

* * @param failureThreshold The number of consecutive failures that must occur in order to open the circuit * @throws IllegalArgumentException if {@code failureThreshold} < 1 * @see CircuitBreakerConfig#getFailureThreshold() */ public CircuitBreakerBuilder withFailureThreshold(int failureThreshold) { return withFailureThreshold(failureThreshold, failureThreshold); } /** * Configures count based failure thresholding by setting the ratio of failures to executions that must occur when in * a CLOSED state in order to open the circuit. For example: 5, 10 would open the circuit if 5 out of the last 10 * executions result in a failure. *

* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} and * {@code failureThresholdingCapacity} will also be used when the circuit breaker is in a HALF_OPEN state to determine * whether to transition back to OPEN or CLOSED. *

* * @param failureThreshold The number of failures that must occur in order to open the circuit * @param failureThresholdingCapacity The capacity for storing execution results when performing failure thresholding * @throws IllegalArgumentException if {@code failureThreshold} < 1, {@code failureThresholdingCapacity} < 1, or * {@code failureThreshold} > {@code failureThresholdingCapacity} * @see CircuitBreakerConfig#getFailureThreshold() * @see CircuitBreakerConfig#getFailureExecutionThreshold() */ public CircuitBreakerBuilder withFailureThreshold(int failureThreshold, int failureThresholdingCapacity) { Assert.isTrue(failureThreshold >= 1, "failureThreshold must be >= 1"); Assert.isTrue(failureThresholdingCapacity >= 1, "failureThresholdingCapacity must be >= 1"); Assert.isTrue(failureThresholdingCapacity >= failureThreshold, "failureThresholdingCapacity must be >= failureThreshold"); config.failureThreshold = failureThreshold; config.failureThresholdingCapacity = failureThresholdingCapacity; return this; } /** * Configures time based failure thresholding by setting the number of failures that must occur within the {@code * failureThresholdingPeriod} when in a CLOSED state in order to open the circuit. *

* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} will also * be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to OPEN or * CLOSED. *

* * @param failureThreshold The number of failures that must occur within the {@code failureThresholdingPeriod} in * order to open the circuit * @param failureThresholdingPeriod The period during which failures are compared to the {@code failureThreshold} * @throws NullPointerException if {@code failureThresholdingPeriod} is null * @throws IllegalArgumentException if {@code failureThreshold} < 1 or {@code failureThresholdingPeriod} < 10 ms * @see CircuitBreakerConfig#getFailureThreshold() * @see CircuitBreakerConfig#getFailureThresholdingPeriod() */ public CircuitBreakerBuilder withFailureThreshold(int failureThreshold, Duration failureThresholdingPeriod) { return withFailureThreshold(failureThreshold, failureThreshold, failureThresholdingPeriod); } /** * Configures time based failure thresholding by setting the number of failures that must occur within the {@code * failureThresholdingPeriod} when in a CLOSED state in order to open the circuit. The number of executions must also * exceed the {@code failureExecutionThreshold} within the {@code failureThresholdingPeriod} when in the CLOSED state * before the circuit can be opened. *

* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} will also * be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to OPEN or * CLOSED. *

* * @param failureThreshold The number of failures that must occur within the {@code failureThresholdingPeriod} in * order to open the circuit * @param failureExecutionThreshold The minimum number of executions that must occur within the {@code * failureThresholdingPeriod} when in the CLOSED state before the circuit can be opened * @param failureThresholdingPeriod The period during which failures are compared to the {@code failureThreshold} * @throws NullPointerException if {@code failureThresholdingPeriod} is null * @throws IllegalArgumentException if {@code failureThreshold} < 1, {@code failureExecutionThreshold} < 1, {@code * failureThreshold} > {@code failureExecutionThreshold}, or {@code failureThresholdingPeriod} < 10 ms * @see CircuitBreakerConfig#getFailureThreshold() * @see CircuitBreakerConfig#getFailureExecutionThreshold() * @see CircuitBreakerConfig#getFailureThresholdingPeriod() */ public CircuitBreakerBuilder withFailureThreshold(int failureThreshold, int failureExecutionThreshold, Duration failureThresholdingPeriod) { Assert.isTrue(failureThreshold >= 1, "failureThreshold must be >= 1"); Assert.isTrue(failureExecutionThreshold >= failureThreshold, "failureExecutionThreshold must be >= failureThreshold"); assertFailureExecutionThreshold(failureExecutionThreshold); assertFailureThresholdingPeriod(failureThresholdingPeriod); config.failureThreshold = failureThreshold; config.failureThresholdingCapacity = failureThreshold; config.failureExecutionThreshold = failureExecutionThreshold; config.failureThresholdingPeriod = failureThresholdingPeriod; return this; } /** * Configures time based failure rate thresholding by setting the percentage rate of failures, from 1 to 100, that * must occur within the rolling {@code failureThresholdingPeriod} when in a CLOSED state in order to open the * circuit. The number of executions must also exceed the {@code failureExecutionThreshold} within the {@code * failureThresholdingPeriod} before the circuit can be opened. *

* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureExecutionThreshold} * will also be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to open * or closed. *

* * @param failureRateThreshold The percentage rate of failures, from 1 to 100, that must occur in order to open the * circuit * @param failureExecutionThreshold The minimum number of executions that must occur within the {@code * failureThresholdingPeriod} when in the CLOSED state before the circuit can be opened, or in the HALF_OPEN state * before it can be re-opened or closed * @param failureThresholdingPeriod The period during which failures are compared to the {@code failureThreshold} * @throws NullPointerException if {@code failureThresholdingPeriod} is null * @throws IllegalArgumentException if {@code failureRateThreshold} < 1 or > 100, {@code failureExecutionThreshold} < * 1, or {@code failureThresholdingPeriod} < 10 ms * @see CircuitBreakerConfig#getFailureRateThreshold() * @see CircuitBreakerConfig#getFailureExecutionThreshold() * @see CircuitBreakerConfig#getFailureThresholdingPeriod() */ public CircuitBreakerBuilder withFailureRateThreshold(int failureRateThreshold, int failureExecutionThreshold, Duration failureThresholdingPeriod) { Assert.isTrue(failureRateThreshold >= 1 && failureRateThreshold <= 100, "failureRateThreshold must be between 1 and 100"); assertFailureExecutionThreshold(failureExecutionThreshold); assertFailureThresholdingPeriod(failureThresholdingPeriod); config.failureRateThreshold = failureRateThreshold; config.failureExecutionThreshold = failureExecutionThreshold; config.failureThresholdingPeriod = failureThresholdingPeriod; return this; } private void assertFailureExecutionThreshold(int failureExecutionThreshold) { Assert.isTrue(failureExecutionThreshold >= 1, "failureExecutionThreshold must be >= 1"); } private void assertFailureThresholdingPeriod(Duration failureThresholdingPeriod) { Assert.notNull(failureThresholdingPeriod, "failureThresholdingPeriod"); Assert.isTrue(failureThresholdingPeriod.toMillis() >= 10, "failureThresholdingPeriod must be >= 10 ms"); } /** * Configures count based success thresholding by setting the number of consecutive successful executions that must * occur when in a HALF_OPEN state in order to close the circuit, else the circuit is re-opened when a failure * occurs. * * @param successThreshold The number of consecutive successful executions that must occur in order to open the * circuit * @throws IllegalArgumentException if {@code successThreshold} < 1 * @see CircuitBreakerConfig#getSuccessThreshold() */ public CircuitBreakerBuilder withSuccessThreshold(int successThreshold) { return withSuccessThreshold(successThreshold, successThreshold); } /** * Configures count based success thresholding by setting the ratio of successful executions that must occur when in a * HALF_OPEN state in order to close the circuit. For example: 5, 10 would close the circuit if 5 out of the last 10 * executions were successful. * * @param successThreshold The number of successful executions that must occur in order to open the circuit * @param successThresholdingCapacity The capacity for storing execution results when performing success thresholding * @throws IllegalArgumentException if {@code successThreshold} < 1, {@code successThresholdingCapacity} < 1, or * {@code successThreshold} > {@code successThresholdingCapacity} * @see CircuitBreakerConfig#getSuccessThreshold() * @see CircuitBreakerConfig#getSuccessThresholdingCapacity() */ public CircuitBreakerBuilder withSuccessThreshold(int successThreshold, int successThresholdingCapacity) { Assert.isTrue(successThreshold >= 1, "successThreshold must be >= 1"); Assert.isTrue(successThresholdingCapacity >= 1, "successThresholdingCapacity must be >= 1"); Assert.isTrue(successThresholdingCapacity >= successThreshold, "successThresholdingCapacity must be >= successThreshold"); config.successThreshold = successThreshold; config.successThresholdingCapacity = successThresholdingCapacity; return this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/CircuitBreakerConfig.java000066400000000000000000000151641444561050700311500ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.CircuitBreakerStateChangedEvent; import java.time.Duration; /** * Configuration for a {@link CircuitBreaker}. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see CircuitBreakerBuilder */ public class CircuitBreakerConfig extends DelayablePolicyConfig { // Failure config int failureThreshold; int failureRateThreshold; int failureThresholdingCapacity; int failureExecutionThreshold; Duration failureThresholdingPeriod; // Success config int successThreshold; int successThresholdingCapacity; // Listeners EventListener openListener; EventListener halfOpenListener; EventListener closeListener; CircuitBreakerConfig() { } CircuitBreakerConfig(CircuitBreakerConfig config) { super(config); failureThreshold = config.failureThreshold; failureRateThreshold = config.failureRateThreshold; failureThresholdingCapacity = config.failureThresholdingCapacity; failureExecutionThreshold = config.failureExecutionThreshold; failureThresholdingPeriod = config.failureThresholdingPeriod; successThreshold = config.successThreshold; successThresholdingCapacity = config.successThresholdingCapacity; openListener = config.openListener; halfOpenListener = config.halfOpenListener; closeListener = config.closeListener; } /** * Returns the delay before allowing another execution on the circuit. Defaults to 1 minute. * * @see CircuitBreakerBuilder#withDelay(Duration) * @see CircuitBreaker#getRemainingDelay() */ public Duration getDelay() { return delay; } /** * Gets the number of failures that must occur within the {@link #getFailureThresholdingCapacity() failure * thresholding capacity} when in a CLOSED or HALF_OPEN state in order to open the circuit. Returns {@code 1} by * default. * * @see CircuitBreakerBuilder#withFailureThreshold(int) * @see CircuitBreakerBuilder#withFailureThreshold(int, int) */ public int getFailureThreshold() { return failureThreshold; } /** * Returns the rolling capacity for storing execution results when performing failure thresholding in the CLOSED or * HALF_OPEN states. {@code 1} by default. Only the most recent executions that fit within this capacity contribute to * thresholding decisions. * * @see CircuitBreakerBuilder#withFailureThreshold(int) * @see CircuitBreakerBuilder#withFailureThreshold(int, int) */ public int getFailureThresholdingCapacity() { return failureThresholdingCapacity; } /** * Used with time based thresholding. Returns percentage rate of failures, from 1 to 100, that must occur when in a * CLOSED or HALF_OPEN state in order to open the circuit, else {@code 0} if failure rate thresholding is not * configured. * * @see CircuitBreakerBuilder#withFailureRateThreshold(int, int, Duration) */ public int getFailureRateThreshold() { return failureRateThreshold; } /** * Used with time based thresholding. Returns the rolling time period during which failure thresholding is performed * when in the CLOSED state, else {@code null} if time based failure thresholding is not configured. Only the most * recent executions that occurred within this rolling time period contribute to thresholding decisions. * * @see CircuitBreakerBuilder#withFailureThreshold(int, Duration) * @see CircuitBreakerBuilder#withFailureThreshold(int, int, Duration) * @see CircuitBreakerBuilder#withFailureRateThreshold(int, int, Duration) */ public Duration getFailureThresholdingPeriod() { return failureThresholdingPeriod; } /** * Used with time based thresholding. Returns the minimum number of executions that must be recorded in the CLOSED * state before the breaker can be opened. For {@link CircuitBreakerBuilder#withFailureRateThreshold(int, int, * Duration) failure rate thresholding} this also determines the minimum number of executions that must be recorded in * the HALF_OPEN state. Returns {@code 0} by default. * * @see CircuitBreakerBuilder#withFailureThreshold(int, int, Duration) * @see CircuitBreakerBuilder#withFailureRateThreshold(int, int, Duration) */ public int getFailureExecutionThreshold() { return failureExecutionThreshold; } /** * Gets the number of successes that must occur within the {@link #getSuccessThresholdingCapacity() success * thresholding capacity} when in a HALF_OPEN state in order to open the circuit. Returns {@code 0} by default, in * which case the {@link #getFailureThreshold() failure threshold} is used instead. * * @see CircuitBreakerBuilder#withSuccessThreshold(int) * @see CircuitBreakerBuilder#withSuccessThreshold(int, int) */ public int getSuccessThreshold() { return successThreshold; } /** * Returns the rolling capacity for storing execution results when performing success thresholding in the HALF_OPEN * state. Only the most recent executions that fit within this capacity contribute to thresholding decisions. * * @see CircuitBreakerBuilder#withSuccessThreshold(int) * @see CircuitBreakerBuilder#withSuccessThreshold(int, int) */ public int getSuccessThresholdingCapacity() { return successThresholdingCapacity; } /** * Returns the open event listener. * * @see CircuitBreakerBuilder#onOpen(EventListener) */ public EventListener getOpenListener() { return openListener; } /** * Returns the half-open event listener. * * @see CircuitBreakerBuilder#onHalfOpen(EventListener) */ public EventListener getHalfOpenListener() { return halfOpenListener; } /** * Returns the close event listener. * * @see CircuitBreakerBuilder#onClose(EventListener) */ public EventListener getCloseListener() { return closeListener; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/CircuitBreakerOpenException.java000066400000000000000000000022671444561050700325230ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * Thrown when an execution is attempted against a {@link CircuitBreaker} that is open. * * @author Jonathan Halterman */ public class CircuitBreakerOpenException extends FailsafeException { private static final long serialVersionUID = 1L; private final CircuitBreaker circuitBreaker; public CircuitBreakerOpenException(CircuitBreaker circuitBreaker) { this.circuitBreaker = circuitBreaker; } /** Returns the {@link CircuitBreaker} that caused the exception. */ public CircuitBreaker getCircuitBreaker() { return circuitBreaker; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/DelayablePolicyBuilder.java000066400000000000000000000134401444561050700314700ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.ContextualSupplier; import dev.failsafe.internal.util.Assert; import java.time.Duration; /** * A builder of policies that can be delayed between executions. * * @param self type * @param config type * @param result type * @author Jonathan Halterman */ public abstract class DelayablePolicyBuilder, R> extends FailurePolicyBuilder { protected DelayablePolicyBuilder(C config) { super(config); } /** * Sets the {@code delay} to occur between execution attempts. * * @throws NullPointerException if {@code delay} is null * @throws IllegalArgumentException if {@code delay} <= 0 */ @SuppressWarnings("unchecked") public S withDelay(Duration delay) { Assert.notNull(delay, "delay"); Assert.isTrue(delay.toNanos() > 0, "delay must be greater than 0"); config.delay = delay; return (S) this; } /** * Sets the {@code delayFunction} that computes the next delay before allowing another execution. * *

* The {@code delayFunction} must complete quickly, not have side-effects, and always return the same result for the * same input. Exceptions thrown by the {@code delayFunction} method will not be handled and will * cause Failsafe's execution to abort. *

*

* Notes: *

    *
  • A negative return value will cause Failsafe to use a configured fixed or backoff delay *
  • Any configured jitter is still applied to DelayFunction provided values *
  • Any configured max duration is still applied to DelayFunction provided values *
  • The {@link ExecutionContext} that is provided to the {@code delayFunction} may be {@code null} if the prior execution * exception was manually recorded outside of a Failsafe execution.
  • *
*

* * @throws NullPointerException if {@code delayFunction} is null */ @SuppressWarnings("unchecked") public S withDelayFn(ContextualSupplier delayFunction) { Assert.notNull(delayFunction, "delayFunction"); config.delayFn = delayFunction; return (S) this; } /** * Sets the {@code delayFunction} that computes the next delay before allowing another execution. Delays will only * occur for exceptions that are assignable from the {@code exception}. *

* The {@code delayFunction} must complete quickly, not have side-effects, and always return the same result for the * same input. Exceptions thrown by the {@code delayFunction} method will not be handled and will * cause Failsafe's execution to abort. *

*

* Notes: *

    *
  • A negative return value will cause Failsafe to use a configured fixed or backoff delay *
  • Any configured jitter is still applied to DelayFunction provided values *
  • Any configured max duration is still applied to DelayFunction provided values *
  • The {@link ExecutionContext} that is provided to the {@code delayFunction} may be {@code null} if the prior execution * exception was manually recorded outside of a Failsafe execution.
  • *
*

* * @param delayFunction the function to use to compute the delay before a next attempt * @param exception the execution exception that is expected in order to trigger the delay * @param exception type * @throws NullPointerException if {@code delayFunction} or {@code exception} are null */ @SuppressWarnings("unchecked") public S withDelayFnOn(ContextualSupplier delayFunction, Class exception) { withDelayFn(delayFunction); Assert.notNull(exception, "exception"); config.delayException = exception; return (S) this; } /** * Sets the {@code delayFunction} that computes the next delay before allowing another execution. Delays will only * occur for results that equal the {@code result}. *

* The {@code delayFunction} must complete quickly, not have side-effects, and always return the same result for the * same input. Exceptions thrown by the {@code delayFunction} method will not be handled and will * cause Failsafe's execution to abort. *

*

* Notes: *

    *
  • A negative return value will cause Failsafe to use a configured fixed or backoff delay *
  • Any configured jitter is still applied to DelayFunction provided values *
  • Any configured max duration is still applied to DelayFunction provided values *
  • The {@link ExecutionContext} that is provided to the {@code delayFunction} may be {@code null} if the prior execution * exception was manually recorded outside of a Failsafe execution.
  • *
*

* * @param delayFunction the function to use to compute the delay before a next attempt * @param result the execution result that is expected in order to trigger the delay * @throws NullPointerException if {@code delayFunction} or {@code result} are null */ @SuppressWarnings("unchecked") public S withDelayFnWhen(ContextualSupplier delayFunction, R result) { withDelayFn(delayFunction); Assert.notNull(result, "result"); config.delayResult = result; return (S) this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/DelayablePolicyConfig.java000066400000000000000000000046371444561050700313170ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.ContextualSupplier; import java.time.Duration; /** * Configuration for policies that can delay between executions. * * @param result type * @author Jonathan Halterman */ public abstract class DelayablePolicyConfig extends FailurePolicyConfig { Duration delay; R delayResult; Class delayException; ContextualSupplier delayFn; protected DelayablePolicyConfig() { } protected DelayablePolicyConfig(DelayablePolicyConfig config) { super(config); delay = config.delay; delayResult = config.delayResult; delayException = config.delayException; delayFn = config.delayFn; } /** * Returns the delay until the next execution attempt can be performed. * * @see DelayablePolicyBuilder#withDelay(Duration) */ public Duration getDelay() { return delay; } /** * Returns the function that determines the next delay before another execution can be performed. * * @see DelayablePolicyBuilder#withDelayFn(ContextualSupplier) * @see DelayablePolicyBuilder#withDelayFnOn(ContextualSupplier, Class) * @see DelayablePolicyBuilder#withDelayFnWhen(ContextualSupplier, Object) */ public ContextualSupplier getDelayFn() { return delayFn; } /** * Returns the Throwable that must be matched in order to delay using the {@link #getDelayFn()}. * * @see DelayablePolicyBuilder#withDelayFnOn(ContextualSupplier, Class) */ public Class getDelayException() { return delayException; } /** * Returns the result that must be matched in order to delay using the {@link #getDelayFn()}. * * @see DelayablePolicyBuilder#withDelayFnWhen(ContextualSupplier, Object) */ public R getDelayResult() { return delayResult; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Execution.java000066400000000000000000000060161444561050700270630ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.internal.util.Assert; import dev.failsafe.internal.util.Lists; import java.time.Duration; /** * Tracks synchronous executions and handles failures according to one or more {@link Policy policies}. Execution * results must be explicitly recorded via one of the {@code record} methods. * * @param result type * @author Jonathan Halterman */ public interface Execution extends ExecutionContext { /** * Creates a new {@code Execution} that will use the {@code outerPolicy} and {@code innerPolicies} to handle * failures. Policies are applied in reverse order, with the last policy being applied first. * * @throws NullPointerException if {@code outerPolicy} is null */ @SafeVarargs static Execution of(Policy outerPolicy, Policy... policies) { return new SyncExecutionImpl<>(Lists.of(Assert.notNull(outerPolicy, "outerPolicy"), policies)); } /** * Records and completes the execution successfully. * * @throws IllegalStateException if the execution is already complete */ void complete(); /** * Returns whether the execution is complete or if it can be retried. An execution is considered complete only when * all configured policies consider the execution complete. */ boolean isComplete(); /** * Returns the time to delay before the next execution attempt. Returns {@code 0} if an execution has not yet * occurred. */ Duration getDelay(); /** * Records an execution {@code result} or {@code exception} which triggers failure handling, if needed, by the * configured policies. If policy handling is not possible or completed, the execution is completed. * * @throws IllegalStateException if the execution is already complete */ void record(R result, Throwable exception); /** * Records an execution {@code result} which triggers failure handling, if needed, by the configured policies. If * policy handling is not possible or completed, the execution is completed. * * @throws IllegalStateException if the execution is already complete */ void recordResult(R result); /** * Records an {@code exception} which triggers failure handling, if needed, by the configured policies. If policy * handling is not possible or completed, the execution is completed. * * @throws IllegalStateException if the execution is already complete */ void recordException(Throwable exception); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/ExecutionContext.java000066400000000000000000000055661444561050700304410ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedRunnable; import java.time.Duration; import java.time.Instant; import java.util.concurrent.CompletableFuture; /** * Contextual execution information. * * @param result type * @author Jonathan Halterman */ public interface ExecutionContext { /** * Sets the {@code cancelCallback} to be called if the execution is cancelled, such as by the resulting {@link Call} * or {@link CompletableFuture}, or a {@link Timeout}. Any exception thrown by the {@code cancelCallback} is ignored. */ void onCancel(CheckedRunnable cancelCallback); /** * Returns the elapsed time since initial execution began. */ Duration getElapsedTime(); /** * Returns the elapsed time since the last execution attempt began. */ Duration getElapsedAttemptTime(); /** * Gets the number of execution attempts so far, including attempts that are blocked before being executed, such as * when a {@link CircuitBreaker} is open. Will return {@code 0} when the first attempt is in progress or has yet to * begin. */ int getAttemptCount(); /** * Gets the number of completed executions so far. Executions that are blocked, such as when a {@link CircuitBreaker} * is open, are not counted. Will return {@code 0} when the first attempt is in progress or has yet to begin. */ int getExecutionCount(); /** * Returns the last exception that was recorded else {@code null}. */ T getLastException(); /** * Returns the last result that was recorded else {@code null}. */ R getLastResult(); /** * Returns the last result that was recorded else the {@code defaultValue}. */ R getLastResult(R defaultValue); /** * Returns the time that the initial execution started. */ Instant getStartTime(); /** * Returns whether the execution has been cancelled. In this case the implementor should attempt to stop execution. */ boolean isCancelled(); /** * Returns {@code true} when an execution result has not yet been recorded, meaning this is the first execution * attempt. */ boolean isFirstAttempt(); /** * Returns {@code true} when an execution result has already been recorded, meaning the execution is being retried. */ boolean isRetry(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/ExecutionImpl.java000066400000000000000000000176151444561050700277140ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedRunnable; import dev.failsafe.internal.util.Assert; import dev.failsafe.spi.ExecutionInternal; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.spi.PolicyExecutor; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * Execution and ExecutionInternal implementation. * * @param result type * @author Jonathan Halterman */ class ExecutionImpl implements ExecutionInternal { // -- Cross-attempt state -- final List> policyExecutors; // When the first execution attempt was started private volatile Instant startTime; // Number of execution attempts private final AtomicInteger attempts; // Number of completed executions private final AtomicInteger executions; // The latest execution attenpt private final AtomicReference> latest; // -- Per-attempt state -- // The result of the previous execution attempt private final ExecutionResult previousResult; // The result of the current execution attempt; volatile ExecutionResult result; // When the most recent execution attempt was started private volatile Instant attemptStartTime; // The index of a PolicyExecutor that cancelled the execution. Integer.MIN_VALUE represents non-cancelled. volatile int cancelledIndex = Integer.MIN_VALUE; // The user-provided callback to be called when an execution is cancelled volatile CheckedRunnable cancelCallback; // Whether the execution has pre-executed indicating it has started private volatile boolean preExecuted; // Whether the execution attempt has been recorded volatile boolean attemptRecorded; // Whether the execution has been completed for all polices volatile boolean completed; /** * Creates a new execution for the {@code policies}. */ ExecutionImpl(List> policies) { policyExecutors = new ArrayList<>(policies.size()); attempts = new AtomicInteger(); executions = new AtomicInteger(); latest = new AtomicReference<>(this); previousResult = null; // Create policy executors ListIterator> policyIterator = policies.listIterator(policies.size()); for (int i = 0; policyIterator.hasPrevious(); i++) { Policy policy = Assert.notNull(policyIterator.previous(), "policies"); PolicyExecutor policyExecutor = policy.toExecutor(i); policyExecutors.add(policyExecutor); } } /** * Create an execution for a new attempt. */ ExecutionImpl(ExecutionImpl execution) { policyExecutors = execution.policyExecutors; startTime = execution.startTime; attempts = execution.attempts; executions = execution.executions; latest = execution.latest; latest.set(this); previousResult = execution.result; } /** Used for testing purposes only */ ExecutionImpl(ExecutionResult previousResult) { policyExecutors = null; attempts = new AtomicInteger(); executions = new AtomicInteger(); latest = new AtomicReference<>(this); this.previousResult = previousResult; } @Override public ExecutionResult getResult() { return result; } @Override public void onCancel(CheckedRunnable cancelCallback) { this.cancelCallback = cancelCallback; } @Override public synchronized void preExecute() { if (!preExecuted) { attemptStartTime = Instant.now(); if (startTime == null) startTime = attemptStartTime; preExecuted = true; } } @Override public boolean isPreExecuted() { return preExecuted; } @Override public synchronized void recordAttempt() { if (!attemptRecorded) { attempts.incrementAndGet(); attemptRecorded = true; } } @Override public synchronized void record(ExecutionResult result) { if (preExecuted && !attemptRecorded) { recordAttempt(); executions.incrementAndGet(); this.result = result; } } /** * Externally called. Records an execution and performs post-execution handling for the {@code result} against all * configured policy executors. Returns whether the result is complete for all policies. * * @throws IllegalStateException if the execution is already complete */ synchronized ExecutionResult postExecute(ExecutionResult result) { Assert.state(!completed, "Execution has already been completed"); record(result); boolean allComplete = true; for (PolicyExecutor policyExecutor : policyExecutors) { result = policyExecutor.postExecute(this, result); allComplete = allComplete && result.isComplete(); } completed = allComplete; return result; } /** Called indirectly by users. */ @Override public boolean cancel() { boolean cancelled = isCancelled(); if (!cancelled) { cancelledIndex = Integer.MAX_VALUE; if (cancelCallback != null) { try { cancelCallback.run(); } catch (Throwable ignore) { } } } return !cancelled && !completed; } /** Called by policies. */ @Override public void cancel(PolicyExecutor policyExecutor) { cancelledIndex = policyExecutor.getPolicyIndex(); if (cancelCallback != null) { try { cancelCallback.run(); } catch (Throwable ignore) { } } } @Override public boolean isCancelled() { return cancelledIndex > Integer.MIN_VALUE; } @Override public boolean isCancelled(PolicyExecutor policyExecutor) { return cancelledIndex > policyExecutor.getPolicyIndex(); } @Override public Object getLock() { return latest; } @Override public ExecutionInternal getLatest() { return latest.get(); } @Override public Duration getElapsedTime() { return startTime == null ? Duration.ZERO : Duration.between(startTime, Instant.now()); } @Override public Duration getElapsedAttemptTime() { return attemptStartTime == null ? Duration.ZERO : Duration.between(attemptStartTime, Instant.now()); } @Override public int getAttemptCount() { return attempts.get(); } @Override public int getExecutionCount() { return executions.get(); } @Override @SuppressWarnings("unchecked") public T getLastException() { ExecutionResult r = result != null ? result : previousResult; return r == null ? null : (T) r.getException(); } @Override public R getLastResult() { ExecutionResult r = result != null ? result : previousResult; return r == null ? null : r.getResult(); } @Override public R getLastResult(R defaultValue) { ExecutionResult r = result != null ? result : previousResult; return r == null ? defaultValue : r.getResult(); } @Override public Instant getStartTime() { return startTime; } @Override public boolean isFirstAttempt() { return attempts.get() == (!attemptRecorded ? 0 : 1); } @Override public boolean isRetry() { return attempts.get() > (!attemptRecorded ? 0 : 1); } @Override public String toString() { return "[" + "attempts=" + attempts + ", executions=" + executions + ", lastResult=" + getLastResult() + ", lastException=" + getLastException() + ']'; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Failsafe.java000066400000000000000000000105171444561050700266330ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.internal.util.Assert; import dev.failsafe.internal.util.Lists; import java.util.Collections; import java.util.List; /** * Simple, sophisticated failure handling. * * @author Jonathan Halterman */ public class Failsafe { /** * Creates and returns a new {@link FailsafeExecutor} instance that will handle failures according to the given {@code * outerPolicy} and {@code policies}. The policies are composed around an execution and will handle execution results * in reverse, with the last policy being applied first. For example, consider: *

*

   *   Failsafe.with(fallback, retryPolicy, circuitBreaker).get(supplier);
   * 
*

*

* This is equivalent to composition using the the {@link FailsafeExecutor#compose(Policy) compose} method: *

   *   Failsafe.with(fallback).compose(retryPolicy).compose(circuitBreaker).get(supplier);
   * 
*

* These result in the following internal composition when executing a {@code runnable} or {@code supplier} and * handling its result: *

*

   *   Fallback(RetryPolicy(CircuitBreaker(Supplier)))
   * 
*

* This means the {@code CircuitBreaker} is first to evaluate the {@code Supplier}'s result, then the {@code * RetryPolicy}, then the {@code Fallback}. Each policy makes its own determination as to whether the result * represents a failure. This allows different policies to be used for handling different types of failures. * * @param result type * @param

policy type * @throws NullPointerException if {@code outerPolicy} is null */ @SafeVarargs public static > FailsafeExecutor with(P outerPolicy, P... policies) { Assert.notNull(outerPolicy, "outerPolicy"); return new FailsafeExecutor<>(Lists.of(outerPolicy, policies)); } /** * Creates and returns a new {@link FailsafeExecutor} instance that will handle failures according to the given {@code * policies}. The {@code policies} are composed around an execution and will handle execution results in reverse, with * the last policy being applied first. For example, consider: *

*

   *   Failsafe.with(Arrays.asList(fallback, retryPolicy, circuitBreaker)).get(supplier);
   * 
*

* This results in the following internal composition when executing a {@code runnable} or {@code supplier} and * handling its result: *

*

   *   Fallback(RetryPolicy(CircuitBreaker(Supplier)))
   * 
*

* This means the {@code CircuitBreaker} is first to evaluate the {@code Supplier}'s result, then the {@code * RetryPolicy}, then the {@code Fallback}. Each policy makes its own determination as to whether the result * represents a failure. This allows different policies to be used for handling different types of failures. * * @param result type * @throws NullPointerException if {@code policies} is null * @throws IllegalArgumentException if {@code policies} is empty */ public static FailsafeExecutor with(List> policies) { Assert.notNull(policies, "policies"); Assert.isTrue(!policies.isEmpty(), "At least one policy must be supplied"); return new FailsafeExecutor<>(policies); } /** * Creates and returns a noop {@link FailsafeExecutor} instance that treats any exception as a failure for the * purposes of calling event listeners, and provides no additional failure handling. * * @param result type * @throws NullPointerException if {@code policies} is null * @throws IllegalArgumentException if {@code policies} is empty */ public static FailsafeExecutor none() { return new FailsafeExecutor<>(Collections.emptyList()); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/FailsafeException.java000066400000000000000000000020551444561050700305100ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * Thrown when a synchronous Failsafe execution fails with an {@link Exception}, wrapping the underlying exception. Use * {@link Throwable#getCause()} to learn the cause of the failure. * * @author Jonathan Halterman */ public class FailsafeException extends RuntimeException { private static final long serialVersionUID = 1L; public FailsafeException() { } public FailsafeException(Throwable t) { super(t); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/FailsafeExecutor.java000066400000000000000000000436411444561050700303560ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.function.*; import dev.failsafe.internal.EventHandler; import dev.failsafe.internal.util.Assert; import dev.failsafe.spi.AsyncExecutionInternal; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.spi.FailsafeFuture; import dev.failsafe.spi.Scheduler; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; import java.util.function.BiConsumer; import java.util.function.Function; import static dev.failsafe.Functions.*; /** *

* An executor that handles failures according to configured {@link FailurePolicyBuilder policies}. Can be created via * {@link Failsafe#with(Policy, Policy[])} to support policy based execution failure handling, or {@link * Failsafe#none()} to support execution with no failure handling. *

* Async executions are run by default on the {@link ForkJoinPool#commonPool()}. Alternative executors can be configured * via {@link #with(ScheduledExecutorService)} and similar methods. All async executions are cancellable and * interruptable via the returned CompletableFuture, even those run by a {@link ForkJoinPool} or {@link * CompletionStage}. * * @param result type * @author Jonathan Halterman */ public class FailsafeExecutor { private Scheduler scheduler = Scheduler.DEFAULT; private Executor executor; /** Policies sorted outermost first */ final List> policies; private volatile EventHandler completeHandler; private volatile EventHandler failureHandler; private volatile EventHandler successHandler; /** * @throws IllegalArgumentException if {@code policies} is empty */ FailsafeExecutor(List> policies) { this.policies = policies; } /** * Returns the currently configured policies. * * @see #compose(Policy) */ public List> getPolicies() { return policies; } /** * Returns a new {@code FailsafeExecutor} that composes the currently configured policies around the given {@code * innerPolicy}. For example, consider: *

*

   *   Failsafe.with(fallback).compose(retryPolicy).compose(circuitBreaker);
   * 
*

* This results in the following internal composition when executing a {@code runnable} or {@code supplier} and * handling its result: *

*

   *   Fallback(RetryPolicy(CircuitBreaker(Supplier)))
   * 
*

* This means the {@code CircuitBreaker} is first to evaluate the {@code Supplier}'s result, then the {@code * RetryPolicy}, then the {@code Fallback}. Each policy makes its own determination as to whether the result * represents a failure. This allows different policies to be used for handling different types of failures. * * @throws NullPointerException if {@code innerPolicy} is null * @see #getPolicies() */ public

> FailsafeExecutor compose(P innerPolicy) { Assert.notNull(innerPolicy, "innerPolicy"); List> composed = new ArrayList<>(policies); composed.add(innerPolicy); return new FailsafeExecutor<>(composed); } /** * Executes the {@code supplier} until a successful result is returned or the configured policies are exceeded. * * @throws NullPointerException if the {@code supplier} is null * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ public T get(CheckedSupplier supplier) { return call(toCtxSupplier(supplier)); } /** * Executes the {@code supplier} until a successful result is returned or the configured policies are exceeded. * * @throws NullPointerException if the {@code supplier} is null * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ public T get(ContextualSupplier supplier) { return call(Assert.notNull(supplier, "supplier")); } /** * Returns a call that can execute the {@code runnable} until a successful result is returned or the configured * policies are exceeded. * * @throws NullPointerException if the {@code runnable} is null */ public Call newCall(ContextualRunnable runnable) { return callSync(toCtxSupplier(runnable)); } /** * Returns a call that can execute the {@code supplier} until a successful result is returned or the configured * policies are exceeded. * * @throws NullPointerException if the {@code supplier} is null */ public Call newCall(ContextualSupplier supplier) { return callSync(Assert.notNull(supplier, "supplier")); } /** * Executes the {@code supplier} asynchronously until a successful result is returned or the configured policies are * exceeded. * * @throws NullPointerException if the {@code supplier} is null * @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution */ public CompletableFuture getAsync(CheckedSupplier supplier) { return callAsync(future -> getPromise(toCtxSupplier(supplier), executor), false); } /** * Executes the {@code supplier} asynchronously until a successful result is returned or the configured policies are * exceeded. * * @throws NullPointerException if the {@code supplier} is null * @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution */ public CompletableFuture getAsync(ContextualSupplier supplier) { return callAsync(future -> getPromise(supplier, executor), false); } /** * This method is intended for integration with asynchronous code. *

* Executes the {@code runnable} asynchronously until a successful result is recorded or the configured policies are * exceeded. Executions must be recorded via one of the {@code AsyncExecution.record} methods which will trigger * failure handling, if needed, by the configured policies, else the resulting {@link CompletableFuture} will be * completed. Any exception that is thrown from the {@code runnable} will automatically be recorded via {@link * AsyncExecution#recordException(Throwable)}. *

* * @throws NullPointerException if the {@code runnable} is null * @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution */ public CompletableFuture getAsyncExecution(AsyncRunnable runnable) { return callAsync(future -> getPromiseExecution(runnable, executor), true); } /** * Executes the {@code supplier} asynchronously until the resulting future is successfully completed or the configured * policies are exceeded. *

Cancelling the resulting {@link CompletableFuture} will automatically cancels the supplied {@link * CompletionStage} if it's a {@link Future}.

* * @throws NullPointerException if the {@code supplier} is null * @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution */ public CompletableFuture getStageAsync(CheckedSupplier> supplier) { return callAsync(future -> getPromiseOfStage(toCtxSupplier(supplier), future, executor), false); } /** * Executes the {@code supplier} asynchronously until the resulting future is successfully completed or the configured * policies are exceeded. *

Cancelling the resulting {@link CompletableFuture} will automatically cancels the supplied {@link * CompletionStage} if it's a {@link Future}.

* * @throws NullPointerException if the {@code supplier} is null * @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution */ public CompletableFuture getStageAsync( ContextualSupplier> supplier) { return callAsync(future -> getPromiseOfStage(supplier, future, executor), false); } /** * Executes the {@code runnable} until successful or until the configured policies are exceeded. * * @throws NullPointerException if the {@code runnable} is null * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ public void run(CheckedRunnable runnable) { call(toCtxSupplier(runnable)); } /** * Executes the {@code runnable} until successful or until the configured policies are exceeded. * * @throws NullPointerException if the {@code runnable} is null * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ public void run(ContextualRunnable runnable) { call(toCtxSupplier(runnable)); } /** * Executes the {@code runnable} asynchronously until successful or until the configured policies are exceeded. * * @throws NullPointerException if the {@code runnable} is null * @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution */ public CompletableFuture runAsync(CheckedRunnable runnable) { return callAsync(future -> getPromise(toCtxSupplier(runnable), executor), false); } /** * Executes the {@code runnable} asynchronously until successful or until the configured policies are exceeded. * * @throws NullPointerException if the {@code runnable} is null * @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution */ public CompletableFuture runAsync(ContextualRunnable runnable) { return callAsync(future -> getPromise(toCtxSupplier(runnable), executor), false); } /** * This method is intended for integration with asynchronous code. *

* Executes the {@code runnable} asynchronously until a successful result is recorded or the configured policies are * exceeded. Executions must be recorded via one of the {@code AsyncExecution.record} methods which will trigger * failure handling, if needed, by the configured policies, else the resulting {@link CompletableFuture} will be * completed. Any exception that is thrown from the {@code runnable} will automatically be recorded via {@link * AsyncExecution#recordException(Throwable)}. *

* * @throws NullPointerException if the {@code runnable} is null * @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution */ public CompletableFuture runAsyncExecution(AsyncRunnable runnable) { return callAsync(future -> getPromiseExecution(runnable, executor), true); } /** * Registers the {@code listener} to be called when an execution is complete. This occurs when an execution is * successful according to all policies, or all policies have been exceeded. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored.

*/ public FailsafeExecutor onComplete(EventListener> listener) { completeHandler = EventHandler.ofExecutionCompleted(Assert.notNull(listener, "listener")); return this; } /** * Registers the {@code listener} to be called when an execution fails. This occurs when the execution fails according * to some policy, and all policies have been exceeded. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

*/ public FailsafeExecutor onFailure(EventListener> listener) { failureHandler = EventHandler.ofExecutionCompleted(Assert.notNull(listener, "listener")); return this; } /** * Registers the {@code listener} to be called when an execution is successful. If multiple policies, are configured, * this handler is called when execution is complete and all policies succeed. If all policies do not * succeed, then the {@link #onFailure(EventListener)} registered listener is called instead. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored.

*/ public FailsafeExecutor onSuccess(EventListener> listener) { successHandler = EventHandler.ofExecutionCompleted(Assert.notNull(listener, "listener")); return this; } /** * Configures the {@code scheduledExecutorService} to use for performing asynchronous executions and listener * callbacks. *

* Note: The {@code scheduledExecutorService} should have a core pool size of at least 2 in order for {@link Timeout * timeouts} to work. *

* * @throws NullPointerException if {@code scheduledExecutorService} is null * @throws IllegalArgumentException if the {@code scheduledExecutorService} has a core pool size of less than 2 */ public FailsafeExecutor with(ScheduledExecutorService scheduledExecutorService) { this.scheduler = Scheduler.of(Assert.notNull(scheduledExecutorService, "scheduledExecutorService")); return this; } /** * Configures the {@code executorService} to use for performing asynchronous executions and listener callbacks. For * async executions that require a delay, an internal ScheduledExecutorService will be used for the delay, then the * {@code executorService} will be used for actual execution. *

* Note: The {@code executorService} should have a core pool size or parallelism of at least 2 in order for {@link * Timeout timeouts} to work. *

* * @throws NullPointerException if {@code executorService} is null */ public FailsafeExecutor with(ExecutorService executorService) { this.scheduler = Scheduler.of(Assert.notNull(executorService, "executorService")); return this; } /** * Configures the {@code executor} to use as a wrapper around executions. If the {@code executor} is actually an * instance of {@link ExecutorService}, then the {@code executor} will be configured via {@link * #with(ExecutorService)} instead. *

* The {@code executor} is responsible for propagating executions. Executions that normally return a result, such as * {@link #get(CheckedSupplier)} will return {@code null} since the {@link Executor} interface does not support * results. *

*

The {@code executor} will not be used for {@link #getStageAsync(CheckedSupplier) getStageAsync} calls since * those require a returned result. *

* * @throws NullPointerException if {@code executor} is null */ public FailsafeExecutor with(Executor executor) { Assert.notNull(executor, "executor"); if (executor instanceof ExecutorService) with((ExecutorService) executor); else this.executor = executor; return this; } /** * Configures the {@code scheduler} to use for performing asynchronous executions and listener callbacks. * * @throws NullPointerException if {@code scheduler} is null */ public FailsafeExecutor with(Scheduler scheduler) { this.scheduler = Assert.notNull(scheduler, "scheduler"); return this; } /** * Calls the {@code innerSupplier} synchronously, handling results according to the configured policies. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private T call(ContextualSupplier innerSupplier) { SyncExecutionImpl execution = new SyncExecutionImpl(this, scheduler, null, Functions.get(innerSupplier, executor)); return execution.executeSync(); } /** * Returns a Call that calls the {@code innerSupplier} synchronously, handling results according to the configured * policies. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private Call callSync(ContextualSupplier innerSupplier) { CallImpl call = new CallImpl<>(); new SyncExecutionImpl(this, scheduler, call, Functions.get(innerSupplier, executor)); return call; } /** * Calls the asynchronous {@code innerFn} via the configured Scheduler, handling results according to the configured * policies. * * @param asyncExecution whether this is a detached, async execution that must be manually completed * @throws NullPointerException if the {@code innerFn} is null * @throws RejectedExecutionException if the {@code innerFn} cannot be scheduled for execution */ @SuppressWarnings({ "unchecked", "rawtypes" }) private CompletableFuture callAsync( Function, Function, CompletableFuture>>> innerFn, boolean asyncExecution) { FailsafeFuture future = new FailsafeFuture(completionHandler); AsyncExecutionImpl execution = new AsyncExecutionImpl(policies, scheduler, future, asyncExecution, innerFn.apply(future)); future.setExecution(execution); execution.executeAsync(); return future; } final BiConsumer, ExecutionContext> completionHandler = (result, context) -> { if (successHandler != null && result.getSuccessAll()) successHandler.handle(result, context); else if (failureHandler != null && !result.getSuccessAll()) failureHandler.handle(result, context); if (completeHandler != null) completeHandler.handle(result, context); }; }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/FailurePolicyBuilder.java000066400000000000000000000166121444561050700312010ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import dev.failsafe.internal.util.Assert; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * A Policy that allows configurable conditions to determine whether an execution is a failure. *
    *
  • By default, any exception is considered a failure and will be handled by the policy. You can override this by * specifying your own {@code handle} conditions. The default exception handling condition will only be overridden by * another condition that handles failure exceptions such as {@link #handle(Class)} or {@link #handleIf(CheckedBiPredicate)}. * Specifying a condition that only handles results, such as {@link #handleResult(Object)} or * {@link #handleResultIf(CheckedPredicate)} will not replace the default exception handling condition.
  • *
  • If multiple {@code handle} conditions are specified, any condition that matches an execution result or exception * will trigger policy handling.
  • *
* * @param self type * @param config type * @param result type * @author Jonathan Halterman */ @SuppressWarnings("unchecked") public abstract class FailurePolicyBuilder, R> extends PolicyBuilder { protected FailurePolicyBuilder(C config) { super(config); } /** * Specifies the exception to handle as a failure. Any exception that is assignable from the {@code exception} will be * handled. * * @throws NullPointerException if {@code exception} is null */ public S handle(Class exception) { Assert.notNull(exception, "exception"); return handle(Arrays.asList(exception)); } /** * Specifies the exceptions to handle as failures. Any exceptions that are assignable from the {@code exceptions} will * be handled. * * @throws NullPointerException if {@code exceptions} is null * @throws IllegalArgumentException if exceptions is empty */ @SafeVarargs public final S handle(Class... exceptions) { Assert.notNull(exceptions, "exceptions"); Assert.isTrue(exceptions.length > 0, "exceptions cannot be empty"); return handle(Arrays.asList(exceptions)); } /** * Specifies the exceptions to handle as failures. Any exceptions that are assignable from the {@code exceptions} will * be handled. * * @throws NullPointerException if {@code exceptions} is null * @throws IllegalArgumentException if exceptions is null or empty */ public S handle(List> exceptions) { Assert.notNull(exceptions, "exceptions"); Assert.isTrue(!exceptions.isEmpty(), "exceptions cannot be empty"); config.exceptionsChecked = true; config.failureConditions.add(failurePredicateFor(exceptions)); return (S) this; } /** * Specifies that a failure has occurred if the {@code failurePredicate} matches the exception. Any exception thrown * from the {@code failurePredicate} is treated as a {@code false} result. * * @throws NullPointerException if {@code failurePredicate} is null */ public S handleIf(CheckedPredicate failurePredicate) { Assert.notNull(failurePredicate, "failurePredicate"); config.exceptionsChecked = true; config.failureConditions.add(failurePredicateFor(failurePredicate)); return (S) this; } /** * Specifies that a failure has occurred if the {@code resultPredicate} matches the execution result. Any exception * thrown from the {@code resultPredicate} is treated as a {@code false} result. * * @throws NullPointerException if {@code resultPredicate} is null */ @SuppressWarnings("unchecked") public S handleIf(CheckedBiPredicate resultPredicate) { Assert.notNull(resultPredicate, "resultPredicate"); config.exceptionsChecked = true; config.failureConditions.add((CheckedBiPredicate) resultPredicate); return (S) this; } /** * Specifies that a failure has occurred if the {@code result} matches the execution result. This method is only * considered when a result is returned from an execution, not when an exception is thrown. */ public S handleResult(R result) { config.failureConditions.add(resultPredicateFor(result)); return (S) this; } /** * Specifies that a failure has occurred if the {@code resultPredicate} matches the execution result. This method is * only considered when a result is returned from an execution, not when an exception is thrown. To handle results or * exceptions with the same condition, use {@link #handleIf(CheckedBiPredicate)}. Any exception thrown from the {@code * resultPredicate} is treated as a {@code false} result. * * @throws NullPointerException if {@code resultPredicate} is null */ public S handleResultIf(CheckedPredicate resultPredicate) { Assert.notNull(resultPredicate, "resultPredicate"); config.failureConditions.add(resultPredicateFor(resultPredicate)); return (S) this; } /** * Returns a predicate that evaluates whether the {@code result} equals an execution result. */ static CheckedBiPredicate resultPredicateFor(R result) { return (t, u) -> result == null ? t == null && u == null : Objects.equals(result, t); } /** * Returns a predicate that evaluates the {@code failurePredicate} against a failure. */ @SuppressWarnings("unchecked") static CheckedBiPredicate failurePredicateFor( CheckedPredicate failurePredicate) { return (t, u) -> u != null && ((CheckedPredicate) failurePredicate).test(u); } /** * Returns a predicate that evaluates the {@code resultPredicate} against a result, when present. *

* Short-circuits to false without invoking {@code resultPredicate}, when result is not present (i.e. * BiPredicate.test(null, Throwable)). */ static CheckedBiPredicate resultPredicateFor(CheckedPredicate resultPredicate) { return (t, u) -> { if (u == null) { return resultPredicate.test(t); } else { // resultPredicate is only defined over the success type. // It doesn't know how to handle a failure of type Throwable, // so we return false here. return false; } }; } /** * Returns a predicate that returns whether any of the {@code failures} are assignable from an execution failure. */ static CheckedBiPredicate failurePredicateFor(List> failures) { return (t, u) -> { if (u == null) return false; for (Class failureType : failures) if (failureType.isAssignableFrom(u.getClass())) return true; return false; }; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/FailurePolicyConfig.java000066400000000000000000000043161444561050700310160ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import java.util.ArrayList; import java.util.List; /** * Configuration for policies that handle specific failures and conditions. * * @param result type * @author Jonathan Halterman */ public abstract class FailurePolicyConfig extends PolicyConfig { /** Indicates whether exceptions are checked by a configured failure condition */ boolean exceptionsChecked; /** Conditions that determine whether an execution is a failure */ List> failureConditions; protected FailurePolicyConfig() { failureConditions = new ArrayList<>(); } protected FailurePolicyConfig(FailurePolicyConfig config) { super(config); exceptionsChecked = config.exceptionsChecked; failureConditions = new ArrayList<>(config.failureConditions); } /** * Returns whether exceptions are checked by a configured failure condition. */ public boolean isExceptionsChecked() { return exceptionsChecked; } /** * Returns the conditions under which a result or Throwable should be treated as a failure and handled. * * @see FailurePolicyBuilder#handle(Class...) * @see FailurePolicyBuilder#handle(List) * @see FailurePolicyBuilder#handleIf(CheckedBiPredicate) * @see FailurePolicyBuilder#handleIf(CheckedPredicate) * @see FailurePolicyBuilder#handleResult(Object) * @see FailurePolicyBuilder#handleResultIf(CheckedPredicate) */ public List> getFailureConditions() { return failureConditions; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Fallback.java000066400000000000000000000204341444561050700266170ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedConsumer; import dev.failsafe.function.CheckedRunnable; import dev.failsafe.function.CheckedSupplier; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.function.CheckedFunction; import dev.failsafe.internal.FallbackImpl; import dev.failsafe.internal.util.Assert; import java.util.concurrent.CompletionStage; import static dev.failsafe.Functions.toFn; /** * A Policy that handles failures using a fallback function or result. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see TimeoutConfig * @see FallbackBuilder */ public interface Fallback extends Policy { /** * Creates a new FallbackBuilder that will be based on the {@code config}. */ static FallbackBuilder builder(FallbackConfig config) { return new FallbackBuilder<>(config); } /** * Returns the {@code fallback} to be executed if execution fails. * * @throws NullPointerException if {@code fallback} is null */ static FallbackBuilder builder(CheckedRunnable fallback) { return new FallbackBuilder<>(toFn(Assert.notNull(fallback, "fallback")), null); } /** * Returns the {@code fallback} to be executed if execution fails. * * @throws NullPointerException if {@code fallback} is null */ static FallbackBuilder builder(CheckedSupplier fallback) { return new FallbackBuilder<>(toFn(Assert.notNull(fallback, "fallback")), null); } /** * Returns the {@code fallback} to be executed if execution fails. The {@code fallback} accepts an {@link * ExecutionAttemptedEvent}. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static FallbackBuilder builder(CheckedConsumer> fallback) { return new FallbackBuilder<>(toFn(Assert.notNull((CheckedConsumer) fallback, "fallback")), null); } /** * Returns the {@code fallback} to be executed if execution fails. The {@code fallback} applies an {@link * ExecutionAttemptedEvent}. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static FallbackBuilder builder(CheckedFunction, ? extends R> fallback) { return new FallbackBuilder<>(Assert.notNull((CheckedFunction) fallback, "fallback"), null); } /** * Returns the {@code fallbackResult} to be provided if execution fails. */ static FallbackBuilder builder(R fallbackResult) { return new FallbackBuilder<>(toFn(fallbackResult), null); } /** * Returns the {@code fallback} to be executed if execution fails and allows an alternative exception to be supplied * instead. The {@code fallback} applies an {@link ExecutionAttemptedEvent} and must return an exception. * * @throws NullPointerException if {@code fallback} is null */ static FallbackBuilder builderOfException( CheckedFunction, ? extends Exception> fallback) { Assert.notNull(fallback, "fallback"); return new FallbackBuilder<>(e -> { throw fallback.apply(e); }, null); } /** * Returns the {@code fallback} to be executed if execution fails. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static FallbackBuilder builderOfStage(CheckedSupplier> fallback) { return new FallbackBuilder<>(null, (CheckedFunction) toFn(Assert.notNull(fallback, "fallback"))); } /** * Returns the {@code fallback} to be executed if execution fails. The {@code fallback} accepts an {@link * ExecutionAttemptedEvent}. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static FallbackBuilder builderOfStage( CheckedFunction, ? extends CompletionStage> fallback) { return new FallbackBuilder<>(null, Assert.notNull((CheckedFunction) fallback, "fallback")); } /** * Returns the {@code fallback} to be executed if execution fails. * * @throws NullPointerException if {@code fallback} is null */ static Fallback of(CheckedRunnable fallback) { return new FallbackImpl<>(new FallbackConfig<>(toFn(Assert.notNull(fallback, "fallback")), null)); } /** * Returns the {@code fallback} to be executed if execution fails. * * @throws NullPointerException if {@code fallback} is null */ static Fallback of(CheckedSupplier fallback) { return new FallbackImpl<>(new FallbackConfig<>(toFn(Assert.notNull(fallback, "fallback")), null)); } /** * Returns the {@code fallback} to be executed if execution fails. The {@code fallback} accepts an {@link * ExecutionAttemptedEvent}. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static Fallback of(CheckedConsumer> fallback) { return new FallbackImpl<>(new FallbackConfig<>(toFn(Assert.notNull((CheckedConsumer) fallback, "fallback")), null)); } /** * Returns the {@code fallback} to be executed if execution fails. The {@code fallback} applies an {@link * ExecutionAttemptedEvent}. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static Fallback of(CheckedFunction, ? extends R> fallback) { return new FallbackImpl<>(new FallbackConfig<>(Assert.notNull((CheckedFunction) fallback, "fallback"), null)); } /** * Returns the {@code fallback} to be executed if execution fails and allows an alternative exception to be supplied * instead. The {@code fallback} applies an {@link ExecutionAttemptedEvent} and must return an exception. * * @throws NullPointerException if {@code fallback} is null */ static Fallback ofException( CheckedFunction, ? extends Exception> fallback) { Assert.notNull(fallback, "fallback"); return new FallbackImpl<>(new FallbackConfig<>(e -> { throw fallback.apply(e); }, null)); } /** * Returns the {@code fallbackResult} to be provided if execution fails. */ static Fallback of(R fallbackResult) { return new FallbackImpl<>(new FallbackConfig<>(toFn(fallbackResult), null)); } /** * Returns the {@code fallback} to be executed if execution fails. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static Fallback ofStage(CheckedSupplier> fallback) { return new FallbackImpl<>(new FallbackConfig<>(null, (CheckedFunction) toFn(Assert.notNull(fallback, "fallback")))); } /** * Returns the {@code fallback} to be executed if execution fails. The {@code fallback} accepts an {@link * ExecutionAttemptedEvent}. * * @throws NullPointerException if {@code fallback} is null */ @SuppressWarnings({ "unchecked", "rawtypes" }) static Fallback ofStage( CheckedFunction, ? extends CompletionStage> fallback) { return new FallbackImpl<>(new FallbackConfig<>(null, Assert.notNull((CheckedFunction) fallback, "fallback"))); } /** * Returns a fallback that will return a null if execution fails. */ static Fallback none() { return FallbackImpl.NONE; } /** * Returns the {@link FallbackConfig} that the Fallback was built with. */ @Override FallbackConfig getConfig(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/FallbackBuilder.java000066400000000000000000000067071444561050700301350ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedFunction; import dev.failsafe.function.CheckedPredicate; import dev.failsafe.internal.FallbackImpl; import dev.failsafe.internal.util.Assert; import java.util.concurrent.CompletableFuture; /** * Builds {@link Fallback} instances. *
    *
  • By default, any exception is considered a failure and will be handled by the policy. You can override this by * specifying your own {@code handle} conditions. The default exception handling condition will only be overridden by * another condition that handles failure exceptions such as {@link #handle(Class)} or {@link #handleIf(CheckedBiPredicate)}. * Specifying a condition that only handles results, such as {@link #handleResult(Object)} or * {@link #handleResultIf(CheckedPredicate)} will not replace the default exception handling condition.
  • *
  • If multiple {@code handle} conditions are specified, any condition that matches an execution result or failure * will trigger policy handling.
  • *
*

* Note: *

    *
  • This class extends {@link FailurePolicyBuilder} which offers additional configuration.
  • *
  • This class is not threadsafe.
  • *
*

* * @param result type * @author Jonathan Halterman * @see FallbackConfig */ public class FallbackBuilder extends FailurePolicyBuilder, FallbackConfig, R> implements PolicyListeners, R> { FallbackBuilder(CheckedFunction, R> fallback, CheckedFunction, CompletableFuture> fallbackStage) { super(new FallbackConfig<>()); config.fallback = fallback; config.fallbackStage = fallbackStage; } FallbackBuilder(FallbackConfig config) { super(new FallbackConfig<>(config)); } /** * Builds a new {@link Fallback} using the builder's configuration. */ public Fallback build() { return new FallbackImpl<>(new FallbackConfig<>(config)); } /** * Registers the {@code listener} to be called when the last execution attempt prior to the fallback failed. You can * also use {@link #onFailure(EventListener) onFailure} to determine when the fallback attempt also fails. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored.

* * @throws NullPointerException if {@code listener} is null */ public FallbackBuilder onFailedAttempt(EventListener> listener) { config.failedAttemptListener = Assert.notNull(listener, "listener"); return this; } /** * Configures the fallback to run asynchronously. */ public FallbackBuilder withAsync() { config.async = true; return this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/FallbackConfig.java000066400000000000000000000067471444561050700277600ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.function.CheckedConsumer; import dev.failsafe.function.CheckedFunction; import dev.failsafe.function.CheckedRunnable; import dev.failsafe.function.CheckedSupplier; import java.util.concurrent.CompletableFuture; /** * Configuration for a {@link Fallback}. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see FallbackBuilder */ public class FallbackConfig extends FailurePolicyConfig { CheckedFunction, R> fallback; CheckedFunction, CompletableFuture> fallbackStage; boolean async; // Listeners EventListener> failedAttemptListener; FallbackConfig() { } FallbackConfig(FallbackConfig config) { super(config); fallback = config.fallback; fallbackStage = config.fallbackStage; async = config.async; failedAttemptListener = config.failedAttemptListener; } FallbackConfig(CheckedFunction, R> fallback, CheckedFunction, CompletableFuture> fallbackStage) { this.fallback = fallback; this.fallbackStage = fallbackStage; } /** * Returns the fallback function, else {@code null} if a fallback stage function was configured instead. * * @see Fallback#of(CheckedRunnable) * @see Fallback#of(CheckedSupplier) * @see Fallback#of(CheckedConsumer) * @see Fallback#of(CheckedFunction) * @see Fallback#of(Object) * @see Fallback#ofException(CheckedFunction) * @see Fallback#builder(CheckedRunnable) * @see Fallback#builder(CheckedSupplier) * @see Fallback#builder(CheckedConsumer) * @see Fallback#builder(CheckedFunction) * @see Fallback#builder(Object) * @see Fallback#builderOfException(CheckedFunction) */ public CheckedFunction, R> getFallback() { return fallback; } /** * Returns the fallback stage function, else {@code null} if a fallback function was configured instead. * * @see Fallback#ofStage(CheckedSupplier) * @see Fallback#ofStage(CheckedFunction) * @see Fallback#builderOfStage(CheckedSupplier) * @see Fallback#builderOfStage(CheckedFunction) */ public CheckedFunction, CompletableFuture> getFallbackStage() { return fallbackStage; } /** * Returns whether the Fallback is configured to handle execution results asynchronously, separate from execution. * * @see FallbackBuilder#withAsync() */ public boolean isAsync() { return async; } /** * Returns the failed attempt event listener. * * @see FallbackBuilder#onFailedAttempt(EventListener) */ public EventListener> getFailedAttemptListener() { return failedAttemptListener; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Functions.java000066400000000000000000000236711444561050700270760ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.*; import dev.failsafe.internal.util.Assert; import dev.failsafe.spi.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; /** * Utilities for creating and applying Failsafe executable functions. * * @author Jonathan Halterman */ final class Functions { /** * Returns a Supplier for synchronous executions that pre-executes the {@code execution}, applies the {@code * supplier}, records the result and returns the result. This implementation also handles Thread interrupts. * * @param result type */ static Function, ExecutionResult> get(ContextualSupplier supplier, Executor executor) { return execution -> { ExecutionResult result; Throwable throwable = null; try { execution.preExecute(); result = ExecutionResult.success(withExecutor(supplier, executor).get(execution)); } catch (Throwable t) { throwable = t; result = ExecutionResult.exception(t); } execution.record(result); // Guard against race with Timeout interrupting the execution synchronized (execution.getLock()) { execution.setInterruptable(false); if (execution.isInterrupted()) { // Clear interrupt flag if interruption was performed by Failsafe Thread.interrupted(); return execution.getResult(); } else if (throwable instanceof InterruptedException) // Set interrupt flag if interruption was not performed by Failsafe Thread.currentThread().interrupt(); } return result; }; } /** * Returns a Function for asynchronous executions that pre-executes the {@code execution}, applies the {@code * supplier}, records the result and returns a promise containing the result. * * @param result type */ static Function, CompletableFuture>> getPromise( ContextualSupplier supplier, Executor executor) { Assert.notNull(supplier, "supplier"); return execution -> { ExecutionResult result; try { execution.preExecute(); result = ExecutionResult.success(withExecutor(supplier, executor).get(execution)); } catch (Throwable t) { result = ExecutionResult.exception(t); } execution.record(result); return CompletableFuture.completedFuture(result); }; } /** * Returns a Function for asynchronous executions that pre-executes the {@code execution}, runs the {@code runnable}, * and attempts to complete the {@code execution} if a failure occurs. Locks to ensure the resulting supplier cannot * be applied multiple times concurrently. * * @param result type */ static Function, CompletableFuture>> getPromiseExecution( AsyncRunnable runnable, Executor executor) { Assert.notNull(runnable, "runnable"); return new Function, CompletableFuture>>() { @Override public synchronized CompletableFuture> apply(AsyncExecutionInternal execution) { try { execution.preExecute(); withExecutor(runnable, executor).run(execution); } catch (Throwable e) { execution.record(null, e); } // Result will be provided later via AsyncExecution.record return ExecutionResult.nullFuture(); } }; } /** * Returns a Function that for asynchronous executions that pre-executes the {@code execution}, applies the {@code * supplier}, records the result and returns a promise containing the result. * * @param result type * @throws UnsupportedOperationException when using */ @SuppressWarnings("unchecked") static Function, CompletableFuture>> getPromiseOfStage( ContextualSupplier> supplier, FailsafeFuture future, Executor executor) { Assert.notNull(supplier, "supplier"); return execution -> { CompletableFuture> promise = new CompletableFuture<>(); try { execution.preExecute(); CompletionStage stage = withExecutor(supplier, executor).get(execution); if (stage == null) { ExecutionResult r = ExecutionResult.success(null); execution.record(r); promise.complete(r); } else { // Propagate outer cancellations to the stage if (stage instanceof Future) future.propagateCancellation((Future) stage); stage.whenComplete((result, exception) -> { if (exception instanceof CompletionException) exception = exception.getCause(); ExecutionResult r = exception == null ? ExecutionResult.success(result) : ExecutionResult.exception(exception); execution.record(r); promise.complete(r); }); } } catch (Throwable t) { ExecutionResult result = ExecutionResult.exception(t); execution.record(result); promise.complete(result); } return promise; }; } /** * Returns a Function that returns an execution result if one was previously recorded, else applies the {@code * innerFn}. * * @param result type */ static Function, CompletableFuture>> toExecutionAware( Function, CompletableFuture>> innerFn) { return execution -> { ExecutionResult result = execution.getResult(); if (result == null) { return innerFn.apply(execution); } else { return CompletableFuture.completedFuture(result); } }; } /** * Returns a Function that asynchronously applies the {@code innerFn} on the first call, synchronously on subsequent * calls, and returns a promise containing the result. * * @param result type */ static Function, CompletableFuture>> toAsync( Function, CompletableFuture>> innerFn, Scheduler scheduler, FailsafeFuture future) { AtomicBoolean scheduled = new AtomicBoolean(); return execution -> { if (scheduled.get()) { return innerFn.apply(execution); } else { CompletableFuture> promise = new CompletableFuture<>(); Callable callable = () -> innerFn.apply(execution).whenComplete((result, error) -> { if (error != null) promise.completeExceptionally(error); else promise.complete(result); }); try { scheduled.set(true); Future scheduledFuture = scheduler.schedule(callable, 0, TimeUnit.NANOSECONDS); // Propagate outer cancellations to the scheduled innerFn and its promise future.setCancelFn(-1, (mayInterrupt, cancelResult) -> { scheduledFuture.cancel(mayInterrupt); // Cancel a pending promise if the execution attempt has not started if (!execution.isPreExecuted()) promise.complete(cancelResult); }); } catch (Throwable t) { promise.completeExceptionally(t); } return promise; } }; } static ContextualSupplier toCtxSupplier(CheckedRunnable runnable) { Assert.notNull(runnable, "runnable"); return ctx -> { runnable.run(); return null; }; } static ContextualSupplier toCtxSupplier(ContextualRunnable runnable) { Assert.notNull(runnable, "runnable"); return ctx -> { runnable.run(ctx); return null; }; } static ContextualSupplier toCtxSupplier(CheckedSupplier supplier) { Assert.notNull(supplier, "supplier"); return ctx -> supplier.get(); } static ContextualSupplier withExecutor(ContextualSupplier supplier, Executor executor) { return executor == null ? supplier : ctx -> { executor.execute(() -> { try { supplier.get(ctx); } catch (Throwable e) { handleExecutorThrowable(e); } }); return null; }; } static AsyncRunnable withExecutor(AsyncRunnable runnable, Executor executor) { return executor == null ? runnable : exec -> { executor.execute(() -> { try { runnable.run(exec); } catch (Throwable e) { handleExecutorThrowable(e); } }); }; } private static void handleExecutorThrowable(Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException) e; if (e instanceof Error) throw (Error) e; throw new FailsafeException(e); } static CheckedFunction toFn(CheckedConsumer consumer) { return t -> { consumer.accept(t); return null; }; } static CheckedFunction toFn(CheckedRunnable runnable) { return t -> { runnable.run(); return null; }; } static CheckedFunction toFn(CheckedSupplier supplier) { return t -> supplier.get(); } static CheckedFunction toFn(R result) { return t -> result; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Policy.java000066400000000000000000000020111444561050700263460ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.spi.PolicyExecutor; /** * A policy for handling executions. * * @param result type * @author Jonathan Halterman */ public interface Policy { /** * Returns the policy config. */ PolicyConfig getConfig(); /** * Returns a {@link PolicyExecutor} capable of handling an execution for the Policy. */ PolicyExecutor toExecutor(int policyIndex); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/PolicyBuilder.java000066400000000000000000000027111444561050700276640ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.internal.util.Assert; import dev.failsafe.event.ExecutionCompletedEvent; /** * Builds policies. * * @param self type * @param config type * @param result type * @author Jonathan Halterman */ @SuppressWarnings("unchecked") public abstract class PolicyBuilder, R> implements PolicyListeners { protected C config; protected PolicyBuilder(C config) { this.config = config; } public S onFailure(EventListener> listener) { config.failureListener = Assert.notNull(listener, "listener"); return (S) this; } @Override public S onSuccess(EventListener> listener) { config.successListener = Assert.notNull(listener, "listener"); return (S) this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/PolicyConfig.java000066400000000000000000000031201444561050700274760ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionCompletedEvent; /** * Configuration for a {@link Policy}. * * @param result type * @author Jonathan Halterman */ public abstract class PolicyConfig { volatile EventListener> successListener; volatile EventListener> failureListener; protected PolicyConfig() { } protected PolicyConfig(PolicyConfig config) { successListener = config.successListener; failureListener = config.failureListener; } /** * Returns the success listener. * * @see PolicyListeners#onSuccess(EventListener) */ public EventListener> getSuccessListener() { return successListener; } /** * Returns the failure listener. * * @see PolicyListeners#onFailure(EventListener) */ public EventListener> getFailureListener() { return failureListener; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/PolicyListeners.java000066400000000000000000000034551444561050700302540ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionCompletedEvent; /** * Configures listeners for a policy execution result. * * @param self type * @param result type * @author Jonathan Halterman */ public interface PolicyListeners { /** * Registers the {@code listener} to be called when the policy fails to handle an execution. This means that not only * was the supplied execution considered a failure by the policy, but that the policy was unable to produce a * successful result. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

*/ S onFailure(EventListener> listener); /** * Registers the {@code listener} to be called when the policy succeeds in handling an execution. This means that the * supplied execution either succeeded, or if it failed, the policy was able to produce a successful result. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored.

*/ S onSuccess(EventListener> listener); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RateLimitExceededException.java000066400000000000000000000022121444561050700323120ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * Thrown when an execution exceeds or would exceed a {@link RateLimiter}. * * @author Jonathan Halterman */ public class RateLimitExceededException extends FailsafeException { private static final long serialVersionUID = 1L; private final RateLimiter rateLimiter; public RateLimitExceededException(RateLimiter rateLimiter) { this.rateLimiter = rateLimiter; } /** Returns the {@link RateLimiter} that caused the exception. */ public RateLimiter getRateLimiter() { return rateLimiter; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RateLimiter.java000066400000000000000000000331301444561050700273360ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import java.time.Duration; /** * A rate limiter allows you to control the rate of executions as a way of preventing system overload. *

* There are two types of rate limiting: smooth and bursty. Smooth rate limiting will evenly spread * out execution requests over-time, effectively smoothing out uneven execution request rates. Bursty rate * limiting allows potential bursts of executions to occur, up to a configured max per time period.

*

Rate limiting is based on permits, which can be requested in order to perform rate limited execution. * Permits are automatically refreshed over time based on the rate limiter's configuration.

*

* This class provides methods that block while waiting for permits to become available, and also methods that return * immediately. The blocking methods include: *

    *
  • {@link #acquirePermit()}
  • *
  • {@link #acquirePermits(int)}
  • *
  • {@link #acquirePermit(Duration)}
  • *
  • {@link #acquirePermits(int, Duration)}
  • *
  • {@link #tryAcquirePermit(Duration)}
  • *
  • {@link #tryAcquirePermits(int, Duration)}
  • *
*

*

* The methods that return immediately include: *

    *
  • {@link #tryAcquirePermit()}
  • *
  • {@link #tryAcquirePermits(int)}
  • *
  • {@link #reservePermit()}
  • *
  • {@link #reservePermits(int)}
  • *
  • {@link #tryReservePermit(Duration)}
  • *
  • {@link #tryReservePermits(int, Duration)}
  • *
*

*

* This class also provides methods that throw {@link RateLimitExceededException} when permits cannot be acquired, and * also methods that return a boolean. The {@code acquire} methods all throw {@link RateLimitExceededException} when * permits cannot be acquired, and the {@code tryAcquire} methods return a boolean. *

*

* The {@code reserve} methods attempt to reserve permits and return an expected wait time before the permit can be * used. This helps integrate with scenarios where you need to wait externally *

*

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see RateLimiterConfig * @see RateLimiterBuilder * @see RateLimitExceededException */ public interface RateLimiter extends Policy { /** * Returns a smooth {@link RateLimiterBuilder} for the {@code maxExecutions} and {@code period}, which control how * frequently an execution is permitted. The individual execution rate is computed as {@code period / maxExecutions}. * For example, with {@code maxExecutions} of {@code 100} and a {@code period} of {@code 1000 millis}, individual * executions will be permitted at a max rate of one every 10 millis. *

By default, the returned {@link RateLimiterBuilder} will have a {@link RateLimiterBuilder#withMaxWaitTime max * wait time} of {@code 0}. *

* Executions are performed with no delay until they exceed the max rate, after which executions are either rejected * or will block and wait until the {@link RateLimiterBuilder#withMaxWaitTime(Duration) max wait time} is exceeded. * * @param maxExecutions The max number of permitted executions per {@code period} * @param period The period after which permitted executions are reset to the {@code maxExecutions} */ static RateLimiterBuilder smoothBuilder(long maxExecutions, Duration period) { return new RateLimiterBuilder<>(period.dividedBy(maxExecutions)); } /** * Returns a smooth {@link RateLimiterBuilder} for the {@code maxRate}, which controls how frequently an execution is * permitted. For example, a {@code maxRate} of {@code Duration.ofMillis(10)} would allow up to one execution every 10 * milliseconds. *

By default, the returned {@link RateLimiterBuilder} will have a {@link RateLimiterBuilder#withMaxWaitTime max * wait time} of {@code 0}. *

* Executions are performed with no delay until they exceed the {@code maxRate}, after which executions are either * rejected or will block and wait until the {@link RateLimiterBuilder#withMaxWaitTime(Duration) max wait time} is * exceeded. * * @param maxRate at which individual executions should be permitted */ static RateLimiterBuilder smoothBuilder(Duration maxRate) { return new RateLimiterBuilder<>(maxRate); } /** * Returns a bursty {@link RateLimiterBuilder} for the {@code maxExecutions} per {@code period}. For example, a {@code * maxExecutions} value of {@code 100} with a {@code period} of {@code Duration.ofSeconds(1)} would allow up to 100 * executions every 1 second. *

By default, the returned {@link RateLimiterBuilder} will have a {@link RateLimiterBuilder#withMaxWaitTime max * wait time} of {@code 0}. *

* Executions are performed with no delay up until the {@code maxExecutions} are reached for the current {@code * period}, after which executions are either rejected or will block and wait until the {@link * RateLimiterBuilder#withMaxWaitTime(Duration) max wait time} is exceeded. * * @param maxExecutions The max number of permitted executions per {@code period} * @param period The period after which permitted executions are reset to the {@code maxExecutions} */ static RateLimiterBuilder burstyBuilder(long maxExecutions, Duration period) { return new RateLimiterBuilder<>(maxExecutions, period); } /** * Creates a new RateLimiterBuilder that will be based on the {@code config}. */ static RateLimiterBuilder builder(RateLimiterConfig config) { return new RateLimiterBuilder<>(config); } /** * Returns the {@link RateLimiterConfig} that the RateLimiter was built with. */ @Override RateLimiterConfig getConfig(); /** * Attempts to acquire a permit to perform an execution against the rate limiter, waiting until one is available or * the thread is interrupted. * * @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit * @see #tryAcquirePermit() * @see #reservePermit() */ default void acquirePermit() throws InterruptedException { acquirePermits(1); } /** * Attempts to acquire the requested {@code permits} to perform executions against the rate limiter, waiting until * they are available or the thread is interrupted. * * @throws IllegalArgumentException if {@code permits} is < 1 * @throws InterruptedException if the current thread is interrupted while waiting to acquire the {@code permits} * @see #tryAcquirePermits(int) * @see #reservePermits(int) */ void acquirePermits(int permits) throws InterruptedException; /** * Attempts to acquire a permit to perform an execution against the rate limiter, waiting up to the {@code * maxWaitTime} until one is available, else throwing {@link RateLimitExceededException} if a permit will not be * available in time. * * @throws NullPointerException if {@code maxWaitTime} is null * @throws RateLimitExceededException if the rate limiter cannot acquire a permit within the {@code maxWaitTime} * @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit * @see #tryAcquirePermit(Duration) */ default void acquirePermit(Duration maxWaitTime) throws InterruptedException { acquirePermits(1, maxWaitTime); } /** * Attempts to acquire the requested {@code permits} to perform executions against the rate limiter, waiting up to the * {@code maxWaitTime} until they are available, else throwing {@link RateLimitExceededException} if the permits will * not be available in time. * * @throws IllegalArgumentException if {@code permits} is < 1 * @throws NullPointerException if {@code maxWaitTime} is null * @throws RateLimitExceededException if the rate limiter cannot acquire a permit within the {@code maxWaitTime} * @throws InterruptedException if the current thread is interrupted while waiting to acquire the {@code permits} * @see #tryAcquirePermits(int, Duration) */ default void acquirePermits(int permits, Duration maxWaitTime) throws InterruptedException { if (!tryAcquirePermits(permits, maxWaitTime)) throw new RateLimitExceededException(this); } /** * Returns whether the rate limiter is smooth. * * @see #smoothBuilder(long, Duration) * @see #smoothBuilder(Duration) */ default boolean isSmooth() { return getConfig().getMaxRate() != null; } /** * Returns whether the rate limiter is bursty. * * @see #burstyBuilder(long, Duration) */ default boolean isBursty() { return getConfig().getPeriod() != null; } /** * Reserves a permit to perform an execution against the rate limiter, and returns the time that the caller is * expected to wait before acting on the permit. Returns {@code 0} if the permit is immediately available and no * waiting is needed. * * @see #acquirePermit() * @see #tryAcquirePermit() */ default Duration reservePermit() { return reservePermits(1); } /** * Reserves the {@code permits} to perform executions against the rate limiter, and returns the time that the caller * is expected to wait before acting on the permits. Returns {@code 0} if the permits are immediately available and no * waiting is needed. * * @throws IllegalArgumentException if {@code permits} is < 1 * @see #acquirePermits(int) * @see #tryAcquirePermits(int) */ Duration reservePermits(int permits); /** * Tries to reserve a permit to perform an execution against the rate limiter, and returns the time that the caller is * expected to wait before acting on the permit, as long as it's less than the {@code maxWaitTime}. *

    *
  • Returns the expected wait time for the permit if it was successfully reserved.
  • *
  • Returns {@code 0} if the permit was successfully reserved and no waiting is needed.
  • *
  • Returns {@code -1 nanoseconds} if the permit was not reserved because the wait time would be greater than the {@code maxWaitTime}.
  • *
* * @throws NullPointerException if {@code maxWaitTime} is null * @see #acquirePermit(Duration) * @see #tryAcquirePermit(Duration) */ default Duration tryReservePermit(Duration maxWaitTime) { return tryReservePermits(1, maxWaitTime); } /** * Tries to reserve the {@code permits} to perform executions against the rate limiter, and returns the time that the * caller is expected to wait before acting on the permits, as long as it's less than the {@code maxWaitTime}. *
    *
  • Returns the expected wait time for the permits if they were successfully reserved.
  • *
  • Returns {@code 0} if the permits were successfully reserved and no waiting is needed.
  • *
  • Returns {@code -1 nanoseconds} if the permits were not reserved because the wait time would be greater than the {@code maxWaitTime}.
  • *
* * @throws IllegalArgumentException if {@code permits} is < 1 * @throws NullPointerException if {@code maxWaitTime} is null * @see #acquirePermit(Duration) * @see #tryAcquirePermit(Duration) */ Duration tryReservePermits(int permits, Duration maxWaitTime); /** * Tries to acquire a permit to perform an execution against the rate limiter, returning immediately without waiting. * * @return whether the requested {@code permits} are successfully acquired or not * @see #acquirePermit() * @see #reservePermits(int) */ default boolean tryAcquirePermit() { return tryAcquirePermits(1); } /** * Tries to acquire the requested {@code permits} to perform executions against the rate limiter, returning * immediately without waiting. * * @return whether the requested {@code permits} are successfully acquired or not * @throws IllegalArgumentException if {@code permits} is < 1 * @see #acquirePermits(int) */ boolean tryAcquirePermits(int permits); /** * Tries to acquire a permit to perform an execution against the rate limiter, waiting up to the {@code maxWaitTime} * until they are available. * * @return whether a permit is successfully acquired * @throws NullPointerException if {@code maxWaitTime} is null * @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit * @see #acquirePermit(Duration) */ default boolean tryAcquirePermit(Duration maxWaitTime) throws InterruptedException { return tryAcquirePermits(1, maxWaitTime); } /** * Tries to acquire the requested {@code permits} to perform executions against the rate limiter, waiting up to the * {@code maxWaitTime} until they are available. * * @return whether the requested {@code permits} are successfully acquired or not * @throws IllegalArgumentException if {@code permits} is < 1 * @throws NullPointerException if {@code maxWaitTime} is null * @throws InterruptedException if the current thread is interrupted while waiting to acquire the {@code permits} * @see #acquirePermits(int, Duration) */ boolean tryAcquirePermits(int permits, Duration maxWaitTime) throws InterruptedException; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RateLimiterBuilder.java000066400000000000000000000044431444561050700306520ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.internal.RateLimiterImpl; import dev.failsafe.internal.util.Assert; import java.time.Duration; /** * Builds {@link RateLimiter} instances. *

* This class is not threadsafe. *

* * @param result type * @author Jonathan Halterman * @see RateLimiterConfig * @see RateLimitExceededException */ public class RateLimiterBuilder extends PolicyBuilder, RateLimiterConfig, R> { RateLimiterBuilder(Duration executionRate) { super(new RateLimiterConfig<>(executionRate)); config.maxWaitTime = Duration.ZERO; } RateLimiterBuilder(long maxPermits, Duration period) { super(new RateLimiterConfig<>(maxPermits, period)); config.maxWaitTime = Duration.ZERO; } RateLimiterBuilder(RateLimiterConfig config) { super(new RateLimiterConfig<>(config)); } /** * Builds a new {@link RateLimiter} using the builder's configuration. */ public RateLimiter build() { return new RateLimiterImpl<>(new RateLimiterConfig<>(config)); } /** * Configures the {@code maxWaitTime} to wait for permits to be available. If permits cannot be acquired before the * {@code maxWaitTime} is exceeded, then the rate limiter will throw {@link RateLimitExceededException}. *

* This setting only applies when the resulting RateLimiter is used with the {@link Failsafe} class. It does not apply * when the RateLimiter is used in a standalone way. *

* * @throws NullPointerException if {@code maxWaitTime} is null */ public RateLimiterBuilder withMaxWaitTime(Duration maxWaitTime) { config.maxWaitTime = Assert.notNull(maxWaitTime, "maxWaitTime"); return this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RateLimiterConfig.java000066400000000000000000000053701444561050700304710ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import java.time.Duration; /** * Configuration for a {@link RateLimiter}. * * @param result type * @author Jonathan Halterman */ public class RateLimiterConfig extends PolicyConfig { // Smoothing Duration maxRate; // Bursting long maxPermits; Duration period; // Common Duration maxWaitTime; RateLimiterConfig(Duration maxRate) { this.maxRate = maxRate; } RateLimiterConfig(long maxPermits, Duration period) { this.maxPermits = maxPermits; this.period = period; } RateLimiterConfig(RateLimiterConfig config) { super(config); maxRate = config.maxRate; maxPermits = config.maxPermits; period = config.period; maxWaitTime = config.maxWaitTime; } /** * For smooth rate limiters, returns the max rate at which individual executions are permitted, else {@code null} if * the rate limiter is not smooth. * * @see RateLimiter#smoothBuilder(long, Duration) * @see RateLimiter#smoothBuilder(Duration) */ public Duration getMaxRate() { return maxRate; } /** * For bursty rate limiters, returns the max permitted executions per {@link #getPeriod() period}, else {@code null} * if the rate limiter is not bursty. * * @see RateLimiter#burstyBuilder(long, Duration) */ public long getMaxPermits() { return maxPermits; } /** * For bursty rate limiters, returns the period after which permits are reset to {@link #getMaxPermits() maxPermits}, * else {@code null} if the rate limiter is not bursty. * * @see RateLimiter#burstyBuilder(long, Duration) */ public Duration getPeriod() { return period; } /** * Returns the max time to wait for permits to be available. If permits cannot be acquired before the max wait time is * exceeded, then the rate limiter will throw {@link RateLimitExceededException}. *

* This setting only applies when the RateLimiter is used with the {@link Failsafe} class. It does not apply when the * RateLimiter is used in a standalone way. *

* * @see RateLimiterBuilder#withMaxWaitTime(Duration) */ public Duration getMaxWaitTime() { return maxWaitTime; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RetryPolicy.java000066400000000000000000000035721444561050700274110ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * A policy that defines when retries should be performed. See {@link RetryPolicyBuilder} for configuration options. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see RetryPolicyConfig * @see RetryPolicyBuilder */ public interface RetryPolicy extends Policy { /** * Creates a RetryPolicyBuilder that by default will build a RetryPolicy that allows 3 execution attempts max with no * delay, unless configured otherwise. * * @see #ofDefaults() */ static RetryPolicyBuilder builder() { return new RetryPolicyBuilder<>(); } /** * Creates a new RetryPolicyBuilder that will be based on the {@code config}. */ static RetryPolicyBuilder builder(RetryPolicyConfig config) { return new RetryPolicyBuilder<>(config); } /** * Creates a RetryPolicy that allows 3 execution attempts max with no delay. To configure additional options on a * RetryPolicy, use {@link #builder()} instead. * * @see #builder() */ static RetryPolicy ofDefaults() { return RetryPolicy.builder().build(); } /** * Returns the {@link RetryPolicyConfig} that the RetryPolicy was built with. */ @Override RetryPolicyConfig getConfig(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RetryPolicyBuilder.java000066400000000000000000000555351444561050700307260ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.event.ExecutionScheduledEvent; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import dev.failsafe.internal.RetryPolicyImpl; import dev.failsafe.internal.util.Assert; import dev.failsafe.internal.util.Durations; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Builds {@link RetryPolicy} instances. *
    *
  • By default, a RetryPolicy will retry up to {@code 2} times when any {@code Exception} is thrown, with no delay * between retry attempts.
  • *
  • You can change the default number of retry attempts and delay between retries by using the {@code with} * configuration methods.
  • *
  • By default, any exception is considered a failure and will be handled by the policy. You can override this by * specifying your own {@code handle} conditions. The default exception handling condition will only be overridden by * another condition that handles exceptions such as {@link #handle(Class)} or {@link #handleIf(CheckedBiPredicate)}. * Specifying a condition that only handles results, such as {@link #handleResult(Object)} or * {@link #handleResultIf(CheckedPredicate)} will not replace the default exception handling condition.
  • *
  • If multiple {@code handle} conditions are specified, any condition that matches an execution result or exception * will trigger policy handling.
  • *
  • The {@code abortOn}, {@code abortWhen} and {@code abortIf} methods describe when retries should be aborted.
  • *
*

* Note: *

    *
  • This class extends {@link DelayablePolicyBuilder} and {@link FailurePolicyBuilder} which offer additional configuration.
  • *
  • This class is not threadsafe.
  • *
*

* * @param result type * @author Jonathan Halterman * @see RetryPolicyConfig */ public class RetryPolicyBuilder extends DelayablePolicyBuilder, RetryPolicyConfig, R> implements PolicyListeners, R> { private static final int DEFAULT_MAX_RETRIES = 2; RetryPolicyBuilder() { super(new RetryPolicyConfig<>()); config.delay = Duration.ZERO; config.maxRetries = DEFAULT_MAX_RETRIES; config.abortConditions = new ArrayList<>(); } RetryPolicyBuilder(RetryPolicyConfig config) { super(new RetryPolicyConfig<>(config)); } /** * Builds a new {@link RetryPolicy} using the builder's configuration. */ public RetryPolicy build() { return new RetryPolicyImpl<>(new RetryPolicyConfig<>(config)); } /** * Specifies that retries should be aborted if the {@code completionPredicate} matches the completion result. Any * exception thrown from the {@code completionPredicate} is treated as a {@code false} result. * * @throws NullPointerException if {@code completionPredicate} is null */ @SuppressWarnings("unchecked") public RetryPolicyBuilder abortIf(CheckedBiPredicate completionPredicate) { Assert.notNull(completionPredicate, "completionPredicate"); config.abortConditions.add((CheckedBiPredicate) completionPredicate); return this; } /** * Specifies that retries should be aborted if the {@code resultPredicate} matches the result. Predicate is not * invoked when the operation fails. Any exception thrown from the {@code resultPredicate} is treated as a {@code * false} result. * * @throws NullPointerException if {@code resultPredicate} is null */ public RetryPolicyBuilder abortIf(CheckedPredicate resultPredicate) { Assert.notNull(resultPredicate, "resultPredicate"); config.abortConditions.add(resultPredicateFor(resultPredicate)); return this; } /** * Specifies when retries should be aborted. Any exception that is assignable from the {@code exception} will be * result in retries being aborted. * * @throws NullPointerException if {@code exception} is null */ public RetryPolicyBuilder abortOn(Class exception) { Assert.notNull(exception, "exception"); return abortOn(Arrays.asList(exception)); } /** * Specifies when retries should be aborted. Any exception that is assignable from the {@code exceptions} will be * result in retries being aborted. * * @throws NullPointerException if {@code exceptions} is null * @throws IllegalArgumentException if exceptions is empty */ @SafeVarargs public final RetryPolicyBuilder abortOn(Class... exceptions) { Assert.notNull(exceptions, "exceptions"); Assert.isTrue(exceptions.length > 0, "exceptions cannot be empty"); return abortOn(Arrays.asList(exceptions)); } /** * Specifies when retries should be aborted. Any exception that is assignable from the {@code exceptions} will be * result in retries being aborted. * * @throws NullPointerException if {@code exceptions} is null * @throws IllegalArgumentException if exceptions is null or empty */ public RetryPolicyBuilder abortOn(List> exceptions) { Assert.notNull(exceptions, "exceptions"); Assert.isTrue(!exceptions.isEmpty(), "exceptions cannot be empty"); config.abortConditions.add(failurePredicateFor(exceptions)); return this; } /** * Specifies that retries should be aborted if the {@code abortPredicate} matches the exception. Any exception thrown * from the {@code abortPredicate} is treated as a {@code false} result. * * @throws NullPointerException if {@code abortPredicate} is null */ public RetryPolicyBuilder abortOn(CheckedPredicate abortPredicate) { Assert.notNull(abortPredicate, "abortPredicate"); config.abortConditions.add(failurePredicateFor(abortPredicate)); return this; } /** * Specifies that retries should be aborted if the execution result matches the {@code result}. */ public RetryPolicyBuilder abortWhen(R result) { config.abortConditions.add(resultPredicateFor(result)); return this; } /** * Registers the {@code listener} to be called when an execution is aborted. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

* * @throws NullPointerException if {@code listener} is null */ public RetryPolicyBuilder onAbort(EventListener> listener) { config.abortListener = Assert.notNull(listener, "listener"); return this; } /** * Registers the {@code listener} to be called when an execution attempt fails. You can also use {@link * #onFailure(EventListener) onFailure} to determine when the execution attempt fails and and all retries have * failed. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

* * @throws NullPointerException if {@code listener} is null */ public RetryPolicyBuilder onFailedAttempt(EventListener> listener) { config.failedAttemptListener = Assert.notNull(listener, "listener"); return this; } /** * Registers the {@code listener} to be called when an execution fails and the {@link * RetryPolicyConfig#getMaxRetries() max retry attempts} or {@link RetryPolicyConfig#getMaxDuration() max duration} * are exceeded. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

* * @throws NullPointerException if {@code listener} is null */ public RetryPolicyBuilder onRetriesExceeded(EventListener> listener) { config.retriesExceededListener = Assert.notNull(listener, "listener"); return this; } /** * Registers the {@code listener} to be called when a retry is about to be attempted. *

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

* * @throws NullPointerException if {@code listener} is null * @see #onRetryScheduled(EventListener) */ public RetryPolicyBuilder onRetry(EventListener> listener) { config.retryListener = Assert.notNull(listener, "listener"); return this; } /** * Registers the {@code listener} to be called when a retry for an async call is about to be scheduled. This method * differs from {@link #onRetry(EventListener)} since it is called when a retry is initially scheduled but before any * configured delay, whereas {@link #onRetry(EventListener) onRetry} is called after a delay, just before the retry * attempt takes place. *

*

Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative * result for a failed execution, use a {@link Fallback}.

* * @throws NullPointerException if {@code listener} is null * @see #onRetry(EventListener) */ public RetryPolicyBuilder onRetryScheduled(EventListener> listener) { config.retryScheduledListener = Assert.notNull(listener, "listener"); return this; } /** * Sets the {@code delay} between retries, exponentially backing off to the {@code maxDelay} and multiplying * consecutive delays by a factor of 2. Replaces any previously configured {@link #withDelay(Duration) fixed} or * {@link #withDelay(Duration, Duration) random} delays. * * @throws NullPointerException if {@code delay} or {@code maxDelay} are null * @throws IllegalArgumentException if {@code delay} is <= 0 or {@code delay} is >= {@code maxDelay} * @throws IllegalStateException if {@code delay} is >= the {@link #withMaxDuration(Duration) maxDuration} or {@code * delay} is < a configured {@link #withJitter(Duration) jitter duration} */ public RetryPolicyBuilder withBackoff(Duration delay, Duration maxDelay) { return withBackoff(delay, maxDelay, 2); } /** * Sets the {@code delay} between retries, exponentially backing off to the {@code maxDelay} and multiplying * consecutive delays by a factor of 2. Replaces any previously configured {@link #withDelay(Duration) fixed} or * {@link #withDelay(Duration, Duration) random} delays. * * @throws NullPointerException if {@code chronoUnit} is null * @throws IllegalArgumentException if {@code delay} is <= 0 or {@code delay} is >= {@code maxDelay} * @throws IllegalStateException if {@code delay} is >= the {@link #withMaxDuration(Duration) maxDuration} or {@code * delay} is < a configured {@link #withJitter(Duration) jitter duration} */ public RetryPolicyBuilder withBackoff(long delay, long maxDelay, ChronoUnit chronoUnit) { return withBackoff(delay, maxDelay, chronoUnit, 2); } /** * Sets the {@code delay} between retries, exponentially backing off to the {@code maxDelay} and multiplying * consecutive delays by the {@code delayFactor}. Replaces any previously configured {@link #withDelay(Duration) * fixed} or {@link #withDelay(Duration, Duration) random} delays. * * @throws NullPointerException if {@code chronoUnit} is null * @throws IllegalArgumentException if {@code delay} <= 0, {@code delay} is >= {@code maxDelay}, or the {@code * delayFactor} is <= 1 * @throws IllegalStateException if {@code delay} is >= the {@link #withMaxDuration(Duration) maxDuration} or {@code * delay} is < a configured {@link #withJitter(Duration) jitter duration} */ public RetryPolicyBuilder withBackoff(long delay, long maxDelay, ChronoUnit chronoUnit, double delayFactor) { return withBackoff(Duration.of(delay, chronoUnit), Duration.of(maxDelay, chronoUnit), delayFactor); } /** * Sets the {@code delay} between retries, exponentially backing off to the {@code maxDelay} and multiplying * consecutive delays by the {@code delayFactor}. Replaces any previously configured {@link #withDelay(Duration) * fixed} or {@link #withDelay(Duration, Duration) random} delays. * * @throws NullPointerException if {@code delay} or {@code maxDelay} are null * @throws IllegalArgumentException if {@code delay} <= 0, {@code delay} is >= {@code maxDelay}, or the {@code * delayFactor} is <= 1 * @throws IllegalStateException if {@code delay} is >= the {@link #withMaxDuration(Duration) maxDuration} or {@code * delay} is < a configured {@link #withJitter(Duration) jitter duration} */ public RetryPolicyBuilder withBackoff(Duration delay, Duration maxDelay, double delayFactor) { Assert.notNull(delay, "delay"); Assert.notNull(maxDelay, "maxDelay"); delay = Durations.ofSafeNanos(delay); maxDelay = Durations.ofSafeNanos(maxDelay); Assert.isTrue(!delay.isNegative() && !delay.isZero(), "The delay must be > 0"); Assert.state(config.maxDuration == null || delay.toNanos() < config.maxDuration.toNanos(), "delay must be < the maxDuration"); Assert.state(config.jitter == null || delay.toNanos() >= config.jitter.toNanos(), "delay must be >= the jitter duration"); Assert.isTrue(delay.toNanos() < maxDelay.toNanos(), "delay must be < the maxDelay"); Assert.isTrue(delayFactor > 1, "delayFactor must be > 1"); config.delay = delay; config.maxDelay = maxDelay; config.delayFactor = delayFactor; // CLear random delay config.delayMin = null; config.delayMax = null; return this; } /** * Sets the fixed {@code delay} to occur between retries. Replaces any previously configured {@link * #withBackoff(Duration, Duration) backoff} or {@link #withDelay(Duration, Duration) random} delays. * * @throws NullPointerException if {@code delay} is null * @throws IllegalArgumentException if {@code delay} <= 0 * @throws IllegalStateException if {@code delay} is >= the {@link #withMaxDuration(Duration) maxDuration} or {@code * delay} is < a configured {@link #withJitter(Duration) jitter duration} */ @Override public RetryPolicyBuilder withDelay(Duration delay) { Assert.notNull(delay, "delay"); delay = Durations.ofSafeNanos(delay); Assert.state(config.maxDuration == null || delay.toNanos() < config.maxDuration.toNanos(), "delay must be < the maxDuration"); Assert.state(config.jitter == null || delay.toNanos() >= config.jitter.toNanos(), "delay must be >= the jitter duration"); super.withDelay(delay); // Clear backoff and random delays config.maxDelay = null; config.delayMin = null; config.delayMax = null; return this; } /** * Sets a random delay between the {@code delayMin} and {@code delayMax} (inclusive) to occur between retries. * Replaces any previously configured {@link #withDelay(Duration) fixed} or {@link #withBackoff(Duration, Duration) * backoff} delays. * * @throws NullPointerException if {@code chronoUnit} is null * @throws IllegalArgumentException if {@code delayMin} or {@code delayMax} are <= 0, or {@code delayMin} >= {@code * delayMax} * @throws IllegalStateException if {@code delayMax} is >= the {@link #withMaxDuration(Duration) maxDuration} or * {@code delayMin} is < a configured {@link #withJitter(Duration) jitter duration} */ public RetryPolicyBuilder withDelay(long delayMin, long delayMax, ChronoUnit chronoUnit) { return withDelay(Duration.of(delayMin, chronoUnit), Duration.of(delayMax, chronoUnit)); } /** * Sets a random delay between the {@code delayMin} and {@code delayMax} (inclusive) to occur between retries. * Replaces any previously configured {@link #withDelay(Duration) fixed} or {@link #withBackoff(Duration, Duration) * backoff} delays. * * @throws NullPointerException if {@code delayMin} or {@code delayMax} are null * @throws IllegalArgumentException if {@code delayMin} or {@code delayMax} are <= 0, or {@code delayMin} >= {@code * delayMax} * @throws IllegalStateException if {@code delayMax} is >= the {@link RetryPolicyBuilder#withMaxDuration(Duration) * maxDuration} or {@code delay} is < a configured {@link #withJitter(Duration) jitter duration} */ public RetryPolicyBuilder withDelay(Duration delayMin, Duration delayMax) { Assert.notNull(delayMin, "delayMin"); Assert.notNull(delayMax, "delayMax"); delayMin = Durations.ofSafeNanos(delayMin); delayMax = Durations.ofSafeNanos(delayMax); Assert.isTrue(!delayMin.isNegative() && !delayMin.isZero(), "delayMin must be > 0"); Assert.isTrue(!delayMax.isNegative() && !delayMax.isZero(), "delayMax must be > 0"); Assert.isTrue(delayMin.toNanos() < delayMax.toNanos(), "delayMin must be < delayMax"); Assert.state(config.maxDuration == null || delayMax.toNanos() < config.maxDuration.toNanos(), "delayMax must be < the maxDuration"); Assert.state(config.jitter == null || delayMin.toNanos() >= config.jitter.toNanos(), "delayMin must be >= the jitter duration"); config.delayMin = delayMin; config.delayMax = delayMax; // Clear fixed and random delays config.delay = Duration.ZERO; config.maxDelay = null; return this; } /** * Sets the {@code jitterFactor} to randomly vary retry delays by. For each retry delay, a random portion of the delay * multiplied by the {@code jitterFactor} will be added or subtracted to the delay. For example: a retry delay of * {@code 100} milliseconds and a {@code jitterFactor} of {@code .25} will result in a random retry delay between * {@code 75} and {@code 125} milliseconds. Replaces any previously configured {@link #withJitter(Duration) jitter * duration}. *

* Jitter should be combined with {@link #withDelay(Duration) fixed}, {@link #withDelay(long, long, ChronoUnit) * random} or {@link #withBackoff(long, long, ChronoUnit) exponential backoff} delays. If no delays are configured, * this setting is ignored. * * @throws IllegalArgumentException if {@code jitterFactor} is < 0 or > 1 */ public RetryPolicyBuilder withJitter(double jitterFactor) { Assert.isTrue(jitterFactor >= 0.0 && jitterFactor <= 1.0, "jitterFactor must be >= 0 and <= 1"); config.jitterFactor = jitterFactor; // Clear the jitter duration config.jitter = null; return this; } /** * Sets the {@code jitter} to randomly vary retry delays by. For each retry delay, a random portion of the {@code * jitter} will be added or subtracted to the delay. For example: a {@code jitter} of {@code 100} milliseconds will * randomly add between {@code -100} and {@code 100} milliseconds to each retry delay. Replaces any previously * configured {@link #withJitter(double) jitter factor}. *

* Jitter should be combined with {@link #withDelay(Duration) fixed}, {@link #withDelay(long, long, ChronoUnit) * random} or {@link #withBackoff(long, long, ChronoUnit) exponential backoff} delays. If no delays are configured, * this setting is ignored. * * @throws NullPointerException if {@code jitter} is null * @throws IllegalArgumentException if {@code jitter} is <= 0 * @throws IllegalStateException if the jitter is greater than the min configured delay */ public RetryPolicyBuilder withJitter(Duration jitter) { Assert.notNull(jitter, "jitter"); jitter = Durations.ofSafeNanos(jitter); Assert.isTrue(jitter.toNanos() > 0, "jitter must be > 0"); boolean validJitter = config.delayMin != null ? jitter.toNanos() <= config.delayMin.toNanos() : config.delay == Duration.ZERO || jitter.toNanos() < config.delay.toNanos(); Assert.state(validJitter, "jitter must be < the minimum configured delay"); config.jitter = jitter; // Clear the jitter factor config.jitterFactor = 0; return this; } /** * Sets the max number of execution attempts to perform. {@code -1} indicates no limit. This method has the same * effect as setting 1 more than {@link #withMaxRetries(int)}. For example, 2 retries equal 3 attempts. * * @throws IllegalArgumentException if {@code maxAttempts} is 0 or less than -1 * @see #withMaxRetries(int) */ public RetryPolicyBuilder withMaxAttempts(int maxAttempts) { Assert.isTrue(maxAttempts != 0, "maxAttempts cannot be 0"); Assert.isTrue(maxAttempts >= -1, "maxAttempts must be >= -1"); config.maxRetries = maxAttempts == -1 ? -1 : maxAttempts - 1; return this; } /** * Sets the max duration to perform retries for, else the execution will be failed. *

* Notes: *

    *
  • This setting will not cause long running executions to be interrupted. For that capability, use a * {@link Timeout} policy {@link TimeoutBuilder#withInterrupt() withInterrupt} set.
  • *
  • This setting will not disable {@link #withMaxRetries(int) max retries}, which are still {@code 2} by default. * A max retries limit can be disabled via withMaxRetries(-1)
  • *
*

* * @throws NullPointerException if {@code maxDuration} is null * @throws IllegalStateException if {@code maxDuration} is <= the {@link #withDelay(Duration) delay} or {@code * maxDuration} is <= the {@link #withDelay(Duration, Duration) max random delay}. */ public RetryPolicyBuilder withMaxDuration(Duration maxDuration) { Assert.notNull(maxDuration, "maxDuration"); maxDuration = Durations.ofSafeNanos(maxDuration); Assert.state(maxDuration.toNanos() > config.delay.toNanos(), "maxDuration must be > the delay"); Assert.state(config.delayMax == null || maxDuration.toNanos() > config.delayMax.toNanos(), "maxDuration must be > the max random delay"); config.maxDuration = maxDuration; return this; } /** * Sets the max number of retries to perform when an execution attempt fails. {@code -1} indicates no limit. This * method has the same effect as setting 1 less than {@link #withMaxAttempts(int)}. For example, 2 retries equal 3 * attempts. * * @throws IllegalArgumentException if {@code maxRetries} < -1 * @see #withMaxAttempts(int) */ public RetryPolicyBuilder withMaxRetries(int maxRetries) { Assert.isTrue(maxRetries >= -1, "maxRetries must be >= to -1"); config.maxRetries = maxRetries; return this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/RetryPolicyConfig.java000066400000000000000000000174501444561050700305370ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.event.ExecutionScheduledEvent; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; /** * Configuration for a {@link RetryPolicy}. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see RetryPolicyBuilder */ public class RetryPolicyConfig extends DelayablePolicyConfig { Duration delayMin; Duration delayMax; double delayFactor; Duration maxDelay; Duration jitter; double jitterFactor; Duration maxDuration; int maxRetries; List> abortConditions; // Listeners EventListener> abortListener; EventListener> failedAttemptListener; EventListener> retriesExceededListener; EventListener> retryListener; EventListener> retryScheduledListener; RetryPolicyConfig() { } RetryPolicyConfig(RetryPolicyConfig config) { super(config); delayMin = config.delayMin; delayMax = config.delayMax; delayFactor = config.delayFactor; maxDelay = config.maxDelay; jitter = config.jitter; jitterFactor = config.jitterFactor; maxDuration = config.maxDuration; maxRetries = config.maxRetries; abortConditions = new ArrayList<>(config.abortConditions); abortListener = config.abortListener; failedAttemptListener = config.failedAttemptListener; retriesExceededListener = config.retriesExceededListener; retryListener = config.retryListener; retryScheduledListener = config.retryScheduledListener; } /** * Returns whether the policy config allows retries according to the configured {@link * RetryPolicyConfig#getMaxRetries() maxRetries} and {@link RetryPolicyConfig#getMaxDuration() maxDuration}. * * @see RetryPolicyBuilder#withMaxRetries(int) * @see RetryPolicyBuilder#withMaxDuration(Duration) */ public boolean allowsRetries() { int maxRetries = getMaxRetries(); Duration maxDuration = getMaxDuration(); return (maxRetries == -1 || maxRetries > 0) && (maxDuration == null || maxDuration.toNanos() > 0); } /** * Returns the conditions for which an execution result or exception will cause retries to be aborted. * * @see RetryPolicyBuilder#abortOn(Class...) * @see RetryPolicyBuilder#abortOn(List) * @see RetryPolicyBuilder#abortOn(CheckedPredicate) * @see RetryPolicyBuilder#abortIf(CheckedBiPredicate) * @see RetryPolicyBuilder#abortIf(CheckedPredicate) * @see RetryPolicyBuilder#abortWhen(Object) */ public List> getAbortConditions() { return abortConditions; } /** * Returns the delay between retries, else {@link Duration#ZERO} if delays have not been configured. If backoff * delays are configured, this value will represent the initial delay. * * @see RetryPolicyBuilder#withDelay(Duration) * @see RetryPolicyBuilder#withBackoff(Duration, Duration, double) * @see RetryPolicyBuilder#withBackoff(Duration, Duration, double) * @see RetryPolicyBuilder#withBackoff(long, long, ChronoUnit) * @see RetryPolicyBuilder#withBackoff(long, long, ChronoUnit, double) */ public Duration getDelay() { return super.getDelay(); } /** * Returns the min random delay between retries, else {@code null} if random delays have not been configured. * * @see RetryPolicyBuilder#withDelay(Duration, Duration) * @see RetryPolicyBuilder#withDelay(long, long, ChronoUnit) */ public Duration getDelayMin() { return delayMin; } /** * Returns the max random delay between retries, else {@code null} if random delays have not been configured. * * @see RetryPolicyBuilder#withDelay(Duration, Duration) * @see RetryPolicyBuilder#withDelay(long, long, ChronoUnit) */ public Duration getDelayMax() { return delayMax; } /** * Returns the delay factor for backoff retries. * * @see RetryPolicyBuilder#withBackoff(Duration, Duration, double) * @see RetryPolicyBuilder#withBackoff(long, long, ChronoUnit, double) */ public double getDelayFactor() { return delayFactor; } /** * Returns the jitter, else {@code null} if none has been configured. * * @see RetryPolicyBuilder#withJitter(Duration) */ public Duration getJitter() { return jitter; } /** * Returns the jitter factor, else {@code 0.0} if none has been configured. * * @see RetryPolicyBuilder#withJitter(double) */ public double getJitterFactor() { return jitterFactor; } /** * Returns the max number of execution attempts to perform. A value of {@code -1} represents no limit. Defaults to * {@code 3}. * * @see RetryPolicyBuilder#withMaxAttempts(int) * @see #getMaxRetries() */ public int getMaxAttempts() { return maxRetries == -1 ? -1 : maxRetries + 1; } /** * Returns the max delay between backoff retries, else {@code null} if backoff delays have not been configured. * * @see RetryPolicyBuilder#withBackoff(Duration, Duration) * @see RetryPolicyBuilder#withBackoff(Duration, Duration, double) * @see RetryPolicyBuilder#withBackoff(long, long, ChronoUnit) * @see RetryPolicyBuilder#withBackoff(long, long, ChronoUnit, double) */ public Duration getMaxDelay() { return maxDelay; } /** * Returns the max duration to perform retries for. * * @see RetryPolicyBuilder#withMaxDuration(Duration) */ public Duration getMaxDuration() { return maxDuration; } /** * Returns the max number of retries to perform when an execution attempt fails. A value of {@code -1} represents no * limit. Defaults to {@code 2}. * * @see RetryPolicyBuilder#withMaxRetries(int) * @see #getMaxAttempts() */ public int getMaxRetries() { return maxRetries; } /** * Returns the abort event listener. * * @see RetryPolicyBuilder#onAbort(EventListener) */ public EventListener> getAbortListener() { return abortListener; } /** * Returns the failed attempt event listener. * * @see RetryPolicyBuilder#onFailedAttempt(EventListener) */ public EventListener> getFailedAttemptListener() { return failedAttemptListener; } /** * Returns the retries exceeded event listener. * * @see RetryPolicyBuilder#onRetriesExceeded(EventListener) */ public EventListener> getRetriesExceededListener() { return retriesExceededListener; } /** * Returns the retry event listener. * * @see RetryPolicyBuilder#onRetry(EventListener) */ public EventListener> getRetryListener() { return retryListener; } /** * Returns the retry scheduled event listener. * * @see RetryPolicyBuilder#onRetryScheduled(EventListener) */ public EventListener> getRetryScheduledListener() { return retryScheduledListener; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/SyncExecutionImpl.java000066400000000000000000000127041444561050700305430ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.spi.PolicyExecutor; import dev.failsafe.spi.Scheduler; import dev.failsafe.spi.SyncExecutionInternal; import java.time.Duration; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; /** * SyncExecution and SyncExecutionInternal implementation. * * @param result type * @author Jonathan Halterman */ final class SyncExecutionImpl extends ExecutionImpl implements SyncExecutionInternal { // -- Cross-attempt state -- // An optional Failsafe executor private final FailsafeExecutor executor; // An optional Failsafe call private final CallImpl call; // The outermost function that executions begin with private Function, ExecutionResult> outerFn; // The interruptable execution thread private final Thread executionThread; // Whether the execution is currently interruptable private final AtomicBoolean interruptable; // Whether the execution has been internally interrupted private final AtomicBoolean interrupted; // -- Per-attempt state -- // The delay time in nanoseconds private volatile long delayNanos; /** * Create a standalone sync execution for the {@code policies}. */ SyncExecutionImpl(List> policies) { super(policies); executor = null; call = null; interruptable = new AtomicBoolean(); interrupted = new AtomicBoolean(); executionThread = Thread.currentThread(); preExecute(); } /** * Create a sync execution for the {@code executor}. */ SyncExecutionImpl(FailsafeExecutor executor, Scheduler scheduler, CallImpl call, Function, ExecutionResult> innerFn) { super(executor.policies); this.executor = executor; this.call = call; interruptable = new AtomicBoolean(); interrupted = new AtomicBoolean(); executionThread = Thread.currentThread(); if (call != null) call.setExecution(this); outerFn = innerFn; for (PolicyExecutor policyExecutor : policyExecutors) outerFn = policyExecutor.apply(outerFn, scheduler); } /** * Create a sync execution for a new attempt. */ private SyncExecutionImpl(SyncExecutionImpl execution) { super(execution); executor = execution.executor; call = execution.call; interruptable = execution.interruptable; interrupted = execution.interrupted; executionThread = execution.executionThread; if (call != null) call.setExecution(this); } @Override public void complete() { postExecute(ExecutionResult.none()); } @Override public boolean isComplete() { return completed; } @Override public Duration getDelay() { return Duration.ofNanos(delayNanos); } @Override public void record(R result, Throwable exception) { preExecute(); postExecute(new ExecutionResult<>(result, exception)); } @Override public void recordResult(R result) { preExecute(); postExecute(new ExecutionResult<>(result, null)); } @Override public void recordException(Throwable exception) { preExecute(); postExecute(new ExecutionResult<>(null, exception)); } @Override public synchronized void preExecute() { if (isStandalone()) { attemptRecorded = false; cancelledIndex = Integer.MIN_VALUE; interrupted.set(false); } super.preExecute(); interruptable.set(true); } @Override synchronized ExecutionResult postExecute(ExecutionResult result) { result = super.postExecute(result); delayNanos = result.getDelay(); return result; } @Override public boolean isInterrupted() { return interrupted.get(); } @Override public void setInterruptable(boolean interruptable) { this.interruptable.set(interruptable); } @Override public void interrupt() { // Guard against race with the execution becoming uninterruptable synchronized (getLock()) { if (interruptable.get()) { interrupted.set(true); executionThread.interrupt(); } } } private boolean isStandalone() { return executor == null; } @Override public SyncExecutionImpl copy() { return isStandalone() ? this : new SyncExecutionImpl<>(this); } /** * Performs a synchronous execution. */ R executeSync() { ExecutionResult result = outerFn.apply(this); completed = result.isComplete(); executor.completionHandler.accept(result, this); Throwable exception = result.getException(); if (exception != null) { if (exception instanceof RuntimeException) throw (RuntimeException) exception; if (exception instanceof Error) throw (Error) exception; throw new FailsafeException(exception); } return result.getResult(); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/Timeout.java000066400000000000000000000073121444561050700265460ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import dev.failsafe.function.AsyncRunnable; import dev.failsafe.internal.TimeoutImpl; import dev.failsafe.internal.util.Assert; import java.time.Duration; /** * A policy that cancels and fails an excecution with a {@link TimeoutExceededException TimeoutExceededException} if a * timeout is exceeded. Execution {@link TimeoutBuilder#withInterrupt() interruption} is optionally supported. * Asynchronous executions are cancelled by calling {@link java.util.concurrent.Future#cancel(boolean) cancel} on their * underlying future. Executions can internally cooperate with cancellation by checking {@link * ExecutionContext#isCancelled()}. *

* This policy uses a separate thread on the configured scheduler or the common pool to perform timeouts checks. *

* The {@link TimeoutBuilder#onFailure(EventListener)} and {@link TimeoutBuilder#onSuccess(EventListener)} event * handlers can be used to handle a timeout being exceeded or not. *

*

Note: {@link TimeoutBuilder#withInterrupt() interruption} will have no effect when performing an {@link * FailsafeExecutor#getAsyncExecution(AsyncRunnable) async execution} since the async thread is unknown to Failsafe.

*

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see TimeoutConfig * @see TimeoutBuilder * @see TimeoutExceededException */ public interface Timeout extends Policy { /** * Returns a {@link TimeoutBuilder} that builds {@link Timeout} instances with the given {@code timeout}. * * @param timeout the duration after which an execution is failed with {@link TimeoutExceededException * TimeoutExceededException}. * @throws NullPointerException If {@code timeout} is null * @throws IllegalArgumentException If {@code timeout} is <= 0 */ static TimeoutBuilder builder(Duration timeout) { Assert.notNull(timeout, "timeout"); Assert.isTrue(timeout.toNanos() > 0, "timeout must be > 0"); return new TimeoutBuilder<>(timeout); } /** * Creates a new TimeoutBuilder that will be based on the {@code config}. */ static TimeoutBuilder builder(TimeoutConfig config) { return new TimeoutBuilder<>(config); } /** * Returns a {@link Timeout} that fails an execution with {@link TimeoutExceededException TimeoutExceededException} if * it exceeds the {@code timeout}. Alias for {@code Timeout.builder(timeout).build()}. To configure additional options * on a Timeout, use {@link #builder(Duration)} instead. * * @param timeout the duration after which an execution is failed with {@link TimeoutExceededException * TimeoutExceededException}. * @param result type * @throws NullPointerException If {@code timeout} is null * @throws IllegalArgumentException If {@code timeout} is <= 0 * * @see #builder(Duration) */ static Timeout of(Duration timeout) { return new TimeoutImpl<>(new TimeoutConfig<>(timeout, false)); } /** * Returns the {@link TimeoutConfig} that the Timeout was built with. */ @Override TimeoutConfig getConfig(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/TimeoutBuilder.java000066400000000000000000000044651444561050700300630ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.AsyncRunnable; import dev.failsafe.internal.TimeoutImpl; import java.time.Duration; /** * Builds {@link Timeout} instances. *

* This class is not threadsafe. *

* * @param result type * @author Jonathan Halterman * @see TimeoutConfig * @see TimeoutExceededException */ public class TimeoutBuilder extends PolicyBuilder, TimeoutConfig, R> { TimeoutBuilder(Duration timeout) { super(new TimeoutConfig<>(timeout, false)); } TimeoutBuilder(TimeoutConfig config) { super(new TimeoutConfig<>(config)); } /** * Builds a new {@link Timeout} using the builder's configuration. */ public Timeout build() { return new TimeoutImpl<>(new TimeoutConfig<>(config)); } /** * Configures the policy to interrupt an execution in addition to cancelling it when the timeout is exceeded. For * synchronous executions this is done by calling {@link Thread#interrupt()} on the execution's thread. For * asynchronous executions this is done by calling {@link java.util.concurrent.Future#cancel(boolean) * Future.cancel(true)}. Executions can internally cooperate with interruption by checking {@link * Thread#isInterrupted()} or by handling {@link InterruptedException} where available. *

* Note: Only configure interrupts if the code being executed is designed to be interrupted. *

*

Note: interruption will have no effect when performing an {@link * FailsafeExecutor#getAsyncExecution(AsyncRunnable) async execution} since the async thread is unknown to * Failsafe.

*/ public TimeoutBuilder withInterrupt() { config.canInterrupt = true; return this; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/TimeoutConfig.java000066400000000000000000000027551444561050700277020ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import java.time.Duration; /** * Configuration for a {@link Timeout}. *

* This class is threadsafe. *

* * @param result type * @author Jonathan Halterman * @see TimeoutBuilder */ public class TimeoutConfig extends PolicyConfig { Duration timeout; boolean canInterrupt; TimeoutConfig(Duration timeout, boolean canInterrupt) { this.timeout = timeout; this.canInterrupt = canInterrupt; } TimeoutConfig(TimeoutConfig config) { super(config); timeout = config.timeout; canInterrupt = config.canInterrupt; } /** * Returns the timeout duration. */ public Duration getTimeout() { return timeout; } /** * Returns whether the policy can interrupt an execution if the timeout is exceeded. * * @see TimeoutBuilder#withInterrupt() */ public boolean canInterrupt() { return canInterrupt; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/TimeoutExceededException.java000066400000000000000000000021251444561050700320510ustar00rootroot00000000000000/* * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; /** * Thrown when an execution exceeds a configured {@link Timeout}. * * @author Jonathan Halterman */ public class TimeoutExceededException extends FailsafeException { private static final long serialVersionUID = 1L; private final Timeout timeout; public TimeoutExceededException(Timeout timeout) { this.timeout = timeout; } /** Returns the {@link Timeout} that caused the exception. */ public Timeout getTimeout() { return timeout; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/000077500000000000000000000000001444561050700253735ustar00rootroot00000000000000CircuitBreakerStateChangedEvent.java000066400000000000000000000021151444561050700343310ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.event; import dev.failsafe.CircuitBreaker.State; /** * Indicates a circuit breaker's state changed. * * @author Jonathan Halterman */ public class CircuitBreakerStateChangedEvent { private final State previousState; public CircuitBreakerStateChangedEvent(State previousState) { this.previousState = previousState; } /** * Returns the previous state of the circuit breaker. */ public State getPreviousState() { return previousState; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/EventListener.java000066400000000000000000000017451444561050700310340ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.event; /** * Listens for events. * * @param event type */ @FunctionalInterface public interface EventListener { void accept(E event) throws Throwable; /** * Accepts an {@code event} and ignores any exceptions that result. */ default void acceptUnchecked(E event) { try { accept(event); } catch (Throwable ignore) { } } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/ExecutionAttemptedEvent.java000066400000000000000000000030201444561050700330460ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.event; import dev.failsafe.ExecutionContext; /** * Indicates an execution was attempted. * * @param result type * @author Jonathan Halterman */ public class ExecutionAttemptedEvent extends ExecutionEvent { private final R result; private final Throwable exception; public ExecutionAttemptedEvent(R result, Throwable exception, ExecutionContext context) { super(context); this.result = result; this.exception = exception; } /** * Returns the failure that preceded the event, else {@code null} if there was none. */ public Throwable getLastException() { return exception; } /** * Returns the result that preceded the event, else {@code null} if there was none. */ public R getLastResult() { return result; } @Override public String toString() { return "ExecutionAttemptedEvent[" + "result=" + result + ", exception=" + exception + ']'; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/ExecutionCompletedEvent.java000066400000000000000000000030251444561050700330400ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.event; import dev.failsafe.ExecutionContext; /** * Indicates an execution was completed or cancelled. * * @param result type * @author Jonathan Halterman */ public class ExecutionCompletedEvent extends ExecutionEvent { private final R result; private final Throwable exception; public ExecutionCompletedEvent(R result, Throwable exception, ExecutionContext context) { super(context); this.result = result; this.exception = exception; } /** * Returns the failure that preceded the event, else {@code null} if there was none. */ public Throwable getException() { return exception; } /** * Returns the result that preceded the event, else {@code null} if there was none. */ public R getResult() { return result; } @Override public String toString() { return "ExecutionCompletedEvent[" + "result=" + result + ", exception=" + exception + ']'; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/ExecutionEvent.java000066400000000000000000000052361444561050700312110ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.event; import dev.failsafe.CircuitBreaker; import dev.failsafe.ExecutionContext; import java.time.Duration; import java.time.Instant; import java.util.Optional; /** * Encapsulates information about a Failsafe execution. * * @author Jonathan Halterman */ public abstract class ExecutionEvent { private final ExecutionContext context; ExecutionEvent(ExecutionContext context) { this.context = context; } /** * Returns the elapsed time since initial execution began. */ public Duration getElapsedTime() { return context.getElapsedTime(); } /** * Gets the number of execution attempts so far, including attempts that are blocked before being executed, such as * when a {@link CircuitBreaker CircuitBreaker} is open. Will return {@code 0} when the first * attempt is in progress or has yet to begin. */ public int getAttemptCount() { return context.getAttemptCount(); } /** * Gets the number of completed executions so far. Executions that are blocked, such as when a {@link * CircuitBreaker CircuitBreaker} is open, are not counted. Will return {@code 0} when the first * attempt is in progress or has yet to begin. */ public int getExecutionCount() { return context.getExecutionCount(); } /** * Returns the time that the initial execution started, else {@link Optional#empty()} if an execution has not started yet. */ public Optional getStartTime() { return Optional.ofNullable(context.getStartTime()); } /** * Returns the elapsed time since the last execution attempt began. */ public Duration getElapsedAttemptTime() { return context.getElapsedAttemptTime(); } /** * Returns {@code true} when {@link #getAttemptCount()} is {@code 0} meaning this is the first execution attempt. */ public boolean isFirstAttempt() { return context.isFirstAttempt(); } /** * Returns {@code true} when {@link #getAttemptCount()} is {@code > 0} meaning the execution is being retried. */ public boolean isRetry() { return context.isRetry(); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/ExecutionScheduledEvent.java000066400000000000000000000042571444561050700330340ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.event; import dev.failsafe.ExecutionContext; import dev.failsafe.Timeout; import dev.failsafe.spi.Scheduler; import java.time.Duration; /** * Indicates an execution was scheduled. A scheduled execution will be executed after the {@link #getDelay() delay} * unless it is cancelled, either explicitly or via {@link java.util.concurrent.Future#cancel(boolean) * Future.cancel(boolean)}, a {@link Timeout Timeout}, or if the underlying {@link Scheduler Scheduler} or {@link * java.util.concurrent.ExecutorService ExecutorService} is shutdown. * * @param result type * @author Jonathan Halterman */ public class ExecutionScheduledEvent extends ExecutionEvent { private final R result; private final Throwable exception; private final Duration delay; public ExecutionScheduledEvent(R result, Throwable exception, Duration delay, ExecutionContext context) { super(context); this.result = result; this.exception = exception; this.delay = delay; } /** * Returns the failure that preceded the event, else {@code null} if there was none. */ public Throwable getLastException() { return exception; } /** * Returns the result that preceded the event, else {@code null} if there was none. */ public R getLastResult() { return result; } /** * Returns the delay before the next execution attempt. */ public Duration getDelay() { return delay; } @Override public String toString() { return "ExecutionScheduledEvent[" + "result=" + result + ", exception=" + exception + ", delay=" + delay + ']'; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/event/package-info.java000066400000000000000000000000751444561050700305640ustar00rootroot00000000000000/** * Event listener types. */ package dev.failsafe.event; failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/000077500000000000000000000000001444561050700260775ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/AsyncRunnable.java000066400000000000000000000017061444561050700315120ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; import dev.failsafe.AsyncExecution; /** * A Runnable that manually triggers asynchronous retries or completion via an asynchronous execution. * * @param result type * @author Jonathan Halterman */ @FunctionalInterface public interface AsyncRunnable { void run(AsyncExecution execution) throws Exception; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/AsyncSupplier.java000066400000000000000000000017421444561050700315470ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; import dev.failsafe.AsyncExecution; /** * A Supplier that manually triggers asynchronous retries or completion via an asynchronous execution. * * @param result type * @param supplied type * @author Jonathan Halterman */ @FunctionalInterface public interface AsyncSupplier { T get(AsyncExecution execution) throws Exception; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/CheckedBiPredicate.java000066400000000000000000000016111444561050700323630ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; /** * A BiPredicate that throws checked exceptions. * * @param first input type * @param second input type * @author Jonathan Halterman */ @FunctionalInterface public interface CheckedBiPredicate { boolean test(T t, U u) throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/CheckedConsumer.java000066400000000000000000000015231444561050700320050ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; /** * A Consumer that throws checked exceptions. * * @author Jonathan Halterman * @param input type */ @FunctionalInterface public interface CheckedConsumer { void accept(T t) throws Throwable; }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/CheckedFunction.java000066400000000000000000000015551444561050700320040ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; /** * A Function that throws checked exceptions. * * @author Jonathan Halterman * @param input type * @param result type */ @FunctionalInterface public interface CheckedFunction { R apply(T t) throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/CheckedPredicate.java000066400000000000000000000015271444561050700321160ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; /** * A Predicate that throws checked exceptions. * * @param input type * @author Jonathan Halterman */ @FunctionalInterface public interface CheckedPredicate { boolean test(T t) throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/CheckedRunnable.java000066400000000000000000000014631444561050700317630ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; /** * A Runnable that throws checked exceptions. * * @author Jonathan Halterman */ @FunctionalInterface public interface CheckedRunnable { void run() throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/CheckedSupplier.java000066400000000000000000000015161444561050700320170ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; /** * A Supplier that throws checked exceptions. * * @param supplied type * @author Jonathan Halterman */ @FunctionalInterface public interface CheckedSupplier { T get() throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/ContextualRunnable.java000066400000000000000000000016251444561050700325630ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; import dev.failsafe.ExecutionContext; /** * A Runnable that provides execution context. * * @param result type * @author Jonathan Halterman */ @FunctionalInterface public interface ContextualRunnable { void run(ExecutionContext context) throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/ContextualSupplier.java000066400000000000000000000016611444561050700326200ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.function; import dev.failsafe.ExecutionContext; /** * A Supplier that provides execution context. * * @param result type * @param supplied type * @author Jonathan Halterman */ @FunctionalInterface public interface ContextualSupplier { T get(ExecutionContext context) throws Throwable; } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/function/package-info.java000066400000000000000000000001061444561050700312630ustar00rootroot00000000000000/** * Functional interface types. */ package dev.failsafe.function; failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/000077500000000000000000000000001444561050700260665ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/BulkheadExecutor.java000066400000000000000000000065431444561050700321770ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.Bulkhead; import dev.failsafe.BulkheadFullException; import dev.failsafe.ExecutionContext; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.spi.FailsafeFuture; import dev.failsafe.spi.PolicyExecutor; import dev.failsafe.spi.Scheduler; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * A PolicyExecutor that handles failures according to a {@link Bulkhead}. * * @param result type * @author Jonathan Halterman */ public class BulkheadExecutor extends PolicyExecutor { private final BulkheadImpl bulkhead; private final Duration maxWaitTime; public BulkheadExecutor(BulkheadImpl bulkhead, int policyIndex) { super(bulkhead, policyIndex); this.bulkhead = bulkhead; maxWaitTime = bulkhead.getConfig().getMaxWaitTime(); } @Override protected ExecutionResult preExecute() { try { return bulkhead.tryAcquirePermit(maxWaitTime) ? null : ExecutionResult.exception(new BulkheadFullException(bulkhead)); } catch (InterruptedException e) { // Set interrupt flag Thread.currentThread().interrupt(); return ExecutionResult.exception(e); } } @Override protected CompletableFuture> preExecuteAsync(Scheduler scheduler, FailsafeFuture future) { CompletableFuture> promise = new CompletableFuture<>(); CompletableFuture acquireFuture = bulkhead.acquirePermitAsync(); acquireFuture.whenComplete((result, error) -> { // Signal for execution to proceed promise.complete(ExecutionResult.none()); }); if (!promise.isDone()) { try { // Schedule bulkhead permit timeout Future timeoutFuture = scheduler.schedule(() -> { promise.complete(ExecutionResult.exception(new BulkheadFullException(bulkhead))); acquireFuture.cancel(true); return null; }, maxWaitTime.toNanos(), TimeUnit.NANOSECONDS); // Propagate outer cancellations to the promise, bulkhead acquire future, and timeout future future.setCancelFn(this, (mayInterrupt, cancelResult) -> { promise.complete(cancelResult); acquireFuture.cancel(mayInterrupt); timeoutFuture.cancel(mayInterrupt); }); } catch (Throwable t) { // Hard scheduling failure promise.completeExceptionally(t); } } return promise; } @Override public void onSuccess(ExecutionResult result) { bulkhead.releasePermit(); } @Override protected ExecutionResult onFailure(ExecutionContext context, ExecutionResult result) { bulkhead.releasePermit(); return result; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/BulkheadImpl.java000066400000000000000000000061341444561050700312760ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.Bulkhead; import dev.failsafe.BulkheadConfig; import dev.failsafe.internal.util.FutureLinkedList; import dev.failsafe.spi.PolicyExecutor; import java.time.Duration; import java.util.concurrent.*; /** * A Bulkhead implementation that supports sync and async waiting. * * @param result type * @author Jonathan Halterman */ public class BulkheadImpl implements Bulkhead { private static final CompletableFuture NULL_FUTURE = CompletableFuture.completedFuture(null); private final BulkheadConfig config; private final int maxPermits; // Mutable state private int permits; private final FutureLinkedList futures = new FutureLinkedList(); public BulkheadImpl(BulkheadConfig config) { this.config = config; maxPermits = config.getMaxConcurrency(); permits = maxPermits; } @Override public BulkheadConfig getConfig() { return config; } @Override public void acquirePermit() throws InterruptedException { try { acquirePermitAsync().get(); } catch (CancellationException | ExecutionException ignore) { // Not possible since the future will always be completed with null } } @Override public synchronized boolean tryAcquirePermit() { if (permits > 0) { permits -= 1; return true; } return false; } @Override public boolean tryAcquirePermit(Duration maxWaitTime) throws InterruptedException { CompletableFuture future = acquirePermitAsync(); if (future == NULL_FUTURE) return true; try { future.get(maxWaitTime.toNanos(), TimeUnit.NANOSECONDS); return true; } catch (CancellationException | ExecutionException | TimeoutException e) { return false; } } /** * Returns a CompletableFuture that is completed when a permit is acquired. Externally completing this future will * remove the waiter from the bulkhead's internal queue. */ synchronized CompletableFuture acquirePermitAsync() { if (permits > 0) { permits -= 1; return NULL_FUTURE; } else { return futures.add(); } } @Override public synchronized void releasePermit() { if (permits < maxPermits) { permits += 1; CompletableFuture future = futures.pollFirst(); if (future != null){ permits -= 1; future.complete(null); } } } @Override public PolicyExecutor toExecutor(int policyIndex) { return new BulkheadExecutor<>(this, policyIndex); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/BurstyRateLimiterStats.java000066400000000000000000000064151444561050700334100ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimiterConfig; import java.time.Duration; /** * A rate limiter implementation that allows bursts of executions, up to the max permits per period. This implementation * tracks the current period and available permits, which can go into a deficit. A deficit of available permits will * cause wait times for callers that can be several periods long, depending on the size of the deficit and the number of * requested permits. */ class BurstyRateLimiterStats extends RateLimiterStats { /* The permits per period */ final long periodPermits; /* The nanos per period */ private final long periodNanos; /* Available permits. Can be negative during a deficit. */ private long availablePermits; private long currentPeriod; BurstyRateLimiterStats(RateLimiterConfig config, Stopwatch stopwatch) { super(stopwatch); periodPermits = config.getMaxPermits(); periodNanos = config.getPeriod().toNanos(); availablePermits = periodPermits; } @Override public synchronized long acquirePermits(long requestedPermits, Duration maxWaitTime) { long currentNanos = stopwatch.elapsedNanos(); long newCurrentPeriod = currentNanos / periodNanos; // Update current period and available permits if (currentPeriod < newCurrentPeriod) { long elapsedPeriods = newCurrentPeriod - currentPeriod; long elapsedPermits = elapsedPeriods * periodPermits; currentPeriod = newCurrentPeriod; availablePermits = availablePermits < 0 ? availablePermits + elapsedPermits : periodPermits; } long waitNanos = 0; if (requestedPermits > availablePermits) { long nextPeriodNanos = (currentPeriod + 1) * periodNanos; long nanosToNextPeriod = nextPeriodNanos - currentNanos; long permitDeficit = requestedPermits - availablePermits; long additionalPeriods = permitDeficit / periodPermits; long additionalUnits = permitDeficit % periodPermits; // Do not wait for an additional period if we're not using any permits from it if (additionalUnits == 0) additionalPeriods -= 1; // The nanos to wait until the beginning of the next period that will have free permits waitNanos = nanosToNextPeriod + (additionalPeriods * periodNanos); if (exceedsMaxWaitTime(waitNanos, maxWaitTime)) return -1; } availablePermits -= requestedPermits; return waitNanos; } synchronized long getAvailablePermits() { return availablePermits; } synchronized long getCurrentPeriod() { return currentPeriod; } @Override synchronized void reset() { stopwatch.reset(); availablePermits = periodPermits; currentPeriod = 0; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/CircuitBreakerExecutor.java000066400000000000000000000034001444561050700333430ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreakerOpenException; import dev.failsafe.ExecutionContext; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.spi.PolicyExecutor; import dev.failsafe.CircuitBreaker; /** * A PolicyExecutor that handles failures according to a {@link CircuitBreaker}. * * @param result type * @author Jonathan Halterman */ public class CircuitBreakerExecutor extends PolicyExecutor { private final CircuitBreakerImpl circuitBreaker; public CircuitBreakerExecutor(CircuitBreakerImpl circuitBreaker, int policyIndex) { super(circuitBreaker, policyIndex); this.circuitBreaker = circuitBreaker; } @Override protected ExecutionResult preExecute() { return circuitBreaker.tryAcquirePermit() ? null : ExecutionResult.exception(new CircuitBreakerOpenException(circuitBreaker)); } @Override public void onSuccess(ExecutionResult result) { circuitBreaker.recordSuccess(); } @Override protected ExecutionResult onFailure(ExecutionContext context, ExecutionResult result) { circuitBreaker.recordExecutionFailure(context); return result; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/CircuitBreakerImpl.java000066400000000000000000000123171444561050700324550ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.*; import dev.failsafe.event.CircuitBreakerStateChangedEvent; import dev.failsafe.event.EventListener; import dev.failsafe.spi.DelayablePolicy; import dev.failsafe.spi.FailurePolicy; import dev.failsafe.spi.PolicyExecutor; import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; /** * A {@link CircuitBreaker} implementation. * * @param result type * @author Jonathan Halterman * @see CircuitBreakerBuilder * @see CircuitBreakerOpenException */ public class CircuitBreakerImpl implements CircuitBreaker, FailurePolicy, DelayablePolicy { private final CircuitBreakerConfig config; /** Writes guarded by "this" */ protected final AtomicReference> state = new AtomicReference<>(); public CircuitBreakerImpl(CircuitBreakerConfig config) { this.config = config; state.set(new ClosedState<>(this)); } @Override public CircuitBreakerConfig getConfig() { return config; } @Override public boolean tryAcquirePermit() { return state.get().tryAcquirePermit(); } @Override public void close() { transitionTo(State.CLOSED, config.getCloseListener(), null); } @Override public State getState() { return state.get().getState(); } @Override public int getExecutionCount() { return state.get().getStats().getExecutionCount(); } @Override public Duration getRemainingDelay() { return state.get().getRemainingDelay(); } @Override public long getFailureCount() { return state.get().getStats().getFailureCount(); } @Override public int getFailureRate() { return state.get().getStats().getFailureRate(); } @Override public int getSuccessCount() { return state.get().getStats().getSuccessCount(); } @Override public int getSuccessRate() { return state.get().getStats().getSuccessRate(); } @Override public void halfOpen() { transitionTo(State.HALF_OPEN, config.getHalfOpenListener(), null); } @Override public boolean isClosed() { return State.CLOSED.equals(getState()); } @Override public boolean isHalfOpen() { return State.HALF_OPEN.equals(getState()); } @Override public boolean isOpen() { return State.OPEN.equals(getState()); } @Override public void open() { transitionTo(State.OPEN, config.getOpenListener(), null); } @Override public void recordFailure() { recordExecutionFailure(null); } @Override public void recordException(Throwable exception) { recordResult(null, exception); } @Override public void recordResult(R result) { recordResult(result, null); } @Override public void recordSuccess() { state.get().recordSuccess(); } @Override public String toString() { return getState().toString(); } protected void recordResult(R result, Throwable exception) { if (isFailure(result, exception)) state.get().recordFailure(null); else state.get().recordSuccess(); } /** * Transitions to the {@code newState} if not already in that state and calls any associated event listener. */ protected void transitionTo(State newState, EventListener listener, ExecutionContext context) { boolean transitioned = false; State currentState; synchronized (this) { currentState = getState(); if (!getState().equals(newState)) { switch (newState) { case CLOSED: state.set(new ClosedState<>(this)); break; case OPEN: Duration computedDelay = computeDelay(context); state.set(new OpenState<>(this, state.get(), computedDelay != null ? computedDelay : config.getDelay())); break; case HALF_OPEN: state.set(new HalfOpenState<>(this)); break; } transitioned = true; } } if (transitioned && listener != null) { try { listener.accept(new CircuitBreakerStateChangedEvent(currentState)); } catch (Throwable ignore) { } } } /** * Records an execution failure. */ protected void recordExecutionFailure(ExecutionContext context) { state.get().recordFailure(context); } /** * Opens the circuit breaker and considers the {@code context} when computing the delay before the circuit breaker * will transition to half open. */ protected void open(ExecutionContext context) { transitionTo(State.OPEN, config.getOpenListener(), context); } @Override public PolicyExecutor toExecutor(int policyIndex) { return new CircuitBreakerExecutor<>(this, policyIndex); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/CircuitState.java000066400000000000000000000035021444561050700313340ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker.State; import dev.failsafe.CircuitBreakerConfig; import dev.failsafe.CircuitBreakerOpenException; import dev.failsafe.ExecutionContext; import java.time.Duration; /** * The state of a circuit. * * @param result type * @author Jonathan Halterman */ abstract class CircuitState { final CircuitBreakerImpl breaker; final CircuitBreakerConfig config; volatile CircuitStats stats; CircuitState(CircuitBreakerImpl breaker, CircuitStats stats) { this.breaker = breaker; this.config = breaker.getConfig(); this.stats = stats; } public Duration getRemainingDelay() { return Duration.ZERO; } public CircuitStats getStats() { return stats; } public abstract State getState(); public synchronized void recordFailure(ExecutionContext context) { stats.recordFailure(); checkThreshold(context); releasePermit(); } public synchronized void recordSuccess() { stats.recordSuccess(); checkThreshold(null); releasePermit(); } public void handleConfigChange() { } void checkThreshold(ExecutionContext context) { } abstract boolean tryAcquirePermit(); void releasePermit() { } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/CircuitStats.java000066400000000000000000000033161444561050700313550ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import dev.failsafe.internal.TimedCircuitStats.Clock; /** * Stats for a circuit breaker. */ interface CircuitStats { static CircuitStats create(CircuitBreaker breaker, int capacity, boolean supportsTimeBased, CircuitStats oldStats) { if (supportsTimeBased && breaker.getConfig().getFailureThresholdingPeriod() != null) return new TimedCircuitStats(TimedCircuitStats.DEFAULT_BUCKET_COUNT, breaker.getConfig().getFailureThresholdingPeriod(), new Clock(), oldStats); else if (capacity > 1) { return new CountingCircuitStats(capacity, oldStats); } else { return new DefaultCircuitStats(); } } default void copyExecutions(CircuitStats oldStats) { for (int i = 0; i < oldStats.getSuccessCount(); i++) recordSuccess(); for (int i = 0; i < oldStats.getFailureCount(); i++) recordFailure(); } int getFailureCount(); int getExecutionCount(); int getSuccessCount(); int getFailureRate(); int getSuccessRate(); void recordFailure(); void recordSuccess(); void reset(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/ClosedState.java000066400000000000000000000043771444561050700311560ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import dev.failsafe.CircuitBreaker.State; import dev.failsafe.ExecutionContext; class ClosedState extends CircuitState { public ClosedState(CircuitBreakerImpl breaker) { super(breaker, CircuitStats.create(breaker, capacityFor(breaker), true, null)); } @Override public boolean tryAcquirePermit() { return true; } @Override public State getState() { return State.CLOSED; } @Override public synchronized void handleConfigChange() { stats = CircuitStats.create(breaker, capacityFor(breaker), true, stats); } /** * Checks to see if the executions and failure thresholds have been exceeded, opening the circuit if so. */ @Override synchronized void checkThreshold(ExecutionContext context) { // Execution threshold can only be set for time based thresholding if (stats.getExecutionCount() >= config.getFailureExecutionThreshold()) { // Failure rate threshold can only be set for time based thresholding double failureRateThreshold = config.getFailureRateThreshold(); if ((failureRateThreshold != 0 && stats.getFailureRate() >= failureRateThreshold) || (failureRateThreshold == 0 && stats.getFailureCount() >= config.getFailureThreshold())) breaker.open(context); } } /** * Returns the capacity of the breaker in the closed state. */ private static int capacityFor(CircuitBreaker breaker) { if (breaker.getConfig().getFailureExecutionThreshold() != 0) return breaker.getConfig().getFailureExecutionThreshold(); else return breaker.getConfig().getFailureThresholdingCapacity(); } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/CountingCircuitStats.java000066400000000000000000000076161444561050700330730ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import java.util.BitSet; /** * A CircuitStats implementation that counts execution results using a BitSet. */ class CountingCircuitStats implements CircuitStats { final BitSet bitSet; private final int size; /** Index to write next entry to */ volatile int currentIndex; private volatile int occupiedBits; private volatile int successes; private volatile int failures; public CountingCircuitStats(int size, CircuitStats oldStats) { this.bitSet = new BitSet(size); this.size = size; if (oldStats != null) { synchronized (oldStats) { copyStats(oldStats); } } } /** * Copies the most recent stats from the {@code oldStats} into this in order from oldest to newest. */ void copyStats(CircuitStats oldStats) { if (oldStats instanceof CountingCircuitStats) { CountingCircuitStats old = (CountingCircuitStats) oldStats; int bitsToCopy = Math.min(old.occupiedBits, size); int oldIndex = old.currentIndex - bitsToCopy; if (oldIndex < 0) oldIndex += old.occupiedBits; for (int i = 0; i < bitsToCopy; i++, oldIndex = old.indexAfter(oldIndex)) setNext(old.bitSet.get(oldIndex)); } else { copyExecutions(oldStats); } } @Override public void recordSuccess() { setNext(true); } @Override public void recordFailure() { setNext(false); } @Override public int getExecutionCount() { return occupiedBits; } @Override public int getFailureCount() { return failures; } @Override public synchronized int getFailureRate() { return (int) Math.round(occupiedBits == 0 ? 0 : (double) failures / (double) occupiedBits * 100.0); } @Override public int getSuccessCount() { return successes; } @Override public synchronized int getSuccessRate() { return (int) Math.round(occupiedBits == 0 ? 0 : (double) successes / (double) occupiedBits * 100.0); } @Override public synchronized void reset() { bitSet.clear(); currentIndex = 0; occupiedBits = 0; successes = 0; failures = 0; } /** * Sets the value of the next bit in the bitset, returning the previous value, else -1 if no previous value was set * for the bit. * * @param value true if positive/success, false if negative/failure */ synchronized int setNext(boolean value) { int previousValue = -1; if (occupiedBits < size) occupiedBits++; else previousValue = bitSet.get(currentIndex) ? 1 : 0; bitSet.set(currentIndex, value); currentIndex = indexAfter(currentIndex); if (value) { if (previousValue != 1) successes++; if (previousValue == 0) failures--; } else { if (previousValue != 0) failures++; if (previousValue == 1) successes--; } return previousValue; } /** * Returns an array representation of the BitSet entries. */ @Override public String toString() { StringBuilder sb = new StringBuilder().append('['); for (int i = 0; i < occupiedBits; i++) { if (i > 0) sb.append(", "); sb.append(bitSet.get(i)); } return sb.append(']').toString(); } /** * Returns the index after the {@code index}. */ private int indexAfter(int index) { return index == size - 1 ? 0 : index + 1; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/DefaultCircuitStats.java000066400000000000000000000014511444561050700326600ustar00rootroot00000000000000package dev.failsafe.internal; /** * A default CircuitStats implementation that tracks a single execution result. */ class DefaultCircuitStats implements CircuitStats { volatile int result = -1; @Override public int getFailureCount() { return result == 0 ? 1 : 0; } @Override public int getExecutionCount() { return result == -1 ? 0 : 1; } @Override public int getSuccessCount() { return result == 1 ? 1 : 0; } @Override public int getFailureRate() { return getFailureCount() * 100; } @Override public int getSuccessRate() { return getSuccessCount() * 100; } @Override public void recordFailure() { result = 0; } @Override public void recordSuccess() { result = 1; } @Override public void reset() { result = -1; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/EventHandler.java000066400000000000000000000040011444561050700313030ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.event.EventListener; import dev.failsafe.ExecutionContext; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.event.ExecutionScheduledEvent; import dev.failsafe.spi.ExecutionResult; import java.time.Duration; /** * Internal handling of events. * * @param result type */ public interface EventHandler { void handle(ExecutionResult result, ExecutionContext context); static EventHandler ofExecutionCompleted(EventListener> handler) { return handler == null ? null : (result, context) -> handler.acceptUnchecked( new ExecutionCompletedEvent<>(result.getResult(), result.getException(), context)); } static EventHandler ofExecutionAttempted(EventListener> handler) { return handler == null ? null : (result, context) -> handler.acceptUnchecked( new ExecutionAttemptedEvent<>(result.getResult(), result.getException(), context)); } static EventHandler ofExecutionScheduled(EventListener> handler) { return handler == null ? null : (result, context) -> handler.acceptUnchecked( new ExecutionScheduledEvent<>(result.getResult(), result.getException(), Duration.ofNanos(result.getDelay()), context)); } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/FallbackExecutor.java000066400000000000000000000107731444561050700321570ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.Fallback; import dev.failsafe.spi.*; import dev.failsafe.FallbackConfig; import java.util.concurrent.*; import java.util.function.Function; /** * A PolicyExecutor that handles failures according to a {@link Fallback}. * * @param result type */ public class FallbackExecutor extends PolicyExecutor { private final FallbackImpl fallback; private final FallbackConfig config; private final EventHandler failedAttemptHandler; public FallbackExecutor(FallbackImpl fallback, int policyIndex) { super(fallback, policyIndex); this.fallback = fallback; this.config = fallback.getConfig(); this.failedAttemptHandler = EventHandler.ofExecutionAttempted(config.getFailedAttemptListener()); } /** * Performs an execution by calling pre-execute else calling the supplier, applying a fallback if it fails, and * calling post-execute. */ @Override public Function, ExecutionResult> apply( Function, ExecutionResult> innerFn, Scheduler scheduler) { return execution -> { ExecutionResult result = innerFn.apply(execution); if (execution.isCancelled(this)) return result; if (isFailure(result)) { if (failedAttemptHandler != null) failedAttemptHandler.handle(result, execution); try { result = fallback == FallbackImpl.NONE ? result.withNonResult() : result.withResult(fallback.apply(result.getResult(), result.getException(), execution)); } catch (Throwable t) { result = ExecutionResult.exception(t); } } return postExecute(execution, result); }; } /** * Performs an async execution by calling pre-execute else calling the supplier and doing a post-execute. */ @Override public Function, CompletableFuture>> applyAsync( Function, CompletableFuture>> innerFn, Scheduler scheduler, FailsafeFuture future) { return execution -> innerFn.apply(execution).thenCompose(result -> { if (result == null || future.isDone()) return ExecutionResult.nullFuture(); if (execution.isCancelled(this)) return CompletableFuture.completedFuture(result); if (!isFailure(result)) return postExecuteAsync(execution, result, scheduler, future); if (failedAttemptHandler != null) failedAttemptHandler.handle(result, execution); CompletableFuture> promise = new CompletableFuture<>(); Callable callable = () -> { try { CompletableFuture fallbackFuture = fallback.applyStage(result.getResult(), result.getException(), execution); fallbackFuture.whenComplete((innerResult, exception) -> { if (exception instanceof CompletionException) exception = exception.getCause(); ExecutionResult r = exception == null ? result.withResult(innerResult) : ExecutionResult.exception(exception); promise.complete(r); }); } catch (Throwable t) { promise.complete(ExecutionResult.exception(t)); } return null; }; try { if (!config.isAsync()) callable.call(); else { Future scheduledFallback = scheduler.schedule(callable, 0, TimeUnit.NANOSECONDS); // Propagate outer cancellations to the Fallback future and its promise future.setCancelFn(this, (mayInterrupt, cancelResult) -> { scheduledFallback.cancel(mayInterrupt); promise.complete(cancelResult); }); } } catch (Throwable t) { // Hard scheduling failure promise.completeExceptionally(t); } return promise.thenCompose(ss -> postExecuteAsync(execution, ss, scheduler, future)); }); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/FallbackImpl.java000066400000000000000000000046701444561050700312610ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.Fallback; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.spi.FailurePolicy; import dev.failsafe.ExecutionContext; import dev.failsafe.FallbackBuilder; import dev.failsafe.FallbackConfig; import dev.failsafe.spi.PolicyExecutor; import java.util.concurrent.CompletableFuture; /** * A {@link Fallback} implementation. * * @param result type * @author Jonathan Halterman * @see FallbackBuilder */ public class FallbackImpl implements Fallback, FailurePolicy { /** * A fallback that will return null if execution fails. */ public static Fallback NONE = Fallback.builder(() -> null).build(); private final FallbackConfig config; public FallbackImpl(FallbackConfig config) { this.config = config; } @Override public FallbackConfig getConfig() { return config; } /** * Returns the applied fallback result. */ protected R apply(R result, Throwable exception, ExecutionContext context) throws Throwable { ExecutionAttemptedEvent event = new ExecutionAttemptedEvent<>(result, exception, context); return config.getFallback() != null ? config.getFallback().apply(event) : config.getFallbackStage().apply(event).get(); } /** * Returns a future applied fallback result. */ protected CompletableFuture applyStage(R result, Throwable exception, ExecutionContext context) throws Throwable { ExecutionAttemptedEvent event = new ExecutionAttemptedEvent<>(result, exception, context); return config.getFallback() != null ? CompletableFuture.completedFuture(config.getFallback().apply(event)) : config.getFallbackStage().apply(event); } @Override public PolicyExecutor toExecutor(int policyIndex) { return new FallbackExecutor<>(this, policyIndex); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/HalfOpenState.java000066400000000000000000000074731444561050700314410ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import dev.failsafe.CircuitBreaker.State; import dev.failsafe.ExecutionContext; import java.util.concurrent.atomic.AtomicInteger; class HalfOpenState extends CircuitState { protected final AtomicInteger permittedExecutions = new AtomicInteger(); public HalfOpenState(CircuitBreakerImpl breaker) { super(breaker, CircuitStats.create(breaker, capacityFor(breaker), false, null)); permittedExecutions.set(capacityFor(breaker)); } @Override public boolean tryAcquirePermit() { return permittedExecutions.getAndUpdate(permits -> permits == 0 ? 0 : permits - 1) > 0; } @Override public void releasePermit() { permittedExecutions.incrementAndGet(); } @Override public State getState() { return State.HALF_OPEN; } @Override public synchronized void handleConfigChange() { stats = CircuitStats.create(breaker, capacityFor(breaker), false, stats); } /** * Checks to determine if a threshold has been met and the circuit should be opened or closed. * *

* If a success threshold is configured, the circuit is opened or closed based on whether the ratio was exceeded. *

* Else the circuit is opened or closed based on whether the failure threshold was exceeded. */ @Override synchronized void checkThreshold(ExecutionContext context) { boolean successesExceeded; boolean failuresExceeded; int successThreshold = config.getSuccessThreshold(); if (successThreshold != 0) { int successThresholdingCapacity = config.getSuccessThresholdingCapacity(); successesExceeded = stats.getSuccessCount() >= successThreshold; failuresExceeded = stats.getFailureCount() > successThresholdingCapacity - successThreshold; } else { // Failure rate threshold can only be set for time based thresholding int failureRateThreshold = config.getFailureRateThreshold(); if (failureRateThreshold != 0) { // Execution threshold can only be set for time based thresholding boolean executionThresholdExceeded = stats.getExecutionCount() >= config.getFailureExecutionThreshold(); failuresExceeded = executionThresholdExceeded && stats.getFailureRate() >= failureRateThreshold; successesExceeded = executionThresholdExceeded && stats.getSuccessRate() > 100 - failureRateThreshold; } else { int failureThresholdingCapacity = config.getFailureThresholdingCapacity(); int failureThreshold = config.getFailureThreshold(); failuresExceeded = stats.getFailureCount() >= failureThreshold; successesExceeded = stats.getSuccessCount() > failureThresholdingCapacity - failureThreshold; } } if (successesExceeded) breaker.close(); else if (failuresExceeded) breaker.open(context); } /** * Returns the capacity of the breaker in the half-open state. */ private static int capacityFor(CircuitBreaker breaker) { int capacity = breaker.getConfig().getSuccessThresholdingCapacity(); if (capacity == 0) capacity = breaker.getConfig().getFailureExecutionThreshold(); if (capacity == 0) capacity = breaker.getConfig().getFailureThresholdingCapacity(); return capacity; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/OpenState.java000066400000000000000000000027571444561050700306460ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker.State; import java.time.Duration; class OpenState extends CircuitState { private final long startTime = System.nanoTime(); private final long delayNanos; public OpenState(CircuitBreakerImpl breaker, CircuitState previousState, Duration delay) { super(breaker, previousState.stats); this.delayNanos = delay.toNanos(); } @Override public boolean tryAcquirePermit() { if (System.nanoTime() - startTime >= delayNanos) { breaker.halfOpen(); return breaker.tryAcquirePermit(); } return false; } @Override public Duration getRemainingDelay() { long elapsedTime = System.nanoTime() - startTime; long remainingDelay = delayNanos - elapsedTime; return Duration.ofNanos(Math.max(remainingDelay, 0)); } @Override public State getState() { return State.OPEN; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/RateLimiterExecutor.java000066400000000000000000000056671444561050700327070ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimitExceededException; import dev.failsafe.RateLimiter; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.spi.FailsafeFuture; import dev.failsafe.spi.PolicyExecutor; import dev.failsafe.spi.Scheduler; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * A PolicyExecutor that handles failures according to a {@link RateLimiter}. * * @param result type * @author Jonathan Halterman */ public class RateLimiterExecutor extends PolicyExecutor { private final RateLimiterImpl rateLimiter; private final Duration maxWaitTime; public RateLimiterExecutor(RateLimiterImpl rateLimiter, int policyIndex) { super(rateLimiter, policyIndex); this.rateLimiter = rateLimiter; maxWaitTime = rateLimiter.getConfig().getMaxWaitTime(); } @Override protected ExecutionResult preExecute() { try { return rateLimiter.tryAcquirePermit(maxWaitTime) ? null : ExecutionResult.exception(new RateLimitExceededException(rateLimiter)); } catch (InterruptedException e) { // Set interrupt flag Thread.currentThread().interrupt(); return ExecutionResult.exception(e); } } @Override protected CompletableFuture> preExecuteAsync(Scheduler scheduler, FailsafeFuture future) { CompletableFuture> promise = new CompletableFuture<>(); long waitNanos = rateLimiter.reservePermits(1, maxWaitTime); if (waitNanos == -1) promise.complete(ExecutionResult.exception(new RateLimitExceededException(rateLimiter))); else { try { // Wait for the permit Future permitWaitFuture = scheduler.schedule(() -> { // Signal for execution to proceed promise.complete(ExecutionResult.none()); return null; }, waitNanos, TimeUnit.NANOSECONDS); // Propagate outer cancellations to the promise and permit wait future future.setCancelFn(this, (mayInterrupt, cancelResult) -> { promise.complete(cancelResult); permitWaitFuture.cancel(mayInterrupt); }); } catch (Throwable t) { // Hard scheduling failure promise.completeExceptionally(t); } } return promise; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/RateLimiterImpl.java000066400000000000000000000057071444561050700320050ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimiter; import dev.failsafe.RateLimiterConfig; import dev.failsafe.internal.RateLimiterStats.Stopwatch; import dev.failsafe.internal.util.Assert; import dev.failsafe.internal.util.Durations; import dev.failsafe.spi.PolicyExecutor; import java.time.Duration; import java.util.concurrent.TimeUnit; /** * A RateLimiter implementation that supports smooth and bursty rate limiting. * * @param result type */ public class RateLimiterImpl implements RateLimiter { private final RateLimiterConfig config; private final RateLimiterStats stats; public RateLimiterImpl(RateLimiterConfig config) { this(config, new Stopwatch()); } RateLimiterImpl(RateLimiterConfig config, Stopwatch stopwatch) { this.config = config; stats = config.getMaxRate() != null ? new SmoothRateLimiterStats(config, stopwatch) : new BurstyRateLimiterStats(config, stopwatch); } @Override public RateLimiterConfig getConfig() { return config; } @Override public void acquirePermits(int permits) throws InterruptedException { long waitNanos = reservePermits(permits).toNanos(); if (waitNanos > 0) TimeUnit.NANOSECONDS.sleep(waitNanos); } @Override public Duration reservePermits(int permits) { Assert.isTrue(permits > 0, "permits must be > 0"); return Duration.ofNanos(stats.acquirePermits(permits, null)); } @Override public boolean tryAcquirePermits(int permits) { return reservePermits(permits, Duration.ZERO) == 0; } @Override public boolean tryAcquirePermits(int permits, Duration maxWaitTime) throws InterruptedException { long waitNanos = reservePermits(permits, maxWaitTime); if (waitNanos == -1) return false; if (waitNanos > 0) TimeUnit.NANOSECONDS.sleep(waitNanos); return true; } @Override public Duration tryReservePermits(int permits, Duration maxWaitTime) { return Duration.ofNanos(reservePermits(permits, maxWaitTime)); } @Override public PolicyExecutor toExecutor(int policyIndex) { return new RateLimiterExecutor<>(this, policyIndex); } long reservePermits(int permits, Duration maxWaitTime) { Assert.isTrue(permits > 0, "permits must be > 0"); Assert.notNull(maxWaitTime, "maxWaitTime"); return stats.acquirePermits(permits, Durations.ofSafeNanos(maxWaitTime)); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/RateLimiterStats.java000066400000000000000000000037321444561050700321760ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import java.time.Duration; abstract class RateLimiterStats { final Stopwatch stopwatch; RateLimiterStats(Stopwatch stopwatch) { this.stopwatch = stopwatch; } static class Stopwatch { private long startTime = System.nanoTime(); long elapsedNanos() { return System.nanoTime() - startTime; } void reset() { startTime = System.nanoTime(); } } /** * Eagerly acquires permits and returns the time in nanos that must be waited in order to use the permits, else * returns {@code -1} if the wait time would exceed the {@code maxWaitTime}. * * @param permits the number of requested permits * @param maxWaitTime the max time to wait for the requested permits, else {@code null} to wait indefinitely */ abstract long acquirePermits(long permits, Duration maxWaitTime); /** * Returns whether the {@code waitNanos} would exceed the {@code maxWaitTime}, else {@code false} if {@code * maxWaitTime} is null. */ boolean exceedsMaxWaitTime(long waitNanos, Duration maxWaitTime) { return maxWaitTime != null && waitNanos > maxWaitTime.toNanos(); } /** * Returns the elapsed time since the rate limiter began. */ Duration getElapsed() { return Duration.ofNanos(stopwatch.elapsedNanos()); } /** * Resets the rate limiter's internal stats. */ abstract void reset(); }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/RetryPolicyExecutor.java000066400000000000000000000277461444561050700327550ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.ExecutionContext; import dev.failsafe.RetryPolicy; import dev.failsafe.RetryPolicyConfig; import dev.failsafe.spi.*; import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import static dev.failsafe.internal.util.RandomDelay.randomDelay; import static dev.failsafe.internal.util.RandomDelay.randomDelayInRange; /** * A PolicyExecutor that handles failures according to a {@link RetryPolicy}. * * @param result type * @author Jonathan Halterman */ public class RetryPolicyExecutor extends PolicyExecutor { private final RetryPolicyImpl retryPolicy; private final RetryPolicyConfig config; // Mutable state private volatile int failedAttempts; private volatile boolean retriesExceeded; /** The last fixed, backoff, random or computed delay time in nanoseconds. */ private volatile long lastDelayNanos; // Handlers private final EventHandler abortHandler; private final EventHandler failedAttemptHandler; private final EventHandler retriesExceededHandler; private final EventHandler retryHandler; private final EventHandler retryScheduledHandler; public RetryPolicyExecutor(RetryPolicyImpl retryPolicy, int policyIndex) { super(retryPolicy, policyIndex); this.retryPolicy = retryPolicy; this.config = retryPolicy.getConfig(); this.abortHandler = EventHandler.ofExecutionCompleted(config.getAbortListener()); this.failedAttemptHandler = EventHandler.ofExecutionAttempted(config.getFailedAttemptListener()); this.retriesExceededHandler = EventHandler.ofExecutionCompleted(config.getRetriesExceededListener()); this.retryHandler = EventHandler.ofExecutionAttempted(config.getRetryListener()); this.retryScheduledHandler = EventHandler.ofExecutionScheduled(config.getRetryScheduledListener()); } @Override public Function, ExecutionResult> apply( Function, ExecutionResult> innerFn, Scheduler scheduler) { return execution -> { while (true) { ExecutionResult result = innerFn.apply(execution); // Returns if retries exceeded or an outer policy cancelled the execution if (retriesExceeded || execution.isCancelled(this)) return result; result = postExecute(execution, result); if (result.isComplete() || execution.isCancelled(this)) return result; try { if (retryScheduledHandler != null) retryScheduledHandler.handle(result, execution); // Guard against race with Timeout so that sleep can either be skipped or interrupted execution.setInterruptable(true); Thread.sleep(TimeUnit.NANOSECONDS.toMillis(result.getDelay())); } catch (InterruptedException e) { // Set interrupt flag if interruption was not performed by Failsafe if (!execution.isInterrupted()) Thread.currentThread().interrupt(); return ExecutionResult.exception(e); } finally { execution.setInterruptable(false); } // Guard against race with Timeout cancelling the execution synchronized (execution.getLock()) { if (execution.isCancelled(this)) return result; // Initialize next attempt execution = execution.copy(); } // Call retry handler if (retryHandler != null) retryHandler.handle(result, execution); } }; } @Override public Function, CompletableFuture>> applyAsync( Function, CompletableFuture>> innerFn, Scheduler scheduler, FailsafeFuture future) { return initialRequest -> { CompletableFuture> promise = new CompletableFuture<>(); AtomicReference> previousResultRef = new AtomicReference<>(); try { handleAsync(initialRequest, innerFn, scheduler, future, promise, previousResultRef); } catch (Throwable t) { promise.completeExceptionally(t); } return promise; }; } public Object handleAsync(AsyncExecutionInternal execution, Function, CompletableFuture>> innerFn, Scheduler scheduler, FailsafeFuture future, CompletableFuture> promise, AtomicReference> previousResultRef) { // Call retry handler ExecutionResult previousResult = previousResultRef.get(); if (retryHandler != null && !execution.isRecorded() && previousResult != null) retryHandler.handle(previousResult, execution); // Propagate execution and handle result innerFn.apply(execution).whenComplete((result, error) -> { if (isValidResult(result, error, promise)) { if (retriesExceeded || execution.isCancelled(this)) { promise.complete(result); } else { postExecuteAsync(execution, result, scheduler, future).whenComplete((postResult, postError) -> { if (isValidResult(postResult, postError, promise)) { // Guard against race with Timeout cancelling the execution synchronized (execution.getLock()) { if (postResult.isComplete() || execution.isCancelled(this)) { promise.complete(postResult); return; } // Guard against race with future.complete or future.cancel synchronized (future) { if (!future.isDone()) { try { if (retryScheduledHandler != null) retryScheduledHandler.handle(postResult, execution); previousResultRef.set(postResult); AsyncExecutionInternal retryExecution = execution.copy(); future.setExecution(retryExecution); Callable retryFn = () -> handleAsync(retryExecution, innerFn, scheduler, future, promise, previousResultRef); Future scheduledRetry = scheduler.schedule(retryFn, postResult.getDelay(), TimeUnit.NANOSECONDS); // Cancel prior inner executions, such as pending timeouts future.cancelDependencies(this, false, null); // Propagate outer cancellations to the thread that the innerFn will run with future.setCancelFn(-1, (mayInterrupt, cancelResult) -> { scheduledRetry.cancel(mayInterrupt); }); // Propagate outer cancellations to the retry future and its promise future.setCancelFn(this, (mayInterrupt, cancelResult) -> { promise.complete(cancelResult); }); } catch (Throwable t) { // Hard scheduling failure promise.completeExceptionally(t); } } } } } }); } } }); return null; } /** * Completes the {@code promise} and returns {@code false} if the {@code result} or {@code error} are invalid, else * returns {@code true}. */ boolean isValidResult(ExecutionResult result, Throwable error, CompletableFuture> promise) { if (error != null) { promise.completeExceptionally(error); return false; } else if (result == null) { promise.complete(null); return false; } return true; } @Override public ExecutionResult onFailure(ExecutionContext context, ExecutionResult result) { if (failedAttemptHandler != null) failedAttemptHandler.handle(result, context); failedAttempts++; long delayNanos = lastDelayNanos; // Determine the computed delay Duration computedDelay = retryPolicy.computeDelay(context); if (computedDelay != null) { delayNanos = computedDelay.toNanos(); } else { // Determine the fixed or random delay delayNanos = getFixedOrRandomDelayNanos(delayNanos); delayNanos = adjustForBackoff(context, delayNanos); lastDelayNanos = delayNanos; } if (delayNanos != 0) delayNanos = adjustForJitter(delayNanos); long elapsedNanos = context.getElapsedTime().toNanos(); delayNanos = adjustForMaxDuration(delayNanos, elapsedNanos); // Calculate result boolean maxRetriesExceeded = config.getMaxRetries() != -1 && failedAttempts > config.getMaxRetries(); boolean maxDurationExceeded = config.getMaxDuration() != null && elapsedNanos > config.getMaxDuration().toNanos(); retriesExceeded = maxRetriesExceeded || maxDurationExceeded; boolean isAbortable = retryPolicy.isAbortable(result.getResult(), result.getException()); boolean shouldRetry = !result.isSuccess() && !isAbortable && !retriesExceeded && config.allowsRetries(); boolean completed = isAbortable || !shouldRetry; boolean success = completed && result.isSuccess() && !isAbortable; // Call event handlers if (abortHandler != null && isAbortable) abortHandler.handle(result, context); else if (retriesExceededHandler != null && !success && retriesExceeded) retriesExceededHandler.handle(result, context); return result.with(delayNanos, completed, success); } /** * Defaults async executions to not be complete until {@link #onFailure(ExecutionContext, ExecutionResult) says they * are}. */ @Override public CompletableFuture> onFailureAsync(ExecutionContext context, ExecutionResult result, Scheduler scheduler, FailsafeFuture future) { return super.onFailureAsync(context, result.withNotComplete(), scheduler, future); } private long getFixedOrRandomDelayNanos(long delayNanos) { Duration delay = config.getDelay(); Duration delayMin = config.getDelayMin(); Duration delayMax = config.getDelayMax(); if (delayNanos == 0 && delay != null && !delay.equals(Duration.ZERO)) delayNanos = delay.toNanos(); else if (delayMin != null && delayMax != null) delayNanos = randomDelayInRange(delayMin.toNanos(), delayMax.toNanos(), Math.random()); return delayNanos; } private long adjustForBackoff(ExecutionContext context, long delayNanos) { if (context.getAttemptCount() != 1 && config.getMaxDelay() != null) delayNanos = (long) Math.min(delayNanos * config.getDelayFactor(), config.getMaxDelay().toNanos()); return delayNanos; } private long adjustForJitter(long delayNanos) { if (config.getJitter() != null) delayNanos = randomDelay(delayNanos, config.getJitter().toNanos(), Math.random()); else if (config.getJitterFactor() > 0.0) delayNanos = randomDelay(delayNanos, config.getJitterFactor(), Math.random()); return delayNanos; } private long adjustForMaxDuration(long delayNanos, long elapsedNanos) { if (config.getMaxDuration() != null) { long maxRemainingDelay = config.getMaxDuration().toNanos() - elapsedNanos; delayNanos = Math.min(delayNanos, maxRemainingDelay < 0 ? 0 : maxRemainingDelay); if (delayNanos < 0) delayNanos = 0; } return delayNanos; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/RetryPolicyImpl.java000066400000000000000000000043511444561050700320430ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RetryPolicy; import dev.failsafe.RetryPolicyBuilder; import dev.failsafe.RetryPolicyConfig; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import dev.failsafe.spi.DelayablePolicy; import dev.failsafe.spi.FailurePolicy; import dev.failsafe.spi.PolicyExecutor; import java.util.List; /** * A {@link RetryPolicy} implementation. * * @param result type * @author Jonathan Halterman * @see RetryPolicyBuilder */ public class RetryPolicyImpl implements RetryPolicy, FailurePolicy, DelayablePolicy { private final RetryPolicyConfig config; public RetryPolicyImpl(RetryPolicyConfig config) { this.config = config; } @Override public RetryPolicyConfig getConfig() { return config; } /** * Returns whether an execution result can be aborted given the configured abort conditions. * * @see RetryPolicyBuilder#abortOn(Class...) * @see RetryPolicyBuilder#abortOn(List) * @see RetryPolicyBuilder#abortOn(CheckedPredicate) * @see RetryPolicyBuilder#abortIf(CheckedBiPredicate) * @see RetryPolicyBuilder#abortIf(CheckedPredicate) * @see RetryPolicyBuilder#abortWhen(R) */ public boolean isAbortable(R result, Throwable failure) { for (CheckedBiPredicate predicate : config.getAbortConditions()) { try { if (predicate.test(result, failure)) return true; } catch (Throwable ignore) { } } return false; } @Override public PolicyExecutor toExecutor(int policyIndex) { return new RetryPolicyExecutor<>(this, policyIndex); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/SmoothRateLimiterStats.java000066400000000000000000000047601444561050700333720ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimiterConfig; import dev.failsafe.internal.util.Maths; import java.time.Duration; /** * A rate limiter implementation that evenly distributes permits over time, based on the max permits per period. This * implementation focuses on the interval between permits, and tracks the next interval in which a permit is free. */ class SmoothRateLimiterStats extends RateLimiterStats { /* The nanos per interval between permits */ final long intervalNanos; // The amount of time, relative to the start time, that the next permit will be free. // Will be a multiple of intervalNanos. private long nextFreePermitNanos; SmoothRateLimiterStats(RateLimiterConfig config, Stopwatch stopwatch) { super(stopwatch); intervalNanos = config.getMaxRate().toNanos(); } @Override public synchronized long acquirePermits(long requestedPermits, Duration maxWaitTime) { long currentNanos = stopwatch.elapsedNanos(); long requestedPermitNanos = requestedPermits * intervalNanos; long waitNanos; long newNextFreePermitNanos; // If a permit is currently available if (currentNanos >= nextFreePermitNanos) { // Nanos at the start of the current interval long currentIntervalNanos = Maths.roundDown(currentNanos, intervalNanos); newNextFreePermitNanos = Maths.add(currentIntervalNanos, requestedPermitNanos); } else { newNextFreePermitNanos = Maths.add(nextFreePermitNanos, requestedPermitNanos); } waitNanos = Math.max(newNextFreePermitNanos - currentNanos - intervalNanos, 0); if (exceedsMaxWaitTime(waitNanos, maxWaitTime)) return -1; nextFreePermitNanos = newNextFreePermitNanos; return waitNanos; } synchronized long getNextFreePermitNanos() { return nextFreePermitNanos; } @Override synchronized void reset() { stopwatch.reset(); nextFreePermitNanos = 0; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/TimedCircuitStats.java000066400000000000000000000151771444561050700323500ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import java.time.Duration; import java.util.Arrays; /** * A CircuitStats implementation that counts execution results within a time period, and buckets results to * minimize overhead. */ class TimedCircuitStats implements CircuitStats { static final int DEFAULT_BUCKET_COUNT = 10; private final Clock clock; private final long bucketSizeMillis; private final long windowSizeMillis; // Mutable state final Bucket[] buckets; private final Stat summary; volatile int currentIndex; public TimedCircuitStats(int bucketCount, Duration thresholdingPeriod, Clock clock, CircuitStats oldStats) { this.clock = clock; this.buckets = new Bucket[bucketCount]; windowSizeMillis = thresholdingPeriod.toMillis(); bucketSizeMillis = thresholdingPeriod.toMillis() / buckets.length; for (int i = 0; i < buckets.length; i++) buckets[i] = new Bucket(); this.summary = new Stat(); if (oldStats == null) { buckets[0].startTimeMillis = clock.currentTimeMillis(); } else { synchronized (oldStats) { copyStats(oldStats); } } } static class Clock { long currentTimeMillis() { return System.currentTimeMillis(); } } static class Stat { int successes; int failures; void reset() { successes = 0; failures = 0; } void add(Bucket bucket) { successes += bucket.successes; failures += bucket.failures; } void remove(Bucket bucket) { successes -= bucket.successes; failures -= bucket.failures; } @Override public String toString() { return "[s=" + successes + ", f=" + failures + ']'; } } static class Bucket extends Stat { long startTimeMillis = -1; void reset(long startTimeMillis) { this.startTimeMillis = startTimeMillis; reset(); } void copyFrom(Bucket other) { startTimeMillis = other.startTimeMillis; successes = other.successes; failures = other.failures; } @Override public String toString() { return "[startTime=" + startTimeMillis + ", s=" + successes + ", f=" + failures + ']'; } } /** * Copies the most recent stats from the {@code oldStats} into this in order from oldest to newest and orders buckets * from oldest to newest, with uninitialized buckets counting as oldest. */ void copyStats(CircuitStats oldStats) { if (oldStats instanceof TimedCircuitStats) { TimedCircuitStats old = (TimedCircuitStats) oldStats; int bucketsToCopy = Math.min(old.buckets.length, buckets.length); // Get oldest index to start copying from int oldIndex = old.indexAfter(old.currentIndex); for (int i = 0; i < bucketsToCopy; i++) oldIndex = old.indexBefore(oldIndex); for (int i = 0; i < bucketsToCopy; i++) { if (i != 0) { oldIndex = old.indexAfter(oldIndex); currentIndex = nextIndex(); } buckets[currentIndex].copyFrom(old.buckets[oldIndex]); summary.add(buckets[currentIndex]); } } else { buckets[0].startTimeMillis = clock.currentTimeMillis(); copyExecutions(oldStats); } } @Override public synchronized void recordSuccess() { getCurrentBucket().successes++; summary.successes++; } @Override public synchronized void recordFailure() { getCurrentBucket().failures++; summary.failures++; } @Override public int getExecutionCount() { return summary.successes + summary.failures; } @Override public int getFailureCount() { return summary.failures; } @Override public synchronized int getFailureRate() { int executions = getExecutionCount(); return (int) Math.round(executions == 0 ? 0 : (double) summary.failures / (double) executions * 100.0); } @Override public int getSuccessCount() { return summary.successes; } @Override public synchronized int getSuccessRate() { int executions = getExecutionCount(); return (int) Math.round(executions == 0 ? 0 : (double) summary.successes / (double) executions * 100.0); } @Override public synchronized void reset() { long startTimeMillis = clock.currentTimeMillis(); for (Bucket bucket : buckets) { bucket.reset(startTimeMillis); startTimeMillis += bucketSizeMillis; } summary.reset(); currentIndex = 0; } /** * Returns the current bucket based on the current time, moving the internal storage to the current bucket if * necessary, resetting bucket stats along the way. */ synchronized Bucket getCurrentBucket() { Bucket previousBucket, currentBucket = buckets[currentIndex]; long currentTime = clock.currentTimeMillis(); long timeDiff = currentTime - currentBucket.startTimeMillis; if (timeDiff >= bucketSizeMillis) { int bucketsToMove = (int) (timeDiff / bucketSizeMillis); if (bucketsToMove <= buckets.length) { // Reset some buckets do { currentIndex = nextIndex(); previousBucket = currentBucket; currentBucket = buckets[currentIndex]; long bucketStartTime = currentBucket.startTimeMillis == -1 ? previousBucket.startTimeMillis + bucketSizeMillis : currentBucket.startTimeMillis + windowSizeMillis; summary.remove(currentBucket); currentBucket.reset(bucketStartTime); bucketsToMove--; } while (bucketsToMove > 0); } else { // Reset all buckets reset(); } } return currentBucket; } /** * Returns the next index. */ private int nextIndex() { return (currentIndex + 1) % buckets.length; } /** * Returns the index after the {@code index}. */ private int indexAfter(int index) { return index == buckets.length - 1 ? 0 : index + 1; } /** * Returns the index before the {@code index}. */ private int indexBefore(int index) { return index == 0 ? buckets.length - 1 : index - 1; } @Override public String toString() { return "TimedCircuitStats[summary=" + summary + ", buckets=" + Arrays.toString(buckets) + ']'; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/TimeoutExecutor.java000066400000000000000000000162271444561050700321060ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.Timeout; import dev.failsafe.TimeoutConfig; import dev.failsafe.TimeoutExceededException; import dev.failsafe.spi.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; /** * A PolicyExecutor that handles failures according to a {@link Timeout}. *

* Timeouts are scheduled to occur in a separate thread. When exceeded, a {@link TimeoutExceededException} is recorded * as the execution result. If another result is recorded before the timeout is exceeded, any pending timeouts are * cancelled. * * @param result type */ public class TimeoutExecutor extends PolicyExecutor { private final Timeout policy; private final TimeoutConfig config; public TimeoutExecutor(TimeoutImpl timeout, int policyIndex) { super(timeout, policyIndex); policy = timeout; config = timeout.getConfig(); } @Override public boolean isFailure(ExecutionResult result) { return !result.isNonResult() && result.getException() instanceof TimeoutExceededException; } /** * Schedules a separate timeout call that fails with {@link TimeoutExceededException} if the policy's timeout is * exceeded. *

* This implementation sets up a race between a timeout being triggered and the {@code innerFn} returning. Whichever * completes first will be the result that's returned. */ @Override public Function, ExecutionResult> apply( Function, ExecutionResult> innerFn, Scheduler scheduler) { return execution -> { // Coordinates a result between the timeout and execution threads AtomicReference> result = new AtomicReference<>(); Future timeoutFuture; try { // Schedule timeout check timeoutFuture = Scheduler.DEFAULT.schedule(() -> { // Guard against race with innerFn returning a result ExecutionResult cancelResult = ExecutionResult.exception(new TimeoutExceededException(policy)); if (result.compareAndSet(null, cancelResult)) { // Guard against race with RetryPolicy updating the latest execution synchronized (execution.getLock()) { // Cancel and interrupt the latest attempt ExecutionInternal latestExecution = execution.getLatest(); latestExecution.record(cancelResult); latestExecution.cancel(this); if (config.canInterrupt()) execution.interrupt(); } } return null; }, config.getTimeout().toNanos(), TimeUnit.NANOSECONDS); } catch (Throwable t) { // Hard scheduling failure return postExecute(execution, ExecutionResult.exception(t)); } // Propagate execution, cancel timeout future if not done, and postExecute result if (result.compareAndSet(null, innerFn.apply(execution))) timeoutFuture.cancel(false); return postExecute(execution, result.get()); }; } /** * Schedules a separate timeout call that blocks and fails with {@link TimeoutExceededException} if the policy's * timeout is exceeded. *

* This implementation sets up a race between a timeout being triggered and the {@code innerFn} returning. Whichever * completes first will be the result that's recorded and used to complete the resulting promise. */ @Override @SuppressWarnings("unchecked") public Function, CompletableFuture>> applyAsync( Function, CompletableFuture>> innerFn, Scheduler scheduler, FailsafeFuture future) { return execution -> { // Coordinates a race between the timeout and execution threads AtomicReference> resultRef = new AtomicReference<>(); AtomicReference> timeoutFutureRef = new AtomicReference<>(); CompletableFuture> promise = new CompletableFuture<>(); // Guard against race with future.complete, future.cancel, AsyncExecution.record or AsyncExecution.complete synchronized (future) { // Schedule timeout if we are not done and not recording a result if (!future.isDone() && !execution.isRecorded()) { try { Future timeoutFuture = (Future) Scheduler.DEFAULT.schedule(() -> { // Guard against race with innerFn returning a result ExecutionResult cancelResult = ExecutionResult.exception(new TimeoutExceededException(policy)); if (resultRef.compareAndSet(null, cancelResult)) { // Guard against race with RetryPolicy updating the latest execution synchronized (execution.getLock()) { // Cancel and interrupt the latest attempt ExecutionInternal latestExecution = execution.getLatest(); latestExecution.record(cancelResult); latestExecution.cancel(this); future.cancelDependencies(this, config.canInterrupt(), cancelResult); } } return null; }, config.getTimeout().toNanos(), TimeUnit.NANOSECONDS); timeoutFutureRef.set(timeoutFuture); // Propagate outer cancellations to the Timeout future and its promise future.setCancelFn(this, (mayInterrupt, cancelResult) -> { timeoutFuture.cancel(mayInterrupt); resultRef.compareAndSet(null, cancelResult); }); } catch (Throwable t) { // Hard scheduling failure promise.completeExceptionally(t); return promise; } } } // Propagate execution, cancel timeout future if not done, and postExecute result innerFn.apply(execution).whenComplete((result, error) -> { if (error != null) { promise.completeExceptionally(error); return; } // Fetch timeout result if any if (!resultRef.compareAndSet(null, result)) result = resultRef.get(); if (result != null) { // Cancel timeout task Future timeoutFuture = timeoutFutureRef.get(); if (timeoutFuture != null && !timeoutFuture.isDone()) timeoutFuture.cancel(false); postExecuteAsync(execution, result, scheduler, future); } promise.complete(result); }); return promise; }; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/TimeoutImpl.java000066400000000000000000000016121444561050700312010ustar00rootroot00000000000000package dev.failsafe.internal; import dev.failsafe.Timeout; import dev.failsafe.TimeoutBuilder; import dev.failsafe.TimeoutConfig; import dev.failsafe.TimeoutExceededException; import dev.failsafe.spi.PolicyExecutor; /** * A {@link Timeout} implementation. * * @param result type * @author Jonathan Halterman * @see TimeoutBuilder * @see TimeoutExceededException */ public class TimeoutImpl implements Timeout { private final TimeoutConfig config; public TimeoutImpl(TimeoutConfig config) { this.config = config; } @Override public TimeoutConfig getConfig() { return config; } @Override public PolicyExecutor toExecutor(int policyIndex) { return new TimeoutExecutor<>(this, policyIndex); } @Override public String toString() { return "Timeout[timeout=" + config.getTimeout() + ", interruptable=" + config.canInterrupt() + ']'; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/000077500000000000000000000000001444561050700270435ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/Assert.java000066400000000000000000000025201444561050700311460ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; /** * Assertion utilities. * * @author Jonathan Halterman */ public final class Assert { private Assert() { } public static void isTrue(boolean expression, String errorMessageFormat, Object... args) { if (!expression) throw new IllegalArgumentException(String.format(errorMessageFormat, args)); } public static T notNull(T reference, String parameterName) { if (reference == null) throw new NullPointerException(parameterName + " cannot be null"); return reference; } public static void state(boolean expression, String errorMessageFormat, Object... args) { if (!expression) throw new IllegalStateException(String.format(errorMessageFormat, args)); } }DelegatingScheduler.java000066400000000000000000000125751444561050700335430ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import dev.failsafe.spi.Scheduler; import java.util.concurrent.*; /** * A {@link Scheduler} implementation that schedules delays on an internal, common ScheduledExecutorService and executes * tasks on either a provided {@link ExecutorService}, {@link ForkJoinPool#commonPool()}, or an internal * {@link ForkJoinPool} instance. If no {@link ExecutorService} is supplied, the {@link ForkJoinPool#commonPool()} will * be used, unless the common pool's parallelism is 1, then an internal {@link ForkJoinPool} with parallelism of 2 will * be created and used. *

* Supports cancellation and interruption of {@link ForkJoinPool} tasks. *

* * @author Jonathan Halterman * @author Ben Manes */ public final class DelegatingScheduler implements Scheduler { public static final DelegatingScheduler INSTANCE = new DelegatingScheduler(); private static volatile ForkJoinPool FORK_JOIN_POOL; private static volatile ScheduledThreadPoolExecutor DELAYER; private final ExecutorService executorService; private DelegatingScheduler() { this.executorService = null; } public DelegatingScheduler(ExecutorService executor) { this.executorService = executor; } private static final class DelayerThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); t.setName("FailsafeDelayScheduler"); return t; } } static final class ScheduledCompletableFuture extends CompletableFuture implements ScheduledFuture { // Guarded by this volatile Future delegate; // Guarded by this Thread forkJoinPoolThread; private final long time; ScheduledCompletableFuture(long delay, TimeUnit unit) { this.time = System.nanoTime() + unit.toNanos(delay); } @Override public long getDelay(TimeUnit unit) { return unit.convert(time - System.nanoTime(), TimeUnit.NANOSECONDS); } @Override public int compareTo(Delayed other) { if (other == this) { return 0; } else if (other instanceof ScheduledCompletableFuture) { return Long.compare(time, ((ScheduledCompletableFuture) other).time); } return Long.compare(getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS)); } @Override public boolean cancel(boolean mayInterruptIfRunning) { boolean result = super.cancel(mayInterruptIfRunning); synchronized (this) { if (delegate != null) result = delegate.cancel(mayInterruptIfRunning); if (forkJoinPoolThread != null && mayInterruptIfRunning) forkJoinPoolThread.interrupt(); } return result; } } private static ScheduledExecutorService delayer() { if (DELAYER == null) { synchronized (DelegatingScheduler.class) { if (DELAYER == null) { ScheduledThreadPoolExecutor delayer = new ScheduledThreadPoolExecutor(1, new DelayerThreadFactory()); delayer.setRemoveOnCancelPolicy(true); DELAYER = delayer; } } } return DELAYER; } private ExecutorService executorService() { if (executorService != null) return executorService; if (FORK_JOIN_POOL == null) { synchronized (DelegatingScheduler.class) { if (FORK_JOIN_POOL == null) { if (ForkJoinPool.getCommonPoolParallelism() > 1) FORK_JOIN_POOL = ForkJoinPool.commonPool(); else FORK_JOIN_POOL = new ForkJoinPool(2); } } } return FORK_JOIN_POOL; } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { ScheduledCompletableFuture promise = new ScheduledCompletableFuture<>(delay, unit); ExecutorService es = executorService(); boolean isForkJoinPool = es instanceof ForkJoinPool; Callable completingCallable = () -> { try { if (isForkJoinPool) { // Guard against race with promise.cancel synchronized (promise) { promise.forkJoinPoolThread = Thread.currentThread(); } } promise.complete(callable.call()); } catch (Throwable t) { promise.completeExceptionally(t); } finally { if (isForkJoinPool) { synchronized (promise) { promise.forkJoinPoolThread = null; } } } return null; }; if (delay == 0) promise.delegate = es.submit(completingCallable); else promise.delegate = delayer().schedule(() -> { // Guard against race with promise.cancel synchronized (promise) { if (!promise.isCancelled()) promise.delegate = es.submit(completingCallable); } }, delay, unit); return promise; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/Durations.java000066400000000000000000000023461444561050700316630ustar00rootroot00000000000000/* * Copyright 2011-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import java.time.Duration; /** * Duration and long utilities. */ public final class Durations { static long MAX_SECONDS_PER_LONG = Long.MAX_VALUE / 1000_000_000L; static Duration MAX_SAFE_NANOS_DURATION = Duration.ofSeconds(MAX_SECONDS_PER_LONG); private Durations() { } /** * Returns either the {@code duration} else a Duration containing the max seconds that can safely be converted to * nanos without overflowing. */ public static Duration ofSafeNanos(Duration duration) { return duration.getSeconds() < MAX_SECONDS_PER_LONG ? duration : MAX_SAFE_NANOS_DURATION; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/FutureLinkedList.java000066400000000000000000000042431444561050700331460ustar00rootroot00000000000000/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import java.util.concurrent.CompletableFuture; /** * A LinkedList of CompletableFutures that removes a future from the list when it's completed. *

* This class is threadsafe. *

* * @author Jonathan Halterman */ public final class FutureLinkedList { Node head; Node tail; static class Node { Node previous; Node next; CompletableFuture future; } /** * Adds a new CompletableFuture to the list and returns it. The returned future will be removed from the list when * it's completed. */ public synchronized CompletableFuture add() { Node node = new Node(); node.future = new CompletableFuture<>(); node.future.whenComplete((result, error) -> remove(node)); if (head == null) head = tail = node; else { tail.next = node; node.previous = tail; tail = node; } return node.future; } /** * Returns and removes the first future in the list, else returns {@code null} if the list is empty. */ public synchronized CompletableFuture pollFirst() { Node previousHead = head; if (head != null) { head = head.next; if (head != null) head.previous = null; } return previousHead == null ? null : previousHead.future; } private synchronized void remove(Node node) { if (node.previous != null) node.previous.next = node.next; if (node.next != null) node.next.previous = node.previous; if (head == node) head = node.next; if (tail == node) tail = node.previous; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/Lists.java000066400000000000000000000021621444561050700310050ustar00rootroot00000000000000/* * Copyright 2011-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * List utilities. * * @author Jonathan Halterman */ public final class Lists { private Lists() { } /** * Returns a list containing the {@code first} element followed by the {@code rest}. */ public static List of(T first, T[] rest) { List result = new ArrayList<>(rest.length + 1); result.add(first); Collections.addAll(result, rest); return result; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/Maths.java000066400000000000000000000023301444561050700307600ustar00rootroot00000000000000/* * Copyright 2011-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; /** * Misc math utilities. */ public final class Maths { private Maths() { } /** * Returns the sum of {@code a} and {@code b} else {@code Long.MAX_VALUE} if the sum would otherwise overflow. */ public static long add(long a, long b) { long naiveSum = a + b; return (a ^ b) < 0L | (a ^ naiveSum) >= 0L ? naiveSum : 9223372036854775807L + (naiveSum >>> 63 ^ 1L); } /** * Returns the {@code input} rounded down to the nearest {@code interval}. */ public static long roundDown(long input, long interval) { return (input / interval) * interval; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/internal/util/RandomDelay.java000066400000000000000000000024461444561050700321130ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; /** * Utilities for computing random delays. * * @author Jonathan Halterman */ public final class RandomDelay { private RandomDelay() { } public static long randomDelayInRange(long delayMin, long delayMax, double random) { return (long) (random * (delayMax - delayMin)) + delayMin; } public static long randomDelay(long delay, long jitter, double random) { double randomAddend = (1 - random * 2) * jitter; return (long) (delay + randomAddend); } public static long randomDelay(long delay, double jitterFactor, double random) { double randomFactor = 1 + (1 - random * 2) * jitterFactor; return (long) (delay * randomFactor); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/package-info.java000066400000000000000000000003321444561050700274370ustar00rootroot00000000000000/** * APIs for performing failsafe executions. *

* {@link dev.failsafe.Failsafe} is the entry point for the library. See {@link dev.failsafe.FailsafeExecutor} * for execution options. */ package dev.failsafe; failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/000077500000000000000000000000001444561050700250455ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/AsyncExecutionInternal.java000066400000000000000000000030071444561050700323460ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.AsyncExecution; /** * Internal async execution APIs. * * @param result type * @author Jonathan Halterman */ public interface AsyncExecutionInternal extends ExecutionInternal, AsyncExecution { /** * Returns whether the execution is an async integration execution. */ boolean isAsyncExecution(); /** * Returns whether one of the public {@link AsyncExecution} record or complete methods have been called. */ boolean isRecorded(); /** * Sets the PolicyExecutor corresponding to the {@code policyIndex} as having post-executed. */ void setPostExecuted(int policyIndex); /** * Returns whether the PolicyExecutor corresponding to the {@code policyIndex} has already post-executed. */ boolean isPostExecuted(int policyIndex); /** * Returns a new copy of the AsyncExecutionInternal. */ AsyncExecutionInternal copy(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/DefaultScheduledFuture.java000066400000000000000000000036401444561050700323130ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * A default ScheduledFuture implementation. Useful for {@link Scheduler} implementations. * * @param result type * @author Jonathan Halterman */ public class DefaultScheduledFuture implements ScheduledFuture { /** * @return {@code 0} */ @Override public long getDelay(TimeUnit unit) { return 0; } /** * @return {@code 0} */ @Override public int compareTo(Delayed o) { return 0; } /** * @return {@code false} */ @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } /** * @return {@code false} */ @Override public boolean isCancelled() { return false; } /** * @return {@code false} */ @Override public boolean isDone() { return false; } /** * @return {@code null} */ @Override public R get() throws InterruptedException, ExecutionException { return null; } /** * @return {@code null} */ @Override public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/DelayablePolicy.java000066400000000000000000000043711444561050700307570ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.DelayablePolicyConfig; import dev.failsafe.ExecutionContext; import dev.failsafe.Policy; import dev.failsafe.internal.util.Durations; import java.time.Duration; /** * A policy that can be delayed between executions. * * @param result type * @author Jonathan Halterman */ public interface DelayablePolicy extends Policy { DelayablePolicyConfig getConfig(); /** * Returns a computed delay for the {@code result} and {@code context} else {@code null} if no delay function is * configured or the computed delay is invalid. */ default Duration computeDelay(ExecutionContext context) { DelayablePolicyConfig config = getConfig(); Duration computed = null; if (context != null && config.getDelayFn() != null) { R result = context.getLastResult(); Throwable exception = context.getLastException(); R delayResult = config.getDelayResult(); Class delayFailure = config.getDelayException(); boolean delayResultMatched = delayResult == null || delayResult.equals(result); boolean delayExceptionMatched = delayFailure == null || (exception != null && delayFailure.isAssignableFrom(exception.getClass())); if (delayResultMatched && delayExceptionMatched) { try { computed = Durations.ofSafeNanos(config.getDelayFn().get(context)); } catch (Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException) e; else throw new RuntimeException(e); } } } return computed != null && !computed.isNegative() ? computed : null; } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/ExecutionInternal.java000066400000000000000000000050521444561050700313520ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.ExecutionContext; /** * Internal execution APIs. * * @param result type * @author Jonathan Halterman */ public interface ExecutionInternal extends ExecutionContext { /** * Returns the recorded result for an execution attempt. */ ExecutionResult getResult(); /** * Called when execution of the user's supplier is about to begin. */ void preExecute(); /** * Returns whether the execution has been pre-executed, indicating the attempt has started. */ boolean isPreExecuted(); /** * Records an execution attempt which may correspond with an execution result. Async executions will have results * recorded separately. */ void recordAttempt(); /** * Records the {@code result} if the execution has been {@link #preExecute() pre-executed} and a result has not * already been recorded. */ void record(ExecutionResult result); /** * Marks the execution as having been cancelled externally, which will cancel pending executions of all policies. * * @return whether cancellation was successful or not. Returns {@code false} if the execution was already cancelled or * completed. */ boolean cancel(); /** * Marks the execution as having been cancelled by the {@code policyExecutor}, which will also cancel pending * executions of any inner policies of the {@code policyExecutor}. Outer policies of the {@code policyExecutor} will * be unaffected. */ void cancel(PolicyExecutor policyExecutor); /** * Returns whether the execution is considered cancelled for the {@code policyExecutor}. */ boolean isCancelled(PolicyExecutor policyExecutor); /** * Returns a lock object that is common across all execution attempts. Useful for guarding against races when mutating * an execution. */ Object getLock(); /** * Returns the most recent execution to be attempted. */ ExecutionInternal getLatest(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/ExecutionResult.java000066400000000000000000000172271444561050700310630ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import java.util.Objects; import java.util.concurrent.CompletableFuture; /** * This class represents the internal result of an execution attempt for zero or more policies, before or after the * policy has handled the result. If a policy is done handling a result or is no longer able to handle a result, such as * when retries are exceeded, the ExecutionResult should be marked as complete. *

This class is immutable.

* * @param result type * @author Jonathan Halterman */ public final class ExecutionResult { private static final CompletableFuture NULL_FUTURE = CompletableFuture.completedFuture(null); private static final ExecutionResult NONE = new ExecutionResult<>(null, null, true, 0, true, true, true); /** The execution result, if any */ private final R result; /** The execution exception, if any */ private final Throwable exception; /** Whether the result represents a non result rather than a {@code null} result */ private final boolean nonResult; /** The amount of time to wait prior to the next execution, according to the policy */ // Do we need this here? perhaps we can set delay nanos right against the Execution internals? private final long delayNanos; /** Whether a policy has completed handling of the execution */ private final boolean complete; /** Whether a policy determined the execution to be a success */ private final boolean success; /** Whether all policies determined the execution to be a success */ private final Boolean successAll; /** * Records an initial execution result with {@code complete} true and {@code success} set to true if {@code exception} * is not null. */ public ExecutionResult(R result, Throwable exception) { this(result, exception, false, 0, true, exception == null, exception == null); } private ExecutionResult(R result, Throwable exception, boolean nonResult, long delayNanos, boolean complete, boolean success, Boolean successAll) { this.nonResult = nonResult; this.result = result; this.exception = exception; this.delayNanos = delayNanos; this.complete = complete; this.success = success; this.successAll = successAll; } /** * Returns a CompletableFuture that is completed with {@code null}. Uses an intern'ed value to avoid new object * creation. */ @SuppressWarnings("unchecked") public static CompletableFuture> nullFuture() { return (CompletableFuture>) NULL_FUTURE; } /** * Returns an ExecutionResult with the {@code result} set, {@code complete} true and {@code success} true. */ public static ExecutionResult success(R result) { return new ExecutionResult<>(result, null, false, 0, true, true, true); } /** * Returns an ExecutionResult with the {@code exception} set, {@code complete} true and {@code success} false. */ public static ExecutionResult exception(Throwable exception) { return new ExecutionResult<>(null, exception, false, 0, true, false, false); } /** * Returns an execution that was completed with a non-result. Uses an intern'ed value to avoid new object creation. */ @SuppressWarnings("unchecked") public static ExecutionResult none() { return (ExecutionResult) NONE; } public R getResult() { return result; } public Throwable getException() { return exception; } public long getDelay() { return delayNanos; } public boolean isComplete() { return complete; } public boolean isNonResult() { return nonResult; } public boolean isSuccess() { return success; } /** * Returns a copy of the ExecutionResult with a non-result, and complete and success set to true. Returns {@code this} * if {@link #success} and {@link #result} are unchanged. */ public ExecutionResult withNonResult() { return success && this.result == null && nonResult ? this : new ExecutionResult<>(null, null, true, delayNanos, true, true, successAll); } /** * Returns a copy of the ExecutionResult with the {@code result} value, and complete and success set to true. Returns * {@code this} if {@link #success} and {@link #result} are unchanged. */ public ExecutionResult withResult(R result) { boolean unchangedNull = this.result == null && result == null && exception == null; boolean unchangedNotNull = this.result != null && this.result.equals(result); return success && (unchangedNull || unchangedNotNull) ? this : new ExecutionResult<>(result, null, nonResult, delayNanos, true, true, successAll); } /** * Returns a copy of the ExecutionResult with {@code complete} set to false, else this if nothing has changed. */ public ExecutionResult withNotComplete() { return !this.complete ? this : new ExecutionResult<>(result, exception, nonResult, delayNanos, false, success, successAll); } /** * Returns a copy of the ExecutionResult with success value of {code false}. */ public ExecutionResult withException() { return !this.success ? this : new ExecutionResult<>(result, exception, nonResult, delayNanos, complete, false, false); } /** * Returns a copy of the ExecutionResult with the {@code complete} and {@code success} values of {@code true}. */ public ExecutionResult withSuccess() { return this.complete && this.success ? this : new ExecutionResult<>(result, exception, nonResult, delayNanos, true, true, successAll); } /** * Returns a copy of the ExecutionResult with the {@code delayNanos} value. */ public ExecutionResult withDelay(long delayNanos) { return this.delayNanos == delayNanos ? this : new ExecutionResult<>(result, exception, nonResult, delayNanos, complete, success, successAll); } /** * Returns a copy of the ExecutionResult with the {@code delayNanos}, {@code complete} and {@code success} values. */ public ExecutionResult with(long delayNanos, boolean complete, boolean success) { return this.delayNanos == delayNanos && this.complete == complete && this.success == success ? this : new ExecutionResult<>(result, exception, nonResult, delayNanos, complete, success, successAll == null ? success : success && successAll); } /** * Returns whether the execution was successful for all policies. */ public boolean getSuccessAll() { return successAll != null && successAll; } @Override public String toString() { return "[" + "result=" + result + ", exception=" + exception + ", nonResult=" + nonResult + ", delayNanos=" + delayNanos + ", complete=" + complete + ", success=" + success + ", successAll=" + successAll + ']'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExecutionResult that = (ExecutionResult) o; return Objects.equals(result, that.result) && Objects.equals(exception, that.exception); } @Override public int hashCode() { return Objects.hash(result, exception); } }failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/FailsafeFuture.java000066400000000000000000000153021444561050700306160ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.ExecutionContext; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.function.BiConsumer; /** * A CompletableFuture implementation that propagates cancellations and calls completion handlers. * * @param result type * @author Jonathan Halterman */ public class FailsafeFuture extends CompletableFuture { private final BiConsumer, ExecutionContext> completionHandler; // Mutable state guarded by "this" // The most recent execution attempt private ExecutionInternal newestExecution; // Functions to apply when this future is cancelled for each policy index, in descending order private Map>> cancelFunctions; // Whether a cancel with interrupt has already occurred private boolean cancelledWithInterrupt; public FailsafeFuture(BiConsumer, ExecutionContext> completionHandler) { this.completionHandler = completionHandler; } /** * If not already completed, completes the future with the {@code value}, calling the complete and success handlers. */ @Override public synchronized boolean complete(R value) { return completeResult(ExecutionResult.success(value)); } /** * If not already completed, completes the future with the {@code exception}, calling the complete and failure * handlers. */ @Override public synchronized boolean completeExceptionally(Throwable exception) { return completeResult(ExecutionResult.exception(exception)); } /** * Cancels the future along with any dependencies. */ @Override public synchronized boolean cancel(boolean mayInterruptIfRunning) { if (isDone()) return false; this.cancelledWithInterrupt = mayInterruptIfRunning; newestExecution.cancel(); boolean cancelResult = super.cancel(mayInterruptIfRunning); cancelDependencies(null, mayInterruptIfRunning, null); ExecutionResult result = ExecutionResult.exception(new CancellationException()); super.completeExceptionally(result.getException()); completionHandler.accept(result, newestExecution); newestExecution = null; cancelFunctions = null; return cancelResult; } /** * Completes the execution with the {@code result} and calls the completion handler. */ public synchronized boolean completeResult(ExecutionResult result) { if (isDone()) return false; Throwable exception = result.getException(); boolean completed; if (exception == null) completed = super.complete(result.getResult()); else completed = super.completeExceptionally(exception); if (completed) completionHandler.accept(result, newestExecution); newestExecution = null; cancelFunctions = null; return completed; } /** * Applies any {@link #setCancelFn(PolicyExecutor, BiConsumer) cancel functions} with the {@code cancelResult} for * PolicyExecutors whose policyIndex is < the policyIndex of the {@code cancellingPolicyExecutor}. * * @param cancellingPolicyExecutor the PolicyExecutor that is requesting the cancellation of inner policy executors */ public synchronized void cancelDependencies(PolicyExecutor cancellingPolicyExecutor, boolean mayInterrupt, ExecutionResult cancelResult) { if (cancelFunctions != null) { int cancellingPolicyIndex = cancellingPolicyExecutor == null ? Integer.MAX_VALUE : cancellingPolicyExecutor.getPolicyIndex(); Iterator>>> it = cancelFunctions.entrySet().iterator(); /* This iteration occurs in descending order to ensure that the {@code cancelResult} can be supplied to outer cancel functions before the inner supplier is cancelled, which would cause PolicyExecutors to complete with CancellationException rather than the expected {@code cancelResult}. */ while (it.hasNext()) { Map.Entry>> entry = it.next(); if (cancellingPolicyIndex > entry.getKey()) { try { entry.getValue().accept(mayInterrupt, cancelResult); } catch (Exception ignore) { } it.remove(); } } } } /** * Sets the {@code execution} representing the most recent attempt, which will be cancelled if this future is * cancelled. */ public synchronized void setExecution(ExecutionInternal execution) { this.newestExecution = execution; } /** * Sets a {@code cancelFn} to be called when a PolicyExecutor {@link #cancelDependencies(PolicyExecutor, boolean, * ExecutionResult) cancels dependencies} with a policyIndex > the given {@code policyIndex}, or when this future is * {@link #cancel(boolean) cancelled}. */ public synchronized void setCancelFn(int policyIndex, BiConsumer> cancelFn) { if (cancelFunctions == null) cancelFunctions = new TreeMap<>(Collections.reverseOrder()); cancelFunctions.put(policyIndex, cancelFn); } /** * Sets a {@code cancelFn} to be called when a PolicyExecutor {@link #cancelDependencies(PolicyExecutor, boolean, * ExecutionResult) cancels dependencies} with a policyIndex > the policyIndex of the given {@code policyExecutor}, or * when this future is {@link #cancel(boolean) cancelled}. */ public synchronized void setCancelFn(PolicyExecutor policyExecutor, BiConsumer> cancelFn) { setCancelFn(policyExecutor.getPolicyIndex(), cancelFn); } /** * Propogates any previous cancellation to the {@code future}, either by cancelling it immediately or setting a cancel * function to cancel it later. */ public synchronized void propagateCancellation(Future future) { if (isCancelled()) future.cancel(cancelledWithInterrupt); else setCancelFn(-2, (mayInterrupt, cancelResult) -> future.cancel(mayInterrupt)); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/FailurePolicy.java000066400000000000000000000041121444561050700304550ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.FailurePolicyBuilder; import dev.failsafe.FailurePolicyConfig; import dev.failsafe.Policy; import dev.failsafe.function.CheckedBiPredicate; import dev.failsafe.function.CheckedPredicate; import java.util.List; /** * A policy that can handle specifically configured failures. * * @param result type * @author Jonathan Halterman */ public interface FailurePolicy extends Policy { FailurePolicyConfig getConfig(); /** * Returns whether an execution {@code result} or {@code exception} are considered a failure according to the policy * configuration. * * @see FailurePolicyBuilder#handle(Class...) * @see FailurePolicyBuilder#handle(List) * @see FailurePolicyBuilder#handleIf(CheckedBiPredicate) * @see FailurePolicyBuilder#handleIf(CheckedPredicate) * @see FailurePolicyBuilder#handleResult(Object) * @see FailurePolicyBuilder#handleResultIf(CheckedPredicate) */ default boolean isFailure(R result, Throwable exception) { FailurePolicyConfig config = getConfig(); if (config.getFailureConditions().isEmpty()) return exception != null; for (CheckedBiPredicate predicate : config.getFailureConditions()) { try { if (predicate.test(result, exception)) return true; } catch (Throwable ignore) { } } // Fail by default if an exception is not checked by a condition return exception != null && !config.isExceptionsChecked(); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/PolicyExecutor.java000066400000000000000000000225161444561050700306740ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.ExecutionContext; import dev.failsafe.Policy; import dev.failsafe.internal.EventHandler; import java.util.concurrent.CompletableFuture; import java.util.function.Function; /** * Handles execution and execution results according to a policy. May contain pre-execution and post-execution * behaviors. Each PolicyExecutor makes its own determination about whether an execution result is a success or * failure. * * @param result type * @author Jonathan Halterman */ public abstract class PolicyExecutor { /** Index of the policy relative to other policies in a composition, innermost first */ private final int policyIndex; /** Optional APIs for policies that support them */ private final FailurePolicy failurePolicy; private final EventHandler successHandler; private final EventHandler failureHandler; protected PolicyExecutor(Policy policy, int policyIndex) { this.policyIndex = policyIndex; this.failurePolicy = policy instanceof FailurePolicy ? (FailurePolicy) policy : null; this.successHandler = EventHandler.ofExecutionCompleted(policy.getConfig().getSuccessListener()); this.failureHandler = EventHandler.ofExecutionCompleted(policy.getConfig().getFailureListener()); } /** * Returns the index of the policy relative to other policies in a composition, where the innermost policy in a * composition has an index of {@code 0}. */ public int getPolicyIndex() { return policyIndex; } /** * Called before execution to return an alternative result or exception such as if execution is not allowed or needed. */ protected ExecutionResult preExecute() { return null; } /** * Called before an async execution to return an alternative result or exception such as if execution is not allowed or * needed. Returns {@code null} if pre execution is not performed. If the resulting future is completed with a {@link * ExecutionResult#isNonResult() non-result}, then execution and post-execution should still be performed. If the * resulting future is completed with {@code null}, then the execution is assumed to have been cancelled. */ protected CompletableFuture> preExecuteAsync(Scheduler scheduler, FailsafeFuture future) { ExecutionResult result = preExecute(); return result == null ? null : CompletableFuture.completedFuture(result); } /** * Performs an execution by calling pre-execute else calling the supplier and doing a post-execute. */ public Function, ExecutionResult> apply( Function, ExecutionResult> innerFn, Scheduler scheduler) { return execution -> { ExecutionResult result = preExecute(); if (result != null) { // Still need to preExecute when short-circuiting an execution with an alternative result execution.preExecute(); return result; } return postExecute(execution, innerFn.apply(execution)); }; } /** * Performs an async execution by calling pre-execute else calling the supplier and doing a post-execute. Implementors * must handle a null result from a supplier, which indicates that an async execution has occurred, that a result will * be recorded separately, and that postExecute handling should not be performed. */ public Function, CompletableFuture>> applyAsync( Function, CompletableFuture>> innerFn, Scheduler scheduler, FailsafeFuture future) { return execution -> { CompletableFuture> promise = new CompletableFuture<>(); Runnable runnable = () -> innerFn.apply(execution) .thenCompose(r -> r == null ? ExecutionResult.nullFuture() : postExecuteAsync(execution, r, scheduler, future)) .whenComplete((postResult, postError) -> { if (postError != null) promise.completeExceptionally(postError); else promise.complete(postResult); }); if (!execution.isRecorded()) { CompletableFuture> preResult = preExecuteAsync(scheduler, future); if (preResult != null) { preResult.whenComplete((result, error) -> { if (error != null) promise.completeExceptionally(error); else if (result != null) { // Check for non-result, which occurs after a rate limiter preExecute delay if (result.isNonResult()) { // Execute and post-execute runnable.run(); } else { // Still need to preExecute when short-circuiting an execution with an alternative result execution.preExecute(); promise.complete(result); } } else { // If result is null, the execution is assumed to have been cancelled promise.complete(null); } }); return promise; } } // Execute and post-execute runnable.run(); return promise; }; } /** * Performs synchronous post-execution handling for a {@code result}. */ public ExecutionResult postExecute(ExecutionInternal execution, ExecutionResult result) { execution.recordAttempt(); if (isFailure(result)) { result = onFailure(execution, result.withException()); handleFailure(result, execution); } else { result = result.withSuccess(); onSuccess(result); handleSuccess(result, execution); } return result; } /** * Performs potentially asynchronous post-execution handling for a {@code result}. */ protected synchronized CompletableFuture> postExecuteAsync(AsyncExecutionInternal execution, ExecutionResult result, Scheduler scheduler, FailsafeFuture future) { CompletableFuture> postFuture = null; /* Guard against post executing twice for the same execution. This will happen if one async execution result is * recorded by a timeout and another via AsyncExecution.record. */ if (!execution.isAsyncExecution() || !execution.isPostExecuted(policyIndex)) { execution.recordAttempt(); if (isFailure(result)) { postFuture = onFailureAsync(execution, result.withException(), scheduler, future).whenComplete( (postResult, error) -> handleFailure(postResult, execution)); } else { result = result.withSuccess(); onSuccess(result); handleSuccess(result, execution); postFuture = CompletableFuture.completedFuture(result); } if (execution.isAsyncExecution()) execution.setPostExecuted(policyIndex); } return postFuture; } /** * Returns whether the {@code result} is a success according to the policy. If the {code result} has no result, it is * not a failure. */ protected boolean isFailure(ExecutionResult result) { if (result.isNonResult()) return false; else if (failurePolicy != null) return failurePolicy.isFailure(result.getResult(), result.getException()); else return result.getException() != null; } /** * Performs post-execution handling for a {@code result} that is considered a success according to {@link * #isFailure(ExecutionResult)}. */ protected void onSuccess(ExecutionResult result) { } /** * Performs post-execution handling for a {@code result} that is considered a failure according to {@link * #isFailure(ExecutionResult)}, possibly creating a new result, else returning the original {@code result}. */ protected ExecutionResult onFailure(ExecutionContext context, ExecutionResult result) { return result; } /** * Performs potentially asynchrononus post-execution handling for a failed {@code result}, possibly creating a new * result, else returning the original {@code result}. */ protected CompletableFuture> onFailureAsync(ExecutionContext context, ExecutionResult result, Scheduler scheduler, FailsafeFuture future) { try { return CompletableFuture.completedFuture(onFailure(context, result)); } catch (Throwable t) { // Handle unexpected hard errors in user code CompletableFuture> r = new CompletableFuture<>(); r.completeExceptionally(t); return r; } } private void handleSuccess(ExecutionResult result, ExecutionContext context) { if (successHandler != null && result.isComplete()) successHandler.handle(result, context); } private void handleFailure(ExecutionResult result, ExecutionContext context) { if (failureHandler != null && result.isComplete()) failureHandler.handle(result, context); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/Scheduler.java000066400000000000000000000033501444561050700276270ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.internal.util.DelegatingScheduler; import java.util.concurrent.*; /** * Schedules executions. * * @author Jonathan Halterman * @see DefaultScheduledFuture */ public interface Scheduler { /** * The default scheduler used by Failsafe if no other scheduler or {@link ScheduledExecutorService} is configured for * an execution. */ Scheduler DEFAULT = DelegatingScheduler.INSTANCE; /** * Schedules the {@code callable} to be called after the {@code delay} for the {@code unit}. */ ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit); /** * Returns a Scheduler adapted from the {@code scheduledExecutorService}. */ static Scheduler of(ScheduledExecutorService scheduledExecutorService) { return scheduledExecutorService::schedule; } /** * Returns a Scheduler adapted from the {@code executorService}. */ static Scheduler of(ExecutorService executorService) { return executorService instanceof ScheduledExecutorService ? of((ScheduledExecutorService) executorService) : new DelegatingScheduler(executorService); } } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/SyncExecutionInternal.java000066400000000000000000000025451444561050700322130ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.spi; import dev.failsafe.Execution; /** * Internal execution APIs. * * @param result type * @author Jonathan Halterman */ public interface SyncExecutionInternal extends ExecutionInternal, Execution { /** * Returns whether the execution is currently interrupted. */ boolean isInterrupted(); /** * Sets whether the execution is currently {@code interruptable}. */ void setInterruptable(boolean interruptable); /** * Interrupts the execution. */ void interrupt(); /** * Returns a new copy of the SyncExecutionInternal if it is not standalone, else returns {@code this} since standalone * executions are referenced externally and cannot be replaced. */ SyncExecutionInternal copy(); } failsafe-failsafe-parent-3.3.2/core/src/main/java/dev/failsafe/spi/package-info.java000066400000000000000000000002451444561050700302350ustar00rootroot00000000000000/** * The Failsafe Service Provider Interface (SPI). These classes and interfaces allow Failsafe to be extended with new * policies. */ package dev.failsafe.spi; failsafe-failsafe-parent-3.3.2/core/src/main/javadoc/000077500000000000000000000000001444561050700224105ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/main/javadoc/overview.html000066400000000000000000000003161444561050700251440ustar00rootroot00000000000000 Failsafe - fault tolerance and resilience patterns for the JVM.

See the project homepage for usage instructions.

failsafe-failsafe-parent-3.3.2/core/src/test/000077500000000000000000000000001444561050700210345ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/000077500000000000000000000000001444561050700217555ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/000077500000000000000000000000001444561050700225335ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/000077500000000000000000000000001444561050700243055ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/AsyncExecutionTest.java000066400000000000000000000145351444561050700307610ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.spi.*; import dev.failsafe.testing.Testing; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Arrays; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static org.mockito.Mockito.*; import static org.testng.Assert.*; @Test public class AsyncExecutionTest extends Testing { Function, CompletableFuture>> innerFn = Functions.getPromise( ctx -> null, null); ConnectException e = new ConnectException(); AsyncExecutionInternal exec; FailsafeFuture future; Callable callable; Scheduler scheduler; @BeforeMethod @SuppressWarnings("unchecked") void beforeMethod() { scheduler = mock(Scheduler.class); when(scheduler.schedule(any(Callable.class), anyLong(), any(TimeUnit.class))).thenReturn( new DefaultScheduledFuture<>()); future = mock(FailsafeFuture.class); callable = mock(Callable.class); } public void testCompleteForNoResult() { // Given exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.ofDefaults()), scheduler, future, true, innerFn); // When exec.preExecute(); exec.complete(); // Then assertEquals(exec.getAttemptCount(), 1); assertEquals(exec.getExecutionCount(), 1); assertTrue(exec.isComplete()); assertNull(exec.getLastResult()); assertNull(exec.getLastException()); verify(future).completeResult(ExecutionResult.none()); } public void testRetryForResult() { // Given retry for null exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.builder().handleResult(null).build()), scheduler, future, true, innerFn); // When / Then exec.preExecute(); exec.recordResult(null); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.recordResult(null); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.recordResult(1); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 3); assertEquals(exec.getExecutionCount(), 3); assertTrue(exec.isComplete()); assertEquals(exec.getLastResult(), 1); assertNull(exec.getLastException()); verifyScheduler(2); verify(future).completeResult(ExecutionResult.success(1)); } public void testRetryForThrowable() { // Given retry on IllegalArgumentException exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.builder().handle(IllegalArgumentException.class).build()), scheduler, future, true, innerFn); // When / Then exec.preExecute(); exec.recordException(new IllegalArgumentException()); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.recordException(e); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 2); assertEquals(exec.getExecutionCount(), 2); assertTrue(exec.isComplete()); assertNull(exec.getLastResult()); assertEquals(exec.getLastException(), e); verifyScheduler(1); verify(future).completeResult(ExecutionResult.exception(e)); } public void testRetryForResultAndThrowable() { // Given retry for null exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.builder().withMaxAttempts(10).handleResult(null).build()), scheduler, future, true, innerFn); // When / Then exec.preExecute(); exec.recordResult(null); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.record(null, null); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.record(1, new IllegalArgumentException()); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.record(1, null); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 4); assertEquals(exec.getExecutionCount(), 4); assertTrue(exec.isComplete()); assertEquals(exec.getLastResult(), 1); assertNull(exec.getLastException()); verifyScheduler(3); verify(future).completeResult(ExecutionResult.success(1)); } public void testGetAttemptCount() { // Given exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.ofDefaults()), scheduler, future, true, innerFn); // When exec.preExecute(); exec.recordException(e); exec = exec.copy(); exec.preExecute(); exec.recordException(e); // Then assertEquals(exec.getAttemptCount(), 2); assertEquals(exec.getExecutionCount(), 2); } @Test(expectedExceptions = IllegalStateException.class) public void shouldThrowOnRetryWhenAlreadyComplete() { exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.ofDefaults()), scheduler, future, true, innerFn); exec.complete(); exec.preExecute(); exec.recordException(e); } public void testCompleteOrRetry() { // Given retry on IllegalArgumentException exec = new AsyncExecutionImpl<>(Arrays.asList(RetryPolicy.ofDefaults()), scheduler, future, true, innerFn); // When / Then exec.preExecute(); exec.record(null, e); assertFalse(exec.isComplete()); exec = exec.copy(); exec.preExecute(); exec.record(null, null); // Then assertEquals(exec.getAttemptCount(), 2); assertEquals(exec.getExecutionCount(), 2); assertTrue(exec.isComplete()); assertNull(exec.getLastResult()); assertNull(exec.getLastException()); verifyScheduler(1); verify(future).completeResult(ExecutionResult.none()); } private void verifyScheduler(int executions) { verify(scheduler, times(executions)).schedule(any(Callable.class), any(Long.class), any(TimeUnit.class)); } }failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/AsyncFailsafeTest.java000066400000000000000000000077561444561050700305370ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; @Test public class AsyncFailsafeTest extends Testing { /** * Asserts that an AsyncExecution that throws is handled the same as one that properly records a failure. */ public void shouldHandleThrowingAsyncExecution() { // Given AtomicInteger counter = new AtomicInteger(); // When assertThrows(() -> Failsafe.with(retryTwice).getAsyncExecution(exec -> { counter.incrementAndGet(); throw new IllegalArgumentException(); }).get(), ExecutionException.class, IllegalArgumentException.class); // Then assertEquals(counter.get(), 3); } /** * Assert handles a supplier that throws instead of returning a future. */ public void shouldHandleThrowingGetStageAsync() { // Given AtomicInteger counter = new AtomicInteger(); // When assertThrows(() -> Failsafe.with(retryTwice).getStageAsync(() -> { counter.incrementAndGet(); throw new IllegalArgumentException(); }).get(), ExecutionException.class, IllegalArgumentException.class); // Then assertEquals(counter.get(), 3); // Given / When counter.set(0); assertThrows(() -> Failsafe.with(retryTwice).getStageAsync(context -> { counter.incrementAndGet(); throw new IllegalArgumentException(); }).get(), ExecutionException.class, IllegalArgumentException.class); // Then assertEquals(counter.get(), 3); } /** * Asserts that asynchronous completion via an execution is supported. Also tests passing results through a Fallback * policy that should never be triggered. */ public void testComplete() { Stats rpStats = new Stats(); RetryPolicy rp = withStatsAndLogs(RetryPolicy.builder().withMaxRetries(3), rpStats).build(); // Passthrough policy that should allow async execution results through Fallback fb = Fallback.builder("test").handleIf((r, f) -> false).build(); Timeout timeout = Timeout.of(Duration.ofMinutes(1)); AtomicInteger counter = new AtomicInteger(); Consumer> test = failsafe -> testGetSuccess(() -> { counter.set(0); rpStats.reset(); }, failsafe, ex -> { System.out.println("Executing"); if (counter.getAndIncrement() < 3) throw new IllegalStateException(); // Trigger AsyncExecution.complete() when possible return COMPLETE_SIGNAL; }, (f, e) -> { assertEquals(e.getAttemptCount(), 4); assertEquals(e.getExecutionCount(), 4); assertEquals(rpStats.failedAttemptCount, 3); assertEquals(rpStats.retryCount, 3); }, null); // Test RetryPolicy, Fallback test.accept(Failsafe.with(rp, fb)); // Test RetryPolicy, Timeout rpStats.reset(); counter.set(0); test.accept(Failsafe.with(rp, timeout)); } interface FastServer extends Server { } @SuppressWarnings("unused") public void shouldSupportCovariance() { FastServer fastService = mock(FastServer.class); CompletionStage stage = Failsafe.with(RetryPolicy.ofDefaults()).getAsync(() -> fastService); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/BulkheadBuilderTest.java000066400000000000000000000023111444561050700310330ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.*; @Test public class BulkheadBuilderTest { public void shouldCreateBuilderFromExistingConfig() { BulkheadConfig initialConfig = Bulkhead.builder(5) .withMaxWaitTime(Duration.ofSeconds(10)) .onSuccess(e -> { }).config; BulkheadConfig newConfig = Bulkhead.builder(initialConfig).config; assertEquals(newConfig.maxConcurrency, 5); assertEquals(newConfig.maxWaitTime, Duration.ofSeconds(10)); assertNotNull(newConfig.successListener); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/CircuitBreakerBuilderTest.java000066400000000000000000000055401444561050700322210ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import java.time.Duration; import static dev.failsafe.testing.Asserts.assertThrows; import static org.testng.Assert.*; @Test public class CircuitBreakerBuilderTest { public void shouldCreateBuilderFromExistingConfig() { CircuitBreakerConfig initialConfig = CircuitBreaker.builder() .withDelay(Duration.ofMillis(55)) .withFailureThreshold(10, 15) .withSuccessThreshold(20, 30) .onClose(e -> { }).config; CircuitBreakerConfig newConfig = CircuitBreaker.builder(initialConfig).config; assertEquals(newConfig.delay, Duration.ofMillis(55)); assertEquals(newConfig.failureThreshold, 10); assertEquals(newConfig.failureThresholdingCapacity, 15); assertEquals(newConfig.successThreshold, 20); assertEquals(newConfig.successThresholdingCapacity, 30); assertNotNull(newConfig.closeListener); } public void shouldRequireValidDelay() { assertThrows(() -> CircuitBreaker.builder().withDelay(null), NullPointerException.class); assertThrows(() -> CircuitBreaker.builder().withDelay(Duration.ofMillis(-1)), IllegalArgumentException.class); } public void shouldRequireValidFailureThreshold() { assertThrows(() -> CircuitBreaker.builder().withFailureThreshold(0), IllegalArgumentException.class); } public void shouldRequireValidFailureThresholdRatio() { assertThrows(() -> CircuitBreaker.builder().withFailureThreshold(0, 2), IllegalArgumentException.class); assertThrows(() -> CircuitBreaker.builder().withFailureThreshold(2, 0), IllegalArgumentException.class); assertThrows(() -> CircuitBreaker.builder().withFailureThreshold(2, 1), IllegalArgumentException.class); } public void shouldRequireValidSuccessThreshold() { assertThrows(() -> CircuitBreaker.builder().withSuccessThreshold(0).build(), IllegalArgumentException.class); } public void shouldRequireValidSuccessThresholdRatio() { assertThrows(() -> CircuitBreaker.builder().withSuccessThreshold(0, 2), IllegalArgumentException.class); assertThrows(() -> CircuitBreaker.builder().withSuccessThreshold(2, 0), IllegalArgumentException.class); assertThrows(() -> CircuitBreaker.builder().withSuccessThreshold(2, 1), IllegalArgumentException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/CircuitBreakerTest.java000066400000000000000000000044001444561050700307040ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class CircuitBreakerTest { public void shouldDefaultDelay() throws Throwable { CircuitBreaker breaker = CircuitBreaker.ofDefaults(); breaker.recordFailure(); Thread.sleep(100); assertTrue(breaker.isOpen()); } public void shouldGetSuccessAndFailureStats() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(5, 10) .withSuccessThreshold(15, 20) .build(); // When for (int i = 0; i < 7; i++) if (i % 2 == 0) breaker.recordSuccess(); else breaker.recordFailure(); // Then assertEquals(breaker.getFailureCount(), 3); assertEquals(breaker.getFailureRate(), 43); assertEquals(breaker.getSuccessCount(), 4); assertEquals(breaker.getSuccessRate(), 57); // When for (int i = 0; i < 15; i++) if (i % 4 == 0) breaker.recordFailure(); else breaker.recordSuccess(); // Then assertEquals(breaker.getFailureCount(), 2); assertEquals(breaker.getFailureRate(), 20); assertEquals(breaker.getSuccessCount(), 8); assertEquals(breaker.getSuccessRate(), 80); // When breaker.halfOpen(); for (int i = 0; i < 15; i++) if (i % 3 == 0) breaker.recordFailure(); else breaker.recordSuccess(); // Then assertEquals(breaker.getFailureCount(), 5); assertEquals(breaker.getFailureRate(), 33); assertEquals(breaker.getSuccessCount(), 10); assertEquals(breaker.getSuccessRate(), 67); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/DelayablePolicyTest.java000066400000000000000000000056231444561050700310600ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.ContextualSupplier; import dev.failsafe.spi.ExecutionResult; import dev.failsafe.testing.Mocking.FooPolicy; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; @Test public class DelayablePolicyTest { ContextualSupplier delay5Millis = ctx -> Duration.ofMillis(5); @Test(expectedExceptions = NullPointerException.class) public void testNullDelayFunction() { FooPolicy.builder().withDelay(null); } @Test(expectedExceptions = NullPointerException.class) public void testNullResult() { FooPolicy.builder().withDelayFnWhen(delay5Millis, null); } @Test(expectedExceptions = NullPointerException.class) public void testNullFailureType() { FooPolicy.builder().withDelayFnOn(delay5Millis, null); } public void shouldComputeDelay() { Duration expected = Duration.ofMillis(5); FooPolicy policy = FooPolicy.builder().withDelayFn(ctx -> expected).build(); assertEquals(policy.computeDelay(execOfResult(null)), expected); } public void shouldComputeDelayForResultValue() { Duration expected = Duration.ofMillis(5); FooPolicy policy = FooPolicy.builder().withDelayFnWhen(delay5Millis, true).build(); assertEquals(policy.computeDelay(execOfResult(true)), expected); assertNull(policy.computeDelay(execOfResult(false))); } public void shouldComputeDelayForNegativeValue() { FooPolicy policy = FooPolicy.builder().withDelayFn(ctx -> Duration.ofMillis(-1)).build(); assertNull(policy.computeDelay(execOfResult(true))); } public void shouldComputeDelayForFailureType() { Duration expected = Duration.ofMillis(5); FooPolicy policy = FooPolicy.builder().withDelayFnOn(delay5Millis, IllegalStateException.class).build(); assertEquals(policy.computeDelay(execOfFailure(new IllegalStateException())), expected); assertNull(policy.computeDelay(execOfFailure(new IllegalArgumentException()))); } static ExecutionContext execOfResult(R result) { return new ExecutionImpl<>(ExecutionResult.success(result)); } static ExecutionContext execOfFailure(Throwable failure) { return new ExecutionImpl<>(ExecutionResult.exception(failure)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/ExecutionTest.java000066400000000000000000000252461444561050700277640ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import java.net.ConnectException; import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; import static dev.failsafe.testing.Testing.failures; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.*; /** * @author Jonathan Halterman */ @Test public class ExecutionTest { ConnectException e = new ConnectException(); public void testRetryForResult() { // Given rpRetry for null Execution exec = Execution.of(RetryPolicy.builder().handleResult(null).build()); // When / Then exec.recordResult(null); assertFalse(exec.isComplete()); exec.recordResult(null); assertFalse(exec.isComplete()); exec.recordResult(1); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 3); assertEquals(exec.getExecutionCount(), 3); assertTrue(exec.isComplete()); assertEquals(exec.getLastResult(), 1); assertNull(exec.getLastException()); // Given 2 max retries exec = Execution.of(RetryPolicy.builder().handleResult(null).build()); // When / Then exec.recordResult(null); assertFalse(exec.isComplete()); exec.recordResult(null); assertFalse(exec.isComplete()); exec.recordResult(null); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 3); assertEquals(exec.getExecutionCount(), 3); assertTrue(exec.isComplete()); assertNull(exec.getLastResult()); assertNull(exec.getLastException()); } public void testRetryForThrowable() { // Given rpRetry on IllegalArgumentException Execution exec = Execution.of(RetryPolicy.builder().handle(IllegalArgumentException.class).build()); // When / Then exec.recordException(new IllegalArgumentException()); assertFalse(exec.isComplete()); exec.recordException(e); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 2); assertEquals(exec.getExecutionCount(), 2); assertTrue(exec.isComplete()); assertNull(exec.getLastResult()); assertEquals(exec.getLastException(), e); // Given 2 max retries exec = Execution.of(RetryPolicy.ofDefaults()); // When / Then exec.recordException(e); assertFalse(exec.isComplete()); exec.recordException(e); assertFalse(exec.isComplete()); exec.recordException(e); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 3); assertEquals(exec.getExecutionCount(), 3); assertTrue(exec.isComplete()); assertNull(exec.getLastResult()); assertEquals(exec.getLastException(), e); } public void testRetryForResultAndThrowable() { // Given rpRetry for null Execution exec = Execution.of(RetryPolicy.builder().withMaxAttempts(10).handleResult(null).build()); // When / Then exec.recordResult(null); assertFalse(exec.isComplete()); exec.record(null, null); assertFalse(exec.isComplete()); exec.record(1, new IllegalArgumentException()); assertFalse(exec.isComplete()); exec.record(1, null); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 4); assertEquals(exec.getExecutionCount(), 4); assertTrue(exec.isComplete()); // Given 2 max retries exec = Execution.of(RetryPolicy.builder().handleResult(null).build()); // When / Then exec.recordResult(null); assertFalse(exec.isComplete()); exec.record(null, e); assertFalse(exec.isComplete()); exec.record(null, e); assertTrue(exec.isComplete()); // Then assertEquals(exec.getAttemptCount(), 3); assertEquals(exec.getExecutionCount(), 3); assertTrue(exec.isComplete()); } public void testGetAttemptCount() { Execution exec = Execution.of(RetryPolicy.ofDefaults()); exec.recordException(e); exec.recordException(e); assertEquals(exec.getAttemptCount(), 2); assertEquals(exec.getExecutionCount(), 2); } public void testGetElapsedMillis() throws Throwable { Execution exec = Execution.of(RetryPolicy.ofDefaults()); assertTrue(exec.getElapsedTime().toMillis() < 100); Thread.sleep(150); assertTrue(exec.getElapsedTime().toMillis() > 100); } @SuppressWarnings("unchecked") public void testIsComplete() { List list = mock(List.class); when(list.size()).thenThrow(failures(2, new IllegalStateException())).thenReturn(5); RetryPolicy retryPolicy = RetryPolicy.builder().handle(IllegalStateException.class).build(); Execution exec = Execution.of(retryPolicy); while (!exec.isComplete()) { try { exec.recordResult(list.size()); } catch (IllegalStateException e) { exec.recordException(e); } } assertEquals(exec.getLastResult(), 5); assertEquals(exec.getAttemptCount(), 3); assertEquals(exec.getExecutionCount(), 3); } public void shouldAdjustDelayForBackoff() { Execution exec = Execution.of( RetryPolicy.builder().withMaxAttempts(10).withBackoff(Duration.ofNanos(1), Duration.ofNanos(10)).build()); assertEquals(exec.getDelay().toNanos(), 0); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 1); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 2); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 4); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 8); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 10); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 10); } public void shouldAdjustDelayForComputedDelay() { Execution exec = Execution.of(RetryPolicy.builder() .withMaxAttempts(10) .withDelayFn(ctx -> Duration.ofNanos(ctx.getAttemptCount() * 2)) .build()); assertEquals(exec.getDelay().toNanos(), 0); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 2); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 4); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 6); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 8); } public void shouldFallbackDelayFromComputedToFixedDelay() { Execution exec = Execution.of(RetryPolicy.builder() .withMaxAttempts(10) .withDelay(Duration.ofNanos(5)) .withDelayFn(ctx -> Duration.ofNanos(ctx.getAttemptCount() % 2 == 0 ? ctx.getAttemptCount() * 2 : -1)) .build()); assertEquals(exec.getDelay().toNanos(), 0); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 5); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 4); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 5); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 8); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 5); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 12); } public void shouldFallbackDelayFromComputedToBackoffDelay() { Execution exec = Execution.of(RetryPolicy.builder() .withMaxAttempts(10) .withBackoff(Duration.ofNanos(1), Duration.ofNanos(10)) .withDelayFn(ctx -> Duration.ofNanos(ctx.getAttemptCount() % 2 == 0 ? ctx.getAttemptCount() * 2 : -1)) .build()); assertEquals(exec.getDelay().toNanos(), 0); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 1); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 4); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 2); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 8); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 4); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 12); exec.recordException(e); assertEquals(exec.getDelay().toNanos(), 8); } public void shouldAdjustDelayForMaxDuration() throws Throwable { Execution exec = Execution.of( RetryPolicy.builder().withDelay(Duration.ofMillis(49)).withMaxDuration(Duration.ofMillis(50)).build()); Thread.sleep(10); exec.recordException(e); assertFalse(exec.isComplete()); assertTrue(exec.getDelay().toNanos() < TimeUnit.MILLISECONDS.toNanos(50) && exec.getDelay().toNanos() > 0); } public void shouldSupportMaxDuration() throws Exception { Execution exec = Execution.of(RetryPolicy.builder().withMaxDuration(Duration.ofMillis(100)).build()); exec.recordException(e); assertFalse(exec.isComplete()); exec.recordException(e); assertFalse(exec.isComplete()); Thread.sleep(105); exec.recordException(e); assertTrue(exec.isComplete()); } public void shouldSupportMaxRetries() { Execution exec = Execution.of(RetryPolicy.builder().withMaxRetries(3).build()); exec.recordException(e); assertFalse(exec.isComplete()); exec.recordException(e); assertFalse(exec.isComplete()); exec.recordException(e); assertFalse(exec.isComplete()); exec.recordException(e); assertTrue(exec.isComplete()); } public void shouldGetDelayMillis() throws Throwable { Execution exec = Execution.of(RetryPolicy.builder() .withDelay(Duration.ofMillis(100)) .withMaxDuration(Duration.ofMillis(101)) .handleResult(null) .build()); assertEquals(exec.getDelay().toMillis(), 0); exec.recordResult(null); assertTrue(exec.getDelay().toMillis() <= 100); Thread.sleep(150); exec.recordResult(null); assertTrue(exec.isComplete()); assertEquals(exec.getDelay().toMillis(), 0); } @Test(expectedExceptions = IllegalStateException.class) public void shouldThrowOnMultipleCompletes() { Execution exec = Execution.of(RetryPolicy.ofDefaults()); exec.complete(); exec.complete(); } @Test(expectedExceptions = IllegalStateException.class) public void shouldThrowOnCanRetryWhenAlreadyComplete() { Execution exec = Execution.of(RetryPolicy.ofDefaults()); exec.complete(); exec.recordException(e); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/FailsafeFutureTest.java000066400000000000000000000062431444561050700307220ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import net.jodah.concurrentunit.Waiter; import dev.failsafe.testing.Asserts; import org.testng.annotations.AfterClass; import org.testng.annotations.Test; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static org.testng.Assert.*; @Test public class FailsafeFutureTest { ExecutorService executor = Executors.newFixedThreadPool(2); @AfterClass protected void afterClass() { executor.shutdownNow(); } /** * Asserts that retries are stopped and completion handlers are called on cancel. */ public void shouldCallOnCompleteWhenCancelled() throws Throwable { Waiter waiter = new Waiter(); CompletableFuture future = Failsafe.with(RetryPolicy.ofDefaults()).with(executor).onComplete(e -> { waiter.assertNull(e.getResult()); waiter.assertTrue(e.getException() instanceof CancellationException); waiter.resume(); }).getAsync(() -> { Thread.sleep(1000); throw new IllegalStateException(); }); // Note: We have to add whenComplete to the returned future separately, otherwise cancel will not be noticed by // Failsafe future.whenComplete((result, failure) -> { waiter.assertNull(result); waiter.assertTrue(failure instanceof CancellationException); waiter.resume(); }); future.cancel(true); waiter.await(1000, 2); future.complete("unexpected2"); Asserts.assertThrows(future::get, CancellationException.class); } /** * Asserts that a completed future ignores subsequent completion attempts. */ public void shouldNotCancelCompletedFuture() throws Throwable { // Given CompletableFuture future = Failsafe.with(RetryPolicy.ofDefaults()).with(executor).getAsync(() -> "test"); // When Thread.sleep(200); assertFalse(future.isCancelled()); assertTrue(future.isDone()); assertFalse(future.cancel(true)); // Then assertFalse(future.isCancelled()); assertTrue(future.isDone()); assertEquals(future.get(), "test"); } /** * Asserts that a cancelled future ignores subsequent completion attempts. */ public void shouldNotCompleteCancelledFuture() { CompletableFuture future = Failsafe.with(RetryPolicy.ofDefaults()).with(executor).getAsync(() -> { Thread.sleep(1000); throw new IllegalStateException(); }); future.cancel(true); future.complete("unexpected"); Asserts.assertThrows(future::get, CancellationException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/FailsafeTest.java000066400000000000000000000032521444561050700275240ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.util.concurrent.TimeoutException; import static org.testng.Assert.assertEquals; /** * Tests general Failsafe behaviors. */ @Test public class FailsafeTest extends Testing { /** * Asserts that errors can be reported through Failsafe. */ public void shouldSupportErrors() { // Given RetryPolicy retryPolicy = RetryPolicy.ofDefaults(); // When / Then testRunFailure(Failsafe.with(retryPolicy), ctx -> { throw new InternalError(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); }, InternalError.class); } /** * Asserts that checked exeptions are wrapped with FailsafeException for sync executions. */ public void shouldWrapCheckedExceptionsForSyncExecutions() { RetryPolicy retryPolicy = RetryPolicy.builder().withMaxRetries(0).build(); assertThrows(() -> Failsafe.with(retryPolicy).run(() -> { throw new TimeoutException(); }), FailsafeException.class, TimeoutException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/FailurePolicyBuilderTest.java000066400000000000000000000024321444561050700320670ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.function.CheckedBiPredicate; import org.testng.annotations.Test; import static dev.failsafe.testing.Testing.unwrapExceptions; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; @Test public class FailurePolicyBuilderTest { public void testResultPredicateOnlyHandlesResults() { CheckedBiPredicate resultPredicate = FailurePolicyBuilder.resultPredicateFor(result -> true); assertEquals(unwrapExceptions(() -> resultPredicate.test("result", null)), Boolean.TRUE); assertNotEquals(unwrapExceptions(() -> resultPredicate.test(null, new RuntimeException())), Boolean.TRUE); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/FailurePolicyTest.java000066400000000000000000000072121444561050700305610ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.testing.Mocking.FooPolicy; import org.testng.annotations.Test; import java.io.IOException; import java.net.ConnectException; import java.util.Arrays; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class FailurePolicyTest { public void testIsFailureForNull() { FooPolicy policy = FooPolicy.builder().build(); assertFalse(policy.isFailure(null, null)); } public void testIsFailureForFailurePredicate() { FooPolicy policy = FooPolicy.builder().handleIf(failure -> failure instanceof ConnectException).build(); assertTrue(policy.isFailure(null, new ConnectException())); assertFalse(policy.isFailure(null, new IllegalStateException())); } public void testIsFailureForResultPredicate() { FooPolicy policy = FooPolicy.builder().handleResultIf(result -> result > 100).build(); assertTrue(policy.isFailure(110, null)); assertFalse(policy.isFailure(50, null)); } public void testIsFailureCompletionPredicate() { FooPolicy policy = FooPolicy.builder() .handleIf((result, failure) -> result == "test" || failure instanceof IllegalArgumentException) .build(); assertTrue(policy.isFailure("test", null)); // No retries needed for successful result assertFalse(policy.isFailure(0, null)); assertTrue(policy.isFailure(null, new IllegalArgumentException())); assertFalse(policy.isFailure(null, new IllegalStateException())); } public void testIgnoresThrowingPredicate() { FooPolicy policy = FooPolicy.builder().handleIf((result, failure) -> { throw new NullPointerException(); }).build(); assertFalse(policy.isFailure(1, null)); } public void testIsFailureForFailure() { FooPolicy policy = FooPolicy.builder().build(); assertTrue(policy.isFailure(null, new Exception())); assertTrue(policy.isFailure(null, new IllegalArgumentException())); policy = FooPolicy.builder().handle(Exception.class).build(); assertTrue(policy.isFailure(null, new Exception())); assertTrue(policy.isFailure(null, new IllegalArgumentException())); policy = FooPolicy.builder().handle(IllegalArgumentException.class, IOException.class).build(); assertTrue(policy.isFailure(null, new IllegalArgumentException())); assertTrue(policy.isFailure(null, new IOException())); assertFalse(policy.isFailure(null, new RuntimeException())); assertFalse(policy.isFailure(null, new IllegalStateException())); policy = FooPolicy.builder().handle(Arrays.asList(IllegalArgumentException.class)).build(); assertTrue(policy.isFailure(null, new IllegalArgumentException())); assertFalse(policy.isFailure(null, new RuntimeException())); assertFalse(policy.isFailure(null, new IllegalStateException())); } public void testIsFailureForResult() { FooPolicy policy = FooPolicy.builder().handleResult(10).build(); assertTrue(policy.isFailure(10, null)); assertFalse(policy.isFailure(5, null)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/FallbackBuilderTest.java000066400000000000000000000022651444561050700310230ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.*; @Test public class FallbackBuilderTest { public void shouldCreateBuilderFromExistingConfig() throws Throwable { FallbackConfig initialConfig = Fallback.builder(10).withAsync().onFailedAttempt(e -> { }).config; FallbackConfig newConfig = Fallback.builder(initialConfig).config; assertEquals(newConfig.fallback.apply(null), Integer.valueOf(10)); assertTrue(newConfig.async); assertNotNull(newConfig.failedAttemptListener); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/ListenersTest.java000066400000000000000000000446471444561050700277770ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.event.EventListener; import net.jodah.concurrentunit.Waiter; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.function.CheckedSupplier; import dev.failsafe.testing.Asserts; import dev.failsafe.testing.Testing; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Mockito.*; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; /** * Tests event listener capabilities of FailsafeExecutor and Policy implementations. */ @Test public class ListenersTest extends Testing { private Server server = mock(Server.class); CheckedSupplier supplier = () -> server.connect(); Waiter waiter; // RetryPolicy listener counters ListenerCounter rpAbort = new ListenerCounter(); ListenerCounter rpFailedAttempt = new ListenerCounter(); ListenerCounter rpRetriesExceeded = new ListenerCounter(); ListenerCounter rpScheduled = new ListenerCounter(); ListenerCounter rpRetry = new ListenerCounter(); ListenerCounter rpSuccess = new ListenerCounter(); ListenerCounter rpFailure = new ListenerCounter(); // CircuitBreaker listener counters ListenerCounter cbOpen = new ListenerCounter(); ListenerCounter cbHalfOpen = new ListenerCounter(); ListenerCounter cbClose = new ListenerCounter(); ListenerCounter cbSuccess = new ListenerCounter(); ListenerCounter cbFailure = new ListenerCounter(); // Fallback listener counters ListenerCounter fbFailedAttempt = new ListenerCounter(); ListenerCounter fbSuccess = new ListenerCounter(); ListenerCounter fbFailure = new ListenerCounter(); // Executor listener counters ListenerCounter complete = new ListenerCounter(); ListenerCounter success = new ListenerCounter(); ListenerCounter failure = new ListenerCounter(); static class ListenerCounter { /** Per listener invocations */ AtomicInteger invocations = new AtomicInteger(); /** Records an invocation of the {@code listener}. */ void record() { invocations.incrementAndGet(); } /** Waits for the expected async invocations and asserts the expected {@code expectedInvocations}. */ void assertEquals(int expectedInvocations) { Assert.assertEquals(invocations.get(), expectedInvocations); } void reset() { invocations.set(0); } } @BeforeMethod void beforeMethod() { reset(server); waiter = new Waiter(); rpAbort.reset(); rpFailedAttempt.reset(); rpRetriesExceeded.reset(); rpSuccess.reset(); rpScheduled.reset(); rpRetry.reset(); rpSuccess.reset(); rpFailure.reset(); cbOpen.reset(); cbHalfOpen.reset(); cbClose.reset(); cbSuccess.reset(); cbFailure.reset(); fbFailedAttempt.reset(); fbSuccess.reset(); fbFailure.reset(); complete.reset(); success.reset(); failure.reset(); } private FailsafeExecutor registerListeners(RetryPolicyBuilder rpBuilder, CircuitBreakerBuilder cbBuilder, FallbackBuilder fbBuilder) { rpBuilder.onAbort(e -> rpAbort.record()); rpBuilder.onFailedAttempt(e -> rpFailedAttempt.record()); rpBuilder.onRetriesExceeded(e -> rpRetriesExceeded.record()); rpBuilder.onRetryScheduled(e -> rpScheduled.record()); rpBuilder.onRetry(e -> rpRetry.record()); rpBuilder.onSuccess(e -> rpSuccess.record()); rpBuilder.onFailure(e -> rpFailure.record()); cbBuilder.onOpen(e -> cbOpen.record()); cbBuilder.onHalfOpen(e -> cbHalfOpen.record()); cbBuilder.onClose(e -> cbClose.record()); cbBuilder.onSuccess(e -> cbSuccess.record()); cbBuilder.onFailure(e -> cbFailure.record()); if (fbBuilder != null) { fbBuilder.onFailedAttempt(e -> fbFailedAttempt.record()); fbBuilder.onSuccess(e -> fbSuccess.record()); fbBuilder.onFailure(e -> fbFailure.record()); } FailsafeExecutor failsafe = fbBuilder == null ? Failsafe.with(rpBuilder.build(), cbBuilder.build()) : Failsafe.with(fbBuilder.build(), rpBuilder.build(), cbBuilder.build()); failsafe.onComplete(e -> { complete.record(); waiter.resume(); }); failsafe.onSuccess(e -> success.record()); failsafe.onFailure(e -> failure.record()); return failsafe; } /** * Asserts that listeners are called the expected number of times for a successful completion. */ private void assertForSuccess(boolean sync) throws Throwable { // Given - Fail 4 times then succeed when(server.connect()).thenThrow(failures(2, new IllegalStateException())).thenReturn(false, false, true); RetryPolicyBuilder rpBuilder = RetryPolicy.builder().withMaxAttempts(10).handleResult(false); CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder() .handleResult(false) .withDelay(Duration.ZERO); FallbackBuilder fbBuilder = Fallback.builder(true); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, fbBuilder); // When if (sync) failsafe.get(supplier); else failsafe.getAsync(supplier).get(); // Then waiter.await(1000); rpAbort.assertEquals(0); rpFailedAttempt.assertEquals(4); rpRetriesExceeded.assertEquals(0); rpScheduled.assertEquals(4); rpRetry.assertEquals(4); rpSuccess.assertEquals(1); rpFailure.assertEquals(0); cbOpen.assertEquals(4); cbHalfOpen.assertEquals(4); cbClose.assertEquals(1); cbSuccess.assertEquals(1); cbFailure.assertEquals(4); fbFailedAttempt.assertEquals(0); fbSuccess.assertEquals(1); fbFailure.assertEquals(0); complete.assertEquals(1); success.assertEquals(1); failure.assertEquals(0); } public void testForSuccessSync() throws Throwable { assertForSuccess(true); } public void testForSuccessAsync() throws Throwable { assertForSuccess(false); } /** * Asserts that listeners are called the expected number of times for an unhandled failure. */ private void assertForUnhandledFailure(boolean sync) throws Throwable { // Given - Fail 2 times then don't match policy when(server.connect()).thenThrow(failures(2, new IllegalStateException())) .thenThrow(IllegalArgumentException.class); RetryPolicyBuilder rpBuilder = RetryPolicy.builder().handle(IllegalStateException.class); CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder().withDelay(Duration.ZERO); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, null); // When if (sync) Asserts.assertThrows(() -> failsafe.get(supplier), IllegalArgumentException.class); else Asserts.assertThrows(() -> failsafe.getAsync(supplier).get(), ExecutionException.class, IllegalArgumentException.class); // Then waiter.await(1000); rpAbort.assertEquals(0); rpFailedAttempt.assertEquals(2); rpRetriesExceeded.assertEquals(0); rpScheduled.assertEquals(2); rpRetry.assertEquals(2); rpSuccess.assertEquals(1); rpFailure.assertEquals(0); cbOpen.assertEquals(3); cbHalfOpen.assertEquals(2); cbClose.assertEquals(0); cbSuccess.assertEquals(0); cbFailure.assertEquals(3); complete.assertEquals(1); failure.assertEquals(1); success.assertEquals(0); } public void testForUnhandledFailureSync() throws Throwable { assertForUnhandledFailure(true); } public void testForUnhandledFailureAsync() throws Throwable { assertForUnhandledFailure(false); } /** * Asserts that listeners are called the expected number of times when retries are exceeded. */ private void assertForRetriesExceeded(boolean sync) throws Throwable { // Given - Fail 4 times and exceed retries when(server.connect()).thenThrow(failures(10, new IllegalStateException())); RetryPolicyBuilder rpBuilder = RetryPolicy.builder() .abortOn(IllegalArgumentException.class) .withMaxRetries(3); CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder().withDelay(Duration.ZERO); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, null); // When if (sync) Asserts.assertThrows(() -> failsafe.get(supplier), IllegalStateException.class); else Asserts.assertThrows(() -> failsafe.getAsync(supplier).get(), ExecutionException.class, IllegalStateException.class); // Then waiter.await(1000); rpAbort.assertEquals(0); rpFailedAttempt.assertEquals(4); rpRetriesExceeded.assertEquals(1); rpScheduled.assertEquals(3); rpRetry.assertEquals(3); rpSuccess.assertEquals(0); rpFailure.assertEquals(1); cbOpen.assertEquals(4); cbHalfOpen.assertEquals(3); cbClose.assertEquals(0); cbSuccess.assertEquals(0); cbFailure.assertEquals(4); complete.assertEquals(1); success.assertEquals(0); failure.assertEquals(1); } public void testForRetriesExceededSync() throws Throwable { assertForRetriesExceeded(true); } public void testForRetriesExceededAsync() throws Throwable { assertForRetriesExceeded(false); } /** * Asserts that listeners are called the expected number of times for an aborted execution. */ private void assertForAbort(boolean sync) throws Throwable { // Given - Fail twice then abort when(server.connect()).thenThrow(failures(3, new IllegalStateException())) .thenThrow(new IllegalArgumentException()); RetryPolicyBuilder rpBuilder = RetryPolicy.builder() .abortOn(IllegalArgumentException.class) .withMaxRetries(3); CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder().withDelay(Duration.ZERO); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, null); // When if (sync) Asserts.assertThrows(() -> failsafe.get(supplier), IllegalArgumentException.class); else Asserts.assertThrows(() -> failsafe.getAsync(supplier).get(), ExecutionException.class, IllegalArgumentException.class); // Then waiter.await(1000); rpAbort.assertEquals(1); rpFailedAttempt.assertEquals(4); rpRetriesExceeded.assertEquals(0); rpScheduled.assertEquals(3); rpRetry.assertEquals(3); rpSuccess.assertEquals(0); rpFailure.assertEquals(1); cbOpen.assertEquals(4); cbHalfOpen.assertEquals(3); cbClose.assertEquals(0); cbSuccess.assertEquals(0); cbFailure.assertEquals(4); complete.assertEquals(1); success.assertEquals(0); failure.assertEquals(1); } public void testForAbortSync() throws Throwable { assertForAbort(true); } public void testForAbortAsync() throws Throwable { assertForAbort(false); } private void assertForFailingRetryPolicy(boolean sync) throws Throwable { when(server.connect()).thenThrow(failures(10, new IllegalStateException())); // Given failing RetryPolicy RetryPolicyBuilder rpBuilder = RetryPolicy.builder(); // And successful CircuitBreaker and Fallback CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder() .handle(NullPointerException.class) .withDelay(Duration.ZERO); FallbackBuilder fbBuilder = Fallback.builder(() -> true).handle(NullPointerException.class); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, fbBuilder); // When if (sync) Testing.ignoreExceptions(() -> failsafe.get(supplier)); else Testing.ignoreExceptions(() -> failsafe.getAsync(supplier)); // Then waiter.await(1000); rpSuccess.assertEquals(0); rpFailure.assertEquals(1); cbSuccess.assertEquals(3); cbFailure.assertEquals(0); fbFailedAttempt.assertEquals(0); fbSuccess.assertEquals(1); fbFailure.assertEquals(0); complete.assertEquals(1); success.assertEquals(0); failure.assertEquals(1); } public void testFailingRetryPolicySync() throws Throwable { assertForFailingRetryPolicy(true); } public void testFailingRetryPolicyAsync() throws Throwable { assertForFailingRetryPolicy(false); } private void assertForFailingCircuitBreaker(boolean sync) throws Throwable { when(server.connect()).thenThrow(failures(10, new IllegalStateException())); // Given successful RetryPolicy RetryPolicyBuilder rpBuilder = RetryPolicy.builder().handle(NullPointerException.class); // And failing CircuitBreaker CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder().withDelay(Duration.ZERO); // And successful Fallback FallbackBuilder fbBuilder = Fallback.builder(() -> true) .handle(NullPointerException.class) .withAsync(); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, fbBuilder); // When if (sync) Testing.ignoreExceptions(() -> failsafe.get(supplier)); else Testing.ignoreExceptions(() -> failsafe.getAsync(supplier)); // Then waiter.await(1000); rpSuccess.assertEquals(1); rpFailure.assertEquals(0); cbSuccess.assertEquals(0); cbFailure.assertEquals(1); fbFailedAttempt.assertEquals(0); fbSuccess.assertEquals(1); fbFailure.assertEquals(0); complete.assertEquals(1); success.assertEquals(0); failure.assertEquals(1); } public void testFailingCircuitBreakerSync() throws Throwable { assertForFailingCircuitBreaker(true); } public void testFailingCircuitBreakerAsync() throws Throwable { assertForFailingCircuitBreaker(false); } private void assertForFailingFallback(boolean sync) throws Throwable { when(server.connect()).thenThrow(failures(10, new IllegalStateException())); // Given successful RetryPolicy and CircuitBreaker RetryPolicyBuilder rpBuilder = RetryPolicy.builder().handle(NullPointerException.class); CircuitBreakerBuilder cbBuilder = CircuitBreaker.builder() .withDelay(Duration.ZERO) .handle(NullPointerException.class); // And failing Fallback FallbackBuilder fbBuilder = Fallback.builder(() -> { throw new Exception(); }).withAsync(); FailsafeExecutor failsafe = registerListeners(rpBuilder, cbBuilder, fbBuilder); // When if (sync) Testing.ignoreExceptions(() -> failsafe.get(supplier)); else Testing.ignoreExceptions(() -> failsafe.getAsync(supplier)); // Then waiter.await(1000); rpSuccess.assertEquals(1); rpFailure.assertEquals(0); cbSuccess.assertEquals(1); cbFailure.assertEquals(0); fbFailedAttempt.assertEquals(1); fbSuccess.assertEquals(0); fbFailure.assertEquals(1); complete.assertEquals(1); success.assertEquals(0); failure.assertEquals(1); } public void testFailingFallbackSync() throws Throwable { assertForFailingFallback(true); } public void testFailingFallbackAsync() throws Throwable { assertForFailingFallback(false); } public void shouldGetElapsedAttemptTime() { RetryPolicy retryPolicy = RetryPolicy.builder() .withMaxAttempts(3) .handleResult(false) .onRetry(e -> assertTrue(e.getElapsedAttemptTime().toMillis() >= 90)) .build(); Failsafe.with(retryPolicy).get(() -> { Thread.sleep(100); return false; }); } /** * Asserts that Failsafe does not block when an error occurs in an event listener. */ public void shouldIgnoreExceptionsInListeners() { // Given EventListener> attemptedError = e -> { throw new AssertionError(); }; EventListener> completedError = e -> { throw new AssertionError(); }; CheckedSupplier noop = () -> null; RetryPolicy rp; // onFailedAttempt rp = RetryPolicy.builder().handleResult(null).withMaxRetries(0).onFailedAttempt(attemptedError).build(); Failsafe.with(rp).get(noop); // RetryPolicy.onRetry rp = RetryPolicy.builder().handleResult(null).withMaxRetries(1).onRetry(attemptedError).build(); Failsafe.with(rp).get(noop); // RetryPolicy.onAbort rp = RetryPolicy.builder().handleResult(null).abortWhen(null).onAbort(completedError).build(); Failsafe.with(rp).get(noop); // RetryPolicy.onRetriesExceeded rp = RetryPolicy.builder().handleResult(null).withMaxRetries(0).onRetriesExceeded(completedError).build(); Failsafe.with(rp).get(noop); // RetryPolicy.onFailure rp = RetryPolicy.builder().handleResult(null).withMaxRetries(0).onFailure(completedError).build(); Failsafe.with(rp).get(noop); // Failsafe.onComplete rp = RetryPolicy.builder().handleResult(null).withMaxRetries(0).build(); Failsafe.with(rp).onComplete(completedError).get(noop); } public void testRetryPolicyOnScheduled() { Recorder recorder = new Recorder(); AtomicInteger executions = new AtomicInteger(); RetryPolicy rp = RetryPolicy.builder().handleResult(null).withMaxRetries(1).onFailedAttempt(e -> { if (executions.get() == 1) { recorder.assertTrue(e.isFirstAttempt()); recorder.assertFalse(e.isRetry()); } else { recorder.assertFalse(e.isFirstAttempt()); recorder.assertTrue(e.isRetry()); } }).onRetry(e -> { recorder.assertFalse(e.isFirstAttempt()); recorder.assertTrue(e.isRetry()); }).onRetryScheduled(e -> { if (executions.get() == 1) { recorder.assertTrue(e.isFirstAttempt()); recorder.assertFalse(e.isRetry()); } else { recorder.assertFalse(e.isFirstAttempt()); recorder.assertTrue(e.isRetry()); } }).onFailure(e -> { recorder.assertFalse(e.isFirstAttempt()); recorder.assertTrue(e.isRetry()); }).build(); Failsafe.with(rp).get(() -> { executions.incrementAndGet(); return null; }); recorder.throwFailures(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/RateLimiterBuilderTest.java000066400000000000000000000035751444561050700315520ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; @Test public class RateLimiterBuilderTest { public void shouldCreateBuilderFromExistingConfig() { RateLimiterConfig initialConfig = RateLimiter.smoothBuilder(Duration.ofMillis(10)) .withMaxWaitTime(Duration.ofSeconds(10)) .onSuccess(e -> { }).config; RateLimiterConfig newConfig = RateLimiter.builder(initialConfig).config; assertEquals(newConfig.maxRate, Duration.ofMillis(10)); assertEquals(newConfig.maxWaitTime, Duration.ofSeconds(10)); assertNotNull(newConfig.successListener); } /** * Asserts that the smooth rate limiter factory methods are equal. */ public void shouldBuildEqualSmoothLimiters() { Duration maxRate1 = RateLimiter.smoothBuilder(100, Duration.ofSeconds(1)).config.getMaxRate(); Duration maxRate2 = RateLimiter.smoothBuilder(Duration.ofMillis(10)).config.getMaxRate(); assertEquals(maxRate1, maxRate2); maxRate1 = RateLimiter.smoothBuilder(20, Duration.ofMillis(300)).config.getMaxRate(); maxRate2 = RateLimiter.smoothBuilder(Duration.ofMillis(15)).config.getMaxRate(); assertEquals(maxRate1, maxRate2); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/RetryPolicyBuilderTest.java000066400000000000000000000160611444561050700316100ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import dev.failsafe.testing.Asserts; import org.testng.annotations.Test; import java.time.Duration; import java.time.temporal.ChronoUnit; import static org.testng.Assert.*; @Test public class RetryPolicyBuilderTest extends Asserts { public void shouldRequireValidDelay() { assertThrows(() -> RetryPolicy.builder().withDelay(null), NullPointerException.class); assertThrows(() -> RetryPolicy.builder().withMaxDuration(Duration.ofMillis(1)).withDelay(Duration.ofMillis(100)), IllegalStateException.class); assertThrows(() -> RetryPolicy.builder().withDelay(Duration.ofMillis(-1)), IllegalArgumentException.class); assertThrows(() -> RetryPolicy.builder().withJitter(Duration.ofMillis(7)).withDelay(Duration.ofMillis(5)), IllegalStateException.class); } public void shouldRequireValidBackoff() { assertThrows(() -> RetryPolicy.builder().withBackoff(0, 0, null), NullPointerException.class); assertThrows(() -> RetryPolicy.builder().withBackoff(-3, 10, ChronoUnit.MILLIS), IllegalArgumentException.class); assertThrows(() -> RetryPolicy.builder().withBackoff(100, 10, ChronoUnit.MILLIS), IllegalArgumentException.class); assertThrows(() -> RetryPolicy.builder().withBackoff(5, 10, ChronoUnit.MILLIS, .5), IllegalArgumentException.class); assertThrows( () -> RetryPolicy.builder().withMaxDuration(Duration.ofMillis(1)).withBackoff(100, 120, ChronoUnit.MILLIS), IllegalStateException.class); assertThrows(() -> RetryPolicy.builder() .withJitter(Duration.ofMillis(7)) .withBackoff(Duration.ofMillis(5), Duration.ofMillis(10)), IllegalStateException.class); } public void shouldRequireValidRandomDelay() { assertThrows(() -> RetryPolicy.builder().withDelay(null, null), NullPointerException.class); assertThrows(() -> RetryPolicy.builder().withDelay(Duration.ZERO, Duration.ZERO), IllegalArgumentException.class); assertThrows(() -> RetryPolicy.builder().withDelay(Duration.ofMillis(10), Duration.ofMillis(5)), IllegalArgumentException.class); assertThrows(() -> RetryPolicy.builder() .withMaxDuration(Duration.ofMillis(7)) .withDelay(Duration.ofMillis(5), Duration.ofMillis(10)), IllegalStateException.class); assertThrows(() -> RetryPolicy.builder() .withJitter(Duration.ofMillis(7)) .withDelay(Duration.ofMillis(5), Duration.ofMillis(10)), IllegalStateException.class); } public void shouldRequireValidMaxRetries() { assertThrows(() -> RetryPolicy.builder().withMaxRetries(-4), IllegalArgumentException.class); } public void shouldRequireValidMaxDuration() { assertThrows(() -> RetryPolicy.builder().withDelay(Duration.ofMillis(10)).withMaxDuration(Duration.ofMillis(5)), IllegalStateException.class); assertThrows(() -> RetryPolicy.builder() .withDelay(Duration.ofMillis(1), Duration.ofMillis(10)) .withMaxDuration(Duration.ofMillis(5)), IllegalStateException.class); } public void shouldConfigureRandomDelay() { RetryPolicy rp = RetryPolicy.builder().withDelay(1, 10, ChronoUnit.NANOS).build(); assertEquals(rp.getConfig().getDelayMin().toNanos(), 1); assertEquals(rp.getConfig().getDelayMax().toNanos(), 10); } public void testConfigureMaxAttempts() { assertEquals(RetryPolicy.builder().withMaxRetries(-1).build().getConfig().getMaxAttempts(), -1); assertEquals(RetryPolicy.builder().withMaxRetries(0).build().getConfig().getMaxAttempts(), 1); assertEquals(RetryPolicy.builder().withMaxRetries(1).build().getConfig().getMaxAttempts(), 2); } public void shouldReplaceWithFixedDelay() { // Replace backoff with fixed delay RetryPolicyBuilder rpb = RetryPolicy.builder() .withBackoff(Duration.ofMillis(1), Duration.ofMillis(10)) .withDelay(Duration.ofMillis(5)); assertEquals(rpb.config.delay, Duration.ofMillis(5)); assertNull(rpb.config.maxDelay); // Replace random with fixed delay rpb = RetryPolicy.builder().withDelay(Duration.ofMillis(1), Duration.ofMillis(10)).withDelay(Duration.ofMillis(5)); assertEquals(rpb.config.delay, Duration.ofMillis(5)); assertNull(rpb.config.delayMin); assertNull(rpb.config.delayMax); } public void shouldDeplaceWithBackoffDelay() { // Replace fixed with backoff delay RetryPolicyBuilder rpb = RetryPolicy.builder() .withDelay(Duration.ofMillis(5)) .withBackoff(Duration.ofMillis(1), Duration.ofMillis(10)); assertEquals(rpb.config.delay, Duration.ofMillis(1)); // Replace random with backoff delay rpb = RetryPolicy.builder() .withDelay(Duration.ofMillis(5), Duration.ofMillis(15)) .withBackoff(Duration.ofMillis(1), Duration.ofMillis(10)); assertEquals(rpb.config.delay, Duration.ofMillis(1)); assertNull(rpb.config.delayMin); assertNull(rpb.config.delayMax); } public void shouldReplaceWithRandomDelay() { // Replace fixed with random delay RetryPolicyBuilder rpb = RetryPolicy.builder() .withDelay(Duration.ofMillis(5)) .withDelay(Duration.ofMillis(1), Duration.ofMillis(10)); assertEquals(rpb.config.delay, Duration.ZERO); // Replace backoff with random delay rpb = RetryPolicy.builder() .withBackoff(Duration.ofMillis(5), Duration.ofMillis(15)) .withDelay(Duration.ofMillis(1), Duration.ofMillis(10)); assertEquals(rpb.config.delay, Duration.ZERO); assertNull(rpb.config.maxDelay); } public void shouldReplaceWithJitterDuration() { RetryPolicyBuilder rpb = RetryPolicy.builder().withJitter(.2).withJitter(Duration.ofMillis(10)); assertEquals(rpb.config.jitterFactor, 0.0); assertEquals(rpb.config.jitter, Duration.ofMillis(10)); } public void shouldReplaceWithJitterFactor() { RetryPolicyBuilder rpb = RetryPolicy.builder().withJitter(Duration.ofMillis(10)).withJitter(.2); assertNull(rpb.config.jitter); assertEquals(rpb.config.jitterFactor, .2); } public void shouldCreateBuilderFromExistingConfig() { RetryPolicyConfig initialConfig = RetryPolicy.builder() .withBackoff(Duration.ofMillis(10), Duration.ofMillis(100)) .withMaxRetries(5) .onFailedAttempt(e -> { }) .withJitter(Duration.ofMillis(5)).config; RetryPolicyConfig newConfig = RetryPolicy.builder(initialConfig).config; assertEquals(newConfig.delay, Duration.ofMillis(10)); assertEquals(newConfig.maxDelay, Duration.ofMillis(100)); assertNotNull(newConfig.failedAttemptListener); assertEquals(newConfig.jitter, Duration.ofMillis(5)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/TimeoutBuilderTest.java000066400000000000000000000022441444561050700307470ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.*; @Test public class TimeoutBuilderTest { public void shouldCreateBuilderFromExistingConfig() { TimeoutConfig initialConfig = Timeout.builder(Duration.ofMillis(50)).withInterrupt().onFailure(e -> { }).config; TimeoutConfig newConfig = Timeout.builder(initialConfig).config; assertEquals(newConfig.timeout, Duration.ofMillis(50)); assertTrue(newConfig.canInterrupt); assertNotNull(newConfig.failureListener); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/000077500000000000000000000000001444561050700264475ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/BlockedExecutionTest.java000066400000000000000000000130631444561050700334040ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.testing.Asserts; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import static org.testng.Assert.assertFalse; /** * Tests scenarios against a small threadpool where executions could be temporarily blocked. */ @Test public class BlockedExecutionTest extends Testing { /** * Asserts that a scheduled execution that is blocked on a threadpool is properly cancelled when a timeout occurs. */ public void shouldCancelScheduledExecutionOnTimeout() throws Throwable { ExecutorService executor = Executors.newSingleThreadExecutor(); Timeout timeout = Timeout.of(Duration.ofMillis(100)); AtomicBoolean supplierCalled = new AtomicBoolean(); executor.submit(Testing.uncheck(() -> Thread.sleep(300))); Future future = Failsafe.with(timeout).with(executor).getAsync(() -> { supplierCalled.set(true); return false; }); Asserts.assertThrows(() -> future.get(1000, TimeUnit.MILLISECONDS), ExecutionException.class, TimeoutExceededException.class); Thread.sleep(300); assertFalse(supplierCalled.get()); executor.shutdownNow(); } /** * Asserts that a scheduled retry that is blocked on a threadpool is properly cancelled when a timeout occurs. */ public void shouldCancelScheduledRetryOnTimeout() { ExecutorService executor = Executors.newSingleThreadExecutor(); Timeout timeout = Timeout.of(Duration.ofMillis(100)); RetryPolicy rp = RetryPolicy.builder() .withDelay(Duration.ofMillis(1000)) .handleResult(false) .build(); Future future = Failsafe.with(timeout).compose(rp).with(executor).getAsync(() -> { // Tie up single thread immediately after execution, before the retry is scheduled executor.submit(Testing.uncheck(() -> Thread.sleep(1000))); return false; }); Asserts.assertThrows(() -> future.get(500, TimeUnit.MILLISECONDS), ExecutionException.class, TimeoutExceededException.class); executor.shutdownNow(); } /** * Asserts that a scheduled fallback that is blocked on a threadpool is properly cancelled when a timeout occurs. */ public void shouldCancelScheduledFallbackOnTimeout() { ExecutorService executor = Executors.newSingleThreadExecutor(); Timeout timeout = Timeout.of(Duration.ofMillis(100)); AtomicBoolean fallbackCalled = new AtomicBoolean(); Fallback fallback = Fallback.builder(() -> { fallbackCalled.set(true); return true; }).handleResult(false).withAsync().build(); Future future = Failsafe.with(timeout).compose(fallback).with(executor).getAsync(() -> { // Tie up single thread immediately after execution, before the fallback is scheduled executor.submit(Testing.uncheck(() -> Thread.sleep(1000))); return false; }); Asserts.assertThrows(() -> future.get(500, TimeUnit.MILLISECONDS), ExecutionException.class, TimeoutExceededException.class); assertFalse(fallbackCalled.get()); executor.shutdownNow(); } /** * Asserts that a scheduled fallback that is blocked on a threadpool is properly cancelled when the outer future is * cancelled. */ public void shouldCancelScheduledFallbackOnCancel() throws Throwable { AtomicBoolean fallbackCalled = new AtomicBoolean(); ExecutorService executor = Executors.newSingleThreadExecutor(); Fallback fallback = Fallback.builder(() -> { fallbackCalled.set(true); return true; }).handleResult(false).withAsync().build(); Future future = Failsafe.with(fallback).with(executor).getAsync(() -> { executor.submit(Testing.uncheck(() -> Thread.sleep(300))); return false; }); Thread.sleep(100); future.cancel(false); Asserts.assertThrows(future::get, CancellationException.class); Thread.sleep(300); assertFalse(fallbackCalled.get()); executor.shutdownNow(); } /** * Asserts that start times are not populated in execution events for an execution that times out while blocked on a * thread pool, and never starts. */ public void shouldNotPopulateStartTime() throws Throwable { Waiter waiter = new Waiter(); Timeout timeout = Timeout.builder(Duration.ofMillis(50)).withInterrupt().onFailure(e -> { waiter.assertTrue(!e.getStartTime().isPresent()); }).build(); ExecutorService executor = Executors.newFixedThreadPool(1); executor.execute(uncheck(() -> { Thread.sleep(500); })); Failsafe.with(timeout).with(executor).onComplete(e -> { waiter.assertTrue(!e.getStartTime().isPresent()); waiter.resume(); }).runAsync(() -> { waiter.fail("Execution should not start due to timeout"); }); waiter.await(1, TimeUnit.SECONDS); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/BulkheadTest.java000066400000000000000000000062701444561050700316760ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.Bulkhead; import dev.failsafe.BulkheadFullException; import dev.failsafe.Failsafe; import dev.failsafe.FailsafeExecutor; import dev.failsafe.function.CheckedRunnable; import dev.failsafe.testing.Testing; import org.testng.Assert; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; /** * Tests various Bulkhead scenarios. */ @Test public class BulkheadTest extends Testing { public void testPermitAcquiredAfterWait() { // Given Bulkhead bulkhead = Bulkhead.builder(2).withMaxWaitTime(Duration.ofSeconds(1)).build(); // When / Then testGetSuccess(() -> { bulkhead.tryAcquirePermit(); bulkhead.tryAcquirePermit(); // bulkhead should be full runInThread(() -> { Thread.sleep(200); bulkhead.releasePermit(); // bulkhead should not be full }); }, Failsafe.with(bulkhead), ctx -> { return "test"; }, "test"); } public void testPermitAcquiredAfterWaitWithLargeQueue(){ Bulkhead bulkhead = Bulkhead.builder(1).withMaxWaitTime(Duration.ofSeconds(1)).build(); FailsafeExecutor exec = Failsafe.with(bulkhead); CompletableFuture[] tasks = new CompletableFuture[10]; for(int i = 0; i < tasks.length; i++){ int index = i; CheckedRunnable sleep = () -> { ignoreExceptions(() ->{ System.out.println("Running sleep task " + (index + 1)); TimeUnit.MILLISECONDS.sleep(10); System.out.println("Finished sleep task " + (index + 1)); }); }; CompletableFuture task = exec.runAsync(sleep); task.whenComplete((r, ex) -> Assert.assertNull(ex)); tasks[i] = task; } CompletableFuture.allOf(tasks).join(); } public void shouldThrowBulkheadFullExceptionAfterPermitsExceeded() { // Given Bulkhead bulkhead = Bulkhead.of(2); bulkhead.tryAcquirePermit(); bulkhead.tryAcquirePermit(); // bulkhead should be full // When / Then testRunFailure(Failsafe.with(bulkhead), ctx -> { }, BulkheadFullException.class); } /** * Asserts that an exceeded maxWaitTime causes BulkheadFullException. */ public void testMaxWaitTimeExceeded() { // Given Bulkhead bulkhead = Bulkhead.builder(2).withMaxWaitTime(Duration.ofMillis(20)).build(); bulkhead.tryAcquirePermit(); bulkhead.tryAcquirePermit(); // bulkhead should be full // When / Then testRunFailure(Failsafe.with(bulkhead), ctx -> { }, BulkheadFullException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/CallCancellationTest.java000066400000000000000000000053301444561050700333430ustar00rootroot00000000000000package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.function.CheckedConsumer; import dev.failsafe.function.ContextualRunnable; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; import static org.testng.Assert.assertTrue; @Test public class CallCancellationTest extends Testing { Waiter waiter; @BeforeMethod void beforeMethod() { waiter = new Waiter(); } private void assertCancel(FailsafeExecutor executor, ContextualRunnable runnable, boolean testWithoutInterrupt) throws Throwable { CheckedConsumer test = interrupt -> { // Given Call call = executor.onComplete(e -> { waiter.assertNull(e.getResult()); if (!interrupt) waiter.assertNull(e.getException()); else waiter.assertTrue(e.getException() instanceof InterruptedException); waiter.resume(); }).newCall(runnable); // When runInThread(() -> { Testing.sleep(300); waiter.assertTrue(call.cancel(interrupt)); }); if (!interrupt) call.execute(); else assertThrows(call::execute, FailsafeException.class, InterruptedException.class); waiter.await(1000); // Then assertTrue(call.isCancelled()); }; // Test without interrupt if (testWithoutInterrupt) test.accept(false); // Test with interrupt test.accept(true); } public void shouldCancelRetriesWithBlockedExecution() throws Throwable { assertCancel(Failsafe.with(RetryPolicy.ofDefaults()), ctx -> { try { waiter.assertFalse(ctx.isCancelled()); Thread.sleep(1000); } catch (InterruptedException e) { waiter.assertTrue(ctx.isCancelled()); throw e; } }, true); } public void shouldCancelRetriesWithPendingDelay() throws Throwable { RetryPolicy retryPolicy = RetryPolicy.builder().withDelay(Duration.ofMinutes(1)).build(); assertCancel(Failsafe.with(retryPolicy), ctx -> { throw new IllegalStateException(); }, false); } public void shouldPropagateCancelToCallback() throws Throwable { AtomicBoolean callbackCalled = new AtomicBoolean(); assertCancel(Failsafe.with(RetryPolicy.ofDefaults()), ctx -> { ctx.onCancel(() -> callbackCalled.set(true)); try { waiter.assertFalse(ctx.isCancelled()); Thread.sleep(1000); } catch (InterruptedException e) { waiter.assertTrue(ctx.isCancelled()); waiter.assertTrue(callbackCalled.get()); throw e; } }, true); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/CircuitBreakerTest.java000066400000000000000000000162421444561050700330550ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import static dev.failsafe.internal.InternalTesting.resetBreaker; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; /** * Tests various CircuitBreaker scenarios. */ @Test public class CircuitBreakerTest extends Testing { public void shouldRejectInitialExecutionWhenCircuitOpen() { // Given CircuitBreaker cb = CircuitBreaker.ofDefaults(); // When / Then testRunFailure(() -> { cb.open(); }, Failsafe.with(cb), ctx -> { }, (f, e) -> { assertEquals(e.getAttemptCount(), 0); assertEquals(e.getExecutionCount(), 0); }, CircuitBreakerOpenException.class); assertTrue(cb.isOpen()); } /** * Should throw CircuitBreakerOpenException when max half-open executions are occurring. */ public void shouldRejectExcessiveAttemptsWhenBreakerHalfOpen() throws Throwable { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).build(); breaker.halfOpen(); Waiter waiter = new Waiter(); // Create some pending half-open executions for (int i = 0; i < 3; i++) runInThread(() -> Failsafe.with(breaker).run(() -> { waiter.resume(); Thread.sleep(1000); })); // Assert that the breaker does not allow any more executions at the moment waiter.await(10000, 3); for (int i = 0; i < 5; i++) assertThrows(() -> Failsafe.with(breaker).get(() -> null), CircuitBreakerOpenException.class); } /** * Tests the handling of a circuit breaker with no conditions. */ public void testCircuitBreakerWithoutConditions() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withDelay(Duration.ZERO).build(); // When / Then testRunFailure(() -> { resetBreaker(breaker); }, Failsafe.with(breaker), ctx -> { throw new IllegalStateException(); }, IllegalStateException.class); assertTrue(breaker.isOpen()); // Given RetryPolicy retryPolicy = RetryPolicy.builder().withMaxRetries(5).build(); AtomicInteger counter = new AtomicInteger(); // When / Then testGetSuccess(() -> { resetBreaker(breaker); }, Failsafe.with(retryPolicy, breaker), ctx -> { if (counter.incrementAndGet() < 3) throw new ConnectException(); return true; }, true); assertTrue(breaker.isClosed()); } public void shouldThrowCircuitBreakerOpenExceptionAfterFailuresExceeded() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(2) .handleResult(false) .withDelay(Duration.ofSeconds(10)) .build(); // When Failsafe.with(breaker).get(() -> false); Failsafe.with(breaker).get(() -> false); // Then testGetFailure(Failsafe.with(breaker), ctx -> { return true; }, CircuitBreakerOpenException.class); } /** * Tests a scenario where CircuitBreaker rejects some retried executions, which prevents the user's Supplier from * being called. */ public void testRejectedWithRetries() { Stats rpStats = new Stats(); Stats cbStats = new Stats(); RetryPolicy rp = withStatsAndLogs(RetryPolicy.builder().withMaxAttempts(7), rpStats).build(); CircuitBreaker cb = withStatsAndLogs(CircuitBreaker.builder().withFailureThreshold(3), cbStats).build(); testRunFailure(() -> { rpStats.reset(); cbStats.reset(); resetBreaker(cb); }, Failsafe.with(rp, cb), ctx -> { System.out.println("Executing"); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 7); assertEquals(e.getExecutionCount(), 3); assertEquals(rpStats.failedAttemptCount, 7); assertEquals(rpStats.retryCount, 6); assertEquals(cb.getExecutionCount(), 3); assertEquals(cb.getFailureCount(), 3); }, CircuitBreakerOpenException.class); } /** * Tests circuit breaker time based failure thresholding state transitions. */ public void shouldSupportTimeBasedFailureThresholding() throws Throwable { // Given CircuitBreaker circuitBreaker = CircuitBreaker.builder() .withFailureThreshold(2, 3, Duration.ofMillis(200)) .withDelay(Duration.ofMillis(0)) .handleResult(false) .build(); FailsafeExecutor executor = Failsafe.with(circuitBreaker); // When / Then executor.get(() -> false); executor.get(() -> true); // Force results to roll off Thread.sleep(210); executor.get(() -> false); executor.get(() -> true); // Force result to another bucket Thread.sleep(50); assertTrue(circuitBreaker.isClosed()); executor.get(() -> false); assertTrue(circuitBreaker.isOpen()); executor.get(() -> false); assertTrue(circuitBreaker.isHalfOpen()); // Half-open -> Open executor.get(() -> false); assertTrue(circuitBreaker.isOpen()); executor.get(() -> false); assertTrue(circuitBreaker.isHalfOpen()); // Half-open -> close executor.get(() -> true); assertTrue(circuitBreaker.isClosed()); } /** * Tests circuit breaker time based failure rate thresholding state transitions. */ public void shouldSupportTimeBasedFailureRateThresholding() throws Throwable { // Given Stats cbStats = new Stats(); CircuitBreaker circuitBreaker = withStatsAndLogs(CircuitBreaker.builder() .withFailureRateThreshold(50, 3, Duration.ofMillis(200)) .withDelay(Duration.ofMillis(0)) .handleResult(false), cbStats).build(); FailsafeExecutor executor = Failsafe.with(circuitBreaker); // When / Then executor.get(() -> false); executor.get(() -> true); // Force results to roll off Thread.sleep(210); executor.get(() -> false); executor.get(() -> true); // Force result to another bucket Thread.sleep(50); executor.get(() -> true); assertTrue(circuitBreaker.isClosed()); executor.get(() -> false); assertTrue(circuitBreaker.isOpen()); executor.get(() -> false); assertTrue(circuitBreaker.isHalfOpen()); executor.get(() -> false); // Half-open -> Open executor.get(() -> false); assertTrue(circuitBreaker.isOpen()); executor.get(() -> false); assertTrue(circuitBreaker.isHalfOpen()); executor.get(() -> true); // Half-open -> close executor.get(() -> true); assertTrue(circuitBreaker.isClosed()); } } DelayableCircuitBreakerTest.java000066400000000000000000000060521444561050700345770ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.functional.DelayableRetryPolicyTest.UncheckedExpectedException; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; @Test public class DelayableCircuitBreakerTest extends Testing { public void testUncheckedExceptionInDelayFunction() { CircuitBreaker breaker = CircuitBreaker.builder().withDelayFn(ctx -> { throw new UncheckedExpectedException(); }).build(); // Sync assertThrows(() -> Failsafe.with(breaker).run((ExecutionContext context) -> { throw new RuntimeException("try again"); }), UncheckedExpectedException.class); // Async assertThrows(() -> Failsafe.with(breaker).runAsync((ExecutionContext context) -> { throw new RuntimeException("try again"); }).get(1, TimeUnit.SECONDS), ExecutionException.class, UncheckedExpectedException.class); } public void shouldDelayOnMatchingResult() { AtomicInteger delays = new AtomicInteger(); CircuitBreaker breaker = CircuitBreaker.builder() .handleResultIf(r -> r > 0) .withDelayFnWhen(ctx -> { delays.incrementAndGet(); // side-effect for test purposes return Duration.ofNanos(1); }, 2) .build(); FailsafeExecutor failsafe = Failsafe.with(breaker); failsafe.get(() -> 0); failsafe.get(() -> 1); breaker.close(); failsafe.get(() -> 2); assertEquals(delays.get(), 1, "Expected a dynamic delay"); } public void shouldDelayOnMatchingFailureType() { AtomicInteger delays = new AtomicInteger(); CircuitBreaker breaker = CircuitBreaker.builder() .handleResultIf(r -> r > 0) .withDelayFnOn(ctx -> { delays.incrementAndGet(); // side-effect for test purposes return Duration.ofNanos(1); }, RuntimeException.class) .build(); Fallback fallback = Fallback.of(0); FailsafeExecutor failsafe = Failsafe.with(fallback, breaker); failsafe.get(() -> 0); failsafe.get(() -> { throw new Exception(); }); breaker.close(); failsafe.get(() -> { throw new IllegalArgumentException(); }); assertEquals(delays.get(), 1, "Expected a dynamic delay"); } }DelayableRetryPolicyTest.java000066400000000000000000000075531444561050700341750ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.ExecutionContext; import dev.failsafe.Failsafe; import dev.failsafe.Fallback; import dev.failsafe.RetryPolicy; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; @Test public class DelayableRetryPolicyTest extends Testing { static class UncheckedExpectedException extends RuntimeException { } static class DelayException extends UncheckedExpectedException { } public void testUncheckedExceptionInDelayFunction() { RetryPolicy retryPolicy = RetryPolicy.builder().withDelayFn(ctx -> { throw new UncheckedExpectedException(); }).build(); // Sync assertThrows(() -> Failsafe.with(retryPolicy).run((ExecutionContext context) -> { throw new RuntimeException("try again"); }), UncheckedExpectedException.class); // Async assertThrows(() -> Failsafe.with(retryPolicy).runAsync((ExecutionContext context) -> { throw new RuntimeException("try again"); }).get(1, TimeUnit.SECONDS), ExecutionException.class, UncheckedExpectedException.class); } public void shouldDelayOnMatchingResult() { AtomicInteger delays = new AtomicInteger(0); RetryPolicy retryPolicy = RetryPolicy.builder() .handleResultIf(result -> true) .withMaxRetries(4) .withDelayFnWhen(ctx -> { delays.incrementAndGet(); // side-effect for test purposes return Duration.ofNanos(1); }, "expected") .build(); Fallback fallback = Fallback.builder(123).handleResultIf(result -> true).build(); AtomicInteger attempts = new AtomicInteger(0); Object result = Failsafe.with(fallback, retryPolicy).get(() -> { int i = attempts.getAndIncrement(); switch (i) { case 0: case 3: return "expected"; default: return i; } }); assertEquals(result, 123, "Fallback should be used"); assertEquals(attempts.get(), 5, "Expecting five attempts (1 + 4 retries)"); assertEquals(delays.get(), 2, "Expecting two dynamic delays matching String result"); } public void shouldDelayOnMatchingFailureType() { AtomicInteger delays = new AtomicInteger(0); RetryPolicy retryPolicy = RetryPolicy.builder() .handle(UncheckedExpectedException.class) .withMaxRetries(4) .withDelayFnOn(ctx -> { delays.incrementAndGet(); // side-effect for test purposes return Duration.ofNanos(1); }, DelayException.class) .build(); AtomicInteger attempts = new AtomicInteger(0); int result = Failsafe.with(Fallback.of(123), retryPolicy).get(() -> { int i = attempts.getAndIncrement(); switch (i) { case 0: case 2: throw new DelayException(); default: throw new UncheckedExpectedException(); } }); assertEquals(result, 123, "Fallback should be used"); assertEquals(attempts.get(), 5, "Expecting five attempts (1 + 4 retries)"); assertEquals(delays.get(), 2, "Expecting two dynamic delays matching DelayException failure"); } }ExecutorConfigurationTest.java000066400000000000000000000100111444561050700344120ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.testing.Testing; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.atomic.AtomicBoolean; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; /** * Tests the configuration of an Executor. */ @Test public class ExecutorConfigurationTest extends Testing { AtomicBoolean executorCalled; AtomicBoolean executionCalled; RetryPolicy retryPolicy = withLogs(RetryPolicy.builder()).build(); Executor executor = execution -> { executorCalled.set(true); execution.run(); }; Executor throwingExecutor = execution -> { executorCalled.set(true); execution.run(); throw new IllegalStateException(); }; @BeforeMethod protected void beforeMethod() { executorCalled = new AtomicBoolean(); executionCalled = new AtomicBoolean(); } public void testSyncExecutionSuccess() { String result = Failsafe.with(retryPolicy).with(executor).get(() -> { executionCalled.set(true); return "result"; }); assertTrue(executorCalled.get()); assertTrue(executionCalled.get()); assertNull(result); } public void testSyncExecutionFailure() { assertThrows(() -> Failsafe.with(retryPolicy).with(executor).run(() -> { executionCalled.set(true); throw new IllegalStateException(); }), IllegalStateException.class); assertTrue(executorCalled.get()); assertTrue(executionCalled.get()); } public void testSyncExecutionThatThrowsFromTheExecutor() { assertThrows(() -> Failsafe.with(retryPolicy).with(throwingExecutor).run(() -> { executionCalled.set(true); }), IllegalStateException.class); assertTrue(executorCalled.get()); assertTrue(executionCalled.get()); } public void testAsyncExecutionSuccess() throws Throwable { AtomicBoolean fjpAssertion = new AtomicBoolean(); String result = Failsafe.with(retryPolicy).with(executor).getAsync(() -> { fjpAssertion.set(Thread.currentThread() instanceof ForkJoinWorkerThread); executionCalled.set(true); return "result"; }).get(); assertTrue(executorCalled.get()); assertTrue(executionCalled.get()); assertTrue(fjpAssertion.get(), "the execution should run on a fork join pool thread"); assertNull(result); } public void testAsyncExecutionFailure() { AtomicBoolean fjpAssertion = new AtomicBoolean(); assertThrows(() -> Failsafe.with(retryPolicy).with(executor).getAsync(() -> { fjpAssertion.set(Thread.currentThread() instanceof ForkJoinWorkerThread); executionCalled.set(true); throw new IllegalStateException(); }).get(), ExecutionException.class, IllegalStateException.class); assertTrue(executorCalled.get()); assertTrue(executionCalled.get()); assertTrue(fjpAssertion.get(), "the execution should run on a fork join pool thread"); } public void testAsyncExecutionThatThrowsFromTheExecutor() { assertThrows(() -> Failsafe.with(retryPolicy).with(throwingExecutor).runAsync(() -> { executionCalled.set(true); }).get(), ExecutionException.class, IllegalStateException.class); assertTrue(executorCalled.get()); assertTrue(executionCalled.get()); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/ExecutorTest.java000066400000000000000000000035401444561050700317520ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.testing.Testing; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; /** * Tests failsafe with an executor. */ @Test public class ExecutorTest extends Testing { AtomicInteger executions = new AtomicInteger(); Executor executor = runnable -> { executions.incrementAndGet(); runnable.run(); }; @BeforeMethod protected void beforeMethod() { executions.set(0); } public void testExecutorWithSyncExecution() { assertThrows(() -> Failsafe.with(RetryPolicy.ofDefaults()).with(executor).run(() -> { throw new IllegalStateException(); }), IllegalStateException.class); assertEquals(executions.get(), 3); } public void testExecutorWithAsyncExecution() { assertThrows(() -> Failsafe.with(RetryPolicy.ofDefaults()).with(executor).runAsync(() -> { throw new IllegalStateException(); }).get(), ExecutionException.class, IllegalStateException.class); assertEquals(executions.get(), 3); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/FallbackTest.java000066400000000000000000000051561444561050700316600ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.Fallback; import dev.failsafe.testing.Testing; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; /** * Tests various Fallback scenarios. */ @Test public class FallbackTest extends Testing { /** * Tests a simple execution that does not fallback. */ public void shouldNotFallback() { testGetSuccess(Failsafe.with(Fallback.of(true)), ctx -> { return false; }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, false); } /** * Tests the handling of a fallback with no conditions. */ public void testFallbackWithoutConditions() { // Given Fallback fallback = Fallback.of(true); // When / Then testRunSuccess(Failsafe.with(fallback), ctx -> { throw new IllegalArgumentException(); }, true); // Given RetryPolicy retryPolicy = RetryPolicy.ofDefaults(); // When / Then testRunSuccess(Failsafe.with(fallback, retryPolicy), ctx -> { throw new IllegalStateException(); }, true); } /** * Tests the handling of a fallback with conditions. */ public void testFallbackWithConditions() { // Given Fallback fallback = Fallback.builder(true).handle(IllegalArgumentException.class).build(); // When / Then testRunFailure(Failsafe.with(fallback), ctx -> { throw new IllegalStateException(); }, IllegalStateException.class); testRunSuccess(Failsafe.with(fallback), ctx -> { throw new IllegalArgumentException(); }, true); } /** * Tests Fallback.ofException. */ public void shouldFallbackOfException() { // Given Fallback fallback = Fallback.ofException(e -> new IllegalStateException(e.getLastException())); // When / Then testRunFailure(Failsafe.with(fallback), ctx -> { throw new IllegalArgumentException(); }, IllegalStateException.class); } } FutureCancellationTest.java000066400000000000000000000171671444561050700336760ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.function.ContextualRunnable; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import dev.failsafe.event.ExecutionCompletedEvent; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import static org.testng.Assert.*; /** * Tests behavior when a FailsafeFuture is explicitly cancelled. */ @Test public class FutureCancellationTest extends Testing { Waiter waiter; @BeforeMethod void beforeMethod() { waiter = new Waiter(); } private void assertCancel(FailsafeExecutor executor, ContextualRunnable runnable) throws Throwable { // Given CompletableFuture future = executor.onComplete(e -> { waiter.assertNull(e.getResult()); waiter.assertTrue(e.getException() instanceof CancellationException); waiter.resume(); }).runAsync(runnable); Testing.sleep(300); // When assertTrue(future.cancel(true)); waiter.await(1000); // Then assertTrue(future.isCancelled()); // assertTrue(future.cancelFunctions.isEmpty()); assertTrue(future.isDone()); assertThrows(future::get, CancellationException.class); } public void shouldCancelAsyncRetriesWithPendingDelay() throws Throwable { RetryPolicy retryPolicy = RetryPolicy.builder().withDelay(Duration.ofMinutes(1)).build(); assertCancel(Failsafe.with(retryPolicy), ctx -> { throw new IllegalStateException(); }); } public void shouldCancelAsyncRetriesWithBlockedExecution() throws Throwable { assertCancel(Failsafe.with(RetryPolicy.ofDefaults()), ctx -> { try { waiter.assertFalse(ctx.isCancelled()); Thread.sleep(1000); } catch (InterruptedException e) { waiter.assertTrue(ctx.isCancelled()); throw e; } }); } public void shouldCancelAsyncTimeoutWithBlockedExecution() throws Throwable { assertCancel(Failsafe.with(Timeout.of(Duration.ofMinutes(1))), ctx -> { try { waiter.assertFalse(ctx.isCancelled()); Thread.sleep(1000); } catch (InterruptedException e) { waiter.assertTrue(ctx.isCancelled()); throw e; } }); } public void shouldCancelAsyncRateLimiterWaitingOnPermit() throws Throwable { RateLimiter limiter = RateLimiter.smoothBuilder(1, Duration.ofSeconds(1)) .withMaxWaitTime(Duration.ofMinutes(1)) .build(); limiter.tryAcquirePermit(); // All permits should be used now assertCancel(Failsafe.with(limiter), ctx -> { fail("Execution should be cancelled during preExecute"); }); } public void shouldCancelBulkheadWaitingOnPermit() throws Throwable { Bulkhead bulkhead = Bulkhead.builder(2).withMaxWaitTime(Duration.ofSeconds(1)).build(); bulkhead.tryAcquirePermit(); bulkhead.tryAcquirePermit(); // bulkhead should be full assertCancel(Failsafe.with(bulkhead), ctx -> { fail("Execution should be cancelled during preExecute"); }); } /** * Asserts that cancelling a FailsafeFuture causes both retry policies to stop. */ public void testCancelWithNestedRetries() throws Throwable { // Given Stats outerRetryStats = new Stats(); Stats innerRetryStats = new Stats(); RetryPolicy outerRetryPolicy = withStatsAndLogs(RetryPolicy.builder(), outerRetryStats).build(); RetryPolicy innerRetryPolicy = withStatsAndLogs( RetryPolicy.builder().withMaxRetries(3).withDelay(Duration.ofMillis(100)), innerRetryStats).build(); AtomicReference> futureRef = new AtomicReference<>(); AtomicReference> completedRef = new AtomicReference<>(); Waiter waiter = new Waiter(); // When futureRef.set(Failsafe.with(outerRetryPolicy, innerRetryPolicy).onComplete(e -> { completedRef.set(e); waiter.resume(); }).runAsync(ctx -> { if (ctx.isFirstAttempt()) throw new IllegalStateException(); else futureRef.get().cancel(false); })); // Then assertThrows(() -> futureRef.get().get(1, TimeUnit.SECONDS), CancellationException.class); waiter.await(1000); assertNull(completedRef.get().getResult()); assertTrue(completedRef.get().getException() instanceof CancellationException); assertEquals(outerRetryStats.failedAttemptCount, 0); assertEquals(innerRetryStats.failedAttemptCount, 1); } /** * Asserts that FailsafeFuture cancellations are propagated to a CompletionStage. */ public void shouldPropagateCancellationToStage() { // Given Policy retryPolicy = RetryPolicy.ofDefaults(); // When CompletableFuture promise = new CompletableFuture<>(); CompletableFuture future = Failsafe.with(retryPolicy).getStageAsync(() -> promise); sleep(200); future.cancel(false); // Then assertThrows(() -> future.get(1, TimeUnit.SECONDS), CancellationException.class); assertThrows(() -> promise.get(1, TimeUnit.SECONDS), CancellationException.class); } /** * Asserts that FailsafeFuture cancellations are propagated to the most recent ExecutionContext. */ public void shouldPropagateCancellationToExecutionContext() throws Throwable { // Given Policy retryPolicy = withLogs(RetryPolicy.builder()).build(); AtomicReference> ctxRef = new AtomicReference<>(); Waiter waiter = new Waiter(); // When Future future = Failsafe.with(retryPolicy).runAsync(ctx -> { ctxRef.set(ctx); if (ctx.getAttemptCount() < 2) throw new Exception(); else { waiter.resume(); Thread.sleep(1000); } }); waiter.await(1000); future.cancel(true); // Then assertTrue(ctxRef.get().isCancelled()); } private void assertInterruptedExceptionOnCancel(FailsafeExecutor failsafe) throws Throwable { Waiter waiter = new Waiter(); CompletableFuture future = failsafe.runAsync(() -> { try { Thread.sleep(1000); waiter.fail("Expected to be interrupted"); } catch (InterruptedException e) { waiter.resume(); } }); Thread.sleep(100); assertTrue(future.cancel(true)); waiter.await(1000); } public void shouldInterruptExecutionOnCancelWithForkJoinPool() throws Throwable { assertInterruptedExceptionOnCancel(Failsafe.with(retryAlways)); } public void shouldInterruptExecutionOnCancelWithScheduledExecutorService() throws Throwable { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); assertInterruptedExceptionOnCancel(Failsafe.with(retryAlways).with(executorService)); executorService.shutdownNow(); } public void shouldInterruptExecutionOnCancelWithExecutorService() throws Throwable { ExecutorService executor = Executors.newFixedThreadPool(2); assertInterruptedExceptionOnCancel(Failsafe.with(retryAlways).with(executor)); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/FutureCompletionTest.java000066400000000000000000000041641444561050700334630ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import java.util.concurrent.CompletableFuture; import static org.testng.Assert.assertFalse; /** * Tests behavior when a FailsafeFuture is explicitly completed. */ @Test public class FutureCompletionTest extends Testing { /** * Asserts that an externally completed FailsafeFuture works as expected. */ public void shouldCompleteFutureExternally() throws Throwable { // Given Waiter waiter = new Waiter(); CompletableFuture future1 = Failsafe.with(retryNever).onSuccess(e -> { waiter.assertFalse(e.getResult()); waiter.resume(); }).getAsync(() -> { waiter.resume(); Thread.sleep(500); return true; }); waiter.await(1000); // When completed future1.complete(false); // Then assertFalse(future1.get()); waiter.await(1000); // Given CompletableFuture future2 = Failsafe.with(retryNever).onFailure(e -> { waiter.assertTrue(e.getException() instanceof IllegalArgumentException); waiter.resume(); }).getAsync(() -> { waiter.resume(); Thread.sleep(500); return true; }); waiter.await(1000); // When completed exceptionally future2.completeExceptionally(new IllegalArgumentException()); // Then assertThrows(() -> unwrapExceptions(future2::get), IllegalArgumentException.class); waiter.await(1000); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/InterruptionTest.java000066400000000000000000000076331444561050700326650ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.testing.Asserts; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.*; /** * Tests various execution interrupt scenarios. */ @Test public class InterruptionTest extends Testing { /** * Asserts that a blocked execution can be interrupted. */ public void testInterruptSyncExecution() { scheduleInterrupt(100); Asserts.assertThrows(() -> Failsafe.with(retryNever).run(() -> { Thread.sleep(1000); fail("Expected interruption"); }), FailsafeException.class, InterruptedException.class); // Clear interrupt flag assertTrue(Thread.interrupted()); } /** * Asserts that a blocked retry policy delay can be interrupted. */ public void testInterruptSyncRetryPolicyDelay() { RetryPolicy rp = RetryPolicy.builder().withDelay(Duration.ofMillis(500)).build(); scheduleInterrupt(100); Asserts.assertThrows(() -> Failsafe.with(rp).run(() -> { throw new Exception(); }), FailsafeException.class, InterruptedException.class); // Clear interrupt flag assertTrue(Thread.interrupted()); } /** * Asserts that a blocked rate limiter acquirePermit can be interrupted. */ public void testInterruptRateLimiterAcquirePermit() { RateLimiter limiter = RateLimiter.smoothBuilder(Duration.ofSeconds(1)) .withMaxWaitTime(Duration.ofSeconds(5)) .build(); limiter.tryAcquirePermit(); // Rate limiter should be full scheduleInterrupt(100); assertThrows(() -> Failsafe.with(limiter).run(() -> { System.out.println("Executing"); throw new Exception(); }), FailsafeException.class, InterruptedException.class); // Clear interrupt flag assertTrue(Thread.interrupted()); } /** * Asserts that a blocked bulkhead acquirePermit can be interrupted. */ public void testInterruptBulkheadAcquirePermit() { Bulkhead bulkhead = Bulkhead.builder(1).withMaxWaitTime(Duration.ofSeconds(5)).build(); bulkhead.tryAcquirePermit(); // Bulkhead should be full scheduleInterrupt(100); assertThrows(() -> Failsafe.with(bulkhead).run(() -> { System.out.println("Executing"); throw new Exception(); }), FailsafeException.class, InterruptedException.class); // Clear interrupt flag assertTrue(Thread.interrupted()); } /** * Ensures that an internally interrupted execution should always have the interrupt flag cleared afterwards. */ public void shouldResetInterruptFlagAfterInterruption() { // Given Timeout timeout = Timeout.builder(Duration.ofMillis(1)).withInterrupt().build(); // When / Then testRunFailure(false, Failsafe.with(timeout), ctx -> { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, (f, e) -> { assertFalse(Thread.currentThread().isInterrupted(), "Interrupt flag should be cleared after Failsafe handling"); }, TimeoutExceededException.class); } /** * Schedules an interrupt of the calling thread. */ private void scheduleInterrupt(long millis) { Thread thread = Thread.currentThread(); runInThread(() -> { Thread.sleep(millis); thread.interrupt(); }); } } NestedCircuitBreakerTest.java000066400000000000000000000057361444561050700341470ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.function.CheckedRunnable; import dev.failsafe.testing.Testing; import dev.failsafe.CircuitBreaker; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import static dev.failsafe.internal.InternalTesting.resetBreaker; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; /** * Tests nested circuit breaker scenarios. */ @Test public class NestedCircuitBreakerTest extends Testing { /** * Tests that multiple circuit breakers handle failures as expected, regardless of order. */ public void testNestedCircuitBreakers() { CircuitBreaker innerCb = CircuitBreaker.builder().handle(IllegalArgumentException.class).build(); CircuitBreaker outerCb = CircuitBreaker.builder().handle(IllegalStateException.class).build(); CheckedRunnable runnable = () -> { throw new IllegalArgumentException(); }; ignoreExceptions(() -> Failsafe.with(outerCb, innerCb).run(runnable)); assertTrue(innerCb.isOpen()); assertTrue(outerCb.isClosed()); innerCb.close(); ignoreExceptions(() -> Failsafe.with(innerCb, outerCb).run(runnable)); assertTrue(innerCb.isOpen()); assertTrue(outerCb.isClosed()); } /** * CircuitBreaker -> CircuitBreaker */ public void testCircuitBreakerCircuitBreaker() { // Given CircuitBreaker cb1 = CircuitBreaker.builder().handle(IllegalStateException.class).build(); CircuitBreaker cb2 = CircuitBreaker.builder().handle(IllegalArgumentException.class).build(); testRunFailure(() -> { resetBreaker(cb1); resetBreaker(cb2); }, Failsafe.with(cb2, cb1), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(1, e.getAttemptCount()); assertEquals(cb1.getFailureCount(), 1); assertTrue(cb1.isOpen()); assertEquals(cb2.getFailureCount(), 0); assertTrue(cb2.isClosed()); }, IllegalStateException.class); testRunFailure(() -> { resetBreaker(cb1); resetBreaker(cb2); }, Failsafe.with(cb2, cb1), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(1, e.getAttemptCount()); assertEquals(cb1.getFailureCount(), 1); assertTrue(cb1.isOpen()); assertEquals(cb2.getFailureCount(), 0); assertTrue(cb2.isClosed()); }, IllegalStateException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/NestedRetryPolicyTest.java000066400000000000000000000120621444561050700336030ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.Fallback; import dev.failsafe.testing.Testing; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import dev.failsafe.function.ContextualRunnable; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; /** * Tests nested retry policy scenarios. */ @Test public class NestedRetryPolicyTest extends Testing { Server server; @BeforeMethod protected void beforeMethod() { server = mock(Server.class); } /** * RetryPolicy -> RetryPolicy *

* Tests a scenario with nested retry policies where the inner policy is exceeded and skipped. */ public void testNestedRetryPoliciesWhereInnerIsExceeded() { Stats outerRetryStats = new Stats(); Stats innerRetryStats = new Stats(); RetryPolicy outerRetryPolicy = withStats(RetryPolicy.builder().withMaxRetries(10), outerRetryStats).build(); RetryPolicy innerRetryPolicy = withStats(RetryPolicy.builder().withMaxRetries(1), innerRetryStats).build(); testGetSuccess(() -> { when(server.connect()).thenThrow(failures(5, new IllegalStateException())).thenReturn(true); outerRetryStats.reset(); innerRetryStats.reset(); }, Failsafe.with(outerRetryPolicy, innerRetryPolicy), ctx -> { return server.connect(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 6); assertEquals(outerRetryStats.failedAttemptCount, 4); assertEquals(outerRetryStats.failureCount, 0); assertEquals(innerRetryStats.failedAttemptCount, 2); assertEquals(innerRetryStats.failureCount, 1); }, true); } /** * Fallback -> RetryPolicy -> RetryPolicy */ public void testFallbackRetryPolicyRetryPolicy() { Stats retryPolicy1Stats = new Stats(); Stats retryPolicy2Stats = new Stats(); RetryPolicy retryPolicy1 = withStats( RetryPolicy.builder().handle(IllegalStateException.class).withMaxRetries(2), retryPolicy1Stats).build(); RetryPolicy retryPolicy2 = withStats( RetryPolicy.builder().handle(IllegalArgumentException.class).withMaxRetries(3), retryPolicy2Stats).build(); Fallback fallback = Fallback.builder(true).withAsync().build(); ContextualRunnable runnable = ctx -> { throw ctx.getAttemptCount() % 2 == 0 ? new IllegalStateException() : new IllegalArgumentException(); }; testRunSuccess(() -> { retryPolicy1Stats.reset(); retryPolicy2Stats.reset(); }, Failsafe.with(fallback, retryPolicy2, retryPolicy1), runnable, (f, e) -> { // Then // Expected RetryPolicy failure sequence: // rp1 java.lang.IllegalStateException - failure, retry // rp1 java.lang.IllegalArgumentException - success // rp2 java.lang.IllegalArgumentException - failure, retry // rp1 java.lang.IllegalStateException - failure, retry, retries exhausted // rp1 java.lang.IllegalArgumentException - success // rp2 java.lang.IllegalArgumentException - failure, retry // rp1 java.lang.IllegalStateException - failure, retries exceeded // rp2 java.lang.IllegalStateException - success assertEquals(retryPolicy1Stats.failedAttemptCount, 3); assertEquals(retryPolicy1Stats.failureCount, 1); assertEquals(retryPolicy2Stats.failedAttemptCount, 2); assertEquals(retryPolicy2Stats.failureCount, 0); }, true); testRunSuccess(() -> { retryPolicy1Stats.reset(); retryPolicy2Stats.reset(); }, Failsafe.with(fallback, retryPolicy1, retryPolicy2), runnable, (f, e) -> { // Expected RetryPolicy failure sequence: // rp2 java.lang.IllegalStateException - success // rp1 java.lang.IllegalStateException - failure, retry // rp2 java.lang.IllegalArgumentException - failure, retry // rp2 java.lang.IllegalStateException - success // rp1 java.lang.IllegalStateException - failure, retry, retries exhausted // rp2 java.lang.IllegalArgumentException - failure, retry // rp2 java.lang.IllegalStateException - success // rp1 java.lang.IllegalStateException - retries exceeded assertEquals(retryPolicy1Stats.failedAttemptCount, 3); assertEquals(retryPolicy1Stats.failureCount, 1); assertEquals(retryPolicy2Stats.failedAttemptCount, 2); assertEquals(retryPolicy2Stats.failureCount, 0); }, true); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/NestedTimeoutTest.java000066400000000000000000000125621444561050700327510ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import java.util.function.BiConsumer; import static org.testng.Assert.*; /** * Tests nested timeout scenarios. */ @Test public class NestedTimeoutTest extends Testing { /** * Timeout -> RetryPolicy -> Timeout *

* Tests a scenario where an inner timeout is exceeded, triggering retries, then eventually the outer timeout is * exceeded. */ public void testTimeoutRetryPolicyTimeout() { Stats innerTimeoutStats = new Stats(); Stats retryStats = new Stats(); Stats outerTimeoutStats = new Stats(); RetryPolicy retryPolicy = withStatsAndLogs(RetryPolicy.builder().withMaxRetries(10), retryStats).build(); BiConsumer, Timeout> test = (innerTimeout, outerTimeout) -> testRunFailure(false, () -> { innerTimeoutStats.reset(); retryStats.reset(); outerTimeoutStats.reset(); }, Failsafe.with(outerTimeout, retryPolicy, innerTimeout), ctx -> { Thread.sleep(150); }, (f, e) -> { assertTrue(e.getAttemptCount() >= 3); assertTrue(e.getExecutionCount() >= 3); assertTrue(innerTimeoutStats.failureCount >= 3); assertTrue(retryStats.failedAttemptCount >= 3); // assertEquals(innerTimeoutStats.failureCount + 1, retryStats.failedAttemptCount); // assertEquals(innerTimeoutStats.executionCount, retryStats.executionCount); assertEquals(outerTimeoutStats.failureCount, 1); }, TimeoutExceededException.class); // Test without interrupt Timeout innerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)), innerTimeoutStats).build(); Timeout outerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(500)), outerTimeoutStats).build(); test.accept(innerTimeout, outerTimeout); // Test with interrupt innerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)).withInterrupt(), innerTimeoutStats).build(); outerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(500)).withInterrupt(), outerTimeoutStats).build(); test.accept(innerTimeout, outerTimeout); } /** * Fallback -> RetryPolicy -> Timeout -> Timeout *

* Tests a scenario with a fallback, retry policy and two timeouts, where the outer timeout triggers first. */ public void testFallbackRetryPolicyTimeoutTimeout() { Stats innerTimeoutStats = new Stats(); Stats outerTimeoutStats = new Stats(); RetryPolicy retryPolicy = RetryPolicy.ofDefaults(); Fallback fallback = Fallback.of(true); BiConsumer, Timeout> test = (innerTimeout, outerTimeout) -> testRunSuccess(false, () -> { innerTimeoutStats.reset(); outerTimeoutStats.reset(); }, Failsafe.with(fallback, retryPolicy, outerTimeout, innerTimeout), ctx -> { Thread.sleep(150); }, (f, e) -> { assertEquals(3, e.getAttemptCount()); assertEquals(innerTimeoutStats.failureCount, 3); assertEquals(outerTimeoutStats.failureCount, 3); }, true); // Test without interrupt Timeout innerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)), innerTimeoutStats).build(); Timeout outerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(50)), outerTimeoutStats).build(); test.accept(innerTimeout, outerTimeout); // Test with interrupt innerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)).withInterrupt(), innerTimeoutStats).build(); outerTimeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(50)).withInterrupt(), outerTimeoutStats).build(); test.accept(innerTimeout, outerTimeout); test.accept(innerTimeout, outerTimeout); } /** * RetryPolicy -> Timeout -> Timeout *

* Tests a scenario where three timeouts should cause all delegates to be cancelled with interrupts. */ public void shouldCancelNestedTimeoutsWithInterrupt() { // Given RetryPolicy rp = RetryPolicy.ofDefaults(); Timeout outerTimeout = Timeout.of(Duration.ofMillis(1000)); Timeout innerTimeout = Timeout.builder(Duration.ofMillis(200)).withInterrupt().build(); // When / Then testGetFailure(false, Failsafe.with(rp, innerTimeout, outerTimeout), ctx -> { assertTrue(ctx.getLastException() == null || ctx.getLastException() instanceof TimeoutExceededException); try { assertFalse(ctx.isCancelled()); Thread.sleep(1000); } catch (InterruptedException e) { assertTrue(ctx.isCancelled()); throw e; } fail("Expected interruption"); return false; }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); }, TimeoutExceededException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/NoPolicyTest.java000066400000000000000000000041761444561050700317160ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.Failsafe; import dev.failsafe.FailsafeExecutor; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; /** * Tests the use of Failsafe.none(). */ @Test public class NoPolicyTest extends Testing { public void testWithNoPolicy() { AtomicInteger successCounter = new AtomicInteger(); AtomicInteger failureCounter = new AtomicInteger(); FailsafeExecutor failsafe = Failsafe.none().onFailure(e -> { System.out.println("Failure"); failureCounter.incrementAndGet(); }).onSuccess(e -> { System.out.println("Success"); successCounter.incrementAndGet(); }); // Test success testGetSuccess(() -> { successCounter.set(0); failureCounter.set(0); }, failsafe, ctx -> { return "success"; }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(successCounter.get(), 1); assertEquals(failureCounter.get(), 0); }, "success"); // Test failure testRunFailure(() -> { successCounter.set(0); failureCounter.set(0); }, failsafe, ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(successCounter.get(), 0); assertEquals(failureCounter.get(), 1); }, IllegalStateException.class); } } PolicyCompositionExecutionTest.java000066400000000000000000000037411444561050700354470ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.CircuitBreaker; import dev.failsafe.Execution; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; /** * Tests the handling of ordered policy execution via an Execution or AsyncExecution. */ @Test public class PolicyCompositionExecutionTest { public void testRetryPolicyCircuitBreaker() { RetryPolicy rp = RetryPolicy.ofDefaults(); CircuitBreaker cb = CircuitBreaker.builder().withFailureThreshold(5).build(); Execution execution = Execution.of(rp, cb); execution.recordException(new Exception()); execution.recordException(new Exception()); assertFalse(execution.isComplete()); execution.recordException(new Exception()); assertTrue(execution.isComplete()); assertTrue(cb.isClosed()); } public void testCircuitBreakerRetryPolicy() { RetryPolicy rp = RetryPolicy.builder().withMaxRetries(1).build(); CircuitBreaker cb = CircuitBreaker.builder().withFailureThreshold(5).build(); Execution execution = Execution.of(cb, rp); execution.recordException(new Exception()); assertFalse(execution.isComplete()); execution.recordException(new Exception()); assertTrue(execution.isComplete()); assertTrue(cb.isClosed()); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/PolicyCompositionTest.java000066400000000000000000000234621444561050700336440ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import com.sun.net.httpserver.Authenticator.Retry; import dev.failsafe.*; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import java.util.function.Consumer; import static dev.failsafe.internal.InternalTesting.resetBreaker; import static dev.failsafe.internal.InternalTesting.resetLimiter; import static org.testng.Assert.*; /** * Tests various policy composition scenarios. */ @Test public class PolicyCompositionTest extends Testing { /** * RetryPolicy -> CircuitBreaker */ public void testRetryPolicyCircuitBreaker() { RetryPolicy rp = RetryPolicy.builder().withMaxRetries(-1).build(); CircuitBreaker cb = CircuitBreaker.builder() .withFailureThreshold(3) .withDelay(Duration.ofMinutes(10)) .build(); Service service = mockService(2, true); testGetSuccess(() -> { service.reset(); resetBreaker(cb); }, Failsafe.with(rp, cb), ctx -> { return service.connect(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(cb.getFailureCount(), 2); assertEquals(cb.getSuccessCount(), 1); assertTrue(cb.isClosed()); }, true); } /** * RetryPolicy -> CircuitBreaker *

* Asserts handling of an open breaker. */ public void testRetryPolicyCircuitBreakerWithOpenBreaker() { // Given RetryPolicy retryPolicy = Testing.withLogs(RetryPolicy.builder()).build(); CircuitBreaker cb = Testing.withLogs(CircuitBreaker.builder()).build(); // When / Then testRunFailure(() -> { resetBreaker(cb); }, Failsafe.with(retryPolicy, cb), ctx -> { Thread.sleep(10); throw new Exception(); }, (f, e) -> { }, CircuitBreakerOpenException.class); } /** * CircuitBreaker -> RetryPolicy */ public void testCircuitBreakerRetryPolicy() { RetryPolicy rp = RetryPolicy.ofDefaults(); CircuitBreaker cb = CircuitBreaker.builder().withFailureThreshold(5).build(); testRunFailure(() -> { resetBreaker(cb); }, Failsafe.with(cb).compose(rp), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(cb.getFailureCount(), 1); assertEquals(cb.getSuccessCount(), 0); assertTrue(cb.isClosed()); }, IllegalStateException.class); } /** * Fallback -> RetryPolicy -> CircuitBreaker */ public void testFallbackRetryPolicyCircuitBreaker() { RetryPolicy rp = RetryPolicy.ofDefaults(); CircuitBreaker cb = CircuitBreaker.builder().withFailureThreshold(5).build(); Fallback fb = Fallback.builder(() -> "test").withAsync().build(); testRunSuccess(() -> { resetBreaker(cb); }, Failsafe.with(fb).compose(rp).compose(cb), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(cb.getFailureCount(), 3); assertEquals(cb.getSuccessCount(), 0); assertTrue(cb.isClosed()); }, "test"); } /** * Fallback -> RetryPolicy */ public void testFallbackRetryPolicy() { Fallback fb = Fallback.of(e -> { assertNull(e.getLastResult()); assertTrue(e.getLastException() instanceof IllegalStateException); return "test"; }); RetryPolicy rp = RetryPolicy.ofDefaults(); testRunSuccess(Failsafe.with(fb).compose(rp), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); }, "test"); } /** * RetryPolicy -> Fallback */ public void testRetryPolicyFallback() { // Given RetryPolicy rp = RetryPolicy.ofDefaults(); Fallback fb = Fallback.of("test"); // When / Then testRunSuccess(Failsafe.with(rp).compose(fb), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); }, "test"); } /** * Fallback -> CircuitBreaker *

* Tests fallback with a circuit breaker that is closed. */ public void testFallbackCircuitBreaker() { // Given Fallback fallback = Fallback.of(e -> { assertNull(e.getLastResult()); assertTrue(e.getLastException() instanceof IllegalStateException); return false; }); CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).build(); // When / Then testGetSuccess(() -> { resetBreaker(breaker); }, Failsafe.with(fallback, breaker), ctx -> { throw new IllegalStateException(); }, false); } /** * Fallback -> CircuitBreaker *

* Tests fallback with a circuit breaker that is open. */ public void testFallbackCircuitBreakerOpen() { // Given Fallback fallback = Fallback.of(e -> { assertNull(e.getLastResult()); assertTrue(e.getLastException() instanceof CircuitBreakerOpenException); return false; }); CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).build(); // When / Then with open breaker testGetSuccess(() -> { breaker.open(); }, Failsafe.with(fallback, breaker), ctx -> { return true; }, false); } /** * RetryPolicy -> Timeout *

* Tests 2 timeouts, then a success, and asserts the ExecutionContext is cancelled after each timeout. */ public void testRetryPolicyTimeout() { // Given RetryPolicy rp = RetryPolicy.builder().onFailedAttempt(e -> { assertTrue(e.getLastException() instanceof TimeoutExceededException); }).build(); Stats timeoutStats = new Stats(); Recorder recorder = new Recorder(); // When / Then Consumer> test = timeout -> testGetSuccess(false, () -> { recorder.reset(); timeoutStats.reset(); }, Failsafe.with(rp, timeout), ctx -> { if (ctx.getAttemptCount() < 2) { Thread.sleep(100); recorder.assertTrue(ctx.isCancelled()); } else { recorder.assertFalse(ctx.isCancelled()); } return "success"; }, (f, e) -> { recorder.throwFailures(); assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); assertEquals(timeoutStats.failureCount, 2); assertEquals(timeoutStats.successCount, 1); }, "success"); // Without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(50)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(50)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * CircuitBreaker -> Timeout */ public void testCircuitBreakerTimeout() { // Given Timeout timeout = Timeout.of(Duration.ofMillis(50)); CircuitBreaker breaker = CircuitBreaker.ofDefaults(); assertTrue(breaker.isClosed()); // When / Then testRunFailure(() -> { resetBreaker(breaker); }, Failsafe.with(breaker, timeout), ctx -> { System.out.println("Executing"); Thread.sleep(100); }, TimeoutExceededException.class); assertTrue(breaker.isOpen()); } /** * Fallback -> Timeout */ public void testFallbackTimeout() { // Given Fallback fallback = Fallback.of(e -> { assertTrue(e.getLastException() instanceof TimeoutExceededException); return false; }); Timeout timeout = Timeout.of(Duration.ofMillis(10)); // When / Then testGetSuccess(false, Failsafe.with(fallback, timeout), ctx -> { Thread.sleep(100); return true; }, false); } /** * RetryPolicy -> RateLimiter */ public void testRetryPolicyRateLimiter() { Stats rpStats = new Stats(); Stats rlStats = new Stats(); RetryPolicy rp = withStatsAndLogs(RetryPolicy.builder().withMaxAttempts(7), rpStats).build(); RateLimiter rl = withStatsAndLogs(RateLimiter.burstyBuilder(3, Duration.ofSeconds(1)), rlStats).build(); testRunFailure(() -> { rpStats.reset(); rlStats.reset(); resetLimiter(rl); }, Failsafe.with(rp, rl), ctx -> { System.out.println("Executing"); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 7); assertEquals(e.getExecutionCount(), 3); assertEquals(rpStats.failedAttemptCount, 7); assertEquals(rpStats.retryCount, 6); }, RateLimitExceededException.class); } /** * RetryPolicy -> Bulkhead */ public void testRetryPolicyBulkhead() { Stats rpStats = new Stats(); Stats rlStats = new Stats(); RetryPolicy rp = withStatsAndLogs(RetryPolicy.builder().withMaxAttempts(7), rpStats).build(); Bulkhead bh = withStatsAndLogs(Bulkhead.builder(2), rlStats).build(); bh.tryAcquirePermit(); bh.tryAcquirePermit(); // bulkhead should be full testRunFailure(() -> { rpStats.reset(); rlStats.reset(); }, Failsafe.with(rp, bh), ctx -> { System.out.println("Executing"); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 7); assertEquals(e.getExecutionCount(), 0); assertEquals(rpStats.failedAttemptCount, 7); assertEquals(rpStats.retryCount, 6); }, BulkheadFullException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/RateLimiterTest.java000066400000000000000000000066221444561050700324010ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.Failsafe; import dev.failsafe.RateLimitExceededException; import dev.failsafe.RateLimiter; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import static dev.failsafe.internal.InternalTesting.resetLimiter; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; /** * Tests various RateLimiter scenarios. */ @Test public class RateLimiterTest extends Testing { public void testReservePermit() { // Given RateLimiter limiter = RateLimiter.smoothBuilder(Duration.ofMillis(100)).build(); // When / Then assertEquals(limiter.reservePermit(), Duration.ZERO); assertTrue(limiter.reservePermit().toMillis() > 0); assertTrue(limiter.reservePermit().toMillis() > 100); } public void testTryReservePermit() { // Given RateLimiter limiter = RateLimiter.smoothBuilder(Duration.ofMillis(100)).build(); // When / Then assertEquals(limiter.tryReservePermit(Duration.ofMillis(1)), Duration.ZERO); assertEquals(limiter.tryReservePermit(Duration.ofMillis(10)), Duration.ofNanos(-1)); assertTrue(limiter.tryReservePermit(Duration.ofMillis(100)).toMillis() > 0); assertTrue(limiter.tryReservePermit(Duration.ofMillis(200)).toMillis() > 100); assertEquals(limiter.tryReservePermit(Duration.ofMillis(100)), Duration.ofNanos(-1)); } public void testPermitAcquiredAfterWait() { // Given RateLimiter limiter = RateLimiter.smoothBuilder(Duration.ofMillis(50)) .withMaxWaitTime(Duration.ofSeconds(1)) .build(); // When / Then testGetSuccess(() -> { resetLimiter(limiter); limiter.tryAcquirePermit(); // limiter should now be out of permits }, Failsafe.with(limiter), ctx -> { return "test"; }, "test"); } public void shouldThrowRateLimitExceededExceptionAfterPermitsExceeded() { // Given RateLimiter limiter = RateLimiter.smoothBuilder(Duration.ofMillis(100)).build(); // When / Then testRunFailure(() -> { resetLimiter(limiter); limiter.tryAcquirePermit(); // limiter should now be out of permits }, Failsafe.with(limiter), ctx -> { }, RateLimitExceededException.class); } /** * Asserts that an exceeded maxWaitTime causes RateLimitExceededException. */ public void testMaxWaitTimeExceeded() { // Given RateLimiter limiter = RateLimiter.smoothBuilder(Duration.ofMillis(10)).build(); // When / Then testRunFailure(() -> { resetLimiter(limiter); runAsync(() -> { limiter.tryAcquirePermits(50, Duration.ofMinutes(1)); // limiter should now be well over its max permits }); Thread.sleep(100); }, Failsafe.with(limiter), ctx -> { }, RateLimitExceededException.class); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/RetryPolicyTest.java000066400000000000000000000061251444561050700324430ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.assertEquals; @Test public class RetryPolicyTest extends Testing { /** * Tests a simple execution that does not retry. */ public void shouldNotRetry() { testGetSuccess(Failsafe.with(RetryPolicy.ofDefaults()), ctx -> { return true; }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, true); } /** * Asserts that a non-handled exception does not trigger retries. */ public void shouldThrowOnNonRetriableFailure() { // Given RetryPolicy retryPolicy = RetryPolicy.builder() .withMaxRetries(-1) .handle(IllegalStateException.class) .build(); // When / Then testRunFailure(Failsafe.with(retryPolicy), ctx -> { if (ctx.getAttemptCount() < 2) throw new IllegalStateException(); throw new IllegalArgumentException(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); }, IllegalArgumentException.class); } /** * Asserts that an execution is failed when the max duration is exceeded. */ public void shouldCompleteWhenMaxDurationExceeded() { Stats stats = new Stats(); RetryPolicy retryPolicy = withStats( RetryPolicy.builder().handleResult(false).withMaxDuration(Duration.ofMillis(100)), stats).build(); testGetSuccess(() -> { stats.reset(); }, Failsafe.with(retryPolicy), ctx -> { Testing.sleep(120); return false; }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(stats.failureCount, 1); }, false); } /** * Asserts that the ExecutionScheduledEvent.getDelay is as expected. */ public void assertScheduledRetryDelay() throws Throwable { // Given Waiter waiter = new Waiter(); RetryPolicy rp = RetryPolicy.builder().withDelay(Duration.ofMillis(10)).onRetryScheduled(e -> { waiter.assertEquals(e.getDelay().toMillis(), 10L); waiter.resume(); }).build(); // Sync when / then ignoreExceptions(() -> Failsafe.with(rp).run(() -> { throw new IllegalStateException(); })); waiter.await(1000); // Async when / then Failsafe.with(rp).runAsync(() -> { throw new IllegalStateException(); }); waiter.await(1000); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/ShutdownExecutorTest.java000066400000000000000000000121071444561050700335050ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import static dev.failsafe.testing.Asserts.assertThrows; import static org.testng.Assert.assertEquals; /** * Tests the handling of an executor service that is shutdown. */ @Test public class ShutdownExecutorTest { Waiter waiter; @BeforeMethod protected void beforeMethod() { waiter = new Waiter(); } /** * Asserts that Failsafe handles an initial scheduling failure due to an executor being shutdown. */ public void shouldHandleInitialSchedulingFailure() { // Given ExecutorService executor = Executors.newFixedThreadPool(1); executor.shutdownNow(); // When Future future = Failsafe.with(Fallback.of(false), RetryPolicy.ofDefaults(), CircuitBreaker.ofDefaults()) .with(executor) .runAsync(() -> waiter.fail("Should not execute supplier since executor has been shutdown")); assertThrows(() -> future.get(1000, TimeUnit.SECONDS), ExecutionException.class, RejectedExecutionException.class); } /** * Asserts that an ExecutorService shutdown() will leave current tasks running while preventing new tasks. */ public void shouldHandleShutdown() throws Throwable { // Given ExecutorService executor = Executors.newSingleThreadExecutor(); AtomicInteger counter = new AtomicInteger(); // When Future future = Failsafe.with(RetryPolicy.ofDefaults()).with(executor).getAsync(() -> { Thread.sleep(200); counter.incrementAndGet(); return "success"; }); Thread.sleep(100); executor.shutdown(); assertEquals("success", future.get()); assertEquals(counter.get(), 1, "Supplier should have completed execution before executor was shutdown"); future = Failsafe.with(RetryPolicy.ofDefaults()).with(executor).getAsync(() -> "test"); assertThrows(future::get, ExecutionException.class, RejectedExecutionException.class); } /** * Asserts that an ExecutorService shutdown() will interrupt current tasks running and prevent new tasks. */ public void shouldHandleShutdownNow() throws Throwable { // Given ExecutorService executor = Executors.newSingleThreadExecutor(); AtomicInteger counter = new AtomicInteger(); // When Future future = Failsafe.with(RetryPolicy.ofDefaults()).with(executor).runAsync(() -> { Thread.sleep(200); counter.incrementAndGet(); }); Thread.sleep(100); executor.shutdownNow(); assertThrows(future::get, ExecutionException.class, RejectedExecutionException.class); assertEquals(counter.get(), 0, "Supplier should have been interrupted after executor shutdownNow"); } /** * Asserts that an ExecutorService shutdown() will not prevent internally scheduled Timeout tasks from cancelling a * sync execution. */ public void testShutdownDoesNotPreventTimeoutSync() { // Given ExecutorService executor = Executors.newSingleThreadExecutor(); Timeout timeout = Timeout.builder(Duration.ofMillis(200)).withInterrupt().build(); AtomicInteger counter = new AtomicInteger(); // When / then assertThrows(() -> Failsafe.with(timeout).with(executor).run(() -> { Thread.sleep(500); counter.incrementAndGet(); }), TimeoutExceededException.class); Testing.runAsync(() -> { Thread.sleep(100); executor.shutdown(); }); assertEquals(counter.get(), 0, "Supplier should have been interrupted after Timeout"); } /** * Asserts that an ExecutorService shutdown() will not prevent internally scheduled Timeout tasks from cancelling an * async execution. */ public void testShutdownDoesNotPreventTimeoutAsync() throws Throwable { // Given ExecutorService executor = Executors.newSingleThreadExecutor(); Timeout timeout = Timeout.builder(Duration.ofMillis(200)).withInterrupt().build(); AtomicInteger counter = new AtomicInteger(); // When Future future = Failsafe.with(timeout).with(executor).runAsync(() -> { Thread.sleep(500); counter.incrementAndGet(); }); Thread.sleep(100); executor.shutdown(); // Then assertThrows(future::get, ExecutionException.class, TimeoutExceededException.class); assertEquals(counter.get(), 0, "Supplier should have been interrupted after Timeout"); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/TimeoutTest.java000066400000000000000000000262361444561050700316110ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.functional; import dev.failsafe.*; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import static org.testng.Assert.assertEquals; /** * Tests various Timeout policy scenarios. */ @Test public class TimeoutTest extends Testing { /** * Tests a simple execution that does not timeout. */ public void shouldNotTimeout() { // Given Timeout timeout = Timeout.of(Duration.ofSeconds(1)); // When / Then testGetSuccess(Failsafe.with(timeout), ctx -> { return "success"; }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, "success"); } public void shouldCancelTimeoutWhenExecutionComplete() { // TODO } /** * Tests that an inner timeout does not prevent outer retries from being performed when the inner Supplier is * blocked. */ public void testRetryTimeoutWithBlockedSupplier() { Stats timeoutStats = new Stats(); Stats rpStats = new Stats(); RetryPolicy retryPolicy = withStatsAndLogs(RetryPolicy.builder(), rpStats).build(); Consumer> test = timeout -> testGetSuccess(false, () -> { timeoutStats.reset(); rpStats.reset(); }, Failsafe.with(retryPolicy, timeout), ctx -> { if (ctx.getAttemptCount() < 2) Thread.sleep(100); return false; }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); assertEquals(timeoutStats.executionCount, 3); assertEquals(rpStats.retryCount, 2); }, false); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(50)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(50)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests that when an outer retry is scheduled any inner timeouts are cancelled. This prevents the timeout from * accidentally cancelling a scheduled retry that may be pending. */ public void testRetryTimeoutWithPendingRetry() { Stats timeoutStats = new Stats(); RetryPolicy retryPolicy = withLogs(RetryPolicy.builder().withDelay(Duration.ofMillis(100))).build(); Consumer> test = timeout -> testRunFailure(() -> { timeoutStats.reset(); }, Failsafe.with(retryPolicy, timeout), ctx -> { throw new IllegalStateException(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); assertEquals(timeoutStats.successCount, 3); assertEquals(timeoutStats.failureCount, 0); }, IllegalStateException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests that an outer timeout will cancel inner retries when the inner Supplier is blocked. The flow should be: *

*
Execution that retries a few times, blocking each time *
Timeout */ public void testTimeoutRetryWithBlockedSupplier() { Stats timeoutStats = new Stats(); RetryPolicy retryPolicy = RetryPolicy.ofDefaults(); Consumer> test = timeout -> testRunFailure(() -> { timeoutStats.reset(); }, Failsafe.with(timeout, retryPolicy), ctx -> { System.out.println("Executing"); Thread.sleep(60); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); assertEquals(timeoutStats.failureCount, 1); }, TimeoutExceededException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(150)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(150)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests that an outer timeout will cancel inner retries when an inner retry is pending. The flow should be: *

*
Execution *
Retry sleep/scheduled that blocks *
Timeout */ public void testTimeoutRetryWithPendingRetry() { Stats timeoutStats = new Stats(); Stats rpStats = new Stats(); RetryPolicy retryPolicy = withStatsAndLogs(RetryPolicy.builder().withDelay(Duration.ofMillis(1000)), rpStats).build(); Consumer> test = timeout -> testRunFailure(false, () -> { timeoutStats.reset(); rpStats.reset(); }, Failsafe.with(timeout).compose(retryPolicy), ctx -> { System.out.println("Executing"); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(timeoutStats.failureCount, 1); assertEquals(rpStats.failedAttemptCount, 1); }, TimeoutExceededException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests an inner timeout that fires while the supplier is blocked. */ public void testFallbackTimeoutWithBlockedSupplier() { Stats timeoutStats = new Stats(); Stats fbStats = new Stats(); Fallback fallback = withStatsAndLogs(Fallback.builder(() -> { System.out.println("Falling back"); throw new IllegalStateException(); }), fbStats).build(); Consumer> test = timeout -> testRunFailure(false, () -> { timeoutStats.reset(); fbStats.reset(); }, Failsafe.with(fallback).compose(timeout), ctx -> { System.out.println("Executing"); Thread.sleep(100); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(timeoutStats.failureCount, 1); assertEquals(fbStats.executionCount, 1); }, IllegalStateException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(10)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(10)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests that an inner timeout will not interrupt an outer fallback. The inner timeout is never triggered since the * supplier completes immediately. */ public void testFallbackTimeout() { Stats timeoutStats = new Stats(); Stats fbStats = new Stats(); Fallback fallback = withStatsAndLogs(Fallback.builder(() -> { System.out.println("Falling back"); throw new IllegalStateException(); }), fbStats).build(); Consumer> test = timeout -> testRunFailure(() -> { timeoutStats.reset(); fbStats.reset(); }, Failsafe.with(fallback).compose(timeout), ctx -> { System.out.println("Executing"); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(timeoutStats.failureCount, 0); assertEquals(fbStats.executionCount, 1); }, IllegalStateException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests that an outer timeout will interrupt an inner supplier that is blocked, skipping the inner fallback. */ public void testTimeoutFallbackWithBlockedSupplier() { Stats timeoutStats = new Stats(); Stats fbStats = new Stats(); Fallback fallback = withStatsAndLogs(Fallback.builder(() -> { System.out.println("Falling back"); throw new IllegalStateException(); }), fbStats).build(); Consumer> test = timeout -> testRunFailure(false, () -> { timeoutStats.reset(); fbStats.reset(); }, Failsafe.with(timeout).compose(fallback), ctx -> { System.out.println("Executing"); Thread.sleep(100); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(timeoutStats.failureCount, 1); assertEquals(fbStats.executionCount, 0); }, TimeoutExceededException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(1)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(1)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } /** * Tests that an outer timeout will interrupt an inner fallback that is blocked. */ public void testTimeoutFallbackWithBlockedFallback() { Stats timeoutStats = new Stats(); Stats fbStats = new Stats(); Fallback fallback = withStatsAndLogs(Fallback.builder(() -> { System.out.println("Falling back"); Thread.sleep(200); throw new IllegalStateException(); }), fbStats).build(); Consumer> test = timeout -> testRunFailure(false, () -> { timeoutStats.reset(); fbStats.reset(); }, Failsafe.with(timeout).compose(fallback), ctx -> { System.out.println("Executing"); throw new Exception(); }, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); assertEquals(timeoutStats.failureCount, 1); assertEquals(fbStats.executionCount, 1); }, TimeoutExceededException.class); // Test without interrupt Timeout timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)), timeoutStats).build(); test.accept(timeout); // Test with interrupt timeout = withStatsAndLogs(Timeout.builder(Duration.ofMillis(100)).withInterrupt(), timeoutStats).build(); test.accept(timeout); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/functional/package-info.java000066400000000000000000000003061444561050700316350ustar00rootroot00000000000000/** * Functional tests for sets of behavior. When possible, each behavior will be tested against sync and various async * executions with the same assertions. */ package dev.failsafe.functional; failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/000077500000000000000000000000001444561050700261215ustar00rootroot00000000000000BurstyRateLimiterStatsTest.java000066400000000000000000000066211444561050700342230ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimiter; import dev.failsafe.RateLimiterConfig; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.assertEquals; @Test public class BurstyRateLimiterStatsTest extends RateLimiterStatsTest { @Override BurstyRateLimiterStats createStats() { RateLimiterConfig config = RateLimiter.burstyBuilder(2, Duration.ofSeconds(1)).build().getConfig(); return new BurstyRateLimiterStats(config, stopwatch); } BurstyRateLimiterStats createStats(long maxPermits, Duration period) { RateLimiterConfig config = RateLimiter.burstyBuilder(maxPermits, period).build().getConfig(); return new BurstyRateLimiterStats(config, stopwatch); } /** * Asserts that wait times and available permits are expected, over time, when calling acquirePermits. */ public void testAcquirePermits() { // Given 2 max permits per second BurstyRateLimiterStats stats = createStats(2, Duration.ofSeconds(1)); assertEquals(acquire(stats, 1, 7), 3000); assertEquals(stats.getAvailablePermits(), -5); assertEquals(stats.getCurrentPeriod(), 0); stopwatch.set(800); assertEquals(acquire(stats, 3), 3200); assertEquals(stats.getAvailablePermits(), -8); assertEquals(stats.getCurrentPeriod(), 0); stopwatch.set(2300); assertEquals(acquire(stats, 1), 2700); assertEquals(stats.getAvailablePermits(), -5); assertEquals(stats.getCurrentPeriod(), 2); stopwatch.set(3500); assertEquals(acquire(stats, 1, 3), 2500); assertEquals((stats.getAvailablePermits()), -6); assertEquals(stats.getCurrentPeriod(), 3); stopwatch.set(7000); assertEquals(acquire(stats, 1), 0); assertEquals((stats.getAvailablePermits()), 1); assertEquals(stats.getCurrentPeriod(), 7); // Given 5 max permits per second stopwatch = new TestStopwatch(); stats = createStats(5, Duration.ofSeconds(1)); stopwatch.set(300); assertEquals(acquire(stats, 3), 0); assertEquals(stats.getAvailablePermits(), 2); assertEquals(stats.getCurrentPeriod(), 0); stopwatch.set(1550); assertEquals(acquire(stats, 10), 450); assertEquals(stats.getAvailablePermits(), -5); assertEquals(stats.getCurrentPeriod(), 1); stopwatch.set(2210); assertEquals(acquire(stats, 2), 790); // Must wait till next period assertEquals(stats.getAvailablePermits(), -2); assertEquals(stats.getCurrentPeriod(), 2); } @Override void printInfo(BurstyRateLimiterStats stats, long waitMillis) { System.out.printf("[%s] elapsedMillis: %5s, availablePermits: %2s, currentPeriod: %s, waitMillis: %s%n", Thread.currentThread().getName(), stats.getElapsed().toMillis(), stats.getAvailablePermits(), stats.getCurrentPeriod(), waitMillis); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/CircuitStatsTest.java000066400000000000000000000025251444561050700322510ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import org.testng.annotations.Test; import java.util.function.Predicate; @Test public abstract class CircuitStatsTest { protected static void recordSuccesses(T stats, int count) { for (int i = 0; i < count; i++) stats.recordSuccess(); } protected static void recordFailures(T stats, int count) { for (int i = 0; i < count; i++) stats.recordFailure(); } protected static void recordExecutions(T stats, int count, Predicate successPredicate) { for (int i = 0; i < count; i++) { if (successPredicate.test(i)) stats.recordSuccess(); else stats.recordFailure(); } } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/ClosedStateTest.java000066400000000000000000000123761444561050700320470ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import org.testng.annotations.Test; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class ClosedStateTest { /** * Asserts that the circuit is opened after a single failure. */ public void testFailureWithDefaultConfig() { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.ofDefaults(); breaker.close(); ClosedState state = new ClosedState<>(breaker); assertFalse(breaker.isOpen()); // When state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the failure ratio is met. */ public void testFailureWithFailureRatio() { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withFailureThreshold(2, 3) .build(); breaker.close(); ClosedState state = new ClosedState<>(breaker); // When state.recordFailure(null); state.recordSuccess(); assertTrue(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the failure threshold is met. */ public void testFailureWithFailureThreshold() { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withFailureThreshold(3) .build(); breaker.close(); ClosedState state = new ClosedState<>(breaker); // When state.recordFailure(null); state.recordSuccess(); state.recordFailure(null); state.recordFailure(null); assertTrue(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is still closed after a single success. */ public void testSuccessWithDefaultConfig() { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.ofDefaults(); breaker.close(); ClosedState state = new ClosedState<>(breaker); assertTrue(breaker.isClosed()); // When state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit stays closed after the failure ratio fails to be met. */ public void testSuccessWithFailureRatio() { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withFailureThreshold(3, 4) .build(); breaker.close(); ClosedState state = new ClosedState<>(breaker); assertTrue(breaker.isClosed()); // When / Then for (int i = 0; i < 20; i++) { state.recordSuccess(); state.recordFailure(null); assertTrue(breaker.isClosed()); } } /** * Asserts that the circuit stays closed after the failure ratio fails to be met. */ public void testSuccessWithFailureThreshold() { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withFailureThreshold(2) .build(); breaker.close(); ClosedState state = new ClosedState<>(breaker); assertTrue(breaker.isClosed()); // When / Then for (int i = 0; i < 20; i++) { state.recordSuccess(); state.recordFailure(null); assertTrue(breaker.isClosed()); } } // Disabled for now since thresholds are not dynamically configurable in 3.0, but may be again in future versions. // /** // * Asserts that the late configuration of a failure ratio is handled by resetting the state's internal tracking. Also // * asserts that executions from prior configurations are carried over to a new configuration. // */ // public void shouldHandleLateSetFailureRatio() { // // Given // CircuitBreaker breaker = CircuitBreaker.ofDefaults(); // ClosedState state = Testing.stateFor(breaker); // // // When // state.recordSuccess(); // assertTrue(breaker.isClosed()); // breaker.withFailureThreshold(2); // state.recordFailure(null); // assertTrue(breaker.isClosed()); // state.recordFailure(null); // // // Then // assertTrue(breaker.isOpen()); // // // Given // breaker = new CircuitBreaker<>(); // state = Testing.stateFor(breaker); // // // When // state.recordSuccess(); // assertTrue(breaker.isClosed()); // breaker.withFailureThreshold(2, 3); // state.recordFailure(null); // assertTrue(breaker.isClosed()); // state.recordFailure(null); // // // Then // assertTrue(breaker.isOpen()); // } } CountingCircuitStatsTest.java000066400000000000000000000076701444561050700337070ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internalpackage dev.failsafe.internal; import dev.failsafe.internal.TimedCircuitStatsTest.TestClock; import org.testng.annotations.Test; import java.time.Duration; import java.util.Arrays; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class CountingCircuitStatsTest extends CircuitStatsTest { CountingCircuitStats stats; public void shouldReturnUninitializedValues() { stats = new CountingCircuitStats(100, null); for (int i = 0; i < 100; i++) { assertEquals(stats.setNext(true), -1); } assertEquals(stats.setNext(true), 1); assertEquals(stats.setNext(true), 1); } public void testMetrics() { stats = new CountingCircuitStats(100, null); assertEquals(stats.getSuccessRate(), 0); assertEquals(stats.getFailureRate(), 0); assertEquals(stats.getExecutionCount(), 0); recordExecutions(stats, 50, i -> i % 3 == 0); assertEquals(stats.getSuccessCount(), 17); assertEquals(stats.getSuccessRate(), 34); assertEquals(stats.getFailureCount(), 33); assertEquals(stats.getFailureRate(), 66); assertEquals(stats.getExecutionCount(), 50); recordSuccesses(stats, 100); assertEquals(stats.getSuccessCount(), 100); assertEquals(stats.getSuccessRate(), 100); assertEquals(stats.getFailureCount(), 0); assertEquals(stats.getFailureRate(), 0); assertEquals(stats.getExecutionCount(), 100); } public void testCopyToEqualSizedStats() { stats = new CountingCircuitStats(5, null); recordSuccesses(stats, 2); recordFailures(stats, 3); stats.currentIndex = 0; CountingCircuitStats right = new CountingCircuitStats(5, stats); assertValues(right, true, true, false, false, false); stats.currentIndex = 2; right = new CountingCircuitStats(5, stats); assertValues(right, false, false, false, true, true); stats.currentIndex = 4; right = new CountingCircuitStats(5, stats); assertValues(right, false, true, true, false, false); } public void testCopyToSmallerStats() { stats = new CountingCircuitStats(10, null); recordSuccesses(stats, 5); recordFailures(stats, 5); stats.currentIndex = 0; CountingCircuitStats right = new CountingCircuitStats(4, stats); assertValues(right, false, false, false, false); stats.currentIndex = 2; right = new CountingCircuitStats(4, stats); assertValues(right, false, false, true, true); stats.currentIndex = 7; right = new CountingCircuitStats(4, stats); assertValues(right, true, true, false, false); } public void testCopyToLargerStats() { stats = new CountingCircuitStats(5, null); recordSuccesses(stats, 2); recordFailures(stats, 3); stats.currentIndex = 0; CountingCircuitStats right = new CountingCircuitStats(6, stats); assertValues(right, true, true, false, false, false); stats.currentIndex = 2; right = new CountingCircuitStats(6, stats); assertValues(right, false, false, false, true, true); stats.currentIndex = 4; right = new CountingCircuitStats(6, stats); assertValues(right, false, true, true, false, false); } public void testCopyFromTimedStats() { TestClock clock = new TestClock(); TimedCircuitStats timedStats = new TimedCircuitStats(4, Duration.ofSeconds(4), clock, null); recordSuccesses(timedStats, 3); clock.set(1200); recordFailures(timedStats, 5); stats = new CountingCircuitStats(10, timedStats); assertValues(stats, true, true, true, false, false, false, false, false); } private static boolean[] valuesFor(CountingCircuitStats stats) { boolean[] values = new boolean[stats.getExecutionCount()]; for (int i = 0; i < values.length; i++) values[i] = stats.bitSet.get(i); return values; } private static void assertValues(CountingCircuitStats bs, boolean... right) { boolean[] left = valuesFor(bs); assertTrue(Arrays.equals(left, right), Arrays.toString(left) + " != " + Arrays.toString(right)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/HalfOpenStateTest.java000066400000000000000000000326631444561050700323330ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import static dev.failsafe.internal.InternalTesting.stateFor; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class HalfOpenStateTest { /** * Asserts that the circuit is opened after a single failure. */ public void testFailureWithDefaultConfig() { // Given CircuitBreaker breaker = CircuitBreaker.ofDefaults(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); // When state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the failure threshold is met. */ public void testFailureWithFailureThreshold() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withFailureThreshold(3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When for (int i = 0; i < 3; i++) { assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordFailure(null); } // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the failure ratio is met. */ public void testFailureWithFailureRatio() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withFailureThreshold(2, 3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); // When state.recordFailure(null); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after a single failure. The failure threshold is ignored. */ public void testFailureWithSuccessAndFailureThresholds() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).withFailureThreshold(2).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordSuccess(); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the success ratio fails to be met. The failure ratio is ignored. */ public void testFailureWithSuccessAndFailureRatios() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(3, 5) .withSuccessThreshold(3, 4) .build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordSuccess(); state.recordFailure(null); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the success ratio fails to be met. */ public void testFailureWithSuccessRatio() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(2, 3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); // When state.recordFailure(null); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after the success ratio fails to be met. The failure threshold is ignored. */ public void testFailureWithSuccessRatioAndFailureThreshold() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withSuccessThreshold(2, 4) .withFailureThreshold(1) .build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordSuccess(); state.recordFailure(null); state.recordFailure(null); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after a single failure. */ public void testFailureWithSuccessThreshold() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); // When state.recordSuccess(); state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is opened after a single failure. */ public void testFailureWithSuccessThresholdAndFailureRatio() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(3, 5) .withSuccessThreshold(3) .build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); // When state.recordFailure(null); // Then assertTrue(breaker.isOpen()); } /** * Asserts that the circuit is closed after a single success. */ public void testSuccessWithDefaultConfig() { // Given CircuitBreaker breaker = CircuitBreaker.ofDefaults(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); // When state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the failure ratio fails to be met. */ public void testSuccessWithFailureRatio() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withFailureThreshold(2, 3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordFailure(null); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the the circuit is closed after a single success. */ public void testSuccessWithFailureThreshold() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withFailureThreshold(3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); // When state.recordFailure(null); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the success ratio is met. The failure ratio is ignored. */ public void testSuccessWithSuccessAndFailureRatios() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(3, 5) .withSuccessThreshold(3, 4) .build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordSuccess(); state.recordFailure(null); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the success threshold is met. The failure threshold is ignored. */ public void testSuccessWithSuccessAndFailureThresholds() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).withFailureThreshold(2).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordSuccess(); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the success ratio is met. */ public void testSuccessWithSuccessRatio() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(2, 3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordFailure(null); state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the success ratio is met. The failure threshold is ignored. */ public void testSuccessWithSuccessRatioAndFailureThreshold() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withSuccessThreshold(3, 4) .withFailureThreshold(2) .build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When state.recordSuccess(); state.recordSuccess(); state.recordFailure(null); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the success threshold is met. */ public void testSuccessWithSuccessThreshold() { // Given CircuitBreaker breaker = CircuitBreaker.builder().withSuccessThreshold(3).build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When for (int i = 0; i < 3; i++) { assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); } // Then assertTrue(breaker.isClosed()); } /** * Asserts that the circuit is closed after the success threshold is met. The failure ratio is ignored. */ public void testSuccessWithSuccessThresholdAndFailureRatio() { // Given CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(3, 5) .withSuccessThreshold(2) .build(); breaker.halfOpen(); HalfOpenState state = stateFor(breaker); // When success threshold exceeded state.recordSuccess(); assertFalse(breaker.isOpen()); assertFalse(breaker.isClosed()); state.recordSuccess(); // Then assertTrue(breaker.isClosed()); } // Disabled for now since thresholds are not dynamically configurable in 3.0, but may be again in future versions. // /** // * Asserts that the late configuration of a failure ratio is handled by resetting the state's internal tracking. Also // * asserts that executions from prior configurations are carried over to a new configuration. // */ // public void shouldHandleLateSetFailureRatio() { // // Given // CircuitBreaker breaker = CircuitBreaker.ofDefaults(); // breaker.halfOpen(); // HalfOpenState state = Testing.stateFor(breaker); // // // When // breaker.withFailureThreshold(2); // state.recordFailure(null); // assertTrue(breaker.isHalfOpen()); // state.recordFailure(null); // // // Then // assertTrue(breaker.isOpen()); // // // Given // breaker = new CircuitBreaker<>().withFailureThreshold(3); // breaker.halfOpen(); // state = Testing.stateFor(breaker); // // // When // state.recordFailure(null); // state.recordFailure(null); // breaker.withFailureThreshold(3, 5); // state.recordSuccess(); // state.recordSuccess(); // assertTrue(breaker.isHalfOpen()); // state.recordFailure(null); // // // Then // assertTrue(breaker.isOpen()); // } // // /** // * Asserts that the late configuration of a success ratio is handled by resetting the state's internal tracking. Also // * asserts that executions from prior configurations are carried over to a new configuration. // */ // public void shouldHandleLateSetSuccessRatio() { // // Given // CircuitBreaker breaker = CircuitBreaker.ofDefaults(); // breaker.halfOpen(); // HalfOpenState state = Testing.stateFor(breaker); // // // When // breaker.withSuccessThreshold(2); // state.recordSuccess(); // assertTrue(breaker.isHalfOpen()); // state.recordSuccess(); // // // Then // assertTrue(breaker.isClosed()); // // // Given // breaker = new CircuitBreaker<>().withFailureThreshold(3); // breaker.halfOpen(); // state = Testing.stateFor(breaker); // // // When // state.recordFailure(null); // state.recordFailure(null); // breaker.withSuccessThreshold(2, 4); // state.recordSuccess(); // assertTrue(breaker.isHalfOpen()); // state.recordSuccess(); // // // Then // assertTrue(breaker.isClosed()); // } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/InternalTesting.java000066400000000000000000000024431444561050700321010ustar00rootroot00000000000000package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import dev.failsafe.RateLimiter; import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicReference; public final class InternalTesting { private InternalTesting() { } @SuppressWarnings("unchecked") public static > T stateFor(CircuitBreaker breaker) { Field stateField; try { stateField = CircuitBreakerImpl.class.getDeclaredField("state"); stateField.setAccessible(true); return ((AtomicReference) stateField.get(breaker)).get(); } catch (Exception e) { throw new IllegalStateException("Could not get circuit breaker state"); } } public static void resetBreaker(CircuitBreaker breaker) { breaker.close(); CircuitState state = stateFor(breaker); state.getStats().reset(); } public static void resetLimiter(RateLimiter limiter) { try { RateLimiterImpl impl = (RateLimiterImpl) limiter; Field statsField = RateLimiterImpl.class.getDeclaredField("stats"); statsField.setAccessible(true); RateLimiterStats stats = (RateLimiterStats) statsField.get(impl); stats.reset(); } catch (Exception e) { throw new IllegalStateException("Could not reset rate limiter"); } } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/OpenStateTest.java000066400000000000000000000051231444561050700315270ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.CircuitBreaker; import dev.failsafe.CircuitBreaker.State; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.*; @Test public class OpenStateTest { public void testTryAcquirePermit() throws Throwable { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withDelay(Duration.ofMillis(100)) .build(); breaker.open(); OpenState state = new OpenState<>(breaker, new ClosedState<>(breaker), breaker.getConfig().getDelay()); assertTrue(breaker.isOpen()); assertFalse(state.tryAcquirePermit()); // When Thread.sleep(110); // Then assertTrue(state.tryAcquirePermit()); assertEquals(breaker.getState(), State.HALF_OPEN); } public void testRemainingDelay() throws Throwable { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withDelay(Duration.ofSeconds(1)) .build(); OpenState state = new OpenState<>(breaker, new ClosedState<>(breaker), breaker.getConfig().getDelay()); // When / Then long remainingDelayMillis = state.getRemainingDelay().toMillis(); assertTrue(remainingDelayMillis < 1000); assertTrue(remainingDelayMillis > 0); Thread.sleep(110); remainingDelayMillis = state.getRemainingDelay().toMillis(); assertTrue(remainingDelayMillis < 900); assertTrue(remainingDelayMillis > 0); } public void testNoRemainingDelay() throws Throwable { // Given CircuitBreakerImpl breaker = (CircuitBreakerImpl) CircuitBreaker.builder() .withDelay(Duration.ofMillis(10)) .build(); assertEquals(breaker.getRemainingDelay(), Duration.ZERO); // When OpenState state = new OpenState<>(breaker, new ClosedState<>(breaker), breaker.getConfig().getDelay()); Thread.sleep(50); // Then assertEquals(state.getRemainingDelay().toMillis(), 0); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/RateLimiterImplTest.java000066400000000000000000000117221444561050700326720ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimitExceededException; import dev.failsafe.RateLimiter; import dev.failsafe.internal.RateLimiterStatsTest.TestStopwatch; import dev.failsafe.testing.Testing; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class RateLimiterImplTest extends Testing { static RateLimiter smoothLimiter = RateLimiter.smoothBuilder(Duration.ofMillis(500)).build(); static RateLimiter burstyLimiter = RateLimiter.burstyBuilder(2, Duration.ofSeconds(1)).build(); TestStopwatch stopwatch; @BeforeMethod protected void beforeMethod() { stopwatch = new TestStopwatch(); } public void testIsSmooth() { assertTrue(smoothLimiter.isSmooth()); assertFalse(smoothLimiter.isBursty()); } public void testIsBursty() { assertTrue(burstyLimiter.isBursty()); assertFalse(burstyLimiter.isSmooth()); } public void testAcquirePermit() { RateLimiterImpl limiter = new RateLimiterImpl<>( RateLimiter.smoothBuilder(Duration.ofMillis(100)).build().getConfig(), stopwatch); long elapsed = timed(() -> { limiter.acquirePermit(); // waits 0 limiter.acquirePermit(); // waits 100 limiter.acquirePermit(); // waits 200 }); assertTrue(elapsed >= 300 && elapsed <= 400); } public void testAcquirePermitWithInterrupt() { RateLimiterImpl limiter = new RateLimiterImpl<>( RateLimiter.smoothBuilder(Duration.ofMillis(1000)).build().getConfig(), stopwatch); Thread thread = Thread.currentThread(); runInThread(() -> { Thread.sleep(100); thread.interrupt(); }); long elapsed = timed(() -> assertThrows(() -> { limiter.acquirePermit(); // waits 0 limiter.acquirePermit(); // waits 1000 }, InterruptedException.class)); assertTrue(elapsed < 1000); } public void testAcquireWithMaxWaitTime() throws Throwable { RateLimiterImpl limiter = new RateLimiterImpl<>( RateLimiter.smoothBuilder(Duration.ofMillis(100)).build().getConfig(), stopwatch); limiter.acquirePermit(Duration.ofMillis(100)); // waits 0 limiter.acquirePermit(Duration.ofMillis(1000)); // waits 100 assertThrows(() -> { limiter.acquirePermit(Duration.ofMillis(100)); // waits 200 }, RateLimitExceededException.class); } public void testTryAcquirePermit() { RateLimiterImpl limiter = new RateLimiterImpl<>( RateLimiter.smoothBuilder(Duration.ofMillis(100)).build().getConfig(), stopwatch); assertTrue(limiter.tryAcquirePermit()); assertFalse(limiter.tryAcquirePermit()); stopwatch.set(150); assertTrue(limiter.tryAcquirePermit()); assertFalse(limiter.tryAcquirePermit()); stopwatch.set(210); assertTrue(limiter.tryAcquirePermit()); assertFalse(limiter.tryAcquirePermit()); } public void testTryAcquirePermitWithMaxWaitTime() throws Throwable { RateLimiterImpl limiter = new RateLimiterImpl<>( RateLimiter.smoothBuilder(Duration.ofMillis(100)).build().getConfig(), stopwatch); assertTrue(limiter.tryAcquirePermit(Duration.ofMillis(50))); assertFalse(limiter.tryAcquirePermit(Duration.ofMillis(50))); long elapsed = timed(() -> assertTrue(limiter.tryAcquirePermit(Duration.ofMillis(100)))); assertTrue(elapsed >= 100 && elapsed < 200); stopwatch.set(200); assertTrue(limiter.tryAcquirePermit(Duration.ofMillis(50))); assertFalse(limiter.tryAcquirePermit(Duration.ofMillis(50))); elapsed = timed(() -> assertTrue(limiter.tryAcquirePermit(Duration.ofMillis(100)))); assertTrue(elapsed >= 100 && elapsed < 200); } public void testTryAcquirePermitsWithMaxWaitTime() throws Throwable { RateLimiterImpl limiter = new RateLimiterImpl<>( RateLimiter.smoothBuilder(Duration.ofMillis(100)).build().getConfig(), stopwatch); assertFalse(limiter.tryAcquirePermits(2, Duration.ofMillis(50))); long elapsed = timed(() -> assertTrue(limiter.tryAcquirePermits(2, Duration.ofMillis(100)))); assertTrue(elapsed >= 100 && elapsed < 200); stopwatch.set(450); assertFalse(limiter.tryAcquirePermits(2, Duration.ofMillis(10))); elapsed = timed(() -> assertTrue(limiter.tryAcquirePermits(2, Duration.ofMillis(300)))); assertTrue(elapsed >= 50 && elapsed < 150); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/RateLimiterStatsTest.java000066400000000000000000000062411444561050700330670ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.internal.RateLimiterStats.Stopwatch; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.testng.Assert.assertEquals; @Test public abstract class RateLimiterStatsTest { TestStopwatch stopwatch; public static class TestStopwatch extends Stopwatch { long currentTimeMillis; void set(long currentTimeMillis) { this.currentTimeMillis = currentTimeMillis; } @Override long elapsedNanos() { return TimeUnit.MILLISECONDS.toNanos(currentTimeMillis); } @Override void reset() { currentTimeMillis = 0; } } /** * Creates a stats with some arbitrary configuration. */ abstract T createStats(); @BeforeMethod protected void beforeMethod() { stopwatch = new TestStopwatch(); } /** * Asserts that a single acquirePermits call yields the same result as numerous individual acquirePermits calls. */ public void shouldAcquirePermitsEqually() { // Given T stats1 = createStats(); T stats2 = createStats(); // When making initial calls long singlsCallWaitMillis = acquire(stats1, 7); long mulipleCallsWaitMillis = acquire(stats2, 1, 7); // Then assertEquals(singlsCallWaitMillis, mulipleCallsWaitMillis); // Given stopwatch.set(2700); // When making additional calls singlsCallWaitMillis = acquire(stats1, 5); mulipleCallsWaitMillis = acquire(stats2, 1, 5); // Then assertEquals(singlsCallWaitMillis, mulipleCallsWaitMillis); } /** * Asserts that acquire on a new stats object with a single permit has zero wait time. */ public void shouldHaveZeroWaitTime() { assertEquals(acquire(createStats(), 1), 0); } /** * Acquires {@code permits}, prints out info, and returns the wait time converted to millis. */ long acquire(T stats, long permits) { return acquire(stats, permits, 1); } /** * Acquires {@code permits} {@code numberOfCalls} times, prints out info, and returns the wait time converted to * millis. */ long acquire(T stats, long permits, int numberOfCalls) { long lastResult = 0; for (int i = 0; i < numberOfCalls; i++) lastResult = stats.acquirePermits(permits, null); lastResult = toMillis(lastResult); printInfo(stats, lastResult); return lastResult; } abstract void printInfo(T stats, long waitMillis); static long toMillis(long nanos) { return TimeUnit.NANOSECONDS.toMillis(nanos); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/RetryPolicyImplTest.java000066400000000000000000000072241444561050700327400ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.io.IOException; import java.net.ConnectException; import java.util.Arrays; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class RetryPolicyImplTest { public void testIsAbortableNull() { RetryPolicyImpl policy = (RetryPolicyImpl) RetryPolicy.builder().build(); assertFalse(policy.isAbortable(null, null)); } public void testIsAbortableCompletionPredicate() { RetryPolicyImpl policy = (RetryPolicyImpl) RetryPolicy.builder() .abortIf((result, failure) -> result == "test" || failure instanceof IllegalArgumentException) .build(); assertTrue(policy.isAbortable("test", null)); assertFalse(policy.isAbortable(0, null)); assertTrue(policy.isAbortable(null, new IllegalArgumentException())); assertFalse(policy.isAbortable(null, new IllegalStateException())); } public void testIsAbortableFailurePredicate() { RetryPolicyImpl policy = (RetryPolicyImpl) RetryPolicy.builder() .abortOn(failure -> failure instanceof ConnectException) .build(); assertTrue(policy.isAbortable(null, new ConnectException())); assertFalse(policy.isAbortable(null, new IllegalArgumentException())); } public void testIsAbortablePredicate() { RetryPolicyImpl policy = (RetryPolicyImpl) RetryPolicy.builder() .abortIf(result -> result > 100) .build(); assertTrue(policy.isAbortable(110, null)); assertFalse(policy.isAbortable(50, null)); assertFalse(policy.isAbortable(50, new IllegalArgumentException())); } public void testIsAbortableFailure() { RetryPolicyImpl policy = (RetryPolicyImpl) RetryPolicy.builder().abortOn(Exception.class).build(); assertTrue(policy.isAbortable(null, new Exception())); assertTrue(policy.isAbortable(null, new IllegalArgumentException())); policy = (RetryPolicyImpl) RetryPolicy.builder() .abortOn(IllegalArgumentException.class, IOException.class) .build(); assertTrue(policy.isAbortable(null, new IllegalArgumentException())); assertTrue(policy.isAbortable(null, new IOException())); assertFalse(policy.isAbortable(null, new RuntimeException())); assertFalse(policy.isAbortable(null, new IllegalStateException())); policy = (RetryPolicyImpl) RetryPolicy.builder() .abortOn(Arrays.asList(IllegalArgumentException.class)) .build(); assertTrue(policy.isAbortable(null, new IllegalArgumentException())); assertFalse(policy.isAbortable(null, new RuntimeException())); assertFalse(policy.isAbortable(null, new IllegalStateException())); } public void testIsAbortableResult() { RetryPolicyImpl policy = (RetryPolicyImpl) RetryPolicy.builder().abortWhen(10).build(); assertTrue(policy.isAbortable(10, null)); assertFalse(policy.isAbortable(5, null)); assertFalse(policy.isAbortable(5, new IllegalArgumentException())); } } SmoothRateLimiterStatsTest.java000066400000000000000000000105131444561050700341770ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.RateLimiter; import dev.failsafe.RateLimiterConfig; import org.testng.annotations.Test; import java.time.Duration; import static org.testng.Assert.assertEquals; @Test public class SmoothRateLimiterStatsTest extends RateLimiterStatsTest { @Override SmoothRateLimiterStats createStats() { RateLimiterConfig config = RateLimiter.smoothBuilder(Duration.ofMillis(500)).build().getConfig(); return new SmoothRateLimiterStats(config, stopwatch); } SmoothRateLimiterStats createStats(Duration rate) { RateLimiterConfig config = RateLimiter.smoothBuilder(rate).build().getConfig(); return new SmoothRateLimiterStats(config, stopwatch); } /** * Asserts that wait times and available permits are expected, over time, when calling acquirePermits. */ public void testAcquirePermits() { // Given 1 per mit every 500 millis SmoothRateLimiterStats stats = createStats(Duration.ofMillis(500)); long waitMillis = acquire(stats, 1, 7); assertResults(stats, waitMillis, 3000, 3500); stopwatch.set(800); waitMillis = acquire(stats, 3); assertResults(stats, waitMillis, 3700, 5000); stopwatch.set(2300); waitMillis = acquire(stats, 1); assertResults(stats, waitMillis, 2700, 5500); stopwatch.set(3500); waitMillis = acquire(stats, 3); assertResults(stats, waitMillis, 3000, 7000); stopwatch.set(9100); waitMillis = acquire(stats, 3); assertResults(stats, waitMillis, 900, 10500); stopwatch.set(11000); waitMillis = acquire(stats, 1); assertResults(stats, waitMillis, 0, 11500); // Given 1 permit every 200 millis stopwatch = new TestStopwatch(); stats = createStats(Duration.ofMillis(200)); waitMillis = acquire(stats, 3); assertResults(stats, waitMillis, 400, 600); stopwatch.set(550); waitMillis = acquire(stats, 2); assertResults(stats, waitMillis, 250, 1000); stopwatch.set(2210); waitMillis = acquire(stats, 2); assertResults(stats, waitMillis, 190, 2600); } public void testAcquireInitialStats() { // Given 1 permit every 100 millis SmoothRateLimiterStats stats = createStats(Duration.ofMillis(100)); assertEquals(toMillis(stats.acquirePermits(1, null)), 0); assertEquals(toMillis(stats.acquirePermits(1, null)), 100); stopwatch.set(100); assertEquals(toMillis(stats.acquirePermits(1, null)), 100); assertEquals(toMillis(stats.acquirePermits(1, null)), 200); // Given 1 permit every 100 millis stats = createStats(Duration.ofMillis(100)); stopwatch.set(0); assertEquals(toMillis(stats.acquirePermits(1, null)), 0); stopwatch.set(150); assertEquals(toMillis(stats.acquirePermits(1, null)), 0); stopwatch.set(250); assertEquals(toMillis(stats.acquirePermits(2, null)), 50); } private static void assertResults(SmoothRateLimiterStats stats, long waitMillis, long expectedWaitMillis, long expectedNextFreePermitMillis) { assertEquals(waitMillis, expectedWaitMillis); assertEquals(toMillis(stats.getNextFreePermitNanos()), expectedNextFreePermitMillis); // Asserts that the nextFreePermitNanos makes sense relative to the elapsedNanos and waitMillis long computedNextFreePermitMillis = toMillis(stats.stopwatch.elapsedNanos()) + waitMillis + toMillis(stats.intervalNanos); assertEquals(toMillis(stats.getNextFreePermitNanos()), computedNextFreePermitMillis); } @Override void printInfo(SmoothRateLimiterStats stats, long waitMillis) { System.out.printf("[%s] elapsedMillis: %4s, waitMillis: %s, nextFreePermitNanos: %s%n", Thread.currentThread().getName(), stats.getElapsed().toMillis(), waitMillis, toMillis(stats.getNextFreePermitNanos())); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/TimedCircuitStatsTest.java000066400000000000000000000230141444561050700332300ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal; import dev.failsafe.internal.TimedCircuitStats.Bucket; import dev.failsafe.internal.TimedCircuitStats.Clock; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; import java.util.Arrays; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class TimedCircuitStatsTest extends CircuitStatsTest { TimedCircuitStats stats; private TestClock clock; static class TestClock extends Clock { long currentTimeMillis; void set(long currentTimeMillis) { this.currentTimeMillis = currentTimeMillis; } @Override long currentTimeMillis() { return currentTimeMillis; } @Override public String toString() { return "TestClock[currentTimeMillis=" + currentTimeMillis + ']'; } } @BeforeMethod protected void beforeMethod() { clock = new TestClock(); } public void testMetrics() { // Given 4 buckets representing 1 second each stats = new TimedCircuitStats(4, Duration.ofSeconds(4), clock, null); assertEquals(stats.getSuccessRate(), 0); assertEquals(stats.getFailureRate(), 0); assertEquals(stats.getExecutionCount(), 0); // Record into bucket 1 recordExecutions(stats, 50, i -> i % 5 == 0); // currentTime = 0 assertEquals(stats.currentIndex, 0); assertEquals(stats.getCurrentBucket().startTimeMillis, 0); assertEquals(stats.getSuccessCount(), 10); assertEquals(stats.getSuccessRate(), 20); assertEquals(stats.getFailureCount(), 40); assertEquals(stats.getFailureRate(), 80); assertEquals(stats.getExecutionCount(), 50); // Record into bucket 2 clock.set(1000); recordSuccesses(stats, 10); assertEquals(stats.currentIndex, 1); assertEquals(stats.getCurrentBucket().startTimeMillis, 1000); assertEquals(stats.getSuccessCount(), 20); assertEquals(stats.getSuccessRate(), 33); assertEquals(stats.getFailureCount(), 40); assertEquals(stats.getFailureRate(), 67); assertEquals(stats.getExecutionCount(), 60); // Record into bucket 3 clock.set(2500); recordFailures(stats, 20); assertEquals(stats.currentIndex, 2); assertEquals(stats.getCurrentBucket().startTimeMillis, 2000); assertEquals(stats.getSuccessCount(), 20); assertEquals(stats.getSuccessRate(), 25); assertEquals(stats.getFailureCount(), 60); assertEquals(stats.getFailureRate(), 75); assertEquals(stats.getExecutionCount(), 80); // Record into bucket 4 clock.set(3100); recordExecutions(stats, 25, i -> i % 5 == 0); assertEquals(stats.currentIndex, 3); assertEquals(stats.getCurrentBucket().startTimeMillis, 3000); assertEquals(stats.getSuccessCount(), 25); assertEquals(stats.getSuccessRate(), 24); assertEquals(stats.getFailureCount(), 80); assertEquals(stats.getFailureRate(), 76); assertEquals(stats.getExecutionCount(), 105); // Record into bucket 2, skipping bucket 1 clock.set(5400); recordSuccesses(stats, 8); assertEquals(stats.currentIndex, 1); // Assert bucket 1 was skipped and reset based on its previous start time Bucket bucket1 = stats.buckets[0]; assertEquals(bucket1.startTimeMillis, 4000); assertEquals(bucket1.successes, 0); assertEquals(bucket1.failures, 0); assertEquals(stats.getCurrentBucket().startTimeMillis, 5000); assertEquals(stats.getSuccessCount(), 13); assertEquals(stats.getSuccessRate(), 25); assertEquals(stats.getFailureCount(), 40); assertEquals(stats.getFailureRate(), 75); assertEquals(stats.getExecutionCount(), 53); // Record into bucket 4, skipping bucket 3 clock.set(7300); recordFailures(stats, 5); assertEquals(stats.currentIndex, 3); // Assert bucket 3 was skipped and reset based on its previous start time Bucket bucket3 = stats.buckets[2]; assertEquals(bucket3.startTimeMillis, 6000); assertEquals(bucket3.successes, 0); assertEquals(bucket3.failures, 0); assertEquals(stats.getCurrentBucket().startTimeMillis, 7000); assertEquals(stats.getSuccessCount(), 8); assertEquals(stats.getSuccessRate(), 62); assertEquals(stats.getFailureCount(), 5); assertEquals(stats.getFailureRate(), 38); assertEquals(stats.getExecutionCount(), 13); // Skip all buckets, starting at 1 again int startTime = 22500; clock.set(startTime); stats.getCurrentBucket(); assertEquals(stats.currentIndex, 0); for (Bucket bucket : stats.buckets) { assertEquals(bucket.startTimeMillis, startTime); assertEquals(bucket.successes, 0); assertEquals(bucket.failures, 0); startTime += 1000; } assertEquals(stats.getSuccessRate(), 0); assertEquals(stats.getFailureRate(), 0); assertEquals(stats.getExecutionCount(), 0); } public void testCopyToEqualSizedStats() { stats = new TimedCircuitStats(4, Duration.ofSeconds(4), clock, null); assertValues(stats, b(0, 0, 0), b(-1, 0, 0), b(-1, 0, 0), b(-1, 0, 0)); recordSuccesses(stats, 2); clock.set(1100); recordFailures(stats, 3); assertEquals(stats.currentIndex, 1); TimedCircuitStats right1 = new TimedCircuitStats(4, Duration.ofSeconds(4), clock, stats); assertValues(right1, b(-1, 0, 0), b(-1, 0, 0), b(0, 2, 0), b(1000, 0, 3)); assertEquals(right1.currentIndex, 3); clock.set(2500); recordSuccesses(right1, 5); assertValues(right1, b(2000, 5, 0), b(-1, 0, 0), b(0, 2, 0), b(1000, 0, 3)); assertEquals(right1.currentIndex, 0); clock.set(2200); recordSuccesses(stats, 2); clock.set(3300); recordFailures(stats, 3); assertEquals(stats.currentIndex, 3); TimedCircuitStats right2 = new TimedCircuitStats(4, Duration.ofSeconds(4), clock, stats); assertValues(right2, b(0, 2, 0), b(1000, 0, 3), b(2000, 2, 0), b(3000, 0, 3)); assertEquals(right2.currentIndex, 3); clock.set(4400); recordSuccesses(right2, 4); assertValues(right2, b(4000, 4, 0), b(1000, 0, 3), b(2000, 2, 0), b(3000, 0, 3)); assertEquals(right2.currentIndex, 0); TimedCircuitStats right3 = new TimedCircuitStats(4, Duration.ofSeconds(4), clock, right2); assertValues(right3, b(1000, 0, 3), b(2000, 2, 0), b(3000, 0, 3), b(4000, 4, 0)); assertEquals(right3.currentIndex, 3); clock.set(7500); recordExecutions(right3, 4, i -> i % 2 == 0); assertValues(right3, b(5000, 0, 0), b(6000, 0, 0), b(7000, 2, 2), b(4000, 4, 0)); assertEquals(right3.currentIndex, 2); } public void testCopyToSmallerStats() { stats = new TimedCircuitStats(5, Duration.ofSeconds(5), clock, null); recordSuccesses(stats, 2); clock.set(1100); recordFailures(stats, 3); clock.set(2200); recordSuccesses(stats, 4); clock.set(3300); recordFailures(stats, 5); clock.set(4400); recordFailures(stats, 6); TimedCircuitStats right1 = new TimedCircuitStats(3, Duration.ofSeconds(3), clock, stats); assertValues(right1, b(2000, 4, 0), b(3000, 0, 5), b(4000, 0, 6)); assertEquals(right1.currentIndex, 2); clock.set(6500); recordSuccesses(right1, 33); assertValues(right1, b(5000, 0, 0), b(6000, 33, 0), b(4000, 0, 6)); assertEquals(right1.currentIndex, 1); } public void testCopyToLargerStats() { stats = new TimedCircuitStats(3, Duration.ofSeconds(3), clock, null); recordSuccesses(stats, 2); clock.set(1100); recordFailures(stats, 3); clock.set(2200); recordSuccesses(stats, 4); TimedCircuitStats right1 = new TimedCircuitStats(5, Duration.ofSeconds(5), clock, stats); assertValues(right1, b(0, 2, 0), b(1000, 0, 3), b(2000, 4, 0), b(-1, 0, 0), b(-1, 0, 0)); assertEquals(right1.currentIndex, 2); clock.set(3300); recordSuccesses(right1, 22); assertValues(right1, b(0, 2, 0), b(1000, 0, 3), b(2000, 4, 0), b(3000, 22, 0), b(-1, 0, 0)); assertEquals(right1.currentIndex, 3); TimedCircuitStats right2 = new TimedCircuitStats(6, Duration.ofSeconds(6), clock, right1); assertValues(right2, b(-1, 0, 0), b(0, 2, 0), b(1000, 0, 3), b(2000, 4, 0), b(3000, 22, 0), b(-1, 0, 0)); assertEquals(right2.currentIndex, 4); clock.set(7250); recordFailures(right2, 123); assertValues(right2, b(5000, 0, 0), b(6000, 0, 0), b(7000, 0, 123), b(2000, 4, 0), b(3000, 22, 0), b(4000, 0, 0)); assertEquals(right2.currentIndex, 2); } /** * Accepts a [startTime, successCount, failureCount] tuple. */ private static int[] b(int... values) { return new int[] { values[0], values[1], values[2] }; } private static int[][] valuesFor(TimedCircuitStats stats) { int[][] values = new int[stats.buckets.length][]; for (int i = 0; i < values.length; i++) values[i] = b((int) stats.buckets[i].startTimeMillis, stats.buckets[i].successes, stats.buckets[i].failures); return values; } private static void assertValues(TimedCircuitStats stats, int[]... right) { int[][] left = valuesFor(stats); assertTrue(Arrays.deepEquals(left, right), Arrays.deepToString(left) + " != " + Arrays.deepToString(right)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/000077500000000000000000000000001444561050700270765ustar00rootroot00000000000000DelegatingSchedulerTest.java000066400000000000000000000054071444561050700344320ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import net.jodah.concurrentunit.Waiter; import dev.failsafe.testing.Asserts; import dev.failsafe.spi.Scheduler; import org.testng.annotations.Test; import java.io.IOException; import java.time.Duration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class DelegatingSchedulerTest { Scheduler scheduler = DelegatingScheduler.INSTANCE; public void shouldSchedule() throws Throwable { // Given Duration delay = Duration.ofMillis(200); Waiter waiter = new Waiter(); long startTime = System.nanoTime(); // When scheduler.schedule(() -> { waiter.resume(); return null; }, delay.toMillis(), TimeUnit.MILLISECONDS); // Then waiter.await(1000); assertTrue(System.nanoTime() - startTime > delay.toNanos()); } public void shouldWrapCheckedExceptions() { Asserts.assertThrows(() -> scheduler.schedule(() -> { throw new IOException(); }, 1, TimeUnit.MILLISECONDS).get(), ExecutionException.class, IOException.class); } public void shouldNotInterruptAlreadyDoneTask() throws Throwable { Future future1 = scheduler.schedule(() -> null, 0, TimeUnit.MILLISECONDS); Thread.sleep(100); assertFalse(future1.cancel(true)); } /** * Asserts that ForkJoinPool clears interrupt flags. */ public void shouldClearInterruptFlagInForkJoinPoolThreads() throws Throwable { Scheduler scheduler = new DelegatingScheduler(new ForkJoinPool(1)); AtomicReference threadRef = new AtomicReference<>(); Waiter waiter = new Waiter(); // Create interruptable execution scheduler.schedule(() -> { threadRef.set(Thread.currentThread()); waiter.resume(); Thread.sleep(10000); return null; }, 0, TimeUnit.MILLISECONDS); waiter.await(1000); threadRef.get().interrupt(); // Check for interrupt flag scheduler.schedule(() -> { waiter.assertFalse(Thread.currentThread().isInterrupted()); waiter.resume(); return null; }, 0, TimeUnit.MILLISECONDS); waiter.await(1000); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/DurationsTest.java000066400000000000000000000024441444561050700325550ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import org.testng.annotations.Test; import java.time.Duration; import static dev.failsafe.internal.util.Durations.*; import static org.testng.Assert.assertEquals; @Test public class DurationsTest { public void testOfSafeNanos() { assertEquals(ofSafeNanos(Duration.ofSeconds(MAX_SECONDS_PER_LONG)), MAX_SAFE_NANOS_DURATION); assertEquals(ofSafeNanos(Duration.ofSeconds(MAX_SECONDS_PER_LONG + 1)), MAX_SAFE_NANOS_DURATION); assertEquals(ofSafeNanos(Duration.ofSeconds(Long.MAX_VALUE)), MAX_SAFE_NANOS_DURATION); Duration safeDuration = Duration.ofSeconds(MAX_SECONDS_PER_LONG - 1000); assertEquals(ofSafeNanos(safeDuration), safeDuration); } } FutureLinkedListTest.java000066400000000000000000000051231444561050700337600ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import org.testng.annotations.Test; import java.util.concurrent.CompletableFuture; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; @Test public class FutureLinkedListTest { public void testAdd() { // Given FutureLinkedList list = new FutureLinkedList(); // When CompletableFuture f1 = list.add(); CompletableFuture f2 = list.add(); CompletableFuture f3 = list.add(); // Then assertNull(list.head.previous); assertEquals(list.head.future, f1); assertEquals(list.tail.future, f3); assertEquals(list.head.next.future, f2); assertEquals(list.tail.previous.future, f2); } public void testPollFirst() { // Given FutureLinkedList list = new FutureLinkedList(); // When / Then assertNull(list.pollFirst()); // Given CompletableFuture f1 = list.add(); CompletableFuture f2 = list.add(); CompletableFuture f3 = list.add(); // When / Then assertEquals(list.pollFirst(), f1); assertEquals(list.head.future, f2); assertNull(list.head.previous); assertEquals(list.pollFirst(), f2); assertEquals(list.head.future, f3); assertNull(list.head.previous); assertEquals(list.pollFirst(), f3); assertNull(list.head); assertNull(list.pollFirst()); } public void testRemove() { // Given FutureLinkedList list = new FutureLinkedList(); CompletableFuture f1 = list.add(); CompletableFuture f2 = list.add(); CompletableFuture f3 = list.add(); // When / Then f1.complete(null); assertEquals(list.head.future, f2); assertNull(list.head.previous); assertEquals(list.tail.previous.future, f2); f2.complete(null); assertEquals(list.head.future, f3); assertEquals(list.tail.future, f3); assertNull(list.head.previous); assertNull(list.head.next); f3.complete(null); assertNull(list.head); assertNull(list.tail); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/ListsTest.java000066400000000000000000000016151444561050700317020ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import org.testng.annotations.Test; import java.util.Arrays; import static org.testng.Assert.assertEquals; @Test public class ListsTest { public void testOf() { assertEquals(Lists.of("a", new String[] { "b", "c" }), Arrays.asList("a", "b", "c")); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/MathsTest.java000066400000000000000000000021771444561050700316640ustar00rootroot00000000000000/* * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @Test public class MathsTest { public void testAdd() { assertEquals(Maths.add(100000, 100000), 100000 * 2); assertEquals(Maths.add(Long.MAX_VALUE - 1000, 1000000), Long.MAX_VALUE); } public void testRoundDown() { assertEquals(Maths.roundDown(0, 20), 0); assertEquals(Maths.roundDown(40, 20), 40); assertEquals(Maths.roundDown(57, 20), 40); assertEquals(Maths.roundDown(57, 15), 45); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/internal/util/RandomDelayTest.java000066400000000000000000000044321444561050700330030ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.internal.util; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @Test public class RandomDelayTest { public void testRandomDelayInRange() { assertEquals(RandomDelay.randomDelayInRange(10, 100, 0), 10); assertEquals(RandomDelay.randomDelayInRange(10, 100, .25), 32); assertEquals(RandomDelay.randomDelayInRange(10, 100, .5), 55); assertEquals(RandomDelay.randomDelayInRange(10, 100, .75), 77); assertEquals(RandomDelay.randomDelayInRange(10, 100, 1), 100); assertEquals(RandomDelay.randomDelayInRange(50, 500, .25), 162); assertEquals(RandomDelay.randomDelayInRange(5000, 50000, .25), 16250); } public void testRandomDelayForFactor() { assertEquals(RandomDelay.randomDelay(100, .5, 0), 150); assertEquals(RandomDelay.randomDelay(100, .5, .25), 125); assertEquals(RandomDelay.randomDelay(100, .5, .5), 100); assertEquals(RandomDelay.randomDelay(100, .5, .75), 75); assertEquals(RandomDelay.randomDelay(100, .5, .9999), 50); assertEquals(RandomDelay.randomDelay(500, .5, .25), 625); assertEquals(RandomDelay.randomDelay(500, .5, .75), 375); assertEquals(RandomDelay.randomDelay(50000, .5, .25), 62500); } public void testRandomDelayForDuration() { assertEquals(RandomDelay.randomDelay(100, 50, 0), 150); assertEquals(RandomDelay.randomDelay(100, 50, .25), 125); assertEquals(RandomDelay.randomDelay(100, 50, .5), 100); assertEquals(RandomDelay.randomDelay(100, 50, .75), 75); assertEquals(RandomDelay.randomDelay(100, 50, .9999), 50); assertEquals(RandomDelay.randomDelay(500, 50, .25), 525); assertEquals(RandomDelay.randomDelay(50000, 5000, .25), 52500); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/000077500000000000000000000000001444561050700256205ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue115Test.java000066400000000000000000000010741444561050700306440ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.time.Duration; /** * Tests https://github.com/jhalterman/failsafe/issues/115 and https://github.com/jhalterman/failsafe/issues/116 */ @Test public class Issue115Test { @Test(expectedExceptions = IllegalStateException.class) public void shouldFailWithJitterLargerThanDelay() { RetryPolicy.builder() .handle(IllegalArgumentException.class) .withDelay(Duration.ofMillis(100)) .withJitter(Duration.ofMillis(200)) .build(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue131Test.java000066400000000000000000000056731444561050700306530ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.issues; import dev.failsafe.CircuitBreaker; import dev.failsafe.Failsafe; import dev.failsafe.FailsafeException; import dev.failsafe.FailsafeExecutor; import dev.failsafe.function.CheckedPredicate; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.Test; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @Test public class Issue131Test { /** * This predicate is invoked in failure scenarios with an arg of null, producing a {@link NullPointerException} * yielding surprising results. */ private static CheckedPredicate handleIfEqualsIgnoreCaseFoo = s -> { return s.equalsIgnoreCase("foo"); // produces NPE when invoked in failing scenarios. }; /** * Simple synchronous case throwing a {@link NullPointerException} instead of the expected {@link FailsafeException}. */ @Test(expectedExceptions = FailsafeException.class) public void syncShouldThrowTheUnderlyingIOException() { CircuitBreaker circuitBreaker = CircuitBreaker.builder() .handleResultIf(handleIfEqualsIgnoreCaseFoo) .build(); FailsafeExecutor failsafe = Failsafe.with(circuitBreaker); // I expect this getAsync() to throw IOException, not NPE. failsafe.get(() -> { throw new IOException("let's blame it on network error"); }); } /** * More alarming async case where the Future is not even completed since Failsafe does not recover from the {@link * NullPointerException} thrown by the predicate. */ public void asyncShouldCompleteTheFuture() throws Throwable { CircuitBreaker circuitBreaker = CircuitBreaker.builder() .handleResultIf(handleIfEqualsIgnoreCaseFoo) .build(); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); FailsafeExecutor failsafe = Failsafe.with(circuitBreaker).with(executor); Waiter waiter = new Waiter(); failsafe.getStageAsync(() -> { CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(new IOException("let's blame it on network error")); return future; }).whenComplete((s, t) -> waiter.resume()); // Never invoked! waiter.await(1000); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue146Test.java000066400000000000000000000021001444561050700306370ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; /** * Tests https://github.com/jhalterman/failsafe/issues/146 */ @Test public class Issue146Test { public void shouldRespectFailureCondition() { AtomicInteger successCounter = new AtomicInteger(); AtomicInteger failureCounter = new AtomicInteger(); AtomicInteger failedAttemptCounter = new AtomicInteger(); RetryPolicy retryPolicy = RetryPolicy.builder() .handleResultIf(Objects::isNull) .withMaxRetries(2) .onSuccess(e -> successCounter.incrementAndGet()) .onFailure(e -> failureCounter.incrementAndGet()) .onFailedAttempt(e -> failedAttemptCounter.incrementAndGet()) .build(); Failsafe.with(retryPolicy).get(() -> null); assertEquals(3, failedAttemptCounter.get()); assertEquals(0, successCounter.get()); assertEquals(1, failureCounter.get()); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue165Test.java000066400000000000000000000017521444561050700306540ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Fallback; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import java.util.concurrent.CompletableFuture; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class Issue165Test { public void testOfStage() { Fallback fallback = Fallback.ofStage(e -> { assertTrue(e.getLastException() instanceof IllegalStateException); return CompletableFuture.supplyAsync(() -> "test"); }); Object result = Failsafe.with(fallback).get(() -> { throw new IllegalStateException(); }); assertEquals(result, "test"); } public void testOfStageAsync() throws Throwable { Fallback fallback = Fallback.builderOfStage(e -> CompletableFuture.completedFuture("test")).withAsync().build(); Object result = Failsafe.with(fallback).getAsync(() -> { throw new IllegalStateException(); }).get(); assertEquals(result, "test"); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue177Test.java000066400000000000000000000006311444561050700306520ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Fallback; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import static org.testng.Assert.assertNull; @Test public class Issue177Test { public void shouldSupportNullFallback() { Fallback fallback = Fallback.builder((Boolean) null).handleResult(false).build(); assertNull(Failsafe.with(fallback).get(() -> false)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue190Test.java000066400000000000000000000027061444561050700306520ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.RetryPolicy; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import dev.failsafe.Failsafe; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; @Test public class Issue190Test { ScheduledExecutorService executor; @BeforeClass protected void beforeClass() { executor = Executors.newSingleThreadScheduledExecutor(); } @AfterClass protected void afterClass() { executor.shutdownNow(); } public void test() throws Throwable { RetryPolicy policy = RetryPolicy.builder().withMaxRetries(5).build(); AtomicInteger failureEvents = new AtomicInteger(); AtomicInteger successEvents = new AtomicInteger(); Waiter waiter = new Waiter(); Failsafe.with(policy).onFailure(e -> { failureEvents.incrementAndGet(); waiter.resume(); }).onSuccess(e -> { successEvents.incrementAndGet(); waiter.resume(); }).getAsyncExecution(execution -> Testing.futureResult(executor, true).whenComplete((result, failure) -> { execution.recordResult(result); })).get(); waiter.await(1000); Assert.assertEquals(failureEvents.get(), 0); Assert.assertEquals(successEvents.get(), 1); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue192Test.java000066400000000000000000000043641444561050700306560ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.testing.Asserts; import dev.failsafe.testing.Testing; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; @Test public class Issue192Test { ScheduledExecutorService executor; static class ExceptionA extends Exception { } static class ExceptionB extends Exception { } static class ExceptionC extends Exception { } @BeforeClass protected void beforeClass() { executor = Executors.newSingleThreadScheduledExecutor(); } @AfterClass protected void afterClass() { executor.shutdownNow(); } /** * Asserts the handling of multiple retry policies with an async execution. */ public void testAsync() { AtomicInteger exceptionA = new AtomicInteger(); AtomicInteger exceptionB = new AtomicInteger(); AtomicInteger exceptionC = new AtomicInteger(); RetryPolicy policyA = RetryPolicy.builder() .handle(ExceptionA.class) .withMaxRetries(5) .onRetry(evt -> exceptionA.incrementAndGet()) .build(); RetryPolicy policyB = RetryPolicy.builder() .handle(ExceptionB.class) .withMaxRetries(3) .onRetry(evt -> exceptionB.incrementAndGet()) .build(); RetryPolicy policyC = RetryPolicy.builder() .handle(ExceptionC.class) .withMaxRetries(2) .onRetry(evt -> exceptionC.incrementAndGet()) .build(); Asserts.assertThrows(() -> Failsafe.with(policyA, policyB, policyC) .getAsyncExecution( execution -> Testing.futureException(executor, new ExceptionB()).whenComplete((result, failure) -> { //System.out.println("Result = " + result + "; failure = " + failure); execution.record(result, failure); })) .get(), ExecutionException.class, ExceptionB.class); Assert.assertEquals(exceptionA.get(), 0); Assert.assertEquals(exceptionB.get(), 3); Assert.assertEquals(exceptionC.get(), 0); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue206Test.java000066400000000000000000000013421444561050700306430ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Fallback; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import static org.testng.Assert.fail; @Test public class Issue206Test { public void test() { try { Failsafe.with(Fallback.builder(e -> true).handleResultIf(r -> true).build()) .onFailure(e -> fail("Unexpected execution failure")) .get(() -> true); } catch (RuntimeException e) { fail("Unexpected exception"); } try { Failsafe.with(Fallback.of(() -> { })).onFailure(e -> fail("Unexpected execution failure")).run(() -> { throw new RuntimeException(); }); } catch (RuntimeException e) { fail("Unexpected exception"); } } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue215Test.java000066400000000000000000000005111444561050700306400ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.time.Duration; @Test public class Issue215Test { public void test() { RetryPolicy.builder() .withBackoff(Duration.ofNanos(Long.MAX_VALUE).minusSeconds(1), Duration.ofSeconds(Long.MAX_VALUE), 1.50); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue218Test.java000066400000000000000000000007041444561050700306470ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.Fallback; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; @Test public class Issue218Test { public void test() { RetryPolicy retryPolicy = RetryPolicy.builder().withMaxAttempts(2).build(); Fallback fallback = Fallback.none(); Failsafe.with(fallback, retryPolicy).run(() -> { throw new Exception(); }); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue224Test.java000066400000000000000000000006461444561050700306510ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.time.Duration; import java.time.temporal.ChronoUnit; @Test public class Issue224Test { public void test() { RetryPolicy.builder().withDelay(10, 100, ChronoUnit.MILLIS).withJitter(Duration.ofMillis(5)).build(); RetryPolicy.builder().withDelay(1, 100, ChronoUnit.MILLIS).withJitter(.5).build(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue231Test.java000066400000000000000000000025551444561050700306500ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Timeout; import dev.failsafe.testing.Asserts; import dev.failsafe.Failsafe; import dev.failsafe.TimeoutExceededException; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import static org.testng.Assert.assertTrue; @Test public class Issue231Test { /** * Timeout, even with interruption, should wait for the execution to complete before completing the future. */ public void shouldWaitForExecutionCompletion() { // Use a separate executorService for this test in case the common pool is full ExecutorService executorService = Executors.newFixedThreadPool(2); Timeout timeout = Timeout.builder(Duration.ofMillis(100)).withInterrupt().build(); AtomicBoolean executionCompleted = new AtomicBoolean(); Asserts.assertThrows(() -> Failsafe.with(timeout).with(executorService).runAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException ignore) { Thread.sleep(200); executionCompleted.set(true); } }).get(), ExecutionException.class, TimeoutExceededException.class); assertTrue(executionCompleted.get()); executorService.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue240Test.java000066400000000000000000000023011444561050700306350ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; @Test public class Issue240Test { public void testHandleResult() { AtomicInteger counter = new AtomicInteger(); RetryPolicy rp = RetryPolicy.builder() .handle(IllegalArgumentException.class) .withMaxRetries(2) .handleResult(null) .build(); Testing.ignoreExceptions(() -> { Failsafe.with(rp).get(() -> { counter.incrementAndGet(); throw new IllegalStateException(); }); }); assertEquals(counter.get(), 1); } public void testAbortWhen() { AtomicInteger counter = new AtomicInteger(); RetryPolicy rp = RetryPolicy.builder() .handle(IllegalArgumentException.class) .withMaxRetries(2) .abortWhen(null) .build(); Testing.ignoreExceptions(() -> { Failsafe.with(rp).get(() -> { counter.incrementAndGet(); throw new IllegalArgumentException(); }); }); assertEquals(counter.get(), 3); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue242Test.java000066400000000000000000000013651444561050700306500ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import dev.failsafe.testing.Testing; import org.testng.annotations.Test; import java.time.Duration; import static dev.failsafe.testing.Testing.withLogs; import static org.testng.Assert.assertTrue; @Test public class Issue242Test extends Testing { public void shouldDelayOnExplicitRetry() throws Throwable { RetryPolicy retryPolicy = withLogs( RetryPolicy.builder().handleResult(null).withDelay(Duration.ofMillis(110))).build(); long elapsed = timed(() -> Failsafe.with(retryPolicy).runAsyncExecution(exec -> { exec.record(null, null); }).get()); assertTrue(elapsed > 200, "Expected delay between retries"); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue260Test.java000066400000000000000000000031111444561050700306370ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import dev.failsafe.Timeout; import dev.failsafe.function.ContextualRunnable; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Function; @Test public class Issue260Test { public void test() throws Throwable { ExecutorService executor = Executors.newSingleThreadExecutor(); Timeout timeout = Timeout.builder(Duration.ofMillis(300)) .withInterrupt() .onFailure(e -> System.out.println("Interrupted")) .build(); RetryPolicy rp = RetryPolicy.builder() .onRetry(e -> System.out.println("Retrying")) .onSuccess(e -> System.out.println("Success")) .build(); Function task = (taskId) -> ctx -> { System.out.println("Starting execution of task " + taskId); try { Thread.sleep(200); } catch (InterruptedException e) { System.out.println("Interrupted task " + taskId); throw e; } }; Future f1 = Failsafe.with(rp, timeout).with(executor).runAsync(task.apply(1)); Future f2 = Failsafe.with(rp, timeout).with(executor).runAsync(task.apply(2)); Future f3 = Failsafe.with(rp, timeout).with(executor).runAsync(task.apply(3)); f1.get(1, TimeUnit.SECONDS); f2.get(1, TimeUnit.SECONDS); f3.get(1, TimeUnit.SECONDS); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue267Test.java000066400000000000000000000023551444561050700306570ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.ExecutionContext; import dev.failsafe.Fallback; import dev.failsafe.Timeout; import dev.failsafe.event.ExecutionAttemptedEvent; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import java.net.ConnectException; import java.time.Duration; import static org.testng.Assert.assertNull; @Test public class Issue267Test { public void test() { Timeout timeout = Timeout.of(Duration.ofMillis(1000L)); Fallback notFoundFallback = Fallback.builder(this::handleNotFound).handleIf(this::causedBy404).build(); Fallback failureHandling = Fallback.ofException(this::handleException); Integer result = Failsafe.with(failureHandling, notFoundFallback, timeout).get(this::connect); assertNull(result); } private Integer connect(ExecutionContext context) throws ConnectException { throw new ConnectException(); } private boolean causedBy404(Object o, Throwable throwable) { return throwable instanceof ConnectException; } private Object handleNotFound(ExecutionAttemptedEvent event) { return null; } private Exception handleException(ExecutionAttemptedEvent event) { return new IllegalArgumentException(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue284Test.java000066400000000000000000000044411444561050700306540ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Fallback; import dev.failsafe.RetryPolicy; import dev.failsafe.Failsafe; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.*; @Test public class Issue284Test { AtomicInteger failedAttempt; AtomicBoolean success; AtomicBoolean failure; AtomicBoolean executed; Fallback fallback; RetryPolicy retryPolicy = RetryPolicy.builder() .handleResult(null) .onFailedAttempt(e -> failedAttempt.incrementAndGet()) .onSuccess(e -> success.set(true)) .onFailure(e -> failure.set(true)) .build(); @BeforeMethod protected void beforeMethod() { failedAttempt = new AtomicInteger(); success = new AtomicBoolean(); failure = new AtomicBoolean(); executed = new AtomicBoolean(); } private Fallback fallbackFor(String result) { return Fallback.builder(result) .handleResult(null) .onFailedAttempt(e -> failedAttempt.incrementAndGet()) .onSuccess(e -> success.set(true)) .onFailure(e -> failure.set(true)) .build(); } public void testFallbackSuccess() { fallback = fallbackFor("hello"); String result = Failsafe.with(fallback).get(() -> null); assertEquals(result, "hello"); assertEquals(failedAttempt.get(), 1); assertTrue(success.get(), "Fallback should have been successful"); } public void testFallbackFailure() { fallback = fallbackFor(null); String result = Failsafe.with(fallback).get(() -> null); assertNull(result); assertEquals(failedAttempt.get(), 1); assertTrue(failure.get(), "Fallback should have failed"); } public void testRetryPolicySuccess() { String result = Failsafe.with(retryPolicy).get(() -> !executed.getAndSet(true) ? null : "hello"); assertEquals(result, "hello"); assertEquals(failedAttempt.get(), 1); assertTrue(success.get(), "RetryPolicy should have been successful"); } public void testRetryPolicyFailure() { String result = Failsafe.with(retryPolicy).get(() -> null); assertNull(result); assertEquals(failedAttempt.get(), 3); assertTrue(failure.get(), "RetryPolicy should have failed"); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue298Test.java000066400000000000000000000022341444561050700306570ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.Fallback; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicBoolean; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @Test public class Issue298Test { AtomicBoolean failedAttemptCalled = new AtomicBoolean(); AtomicBoolean failureCalled = new AtomicBoolean(); Fallback fallback = Fallback.builder(e -> "success") .onFailedAttempt(e -> failedAttemptCalled.set(true)) .onFailure(e -> failureCalled.set(true)) .build(); @BeforeMethod protected void beforeMethod() { failedAttemptCalled.set(false); failureCalled.set(false); } public void testSync() { Failsafe.with(fallback).get(() -> { throw new Exception(); }); assertTrue(failedAttemptCalled.get()); assertFalse(failureCalled.get()); } public void testAsync() throws Throwable { Failsafe.with(fallback).getAsync(() -> { throw new Exception(); }).get(); assertTrue(failedAttemptCalled.get()); assertFalse(failureCalled.get()); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue311Test.java000066400000000000000000000035131444561050700306420ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.*; @Test public class Issue311Test { public void failsafeFail() throws Throwable { AtomicInteger counter = new AtomicInteger(0); Executor executor = Executors.newSingleThreadExecutor(); Failsafe.with(RetryPolicy.builder().handle(RuntimeException.class).withMaxAttempts(2).build()) .with(executor) .runAsync(() -> { if (counter.incrementAndGet() == 1) throw new RuntimeException(); }) .get(); assertEquals(counter.get(), 2); ((ExecutorService) executor).shutdownNow(); } public void testNullCompletionStage() throws Throwable { assertNull(Failsafe.none().getStageAsync(() -> { return null; }).get()); } public void testRunAsyncWithThreadLocalInExecutor() throws Throwable { ThreadLocal threadLocal = new ThreadLocal<>(); Executor executor = runnable -> { threadLocal.set(true); runnable.run(); }; Failsafe.none().with(executor).runAsync(() -> { assertTrue(threadLocal.get()); }).get(); } public void testGetStageAsyncWithThreadLocalInExecutor() throws Throwable { ThreadLocal threadLocal = new ThreadLocal<>(); Executor executor = runnable -> { threadLocal.set(true); runnable.run(); }; assertNull(Failsafe.none().with(executor).getStageAsync(() -> { assertTrue(threadLocal.get()); return CompletableFuture.completedFuture("ignored"); }).get()); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue36Test.java000066400000000000000000000100411444561050700305600ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.issues; import dev.failsafe.testing.Testing; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import dev.failsafe.RetryPolicyBuilder; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; /** * https://github.com/jhalterman/failsafe/issues/36 */ @Test public class Issue36Test { RetryPolicyBuilder rpBuilder = RetryPolicy.builder() .handleResultIf(r -> r == null || !r) .handle(Exception.class) .withMaxRetries(3); AtomicInteger calls; AtomicInteger failedAttempts; AtomicInteger retries; @BeforeMethod protected void beforeMethod() { calls = new AtomicInteger(); failedAttempts = new AtomicInteger(); retries = new AtomicInteger(); } public void test() { try { Failsafe.with(rpBuilder.onFailedAttempt(e -> failedAttempts.incrementAndGet()) .onRetry(e -> retries.incrementAndGet()) .build()).get(() -> { calls.incrementAndGet(); throw new Exception(); }); fail(); } catch (Exception expected) { } // Then assertCounters(); } void assertCounters() { assertEquals(calls.get(), 4); assertEquals(failedAttempts.get(), 4); assertEquals(retries.get(), 3); } @Test public void failedAttemptListener_WithFailedResponses_ShouldBeCalled() { AtomicInteger listenerCallbacks = new AtomicInteger(); RetryPolicy policy = RetryPolicy.builder() .handleResultIf(response -> response != null && !response) .handle(Exception.class) .withMaxRetries(3) .onFailedAttempt(e -> listenerCallbacks.incrementAndGet()) .build(); Failsafe.with(policy).get(() -> false); assertEquals(listenerCallbacks.get(), 4); } @Test public void retryListener_WithFailedResponses_ShouldBeCalled() { AtomicInteger listenerCallbacks = new AtomicInteger(); RetryPolicy policy = RetryPolicy.builder() .handleResultIf(response -> response != null && !response) .handle(Exception.class) .withMaxRetries(3) .onRetry(e -> listenerCallbacks.incrementAndGet()) .build(); Failsafe.with(policy).get(() -> false); assertEquals(listenerCallbacks.get(), 3); } @Test public void failedAttemptListener_WithExceptions_ShouldBeCalled() { AtomicInteger listenerCallbacks = new AtomicInteger(); RetryPolicy policy = RetryPolicy.builder() .handleResultIf(response -> response != null && !response) .handle(Exception.class) .withMaxRetries(3) .onFailedAttempt(e -> listenerCallbacks.incrementAndGet()) .build(); Testing.ignoreExceptions(() -> Failsafe.with(policy).get(() -> { throw new RuntimeException(); })); assertEquals(listenerCallbacks.get(), 4); } @Test public void retryListener_WithExceptions_ShouldBeCalled() { AtomicInteger listenerCallbacks = new AtomicInteger(); RetryPolicy policy = RetryPolicy.builder() .handleResultIf(response -> response != null && !response) .handle(Exception.class) .withMaxRetries(3) .onRetry(e -> listenerCallbacks.incrementAndGet()) .build(); Testing.ignoreExceptions(() -> Failsafe.with(policy).get(() -> { throw new RuntimeException(); })); assertEquals(listenerCallbacks.get(), 3); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue52Test.java000066400000000000000000000047041444561050700305670ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.issues; import dev.failsafe.RetryPolicy; import dev.failsafe.testing.Asserts; import dev.failsafe.Failsafe; import org.testng.annotations.AfterClass; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class Issue52Test { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); @AfterClass protected void afterClass() { scheduler.shutdownNow(); } @Test(expectedExceptions = CancellationException.class) public void shouldCancelExecutionViaFuture() throws Throwable { Future proxyFuture = Failsafe.with(RetryPolicy.builder().withDelay(Duration.ofMillis(10)).build()) .with(scheduler) .getAsync(exec -> { throw new IllegalStateException(); }); assertTrue(proxyFuture.cancel(true)); proxyFuture.get(); // should throw CancellationException per .getAsync() javadoc. } public void shouldCancelExecutionViaCompletableFuture() throws Throwable { AtomicInteger counter = new AtomicInteger(); CompletableFuture proxyFuture = Failsafe.with( RetryPolicy.builder().withDelay(Duration.ofMillis(10)).build()).with(scheduler).getStageAsync(exec -> { Thread.sleep(100); counter.incrementAndGet(); CompletableFuture result = new CompletableFuture<>(); result.completeExceptionally(new RuntimeException()); return result; }); assertTrue(proxyFuture.cancel(true)); int count = counter.get(); assertTrue(proxyFuture.isCancelled()); Asserts.assertThrows(proxyFuture::get, CancellationException.class); // Assert that execution has actually stopped Thread.sleep(20); assertEquals(count, counter.get()); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue55Test.java000066400000000000000000000032031444561050700305630ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.issues; import dev.failsafe.Fallback; import dev.failsafe.RetryPolicy; import dev.failsafe.Failsafe; import org.testng.annotations.Test; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; @Test public class Issue55Test { public void shouldOnlyFallbackOnFailure() throws Throwable { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); AtomicInteger counter = new AtomicInteger(); Failsafe.with(Fallback.of(counter::incrementAndGet), RetryPolicy.ofDefaults()).with(executor).getAsync(() -> null); Thread.sleep(100); assertEquals(counter.get(), 0); Failsafe.with(Fallback.of(counter::incrementAndGet), RetryPolicy.builder().withMaxRetries(1).build()) .with(executor) .runAsync(() -> { throw new RuntimeException(); }); Thread.sleep(100); assertEquals(counter.get(), 1); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue5Test.java000066400000000000000000000031101444561050700304730ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.*; @Test public class Issue5Test { /** * Asserts that a failure is handled as expected by a listener registered via whenFailure. */ public void test() throws Throwable { Waiter waiter = new Waiter(); RetryPolicy retryPolicy = RetryPolicy.builder() .withDelay(Duration.ofMillis(100)) .withMaxDuration(Duration.ofSeconds(2)) .withMaxRetries(3) .handleResult(null) .build(); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); Failsafe.with(retryPolicy).with(executor).onFailure(e -> { waiter.assertNull(e.getResult()); waiter.assertNull(e.getException()); waiter.resume(); }).getAsync(() -> null); waiter.await(1000); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue75Test.java000066400000000000000000000017321444561050700305720ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.Fallback; import dev.failsafe.CircuitBreaker; import dev.failsafe.Failsafe; import org.testng.Assert; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; public class Issue75Test { @Test public void testThatFailSafeIsBrokenWithFallback() throws Exception { CircuitBreaker breaker = CircuitBreaker.builder() .withFailureThreshold(10, 100) .withSuccessThreshold(2) .withDelay(Duration.ofMillis(100)) .build(); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); int result = Failsafe.with(Fallback.of(e -> 999), breaker) .with(executor) .getStageAsync(() -> CompletableFuture.completedFuture(223)) .get(); Assert.assertEquals(result, 223); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue76Test.java000066400000000000000000000026771444561050700306040ustar00rootroot00000000000000package dev.failsafe.issues; import net.jodah.concurrentunit.Waiter; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import org.testng.annotations.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; @Test public class Issue76Test { public void shouldAbortOnSyncError() { AssertionError error = new AssertionError(); try { Failsafe.with(RetryPolicy.builder().abortOn(AssertionError.class).build()).run(() -> { throw error; }); fail(); } catch (AssertionError e) { assertEquals(e, error); } } public void shouldAbortOnAsyncError() throws Exception { final AssertionError error = new AssertionError(); Waiter waiter = new Waiter(); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); Future future = Failsafe.with(RetryPolicy.builder().abortOn(AssertionError.class).onAbort(e -> { waiter.assertEquals(e.getException(), error); waiter.resume(); }).build()).with(executor).runAsync(() -> { throw error; }); waiter.await(1000); try { future.get(); fail(); } catch (ExecutionException e) { assertEquals(e.getCause(), error); } finally { executor.shutdownNow(); } } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue84Test.java000066400000000000000000000035521444561050700305740ustar00rootroot00000000000000package dev.failsafe.issues; import dev.failsafe.CircuitBreaker; import dev.failsafe.CircuitBreakerOpenException; import dev.failsafe.Failsafe; import dev.failsafe.Fallback; import dev.failsafe.testing.Asserts; import org.testng.annotations.Test; import java.time.Duration; import java.util.concurrent.*; import static org.testng.Assert.assertFalse; @Test public class Issue84Test { public void shouldHandleCircuitBreakerOpenException() throws Throwable { ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); CircuitBreaker circuitBreaker = CircuitBreaker.builder() .withDelay(Duration.ofMinutes(10)) .handleResult(false) .build(); circuitBreaker.open(); // Synchronous Asserts.assertThrows(() -> Failsafe.with(circuitBreaker).get(() -> true), CircuitBreakerOpenException.class); // Synchronous with fallback assertFalse(Failsafe.with(Fallback.of(false), circuitBreaker).get(() -> true)); // Asynchronous Future future1 = Failsafe.with(circuitBreaker).with(executor).getAsync(() -> true); Asserts.assertThrows(future1::get, ExecutionException.class, CircuitBreakerOpenException.class); // Asynchronous with fallback Future future2 = Failsafe.with(Fallback.of(false), circuitBreaker).with(executor).getAsync(() -> true); assertFalse(future2.get()); // Future Future future3 = Failsafe.with(circuitBreaker) .with(executor) .getStageAsync(() -> CompletableFuture.completedFuture(false)); Asserts.assertThrows(future3::get, ExecutionException.class, CircuitBreakerOpenException.class); // Future with fallback Future future4 = Failsafe.with(Fallback.of(false), circuitBreaker) .getStageAsync(() -> CompletableFuture.completedFuture(false)); assertFalse(future4.get()); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/issues/Issue9Test.java000066400000000000000000000042561444561050700305130ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.issues; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import net.jodah.concurrentunit.Waiter; import org.testng.annotations.Test; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; import static dev.failsafe.testing.Mocking.failures; import static org.mockito.Mockito.*; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class Issue9Test { public interface Service { boolean connect(); } public void test() throws Throwable { // Given - Fail twice then succeed AtomicInteger retryCounter = new AtomicInteger(); Service service = mock(Service.class); when(service.connect()).thenThrow(failures(2, new IllegalStateException())).thenReturn(true); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); Waiter waiter = new Waiter(); // When AtomicInteger successCounter = new AtomicInteger(); Future future = Failsafe.with( RetryPolicy.builder().withMaxRetries(2).onRetry(e -> retryCounter.incrementAndGet()).build()) .with(executor) .onSuccess(p -> { successCounter.incrementAndGet(); waiter.resume(); }) .getAsync(service::connect); // Then waiter.await(1000); verify(service, times(3)).connect(); assertTrue(future.get()); assertEquals(retryCounter.get(), 2); assertEquals(successCounter.get(), 1); executor.shutdownNow(); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/000077500000000000000000000000001444561050700257625ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/Asserts.java000066400000000000000000000127231444561050700302560ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.testing; import dev.failsafe.function.CheckedRunnable; import dev.failsafe.function.CheckedSupplier; import org.testng.Assert; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Formatter; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import static org.testng.Assert.assertEquals; /** * Utilities to assist with performing assertions. */ public class Asserts { static class CompositeError extends Error { private final List errors; CompositeError(List errors) { this.errors = errors; } @Override public String getMessage() { Formatter fmt = new Formatter().format("Errors encountered:%n%n"); int index = 1; for (Error error : errors) { StringWriter writer = new StringWriter(); error.printStackTrace(new PrintWriter(writer)); fmt.format("%s) %s%n", index++, writer.getBuffer()); } if (errors.size() == 1) { fmt.format("1 error"); } else { fmt.format("%s errors", errors.size()); } return fmt.toString(); } } /** * Records assertions from any thread so that they can be re-thrown from a main test thread. */ public static class Recorder { private List errors = new CopyOnWriteArrayList<>(); public void reset() { errors.clear(); } public void assertEquals(Object expected, Object actual) { if (expected == null && actual == null) return; if (expected != null && expected.equals(actual)) return; fail(format(expected, actual)); } public void assertFalse(boolean condition) { if (condition) fail("expected false"); } public void assertNotNull(Object object) { if (object == null) fail("expected not null"); } public void assertNull(Object object) { if (object != null) fail(format("null", object)); } public void assertTrue(boolean condition) { if (!condition) fail("expected true"); } public void fail(String reason) { fail(new AssertionError(reason)); } public void fail(Throwable reason) { if (reason instanceof AssertionError) errors.add((AssertionError) reason); else { AssertionError error = new AssertionError(); error.initCause(reason); errors.add(error); } } public void throwFailures() { if (!errors.isEmpty()) throw new CompositeError(errors); } private String format(Object expected, Object actual) { return "expected:<" + expected + "> but was:<" + actual + ">"; } } @SafeVarargs public static boolean matches(Throwable actual, Class... throwableHierarchy) { Throwable current = actual; for (Class expected : throwableHierarchy) { if (!expected.isInstance(current)) return false; current = current.getCause(); } return true; } @SafeVarargs public static void assertMatches(Throwable actual, Class... throwableHierarchy) { assertMatches(actual, Arrays.asList(throwableHierarchy)); } public static void assertMatches(Throwable actual, List> throwableHierarchy) { Throwable current = actual; for (Class expected : throwableHierarchy) { if (!expected.equals(current.getClass())) Assert.fail( String.format("Bad exception type. Expected %s but was %s", Arrays.toString(throwableHierarchy.toArray()), actual), actual); current = current.getCause(); } } public static void assertThrows(CheckedRunnable runnable, Throwable throwable) { try { runnable.run(); Assert.fail("No exception was thrown"); } catch (Throwable t) { assertEquals(t, throwable, "The expected exception was not thrown"); } } @SafeVarargs public static void assertThrows(CheckedRunnable runnable, Class... expectedExceptions) { assertThrows(runnable, t -> Arrays.asList(expectedExceptions)); } public static void assertThrows(CheckedRunnable runnable, List> expectedExceptions) { assertThrows(runnable, t -> expectedExceptions); } public static void assertThrowsSup(CheckedSupplier supplier, List> expectedExceptions) { assertThrows(supplier::get, t -> expectedExceptions); } public static void assertThrows(CheckedRunnable runnable, Function>> expectedExceptionsFn) { try { runnable.run(); Assert.fail("No exception was thrown. Expected: " + expectedExceptionsFn.apply(null)); } catch (Throwable t) { assertMatches(t, expectedExceptionsFn.apply(t)); } } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/Logging.java000066400000000000000000000202631444561050700302160ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.testing; import dev.failsafe.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * Logging and stats for tests. */ public class Logging extends Mocking { public static class Stats { // Common public volatile int executionCount; public volatile int failureCount; public volatile int successCount; // RetryPolicy public volatile int failedAttemptCount; public volatile int retryCount; public volatile int retryScheduledCount; public volatile int retriesExceededCount; public volatile int abortCount; // CircuitBreaker public volatile int openCount; public volatile int halfOpenCount; public volatile int closedCount; public void reset() { executionCount = 0; failureCount = 0; successCount = 0; failedAttemptCount = 0; retryCount = 0; retryScheduledCount = 0; retriesExceededCount = 0; abortCount = 0; openCount = 0; halfOpenCount = 0; closedCount = 0; } } static volatile long lastTimestamp; public static void log(Object object, String msg, Object... args) { Class clazz = object instanceof Class ? (Class) object : object.getClass(); log(clazz.getSimpleName() + " " + String.format(msg, args)); } public static void log(Class clazz, String msg) { log(clazz.getSimpleName() + " " + msg); } public static void log(String msg) { long currentTimestamp = System.currentTimeMillis(); if (lastTimestamp + 80 < currentTimestamp) System.out.printf("%n%n"); lastTimestamp = currentTimestamp; String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("H:mm:ss.SSS")); StringBuilder threadName = new StringBuilder(Thread.currentThread().getName()); for (int i = threadName.length(); i < 35; i++) threadName.append(" "); System.out.println("[" + time + "] " + "[" + threadName + "] " + msg); } /** * Note: The internal stats that are logged are not reset, even across multiple executions. */ public static RetryPolicyBuilder withLogs(RetryPolicyBuilder retryPolicy) { return withStatsAndLogs(retryPolicy, new Stats(), true); } /** * Note: The internal stats that are logged are not reset, even across multiple executions. */ public static TimeoutBuilder withLogs(TimeoutBuilder builder) { return withStatsAndLogs(builder, new Stats(), true); } /** * Note: The internal stats that are logged are not reset, even across multiple executions. */ public static CircuitBreakerBuilder withLogs(CircuitBreakerBuilder builder) { return withStatsAndLogs(builder, new Stats(), true); } /** * Note: The internal stats that are logged are not reset, even across multiple executions. */ public static , R>, R> T withLogs(T policy) { return withStatsAndLogs(policy, new Stats(), true); } public static RetryPolicyBuilder withStats(RetryPolicyBuilder builder, Stats stats) { return withStatsAndLogs(builder, stats, false); } public static RetryPolicyBuilder withStatsAndLogs(RetryPolicyBuilder builder, Stats stats) { return withStatsAndLogs(builder, stats, true); } private static RetryPolicyBuilder withStatsAndLogs(RetryPolicyBuilder builder, Stats stats, boolean withLogging) { builder.onFailedAttempt(e -> { stats.executionCount++; stats.failedAttemptCount++; if (withLogging) System.out.printf("RetryPolicy %s failed attempt [result: %s, failure: %s, attempts: %s, executions: %s]%n", builder.hashCode(), e.getLastResult(), e.getLastException(), e.getAttemptCount(), e.getExecutionCount()); }).onRetry(e -> { stats.retryCount++; if (withLogging) System.out.printf("RetryPolicy %s retrying [result: %s, failure: %s]%n", builder.hashCode(), e.getLastResult(), e.getLastException()); }).onRetryScheduled(e -> { stats.retryScheduledCount++; if (withLogging) System.out.printf("RetryPolicy %s scheduled [delay: %s ms]%n", builder.hashCode(), e.getDelay().toMillis()); }).onRetriesExceeded(e -> { stats.retriesExceededCount++; if (withLogging) System.out.printf("RetryPolicy %s retries exceeded%n", builder.hashCode()); }).onAbort(e -> { stats.abortCount++; if (withLogging) System.out.printf("RetryPolicy %s abort%n", builder.hashCode()); }); withStatsAndLogs((PolicyBuilder) builder, stats, withLogging); return builder; } public static TimeoutBuilder withStats(TimeoutBuilder builder, Stats stats) { return withStatsAndLogs(builder, stats, false); } public static TimeoutBuilder withStatsAndLogs(TimeoutBuilder builder, Stats stats) { return withStatsAndLogs(builder, stats, true); } private static TimeoutBuilder withStatsAndLogs(TimeoutBuilder builder, Stats stats, boolean withLogging) { return builder.onSuccess(e -> { stats.executionCount++; stats.successCount++; if (withLogging) System.out.printf("Timeout %s success policy executions=%s, successes=%s%n", builder.hashCode(), stats.executionCount, stats.successCount); }).onFailure(e -> { stats.executionCount++; stats.failureCount++; if (withLogging) System.out.printf("Timeout %s exceeded policy executions=%s, failure=%s%n", builder.hashCode(), stats.executionCount, stats.failureCount); }); } public static CircuitBreakerBuilder withStats(CircuitBreakerBuilder builder, Stats stats) { return withStatsAndLogs(builder, stats, false); } public static CircuitBreakerBuilder withStatsAndLogs(CircuitBreakerBuilder builder, Stats stats) { return withStatsAndLogs(builder, stats, true); } private static CircuitBreakerBuilder withStatsAndLogs(CircuitBreakerBuilder builder, Stats stats, boolean withLogging) { builder.onOpen(e -> { stats.openCount++; if (withLogging) System.out.println("CircuitBreaker opening"); }).onHalfOpen(e -> { stats.halfOpenCount++; if (withLogging) System.out.println("CircuitBreaker half-opening"); }).onClose(e -> { stats.closedCount++; if (withLogging) System.out.println("CircuitBreaker closing"); }); withStatsAndLogs((PolicyBuilder) builder, stats, withLogging); return builder; } public static , R>, R> T withStats(T builder, Stats stats) { return withStatsAndLogs(builder, stats, false); } public static , R>, R> T withStatsAndLogs(T builder, Stats stats) { return withStatsAndLogs(builder, stats, true); } private static , R>, R> T withStatsAndLogs(T builder, Stats stats, boolean withLogging) { builder.onSuccess(e -> { stats.executionCount++; stats.successCount++; if (withLogging) System.out.printf("%s success [result: %s, attempts: %s, executions: %s]%n", builder.getClass().getSimpleName(), e.getResult(), e.getAttemptCount(), e.getExecutionCount()); }); builder.onFailure(e -> { stats.executionCount++; stats.failureCount++; if (withLogging) System.out.printf("%s failure [result: %s, failure: %s, attempts: %s, executions: %s]%n", builder.getClass().getSimpleName(), e.getResult(), e.getException(), e.getAttemptCount(), e.getExecutionCount()); }); return builder; } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/Mocking.java000066400000000000000000000061401444561050700302150ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.testing; import dev.failsafe.DelayablePolicyBuilder; import dev.failsafe.DelayablePolicyConfig; import dev.failsafe.spi.DelayablePolicy; import dev.failsafe.spi.FailurePolicy; import dev.failsafe.Policy; import dev.failsafe.spi.PolicyExecutor; /** * Utilities to assist with creating mocks. */ public class Mocking extends Asserts { public static class FooConfig extends DelayablePolicyConfig { } public static class FooPolicyBuilder extends DelayablePolicyBuilder, FooConfig, R> { FooPolicyBuilder() { super(new FooConfig<>()); } public FooPolicy build() { return new FooPolicy<>(config); } } public static class FooPolicy implements Policy, FailurePolicy, DelayablePolicy { FooConfig config; FooPolicy(FooConfig config) { this.config = config; } public static FooPolicyBuilder builder() { return new FooPolicyBuilder<>(); } @Override public FooConfig getConfig() { return config; } @Override public PolicyExecutor toExecutor(int policyIndex) { return null; } } public static class ConnectException extends RuntimeException { } /** * A mock Service implementation that throws a specified number of failures then returns a result. */ public static class Service { int numFailuresExpected; boolean resultExpected; int numFailuresSoFar; public Service() { numFailuresExpected = -1; } public Service(boolean result) { resultExpected = result; } public Service(int numFailures, boolean result) { numFailuresExpected = numFailures; resultExpected = result; } public boolean connect() { if (numFailuresExpected > -1 && numFailuresExpected == numFailuresSoFar) return resultExpected; else { numFailuresSoFar++; throw new ConnectException(); } } public void reset() { numFailuresSoFar = 0; } } public static Service mockFailingService() { return new Service(); } public static Service mockService(boolean result) { return new Service(result); } public static Service mockService(int numFailures, boolean result) { return new Service(numFailures, result); } public static Exception[] failures(int numFailures, Exception failure) { Exception[] failures = new Exception[numFailures]; for (int i = 0; i < numFailures; i++) failures[i] = failure; return failures; } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/TestCaseLogger.java000066400000000000000000000034021444561050700314770ustar00rootroot00000000000000/* * Copyright 2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.testing; import org.testng.*; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Logs test invocations. */ public class TestCaseLogger implements IInvokedMethodListener { private static final Map START_TIMES = new ConcurrentHashMap<>(); public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { if (!method.isTestMethod()) return; ITestNGMethod testMethod = method.getTestMethod(); IClass clazz = testMethod.getTestClass(); START_TIMES.put(testMethod, System.currentTimeMillis()); System.out.printf("BEFORE %s#%s%n", clazz.getRealClass().getName(), testMethod.getMethodName()); } public void afterInvocation(IInvokedMethod method, ITestResult testResult) { if (!method.isTestMethod()) return; ITestNGMethod testMethod = method.getTestMethod(); IClass clazz = testMethod.getTestClass(); double elapsed = (System.currentTimeMillis() - START_TIMES.remove(testMethod)) / 1000.0; if (elapsed > 1) System.out.printf("AFTER %s#%s Ran for %s seconds%n", clazz.getRealClass().getName(), testMethod.getMethodName(), elapsed); } }failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/Testing.java000066400000000000000000000367711444561050700302600ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.testing; import dev.failsafe.FailsafeException; import dev.failsafe.FailsafeExecutor; import dev.failsafe.RetryPolicy; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.function.*; import dev.failsafe.internal.util.Lists; import net.jodah.concurrentunit.Waiter; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; /** * Utilities to assist with writing tests. */ public class Testing extends Logging { // Signals the test framework to call AsyncExecution.complete() for AsyncExecutions // Otherwise this is treated as a null expected result public static Object COMPLETE_SIGNAL = new Object(); public RetryPolicy retryAlways = RetryPolicy.builder().withMaxRetries(-1).build(); public RetryPolicy retryNever = RetryPolicy.builder().withMaxRetries(0).build(); public RetryPolicy retryTwice = RetryPolicy.ofDefaults(); public interface Then { void accept(CompletableFuture future, ExecutionCompletedEvent event); } public interface Server { boolean connect(); } public static Throwable getThrowable(CheckedRunnable runnable) { try { runnable.run(); } catch (Throwable t) { return t; } return null; } public static T ignoreExceptions(CheckedSupplier supplier) { try { return supplier.get(); } catch (Throwable t) { return null; } } public static void ignoreExceptions(CheckedRunnable runnable) { try { runnable.run(); } catch (Throwable e) { } } public static void runInThread(CheckedRunnable runnable) { new Thread(() -> ignoreExceptions(runnable)).start(); } public static void runAsync(CheckedRunnable runnable) { CompletableFuture.runAsync(() -> { try { runnable.run(); } catch (Throwable throwable) { throwable.printStackTrace(); } }); } public static long timed(CheckedRunnable runnable) { long startTime = System.currentTimeMillis(); try { runnable.run(); } catch (Throwable ignore) { } return System.currentTimeMillis() - startTime; } /** * Returns a future that is completed with the {@code result} on the {@code executor}. */ public static CompletableFuture futureResult(ScheduledExecutorService executor, Object result) { CompletableFuture future = new CompletableFuture<>(); executor.schedule(() -> future.complete(result), 0, TimeUnit.MILLISECONDS); return future; } /** * Returns a future that is completed with the {@code exception} on the {@code executor}. */ public static CompletableFuture futureException(ScheduledExecutorService executor, Exception exception) { CompletableFuture future = new CompletableFuture<>(); executor.schedule(() -> future.completeExceptionally(exception), 0, TimeUnit.MILLISECONDS); return future; } public static void sleep(long duration) { try { Thread.sleep(duration); } catch (InterruptedException ignore) { } } /** * Unwraps and throws ExecutionException and FailsafeException causes. */ public static T unwrapExceptions(CheckedSupplier supplier) { try { return supplier.get(); } catch (ExecutionException e) { sneakyThrow(e.getCause()); return null; } catch (FailsafeException e) { sneakyThrow(e.getCause() == null ? e : e.getCause()); return null; } catch (RuntimeException | Error e) { e.printStackTrace(); throw e; } catch (Throwable t) { throw new RuntimeException(t); } } @SuppressWarnings("unchecked") public static void sneakyThrow(Throwable e) throws E { throw (E) e; } public static Runnable uncheck(CheckedRunnable runnable) { return () -> { try { runnable.run(); } catch (Throwable e) { throw new RuntimeException(e); } }; } public static void testRunSuccess(FailsafeExecutor failsafe, ContextualRunnable when, T expectedResult) { testRunSuccess(null, failsafe, when, null, expectedResult); } public static void testRunSuccess(FailsafeExecutor failsafe, ContextualRunnable when, Then then, T expectedResult) { testRunSuccess(null, failsafe, when, then, expectedResult); } public static void testRunSuccess(CheckedRunnable given, FailsafeExecutor failsafe, ContextualRunnable when, Then then, T expectedResult) { testRunSuccess(true, given, failsafe, when, then, expectedResult); } public static void testRunSuccess(boolean runAsyncExecutions, CheckedRunnable given, FailsafeExecutor failsafe, ContextualRunnable when, Then then, T expectedResult) { ContextualSupplier whenSupplier = ctx -> { when.run(ctx); return null; }; testGetInternal(runAsyncExecutions, given, failsafe, whenSupplier, then, expectedResult, null); } public static void testGetSuccess(boolean runAsyncExecutions, FailsafeExecutor failsafe, ContextualSupplier when, T expectedResult) { testGetInternal(runAsyncExecutions, null, failsafe, when, null, expectedResult, null); } public static void testGetSuccess(FailsafeExecutor failsafe, ContextualSupplier when, T expectedResult) { testGetInternal(true, null, failsafe, when, null, expectedResult, null); } public static void testGetSuccess(FailsafeExecutor failsafe, ContextualSupplier when, Then then, T expectedResult) { testGetInternal(true, null, failsafe, when, then, expectedResult, null); } public static void testGetSuccess(CheckedRunnable given, FailsafeExecutor failsafe, ContextualSupplier when, T expectedResult) { testGetInternal(true, given, failsafe, when, null, expectedResult, null); } public static void testGetSuccess(CheckedRunnable given, FailsafeExecutor failsafe, ContextualSupplier when, Then then, T expectedResult) { testGetInternal(true, given, failsafe, when, then, expectedResult, null); } public static void testGetSuccess(boolean runAsyncExecutions, CheckedRunnable given, FailsafeExecutor failsafe, ContextualSupplier when, Then then, T expectedResult) { testGetInternal(runAsyncExecutions, given, failsafe, when, then, expectedResult, null); } @SafeVarargs public static void testRunFailure(FailsafeExecutor failsafe, ContextualRunnable when, Class... expectedExceptions) { testRunFailure(true, null, failsafe, when, null, expectedExceptions); } @SafeVarargs public static void testRunFailure(FailsafeExecutor failsafe, ContextualRunnable when, Then then, Class... expectedExceptions) { testRunFailure(true, null, failsafe, when, then, expectedExceptions); } @SafeVarargs public static void testRunFailure(boolean runAsyncExecutions, FailsafeExecutor failsafe, ContextualRunnable when, Then then, Class... expectedExceptions) { testRunFailure(runAsyncExecutions, null, failsafe, when, then, expectedExceptions); } @SafeVarargs public static void testRunFailure(CheckedRunnable given, FailsafeExecutor failsafe, ContextualRunnable when, Class... expectedExceptions) { testRunFailure(true, given, failsafe, when, null, expectedExceptions); } @SafeVarargs public static void testRunFailure(CheckedRunnable given, FailsafeExecutor failsafe, ContextualRunnable when, Then then, Class... expectedExceptions) { testRunFailure(true, given, failsafe, when, then, expectedExceptions); } @SafeVarargs public static void testRunFailure(boolean runAsyncExecutions, CheckedRunnable given, FailsafeExecutor failsafe, ContextualRunnable when, Then then, Class... expectedExceptions) { ContextualSupplier whenSupplier = ctx -> { when.run(ctx); return null; }; testGetInternal(runAsyncExecutions, given, failsafe, whenSupplier, then, null, expectedExceptions); } @SafeVarargs public static void testGetFailure(FailsafeExecutor failsafe, ContextualSupplier when, Class... expectedExceptions) { testGetInternal(true, null, failsafe, when, null, null, expectedExceptions); } @SafeVarargs public static void testGetFailure(boolean runAsyncExecutions, FailsafeExecutor failsafe, ContextualSupplier when, Then then, Class... expectedExceptions) { testGetInternal(runAsyncExecutions, null, failsafe, when, then, null, expectedExceptions); } @SafeVarargs public static void testGetFailure(FailsafeExecutor failsafe, ContextualSupplier when, Then then, Class... expectedExceptions) { testGetInternal(true, null, failsafe, when, then, null, expectedExceptions); } @SafeVarargs public static void testGetFailure(CheckedRunnable given, FailsafeExecutor failsafe, ContextualSupplier when, Then then, Class... expectedExceptions) { testGetInternal(true, given, failsafe, when, then, null, expectedExceptions); } /** * This method helps ensure behavior is identical between sync and async executions. *

* Does a .get, .getAsync, .getAsyncExecution, and .getStageAsync against the failsafe, performing pre-test setup and * post-test assertion checks. {@code expectedResult} and {@code expectedExceptions} are verified against the returned * result or thrown exceptions _and_ the ExecutionCompletedEvent's result and failure. * * @param given The pre-execution setup to perform. Useful for resetting stats and mocks. * @param failsafe The FailsafeExecutor to execute with * @param when the Supplier to provide to the FailsafeExecutor * @param then post-test Assertions that are provided with Future (if any) and ExecutionCompletedEvent * @param expectedResult The expected result to assert against the actual result * @param expectedExceptions The expected exceptions to assert against the actual exceptions * @param runAsyncExecutions Indicates whether to run the AsyncExecution tests, including .getAsyncExecution. These * may be skipped for tests that involve timeouts, which don't work reliably against AsyncExecutions, since those may * return immediately. */ private static void testGetInternal(boolean runAsyncExecutions, CheckedRunnable given, FailsafeExecutor failsafe, ContextualSupplier when, Then then, T expectedResult, Class[] expectedExceptions) { AtomicReference> futureRef = new AtomicReference<>(); AtomicReference> completedEventRef = new AtomicReference<>(); Waiter completionListenerWaiter = new Waiter(); EventListener> setCompletedEventFn = e -> { completedEventRef.set(e); completionListenerWaiter.resume(); }; List> expected = new LinkedList<>(); Class[] expectedExInner = expectedExceptions == null ? new Class[] {} : expectedExceptions; Collections.addAll(expected, expectedExInner); // Assert results by treating COMPLETE_SIGNAL as a null expected result Consumer resultAssertion = result -> { if (result == COMPLETE_SIGNAL) assertNull(expectedResult); else assertEquals(result, expectedResult); }; Runnable postTestFn = () -> { ignoreExceptions(() -> completionListenerWaiter.await(5000)); ExecutionCompletedEvent completedEvent = completedEventRef.get(); if (expectedExInner.length > 0) { assertNull(completedEvent.getResult()); assertMatches(completedEvent.getException(), Arrays.asList(expectedExInner)); } else { resultAssertion.accept(completedEvent.getResult()); assertNull(completedEvent.getException()); } if (then != null) then.accept(futureRef.get(), completedEvent); }; // Run sync test System.out.println("\nRunning sync test"); if (given != null) uncheck(given).run(); if (expectedExInner.length == 0) { resultAssertion.accept(unwrapExceptions(() -> failsafe.onComplete(setCompletedEventFn).get(when))); } else { assertThrows(() -> failsafe.onComplete(setCompletedEventFn).get(when), t -> { // Insert FailsafeException into expected exceptions if needed if (t instanceof FailsafeException && !FailsafeException.class.equals(expected.get(0)) && !RuntimeException.class.isAssignableFrom(expected.get(0))) return Lists.of(FailsafeException.class, expectedExInner); return expected; }); } postTestFn.run(); if (expectedExInner.length > 0) expected.add(0, ExecutionException.class); // Create async tester Consumer, CompletableFuture>> asyncTester = test -> { if (given != null) uncheck(given).run(); CompletableFuture future = test.apply(failsafe.onComplete(setCompletedEventFn)); futureRef.set(future); if (expectedExInner.length == 0) resultAssertion.accept(unwrapExceptions(future::get)); else assertThrowsSup(future::get, expected); postTestFn.run(); }; // Run async test System.out.println("\nRunning async test"); asyncTester.accept(executor -> executor.getAsync(when)); // Run async execution test if (runAsyncExecutions) { System.out.println("\nRunning async execution test"); AsyncRunnable asyncExecutionWhen = exec -> { // Run supplier in a different thread runInThread(() -> { try { T result = when.get(exec); if (result == COMPLETE_SIGNAL) exec.complete(); else exec.recordResult(result); } catch (Throwable t) { exec.recordException(t); } }); }; asyncTester.accept(executor -> executor.getAsyncExecution(asyncExecutionWhen)); } // Run stage async test System.out.println("\nRunning get stage async test"); ContextualSupplier> stageAsyncWhen = ctx -> { CompletableFuture promise = new CompletableFuture<>(); // Run supplier in a different thread runInThread(() -> { try { promise.complete(when.get(ctx)); } catch (Throwable t) { promise.completeExceptionally(t); } }); return promise; }; asyncTester.accept(executor -> executor.getStageAsync(stageAsyncWhen)); } } failsafe-failsafe-parent-3.3.2/core/src/test/java/dev/failsafe/testing/package-info.java000066400000000000000000000001131444561050700311440ustar00rootroot00000000000000/** * Utilities to assist with testing. */ package dev.failsafe.testing; failsafe-failsafe-parent-3.3.2/examples/000077500000000000000000000000001444561050700201545ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/pom.xml000066400000000000000000000031341444561050700214720ustar00rootroot00000000000000 4.0.0 dev.failsafe failsafe-examples 1.0-SNAPSHOT Failsafe Examples 3.2.1 1.8 1.8 ${project.groupId} failsafe ${failsafe.version} io.reactivex rxjava 1.0.12 io.netty netty-all 4.1.51.Final io.vertx vertx-core 3.9.8 org.testng testng 6.9.10 org.mockito mockito-core 4.2.0 failsafe-failsafe-parent-3.3.2/examples/src/000077500000000000000000000000001444561050700207435ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/src/main/000077500000000000000000000000001444561050700216675ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/src/main/java/000077500000000000000000000000001444561050700226105ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/000077500000000000000000000000001444561050700233665ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/000077500000000000000000000000001444561050700251405ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/000077500000000000000000000000001444561050700267565ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/AsyncExample.java000066400000000000000000000041261444561050700322150ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.examples; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; public class AsyncExample { static ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); static RetryPolicy retryPolicy = RetryPolicy.builder() .withDelay(Duration.ofMillis(100)) .withJitter(.25) .onRetry(e -> System.out.println("Connection attempt failed. Retrying.")) .onSuccess(e -> System.out.println("Success")) .onFailure(e -> System.out.println("Connection attempts failed")) .build(); static Service service = new Service(); public static class Service { AtomicInteger failures = new AtomicInteger(); // Fail 2 times then succeed CompletableFuture connect() { CompletableFuture future = new CompletableFuture<>(); executor.submit(() -> { if (failures.getAndIncrement() < 2) future.completeExceptionally(new RuntimeException()); else future.complete(true); }); return future; } } public static void main(String... args) throws Throwable { Failsafe.with(retryPolicy) .with(executor) .getAsyncExecution(execution -> service.connect().whenComplete(execution::record)); Thread.sleep(3000); System.exit(0); } } failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/Java8Example.java000066400000000000000000000043211444561050700321060ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.examples; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; public class Java8Example { @SuppressWarnings("unused") public static void main(String... args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); RetryPolicy retryPolicy = RetryPolicy.ofDefaults(); // Create a retryable functional interface Function bar = value -> Failsafe.with(retryPolicy).get(() -> value + "bar"); // Create a retryable Stream operation Failsafe.with(retryPolicy).get(() -> Stream.of("foo") .map(value -> Failsafe.with(retryPolicy).get(() -> value + "bar")) .collect(Collectors.toList())); // Create an individual retryable Stream operation Stream.of("foo").map(value -> Failsafe.with(retryPolicy).get(() -> value + "bar")).forEach(System.out::println); // Create a retryable CompletableFuture Failsafe.with(retryPolicy).with(executor).getStageAsync(() -> CompletableFuture.supplyAsync(() -> "foo") .thenApplyAsync(value -> value + "bar") .thenAccept(System.out::println)); // Create an individual retryable CompletableFuture stages CompletableFuture.supplyAsync(() -> Failsafe.with(retryPolicy).get(() -> "foo")) .thenApplyAsync(value -> Failsafe.with(retryPolicy).get(() -> value + "bar")) .thenAccept(System.out::println); } } failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/NettyExample.java000066400000000000000000000057461444561050700322540ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.examples; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import java.time.Duration; public class NettyExample { static final String HOST = System.getProperty("host", "127.0.0.1"); static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); public static void main(String... args) throws Throwable { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = createBootstrap(group); RetryPolicy retryPolicy = RetryPolicy.builder() .withDelay(Duration.ofSeconds(1)) .onSuccess(e -> System.out.println("Success!")) .onFailure(e -> System.out.println("Connection attempts failed")) .build(); Failsafe.with(retryPolicy) .with(group) .runAsyncExecution( execution -> bootstrap.connect(HOST, PORT).addListener((ChannelFutureListener) channelFuture -> { if (channelFuture.isSuccess()) { execution.complete(); try { channelFuture.sync(); channelFuture.channel().closeFuture().sync(); } catch (Exception ignore) { group.shutdownGracefully(); } } else execution.recordFailure(channelFuture.cause()); })); Thread.sleep(5000); } static Bootstrap createBootstrap(EventLoopGroup group) { return new Bootstrap().group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // Close the connection when an exception is raised. cause.printStackTrace(); ctx.close(); } }); } }); } } failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/RetryLoopExample.java000066400000000000000000000034161444561050700331000ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.examples; import dev.failsafe.Execution; import dev.failsafe.RetryPolicy; import java.time.temporal.ChronoUnit; import java.util.List; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; @SuppressWarnings("unchecked") public class RetryLoopExample { static List list; static { list = mock(List.class); when(list.size()).thenThrow(IllegalStateException.class, IllegalStateException.class).thenReturn(5); } public static void main(String... args) throws Throwable { RetryPolicy retryPolicy = RetryPolicy.builder() .handle(IllegalStateException.class) .withBackoff(10, 40, ChronoUnit.MILLIS) .build(); Execution execution = Execution.of(retryPolicy); while (!execution.isComplete()) { try { execution.recordResult(list.size()); } catch (IllegalStateException e) { execution.recordFailure(e); // Wait before retrying Thread.sleep(execution.getDelay().toMillis()); } } assertEquals(execution.getLastResult(), 5); assertEquals(execution.getAttemptCount(), 3); } } failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/RxJavaExample.java000066400000000000000000000034371444561050700323370ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.examples; import dev.failsafe.Execution; import dev.failsafe.RetryPolicy; import rx.Observable; import rx.Subscriber; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class RxJavaExample { public static void main(String... args) { AtomicInteger failures = new AtomicInteger(); RetryPolicy retryPolicy = RetryPolicy.builder().withDelay(Duration.ofSeconds(1)).build(); Observable.create((Subscriber s) -> { // Fail 2 times then succeed if (failures.getAndIncrement() < 2) s.onError(new RuntimeException()); else System.out.println("Subscriber completed successfully"); }).retryWhen(attempts -> { Execution execution = Execution.of(retryPolicy); return attempts.flatMap(failure -> { System.out.println("Failure detected"); execution.recordFailure(failure); if (!execution.isComplete()) return Observable.timer(execution.getDelay().toNanos(), TimeUnit.NANOSECONDS); else return Observable.error(failure); }); }).toBlocking().forEach(System.out::println); } } failsafe-failsafe-parent-3.3.2/examples/src/main/java/dev/failsafe/examples/VertxExample.java000066400000000000000000000061021444561050700322440ustar00rootroot00000000000000/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.examples; import io.vertx.core.Vertx; import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.ReplyException; import io.vertx.core.eventbus.ReplyFailure; import dev.failsafe.Failsafe; import dev.failsafe.RetryPolicy; import dev.failsafe.spi.DefaultScheduledFuture; import dev.failsafe.spi.Scheduler; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; public class VertxExample { static Vertx vertx = Vertx.vertx(); /** Create RetryPolicy to handle Vert.x failures */ static RetryPolicy> retryPolicy = RetryPolicy.>builder() .handleIf((ReplyException failure) -> ReplyFailure.RECIPIENT_FAILURE.equals(failure.failureType()) || ReplyFailure.TIMEOUT.equals(failure.failureType())) .withDelay(Duration.ofSeconds(1)) .onRetry(e -> System.out.println("Received failed reply. Retrying.")) .onSuccess(e -> System.out.println("Received reply " + e.getResult().body())) .onFailure(e -> System.out.println("Execution and retries failed")) .build(); /** Adapt Vert.x timer to a Failsafe Scheduler */ static Scheduler scheduler = (callable, delay, unit) -> { Runnable runnable = () -> { try { callable.call(); } catch (Exception ignore) { } }; return new DefaultScheduledFuture() { long timerId; { if (delay == 0) vertx.getOrCreateContext().runOnContext(e -> runnable.run()); else timerId = vertx.setTimer(unit.toMillis(delay), tid -> runnable.run()); } @Override public boolean cancel(boolean mayInterruptIfRunning) { return delay != 0 && vertx.cancelTimer(timerId); } }; }; /** * A Vert.x sender and retryable receiver example. */ public static void main(String... args) throws Throwable { // Receiver that fails 2 times then succeeds AtomicInteger failures = new AtomicInteger(); vertx.eventBus().consumer("ping-address", message -> { if (failures.getAndIncrement() < 2) message.fail(1, "Failed"); else { message.reply("pong!"); } }); // Retryable sender Failsafe.with(retryPolicy) .with(scheduler) .getAsyncExecution(execution -> vertx.eventBus().send("ping-address", "ping!", reply -> { if (reply.succeeded()) execution.recordResult(reply.result()); else execution.recordFailure(reply.cause()); })); Thread.sleep(5000); System.exit(0); } } failsafe-failsafe-parent-3.3.2/modules/000077500000000000000000000000001444561050700200065ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/000077500000000000000000000000001444561050700213175ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/pom.xml000066400000000000000000000031101444561050700226270ustar00rootroot00000000000000 4.0.0 dev.failsafe failsafe-parent 3.3.2 ../../pom.xml failsafe-okhttp Failsafe OkHttp ${project.groupId}.okhttp ${project.groupId} failsafe ${project.version} com.squareup.okhttp3 okhttp 4.9.3 ${project.groupId} failsafe ${project.version} test-jar test com.github.tomakehurst wiremock-jre8 2.32.0 test org.moditect moditect-maven-plugin failsafe-failsafe-parent-3.3.2/modules/okhttp/src/000077500000000000000000000000001444561050700221065ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/main/000077500000000000000000000000001444561050700230325ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/main/java/000077500000000000000000000000001444561050700237535ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/main/java/dev/000077500000000000000000000000001444561050700245315ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/main/java/dev/failsafe/000077500000000000000000000000001444561050700263035ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/main/java/dev/failsafe/okhttp/000077500000000000000000000000001444561050700276145ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/main/java/dev/failsafe/okhttp/FailsafeCall.java000066400000000000000000000135731444561050700327760ustar00rootroot00000000000000/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.okhttp; import dev.failsafe.*; import dev.failsafe.internal.util.Assert; import okhttp3.Callback; import okhttp3.Response; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; /** * A Failsafe wrapped OkHttp {@link Call}. Supports synchronous and asynchronous executions, and cancellation. * * @author Jonathan Halterman */ public final class FailsafeCall { private final FailsafeExecutor failsafe; private final okhttp3.Call initialCall; private volatile Call failsafeCall; private volatile CompletableFuture failsafeFuture; private AtomicBoolean cancelled = new AtomicBoolean(); private AtomicBoolean executed = new AtomicBoolean(); private FailsafeCall(FailsafeExecutor failsafe, okhttp3.Call call) { this.failsafe = failsafe; this.initialCall = call; } public static final class FailsafeCallBuilder { private FailsafeExecutor failsafe; private FailsafeCallBuilder(FailsafeExecutor failsafe) { this.failsafe = failsafe; } public

> FailsafeCallBuilder compose(P innerPolicy) { failsafe = failsafe.compose(innerPolicy); return this; } public FailsafeCall compose(okhttp3.Call call) { return new FailsafeCall(failsafe, call); } } /** * Returns a FailsafeCallBuilder for the {@code outerPolicy} and {@code policies}. See {@link Failsafe#with(Policy, * Policy[])} for docs on how policy composition works. * * @param

policy type * @throws NullPointerException if {@code call} or {@code outerPolicy} are null */ @SafeVarargs public static

> FailsafeCallBuilder with(P outerPolicy, P... policies) { return new FailsafeCallBuilder(Failsafe.with(outerPolicy, policies)); } /** * Returns a FailsafeCallBuilder for the {@code failsafeExecutor}. * * @throws NullPointerException if {@code failsafeExecutor} is null */ public static FailsafeCallBuilder with(FailsafeExecutor failsafeExecutor) { return new FailsafeCallBuilder(Assert.notNull(failsafeExecutor, "failsafeExecutor")); } /** * Cancels the call. */ public void cancel() { if (!cancelled.compareAndSet(false, true)) return; if (failsafeCall != null) failsafeCall.cancel(false); if (failsafeFuture != null) failsafeFuture.cancel(false); } /** * Returns a clone of the FailsafeCall. */ public FailsafeCall clone() { return new FailsafeCall(failsafe, initialCall.clone()); } /** * Executes the call until a successful response is returned or the configured policies are exceeded. To avoid leaking * resources callers should {@link Response#close() close} the Response which in turn will close the underlying * ResponseBody. * * @throws IllegalStateException if the call has already been executed * @throws IOException if the request could not be executed due to cancellation, a connectivity problem, or timeout * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ public Response execute() throws IOException { Assert.isTrue(executed.compareAndSet(false, true), "already executed"); failsafeCall = failsafe.newCall(ctx -> { return prepareCall(ctx).execute(); }); try { return failsafeCall.execute(); } catch (FailsafeException e) { if (e.getCause() instanceof IOException) throw (IOException) e.getCause(); throw e; } } /** * Executes the call asynchronously until a successful result is returned or the configured policies are exceeded. To * avoid leaking resources callers should {@link Response#close() close} the Response which in turn will close the * underlying ResponseBody. */ public CompletableFuture executeAsync() { if (!executed.compareAndSet(false, true)) { CompletableFuture result = new CompletableFuture<>(); result.completeExceptionally(new IllegalStateException("already executed")); return result; } failsafeFuture = failsafe.getAsyncExecution(exec -> { prepareCall(exec).enqueue(new Callback() { @Override public void onResponse(okhttp3.Call call, Response response) { exec.recordResult(response); } @Override public void onFailure(okhttp3.Call call, IOException e) { exec.recordException(e); } }); }); return failsafeFuture; } /** * Returns whether the call has been cancelled. */ public boolean isCancelled() { return cancelled.get(); } /** * Returns whether the call has been executed. */ public boolean isExecuted() { return executed.get(); } private okhttp3.Call prepareCall(ExecutionContext ctx) { okhttp3.Call call; if (ctx.isFirstAttempt()) { call = initialCall; } else { Response response = ctx.getLastResult(); if (response != null) response.close(); call = initialCall.clone(); } // Propagate cancellation to the call ctx.onCancel(() -> { cancelled.set(true); call.cancel(); }); return call; } } failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/000077500000000000000000000000001444561050700230655ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/000077500000000000000000000000001444561050700240065ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/dev/000077500000000000000000000000001444561050700245645ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/dev/failsafe/000077500000000000000000000000001444561050700263365ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/dev/failsafe/okhttp/000077500000000000000000000000001444561050700276475ustar00rootroot00000000000000FailsafeCallTest.java000066400000000000000000000165351444561050700336130ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/dev/failsafe/okhttp/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.okhttp; import com.github.tomakehurst.wiremock.WireMockServer; import dev.failsafe.*; import dev.failsafe.okhttp.testing.OkHttpTesting; import okhttp3.Call; import okhttp3.*; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; import java.time.Duration; import java.util.concurrent.CancellationException; import java.util.concurrent.Future; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class FailsafeCallTest extends OkHttpTesting { public static final String URL = "http://localhost:8080"; WireMockServer server; OkHttpClient client = new OkHttpClient.Builder().build(); @BeforeMethod protected void beforeMethod() { server = new WireMockServer(); server.start(); } @AfterMethod protected void afterMethod() { server.stop(); } public void testSuccess() { // Given mockResponse(200, "foo"); FailsafeExecutor failsafe = Failsafe.with(RetryPolicy.ofDefaults()); Call call = callFor("/test"); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, 200, "foo"); assertCalled("/test", 2); } public void testRetryPolicyOn400() { // Given mockResponse(400, "foo"); RetryPolicy retryPolicy = RetryPolicy.builder().handleResultIf(r -> r.code() == 400).build(); FailsafeExecutor failsafe = Failsafe.with(retryPolicy); Call call = callFor("/test"); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); }, 400, "foo"); assertCalled("/test", 6); } public void testRetryPolicyOnResult() { // Given mockResponse(200, "bad"); RetryPolicy retryPolicy = RetryPolicy.builder() .handleResultIf(r -> "bad".equals(r.peekBody(Long.MAX_VALUE).string())) .build(); FailsafeExecutor failsafe = Failsafe.with(retryPolicy); Call call = callFor("/test"); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); }, 200, "bad"); assertCalled("/test", 6); } public void testRetryPolicyFallback() { // Given mockResponse(400, "foo"); Fallback fallback = Fallback.builder(r -> { Response response = r.getLastResult(); ResponseBody body = ResponseBody.create("fallback", response.body().contentType()); return response.newBuilder().code(200).body(body).build(); }).handleResultIf(r -> r.code() == 400).build(); RetryPolicy retryPolicy = RetryPolicy.builder().handleResultIf(r -> r.code() == 400).build(); FailsafeExecutor failsafe = Failsafe.with(fallback, retryPolicy); Call call = callFor("/test"); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); }, 200, "fallback"); assertCalled("/test", 6); } /** * Asserts that an open circuit breaker prevents executions from occurring, even with outer retries. */ public void testCircuitBreaker() { // Given mockResponse(200, "foo"); CircuitBreaker breaker = CircuitBreaker.ofDefaults(); FailsafeExecutor failsafe = Failsafe.with(RetryPolicy.ofDefaults(), breaker); Call call = callFor("/test"); breaker.open(); // When / Then testFailure(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 0); }, CircuitBreakerOpenException.class); assertCalled("/test", 0); } public void testTimeout() { // Given mockDelayedResponse(200, "foo", 1000); FailsafeExecutor failsafe = Failsafe.with(Timeout.of(Duration.ofMillis(100))); Call call = callFor("/test"); // When / Then testFailure(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, TimeoutExceededException.class); assertCalled("/test", 2); } public void testIOException() { server.stop(); FailsafeExecutor failsafe = Failsafe.none(); Call call = callFor("/test"); testFailure(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, java.net.ConnectException.class); } public void testCancel() { // Given mockDelayedResponse(200, "foo", 1000); FailsafeExecutor failsafe = Failsafe.none(); Call call = callFor("/test"); // When / Then Sync FailsafeCall failsafeCall = FailsafeCall.with(failsafe).compose(call); runInThread(() -> { sleep(150); failsafeCall.cancel(); }); assertThrows(failsafeCall::execute, IOException.class); assertTrue(call.isCanceled()); assertTrue(failsafeCall.isCancelled()); // When / Then Async Call call2 = call.clone(); FailsafeCall failsafeCall2 = FailsafeCall.with(failsafe).compose(call2); runInThread(() -> { sleep(150); failsafeCall2.cancel(); }); assertThrows(() -> failsafeCall2.executeAsync().get(), CancellationException.class); assertTrue(call2.isCanceled()); assertTrue(failsafeCall2.isCancelled()); assertCalled("/test", 2); } public void testCancelViaFuture() { // Given mockDelayedResponse(200, "foo", 1000); FailsafeExecutor failsafe = Failsafe.none(); Call call = callFor("/test"); // When / Then Async FailsafeCall failsafeCall = FailsafeCall.with(failsafe).compose(call); Future future = failsafeCall.executeAsync(); sleep(150); future.cancel(false); assertThrows(future::get, CancellationException.class); assertTrue(call.isCanceled()); assertTrue(failsafeCall.isCancelled()); assertCalled("/test", 1); } private Call callFor(String path) { return client.newCall(new Request.Builder().url(URL + path).build()); } private void mockResponse(int responseCode, String body) { stubFor(get(urlPathEqualTo("/test")).willReturn( aResponse().withStatus(responseCode).withHeader("Content-Type", "text/plain").withBody(body))); } private void mockDelayedResponse(int responseCode, String body, int delayMillis) { stubFor(get(urlEqualTo("/test")).willReturn( aResponse().withStatus(responseCode).withFixedDelay(delayMillis).withBody(body))); } private void assertCalled(String url, int times) { verify(times, getRequestedFor(urlPathEqualTo(url))); } } failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/dev/failsafe/okhttp/testing/000077500000000000000000000000001444561050700313245ustar00rootroot00000000000000OkHttpTesting.java000066400000000000000000000073701444561050700346660ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/okhttp/src/test/java/dev/failsafe/okhttp/testingpackage dev.failsafe.okhttp.testing; import dev.failsafe.FailsafeExecutor; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.okhttp.FailsafeCall; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; public class OkHttpTesting extends Testing { public void testRequest(FailsafeExecutor failsafe, Call when, Then then, int expectedStatus, T expectedResult) { test(failsafe, when, then, expectedStatus, expectedResult, null); } @SafeVarargs public final void testFailure(FailsafeExecutor failsafe, Call when, Then then, Class... expectedExceptions) { test(failsafe, when, then, 0, null, expectedExceptions); } private void test(FailsafeExecutor failsafe, Call when, Then then, int expectedStatus, T expectedResult, Class[] expectedExceptions) { AtomicReference> futureRef = new AtomicReference<>(); AtomicReference> completedEventRef = new AtomicReference<>(); Waiter completionListenerWaiter = new Waiter(); EventListener> setCompletedEventFn = e -> { completedEventRef.set(e); completionListenerWaiter.resume(); }; List> expected = new LinkedList<>(); Class[] expectedExInner = expectedExceptions == null ? new Class[] {} : expectedExceptions; Collections.addAll(expected, expectedExInner); failsafe.onComplete(setCompletedEventFn); Runnable postTestFn = () -> { ignoreExceptions(() -> completionListenerWaiter.await(5000)); ExecutionCompletedEvent completedEvent = completedEventRef.get(); if (expectedExceptions == null) { assertEquals(completedEvent.getResult().code(), expectedStatus); assertNull(completedEvent.getException()); } else { assertNull(completedEvent.getResult()); assertMatches(completedEvent.getException(), expectedExceptions); } if (then != null) then.accept(futureRef.get(), completedEvent); }; Consumer assertResult = response -> { String result = unwrapExceptions(() -> response.body().string()); assertEquals(result, expectedResult); assertEquals(response.code(), expectedStatus); }; // Run sync test and assert result System.out.println("\nRunning sync test"); FailsafeCall failsafeCall = FailsafeCall.with(failsafe).compose(when); if (expectedExceptions == null) { assertResult.accept(unwrapExceptions(failsafeCall::execute)); } else { assertThrows(failsafeCall::execute, expectedExceptions); } postTestFn.run(); if (expectedExInner.length > 0) expected.add(0, ExecutionException.class); // Run async test and assert result System.out.println("\nRunning async test"); failsafeCall = failsafeCall.clone(); CompletableFuture future = failsafeCall.executeAsync(); futureRef.set(future); if (expectedExInner.length == 0) { assertResult.accept(unwrapExceptions(future::get)); } else { assertThrowsSup(future::get, expected); } postTestFn.run(); } } failsafe-failsafe-parent-3.3.2/modules/retrofit/000077500000000000000000000000001444561050700216445ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/pom.xml000066400000000000000000000036721444561050700231710ustar00rootroot00000000000000 4.0.0 dev.failsafe failsafe-parent 3.3.2 ../../pom.xml failsafe-retrofit Failsafe Retrofit ${project.groupId}.retrofit ${project.groupId} failsafe ${project.version} com.squareup.retrofit2 retrofit 2.9.0 ${project.groupId} failsafe ${project.version} test-jar test com.google.code.gson gson 2.9.0 test com.squareup.retrofit2 converter-gson 2.3.0 test com.github.tomakehurst wiremock-jre8 2.32.0 test org.moditect moditect-maven-plugin failsafe-failsafe-parent-3.3.2/modules/retrofit/src/000077500000000000000000000000001444561050700224335ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/main/000077500000000000000000000000001444561050700233575ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/main/java/000077500000000000000000000000001444561050700243005ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/main/java/dev/000077500000000000000000000000001444561050700250565ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/main/java/dev/failsafe/000077500000000000000000000000001444561050700266305ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/main/java/dev/failsafe/retrofit/000077500000000000000000000000001444561050700304665ustar00rootroot00000000000000FailsafeCall.java000066400000000000000000000132241444561050700335620ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/main/java/dev/failsafe/retrofit/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.failsafe.retrofit; import dev.failsafe.*; import dev.failsafe.internal.util.Assert; import retrofit2.Callback; import retrofit2.Response; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; /** * A Failsafe wrapped Retrofit {@link Call}. Supports synchronous and asynchronous executions, and cancellation. * * @param response type * @author Jonathan Halterman */ public final class FailsafeCall { private final FailsafeExecutor> failsafe; private final retrofit2.Call initialCall; private volatile Call> failsafeCall; private volatile CompletableFuture> failsafeFuture; private AtomicBoolean cancelled = new AtomicBoolean(); private AtomicBoolean executed = new AtomicBoolean(); private FailsafeCall(FailsafeExecutor> failsafe, retrofit2.Call call) { this.failsafe = failsafe; this.initialCall = call; } public static final class FailsafeCallBuilder { private FailsafeExecutor> failsafe; private FailsafeCallBuilder(FailsafeExecutor> failsafe) { this.failsafe = failsafe; } public

>> FailsafeCallBuilder compose(P innerPolicy) { failsafe = failsafe.compose(innerPolicy); return this; } public FailsafeCall compose(retrofit2.Call call) { return new FailsafeCall<>(failsafe, call); } } /** * Returns a FailsafeCallBuilder for the {@code outerPolicy} and {@code policies}. See {@link Failsafe#with(Policy, * Policy[])} for docs on how policy composition works. * * @param result type * @param

policy type * @throws NullPointerException if {@code call} or {@code outerPolicy} are null */ @SafeVarargs public static >> FailsafeCallBuilder with(P outerPolicy, P... policies) { return new FailsafeCallBuilder<>(Failsafe.with(outerPolicy, policies)); } /** * Returns a FailsafeCallBuilder for the {@code failsafeExecutor}. * * @param result type * @throws NullPointerException if {@code failsafeExecutor} is null */ public static FailsafeCallBuilder with(FailsafeExecutor> failsafeExecutor) { return new FailsafeCallBuilder<>(Assert.notNull(failsafeExecutor, "failsafeExecutor")); } /** * Cancels the call. */ public void cancel() { if (!cancelled.compareAndSet(false, true)) return; if (failsafeCall != null) failsafeCall.cancel(false); if (failsafeFuture != null) failsafeFuture.cancel(false); } /** * Returns a clone of the FailsafeCall. */ public FailsafeCall clone() { return new FailsafeCall<>(failsafe, initialCall.clone()); } /** * Executes the call until a successful response is returned or the configured policies are exceeded. * * @throws IllegalStateException if the call has already been executed * @throws IOException if the request could not be executed due to cancellation, a connectivity problem, or timeout * @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can * be used to learn the underlying checked exception. */ public Response execute() throws IOException { Assert.isTrue(executed.compareAndSet(false, true), "already executed"); failsafeCall = failsafe.newCall(ctx -> { return prepareCall(ctx).execute(); }); try { return failsafeCall.execute(); } catch (FailsafeException e) { if (e.getCause() instanceof IOException) throw (IOException) e.getCause(); throw e; } } /** * Executes the call asynchronously until a successful result is returned or the configured policies are exceeded. */ public CompletableFuture> executeAsync() { if (!executed.compareAndSet(false, true)) { CompletableFuture> result = new CompletableFuture<>(); result.completeExceptionally(new IllegalStateException("already executed")); return result; } failsafeFuture = failsafe.getAsyncExecution(exec -> { prepareCall(exec).enqueue(new Callback() { @Override public void onResponse(retrofit2.Call call, Response response) { exec.recordResult(response); } @Override public void onFailure(retrofit2.Call call, Throwable throwable) { exec.recordException(throwable); } }); }); return failsafeFuture; } /** * Returns whether the call has been cancelled. */ public boolean isCancelled() { return cancelled.get(); } /** * Returns whether the call has been executed. */ public boolean isExecuted() { return executed.get(); } private retrofit2.Call prepareCall(ExecutionContext> ctx) { retrofit2.Call call = ctx.isFirstAttempt() ? initialCall : initialCall.clone(); // Propagate cancellation to the call ctx.onCancel(() -> { cancelled.set(true); call.cancel(); }); return call; } } failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/000077500000000000000000000000001444561050700234125ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/000077500000000000000000000000001444561050700243335ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/dev/000077500000000000000000000000001444561050700251115ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/dev/retrofit/000077500000000000000000000000001444561050700267475ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/dev/retrofit/FailsafeCallTest.java000066400000000000000000000175301444561050700327660ustar00rootroot00000000000000/* * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package dev.retrofit; import com.github.tomakehurst.wiremock.WireMockServer; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import dev.failsafe.*; import dev.failsafe.retrofit.FailsafeCall; import dev.retrofit.TestService.User; import dev.retrofit.testing.RetrofitTesting; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import retrofit2.Call; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import java.io.IOException; import java.time.Duration; import java.util.concurrent.CancellationException; import java.util.concurrent.Future; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @Test public class FailsafeCallTest extends RetrofitTesting { public static final String URL = "http://localhost:8080"; WireMockServer server; User fooUser = new User("foo"); Gson gson = new GsonBuilder().create(); TestService service = new Retrofit.Builder().baseUrl(URL) .addConverterFactory(GsonConverterFactory.create()) .build() .create(TestService.class); @BeforeMethod protected void beforeMethod() { server = new WireMockServer(); server.start(); } @AfterMethod protected void afterMethod() { server.stop(); } public void testSuccess() { // Given mockResponse(200, fooUser); FailsafeExecutor> failsafe = Failsafe.with(RetryPolicy.ofDefaults()); Call call = service.testUser(); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, 200, fooUser); assertCalled("/test", 2); } public void testRetryPolicyOn400() { // Given mockResponse(400, fooUser); RetryPolicy> retryPolicy = RetryPolicy.>builder() .handleResultIf(r -> r.code() == 400) .build(); FailsafeExecutor> failsafe = Failsafe.with(retryPolicy); Call call = service.testUser(); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); }, 400, null); // Retrofit puts the response for a 400 in the error body assertCalled("/test", 6); } public void testRetryPolicyOnResult() { // Given User bad = new User("bad"); mockResponse(200, bad); RetryPolicy> retryPolicy = RetryPolicy.>builder() .handleResultIf(r -> bad.equals(r.body())) .build(); FailsafeExecutor> failsafe = Failsafe.with(retryPolicy); Call call = service.testUser(); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); }, 200, bad); assertCalled("/test", 6); } public void testRetryPolicyFallback() { // Given mockResponse(400, fooUser); User fallbackUser = new User("fallback"); Fallback> fallback = Fallback.>builder(r -> { return Response.success(200, fallbackUser); }).handleResultIf(r -> r.code() == 400).build(); RetryPolicy> retryPolicy = RetryPolicy.>builder() .handleResultIf(r -> r.code() == 400) .build(); FailsafeExecutor> failsafe = Failsafe.with(fallback, retryPolicy); Call call = service.testUser(); // When / Then testRequest(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 3); }, 200, fallbackUser); assertCalled("/test", 6); } /** * Asserts that an open circuit breaker prevents executions from occurring, even with outer retries. */ public void testCircuitBreaker() { // Given mockResponse(200, fooUser); CircuitBreaker> breaker = CircuitBreaker.ofDefaults(); FailsafeExecutor> failsafe = Failsafe.with(RetryPolicy.ofDefaults(), breaker); Call call = service.testUser(); breaker.open(); // When / Then testFailure(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 3); assertEquals(e.getExecutionCount(), 0); }, CircuitBreakerOpenException.class); assertCalled("/test", 0); } public void testTimeout() { // Given mockDelayedResponse(200, "foo", 1000); FailsafeExecutor> failsafe = Failsafe.with(Timeout.of(Duration.ofMillis(100))); Call call = service.testUser(); // When / Then testFailure(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, TimeoutExceededException.class); assertCalled("/test", 2); } public void testIOException() { server.stop(); FailsafeExecutor> failsafe = Failsafe.none(); Call call = service.testUser(); testFailure(failsafe, call, (f, e) -> { assertEquals(e.getAttemptCount(), 1); assertEquals(e.getExecutionCount(), 1); }, java.net.ConnectException.class); } public void testCancel() { // Given mockDelayedResponse(200, "foo", 1000); FailsafeExecutor> failsafe = Failsafe.none(); Call call = service.testUser(); // When / Then Sync FailsafeCall failsafeCall = FailsafeCall.with(failsafe).compose(call); runInThread(() -> { sleep(150); failsafeCall.cancel(); }); assertThrows(failsafeCall::execute, IOException.class); assertTrue(call.isCanceled()); assertTrue(failsafeCall.isCancelled()); // When / Then Async Call call2 = call.clone(); FailsafeCall failsafeCall2 = FailsafeCall.with(failsafe).compose(call2); runInThread(() -> { sleep(150); failsafeCall2.cancel(); }); assertThrows(() -> failsafeCall2.executeAsync().get(), CancellationException.class); assertTrue(call2.isCanceled()); assertTrue(failsafeCall2.isCancelled()); assertCalled("/test", 2); } public void testCancelViaFuture() { // Given mockDelayedResponse(200, "foo", 1000); FailsafeExecutor> failsafe = Failsafe.none(); Call call = service.testUser(); FailsafeCall failsafeCall = FailsafeCall.with(failsafe).compose(call); // When / Then Async Future> future = failsafeCall.executeAsync(); sleep(150); future.cancel(false); assertThrows(future::get, CancellationException.class); assertTrue(call.isCanceled()); assertTrue(failsafeCall.isCancelled()); assertCalled("/test", 1); } private void mockResponse(int responseCode, User body) { stubFor(get(urlPathEqualTo("/test")).willReturn( aResponse().withStatus(responseCode).withHeader("Content-Type", "application/json").withBody(gson.toJson(body)))); } private void mockDelayedResponse(int responseCode, String body, int delayMillis) { stubFor(get(urlEqualTo("/test")).willReturn( aResponse().withStatus(responseCode).withFixedDelay(delayMillis).withBody(body))); } private void assertCalled(String url, int times) { verify(times, getRequestedFor(urlPathEqualTo(url))); } } failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/dev/retrofit/TestService.java000066400000000000000000000012361444561050700320540ustar00rootroot00000000000000package dev.retrofit; import retrofit2.Call; import retrofit2.http.GET; import java.util.Objects; public interface TestService { @GET("/test") Call testUser(); public static class User { public String getName() { return name; } public void setName(String name) { this.name = name; } private String name; public User(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(name, user.name); } } } failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/dev/retrofit/testing/000077500000000000000000000000001444561050700304245ustar00rootroot00000000000000RetrofitTesting.java000066400000000000000000000072761444561050700343600ustar00rootroot00000000000000failsafe-failsafe-parent-3.3.2/modules/retrofit/src/test/java/dev/retrofit/testingpackage dev.retrofit.testing; import dev.failsafe.FailsafeExecutor; import dev.failsafe.event.EventListener; import dev.failsafe.event.ExecutionCompletedEvent; import dev.failsafe.retrofit.FailsafeCall; import dev.failsafe.testing.Testing; import net.jodah.concurrentunit.Waiter; import retrofit2.Call; import retrofit2.Response; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; public class RetrofitTesting extends Testing { public void testRequest(FailsafeExecutor> failsafe, Call when, Then> then, int expectedStatus, T expectedResult) { test(failsafe, when, then, expectedStatus, expectedResult, null); } @SafeVarargs public final void testFailure(FailsafeExecutor> failsafe, Call when, Then> then, Class... expectedExceptions) { test(failsafe, when, then, 0, null, expectedExceptions); } private void test(FailsafeExecutor> failsafe, Call when, Then> then, int expectedStatus, T expectedResult, Class[] expectedExceptions) { AtomicReference>> futureRef = new AtomicReference<>(); AtomicReference>> completedEventRef = new AtomicReference<>(); Waiter completionListenerWaiter = new Waiter(); EventListener>> setCompletedEventFn = e -> { completedEventRef.set(e); completionListenerWaiter.resume(); }; List> expected = new LinkedList<>(); Class[] expectedExInner = expectedExceptions == null ? new Class[] {} : expectedExceptions; Collections.addAll(expected, expectedExInner); failsafe.onComplete(setCompletedEventFn); Runnable postTestFn = () -> { ignoreExceptions(() -> completionListenerWaiter.await(5000)); ExecutionCompletedEvent> completedEvent = completedEventRef.get(); if (expectedExceptions == null) { assertEquals(completedEvent.getResult().code(), expectedStatus); assertNull(completedEvent.getException()); } else { assertNull(completedEvent.getResult()); assertMatches(completedEvent.getException(), expectedExceptions); } if (then != null) then.accept(futureRef.get(), completedEvent); }; Consumer> assertResult = response -> { T result = unwrapExceptions(response::body); assertEquals(result, expectedResult); assertEquals(response.code(), expectedStatus); }; // Run sync test and assert result System.out.println("\nRunning sync test"); FailsafeCall failsafeCall = FailsafeCall.with(failsafe).compose(when); if (expectedExceptions == null) { assertResult.accept(unwrapExceptions(failsafeCall::execute)); } else { assertThrows(failsafeCall::execute, expectedExceptions); } postTestFn.run(); if (expectedExInner.length > 0) expected.add(0, ExecutionException.class); // Run async test and assert result System.out.println("\nRunning async test"); failsafeCall = failsafeCall.clone(); CompletableFuture> future = failsafeCall.executeAsync(); futureRef.set(future); if (expectedExInner.length == 0) { assertResult.accept(unwrapExceptions(future::get)); } else { assertThrowsSup(future::get, expected); } postTestFn.run(); } } failsafe-failsafe-parent-3.3.2/pom.xml000066400000000000000000000217261444561050700176630ustar00rootroot00000000000000 4.0.0 pom dev.failsafe failsafe-parent 3.3.2 Failsafe Parent Fault tolerance and resilience patterns https://failsafe.dev Apache License, Version 2.0 http://apache.org/licenses/LICENSE-2.0 repo Jonathan Halterman http://jodah.net scm:git:git@github.com:failsafe-lib/failsafe.git scm:git:git@github.com:failsafe-lib/failsafe.git https://github.com/failsafe-lib/failsafe failsafe-parent-3.3.2 undefined 1.8 1.8 core modules/okhttp modules/retrofit org.testng testng 6.9.10 test org.mockito mockito-core 4.2.0 test net.jodah concurrentunit 0.4.4 test org.apache.maven.plugins maven-compiler-plugin 3.10.1 org.apache.maven.plugins maven-surefire-plugin 2.22.2 org.apache.maven.plugins maven-jar-plugin 3.1.1 org.moditect moditect-maven-plugin 1.0.0.RC2 add-module-infos package add-module-info 9 true ${java.module.name} *; true --multi-release=9 org.apache.maven.plugins maven-surefire-plugin false listener dev.failsafe.testing.TestCaseLogger maven-release-plugin release -Prelease forked-path maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.felix maven-bundle-plugin 5.1.3 true bundle-manifest process-classes manifest jar bundle ${project.description} ${project.version} Failsafe dev.failsafe dev.failsafe dev.failsafe*;version=1.0 * org.apache.maven.plugins maven-javadoc-plugin 2.10.4 attach-javadocs jar false protected *.internal -Xdoclint:none -notimestamp -link http://docs.oracle.com/javase/8/docs/api/ -link https://failsafe.dev/javadoc/core/ org.eluder.coveralls coveralls-maven-plugin 3.1.0 org.jacoco jacoco-maven-plugin 0.8.7 prepare-agent prepare-agent release org.apache.maven.plugins maven-source-plugin 2.1.2 attach-sources jar-no-fork org.apache.maven.plugins maven-gpg-plugin 1.1 sign-artifacts verify sign org.sonatype.plugins nexus-staging-maven-plugin 1.6.7 true sonatype-nexus-staging https://s01.oss.sonatype.org/ true org.apache.maven.plugins maven-release-plugin deploy nexus-staging:release