redfin/patience

Name: patience

Owner: Redfin

Description: A simple, fluent, highly customizable Java library for waiting for expected conditions.

Created: 2016-12-08 20:12:03.0

Updated: 2017-12-24 12:56:42.0

Pushed: 2018-01-17 22:49:30.0

Homepage: null

Size: 160

Language: Java

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status License

Patience

Overview

Patience is a fluent, customizable Java library for waiting on expected conditions. There are a few interfaces that allow for highly different behaviors at key sections along with immutable classes to implement the generic behavior that occurs around those customization points.

Installation
endency>
<groupId>com.redfin</groupId>
<artifactId>patience</artifactId>
<version>4.0.0</version>
pendency>
Main classes

The initial entry points in the library are the PatientWait and the PatientRetry objects. These two classes are similar in that they are immutable instances that serve as factories for the PatientWaitFuture and PatientRetryFuture classes. They are both intended to be reusable. The main customization points are the PatientExecutionHandler, which allow the user to customize the code that executes around the PatientExecutable given to create the future instances, and the DelaySupplierFactory, which allows customization of the delay between unsuccessful attempts.

The future classes PatientWaitFuture and PatientRetryFuture are also immutable classes but are not intended for re-use. If a valid result (as determined by the given Predicate used as a filter) is retrieved from the PatientExecutable then the future instance will return the valid result when the get method is called. If no valid result is found and the timeout has been reached (PatientWaitFuture) or the maximum number of retries has been reached (PatientRetryFuture) then an exception will be thrown. There are check methods that can be used instead of the get methods which return true or false depending on whether a valid result is found or not if you just need to know if it was successful instead of returning a value or throwing an exception.

PatientWait Example usage

First, you would create the PatientWait instance via the builder and set the desired behaviors and properties. Then you can use that factory (repeatedly) to generate PatientWaitFuture instances. Finally you would call get to begin waiting.

entWait wait = PatientWait.builder()
                          .withInitialDelay(Duration.ofSeconds(1))
                          .withDefaultTimeout(Duration.ofMinutes(2))
                          .withExecutionHandler(PatientExecutionHandlers.simple())
                          .withDelaySupplier(DelaySupplier.fixed(Duration.ofMillis(500)))
                          .build();
le result = wait.from(Math::random)
                .withFilter(dbl -> dbl > 0.5)
                .withMessage("Never generated a random double that was greater than 0.5")
                .get(Duration.ofMinutes(1));

After this call to get, first the executing thread will sleep for 1 second (the initial delay). Then the executable (Math.random()) will be called and the resutl will be tested with the given filter. If it passes the filter (is greater than 0.5) the double result variable will be set to the generated value. If the generated value doesn't pass the filter, then the executing thread will sleep for 500 ms and the cycle will repeat. If no valid value is generated and 2 minutes has been reached (or less if the next sleep would put the execution over the timeout period) then a PatientTimeoutException will be thrown with the given message.

Note that the PatientWait type was developed for automation tests that have external components. We did not want to stop an executing test in a preemptive manner during a network call or to execute the code in a different thread than the rest of the test, but rather just wanted a sane way to tell the test to stop retrying if an external service is down. Best practices are to make the from(PatientExecutable) executable be as “atomic” as possible and to note that the PatientWait does not guarantee that a result will return or even that a successful result is returned within the timeout. If you need that level of guarantee then you should look into using an ExecutorService which would execute your code in a different thread and allow you to set a timeout.

If you just need to check if a valid result is ever found within a given timeout and you don't want an exception thrown if it is not, there is also a check method available.

entWait wait = PatientWait.builder()
                          .withInitialDelay(Duration.ofSeconds(1))
                          .withDefaultTimeout(Duration.ofMinutes(2))
                          .withExecutionHandler(PatientExecutionHandlers.simple())
                          .withDelaySupplier(DelaySupplier.fixed(Duration.ofMillis(500)))
                          .build();
ean resultFound = wait.from(Math::random)
                      .withFilter(dbl -> dbl > 0.5)
                      .check(Duration.ofMinutes(1));
PatientRetry Example usage

First, you would create the PatientRetry instance via the builder and set the desired behaviors and properties. Then you can use that factory (repeatedly) to generate PatientRetryFuture instances. Finally you would call get to begin waiting.

entRetry retry = PatientRetry.builder()
                             .withInitialDelay(Duration.ofSeconds(1))
                             .withDefaultNumberOfRetries(10)
                             .withExecutionHandler(PatientExecutionHandlers.simple())
                             .withDelaySupplier(DelaySupplier.fixed(Duration.ofMillis(500)))
                             .build();
le result = retry.from(Math::random)
                 .withFilter(dbl -> dbl > 0.5)
                 .withMessage("Never generated a random double that was greater than 0.5")
                 .get(5); // number of retries

After this call to get, first the executing thread will sleep for 1 second (the initial delay). Then the executable (Math.random()) will be called and the result will be tested with the given filter. If it passes the filter (is greater than 0.5) the double result variable will be set to the generated value. If the generated value doesn't pass the filter, then the executing thread will sleep for 500 ms and the cycle will repeat. If no valid value is generated and 6 attempts have occurred (the initial attempt and 5 retries) then a PatientRetryException will be thrown with the given message.

If you just need to check if a valid result is ever found within a given timeout and you don't want an exception thrown if it is not, there is also a check method available.

entRetry retry = PatientRetry.builder()
                             .withInitialDelay(Duration.ofSeconds(1))
                             .withDefaultNumberOfRetries(10)
                             .withExecutionHandler(PatientExecutionHandlers.simple())
                             .withDelaySupplier(DelaySupplier.fixed(Duration.ofMillis(500)))
                             .build();
ean resultFound = retry.from(Math::random)
                       .withFilter(dbl -> dbl > 0.5)
                       .check(5); // number of retries

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.