Skip to content

Commit

Permalink
Merge pull request #60 from spotify/update-root
Browse files Browse the repository at this point in the history
Project updates
  • Loading branch information
caesar-ralf committed Feb 7, 2024
2 parents 2631eda + e54f095 commit f12c25c
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
# Test on each supported Java LTS version
strategy:
matrix:
java_version: [8, 11, 17]
java_version: [8, 11, 17, 21]
steps:
- uses: actions/checkout@v2
with:
Expand Down
139 changes: 64 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,66 @@
### Futures-extra

Futures-extra is a set of small utility functions to simplify working with
Guava's ListenableFuture class

### Build status

[![Build Status](https://github.com/spotify/futures-extra/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/spotify/futures-extra/actions/workflows/ci.yaml?query=branch%3Amaster)
[![GitHub license](https://img.shields.io/github/license/spotify/scio.svg)](./LICENSE)

### Maven central

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.spotify/futures-extra/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.spotify/futures-extra)

Futures-extra is a set of small utility functions to simplify working with Guava's ListenableFuture class

### Build dependencies

* Java 8 or higher
* Maven

### Runtime dependencies

* Java 8 or higher
* Guava 21.0 or higher
* Google [`api-common`](https://mvnrepository.com/artifact/com.google.api/api-common) from `com.google.api`

### Usage

Futures-extra is meant to be used as a library embedded in other software.
To import it with maven, use this:
Futures-extra is meant to be used as a library embedded in other software. To import it with maven, use this:

<dependency>
<groupId>com.spotify</groupId>
<artifactId>futures-extra</artifactId>
<version>4.2.0</version>
</dependency>
```xml
<dependency>
<groupId>com.spotify</groupId>
<artifactId>futures-extra</artifactId>
<version>4.2.0</version>
</dependency>
```

### Examples

#### Joining multiple futures

A common use case is waiting for two or more futures and then transforming the
result to something else. You can do this in a couple of different ways, here
are two of them:
A common use case is waiting for two or more futures and then transforming the result to something else. You can do this in a couple of different ways, here are two of them:

```java
final ListenableFuture<A> futureA = getFutureA();
final ListenableFuture<B> futureB = getFutureB();

ListenableFuture<C> ret = Futures.transform(Futures.allAsList(futureA, futureB),
(Function<List<?>, C>)list -> combine((A) list.get(0), (B) list.get(1), executor);
(Function<List<?>, C>) list -> combine((A) list.get(0), (B) list.get(1), executor);
```

where combine is a method with parameters of type A and B returning C.

This one has the problem that you have to manually make sure that the casts and
ordering are correct, otherwise you will get ClassCastException.
This one has the problem that you have to manually make sure that the casts and ordering are correct, otherwise you will get ClassCastException.

You could also access the futures directly to avoid casts:

```java
final ListenableFuture<A> futureA = getFutureA();
final ListenableFuture<B> futureB = getFutureB();

ListenableFuture<C> ret = Futures.transform(Futures.allAsList(futureA, futureB),
(Function<List<?>, C>)list -> combine(Futures.getUnchecked(futureA), Futures.getUnchecked(futureB), executor);
(Function<List<?>, C>) list -> combine(Futures.getUnchecked(futureA), Futures.getUnchecked(futureB), executor);
```
Now you instead need to make sure that the futures in the transform input are
the same as the ones you getUnchecked. If you fail to do this, things may work
anyway (which is a good way of hiding bugs), but block the thread, actually
removing the asynchronous advantage. Even worse - the future may never finish,
blocking the thread forever.

Now you instead need to make sure that the futures in the transform input are the same as the ones you getUnchecked. If you fail to do this, things may work anyway (which is a good way of hiding bugs), but block the thread, actually removing the asynchronous advantage. Even worse - the future may never finish, blocking the thread forever.

To simplify these use cases we have a couple of helper functions:

```java
final ListenableFuture<A> futureA = getFutureA();
final ListenableFuture<B> futureB = getFutureB();
Expand All @@ -76,42 +69,41 @@ ListenableFuture<C> ret = FuturesExtra.syncTransform2(futureA, futureB,
(a, b) -> combine(a, b), executor);
```

This is much clearer! We don't need any type information because the lambda can
infer it, and we avoid the potential bugs that can occur as a result of the
first to examples.
This is much clearer! We don't need any type information because the lambda can infer it, and we avoid the potential bugs that can occur as a result of the first to examples.
The tuple transform can be used up to 6 arguments named syncTransform2() through
syncTransform6(). If you need more than that you could probably benefit from
some refactoring, but you can also use FuturesExtra.join():
The tuple transform can be used up to 6 arguments named syncTransform2() through syncTransform6(). If you need more than that you could probably benefit from some refactoring, but you can also use FuturesExtra.join():
```java
final ListenableFuture<A> futureA = getFutureA();
final ListenableFuture<B> futureB = getFutureB();
final ListenableFuture<JoinedResults> futureJoined = FuturesExtra.join(executor, futureA, futureB);
return Futures.transform(futureJoined,
joined -> combine(joined.get(futureA), joined.get(futureB)), executor);
return Futures.
transform(futureJoined,
joined ->
combine(joined.get(futureA),joined.
get(futureB)),executor);
```
This supports an arbitrary number of futures, but is slightly more complex.
However, it is much safer than the first two examples, because joined.get(...)
will fail if you try to get the value of a future that was not part of the
input.
This supports an arbitrary number of futures, but is slightly more complex. However, it is much safer than the first two examples, because joined.get(...)
will fail if you try to get the value of a future that was not part of the input.
#### Timeouts
Sometimes you want to stop waiting for a future after a specific timeout and to
do this you generally need to have some sort of scheduling involved. To simplify
that, you can use this:
Sometimes you want to stop waiting for a future after a specific timeout and to do this you generally need to have some sort of scheduling involved. To simplify that, you can use this:
```java
final ListenableFuture<A> future = getFuture();
final ListenableFuture<A> futureWithTimeout = FuturesExtra.makeTimeoutFuture(scheduledExecutor, future, 100, TimeUnit.MILLISECONDS);
```
#### Select
If you have some futures and want to succeed as soon as the first one succeeds,
you can use select:
If you have some futures and want to succeed as soon as the first one succeeds, you can use select:
```java
final List<ListenableFuture<A>> futures = getFutures();
final ListenableFuture<A> firstSuccessful = FuturesExtra.select(futures, executor);
Expand All @@ -120,67 +112,66 @@ final ListenableFuture<A> firstSuccessful = FuturesExtra.select(futures, executo
#### Success/Failure callbacks
You can attach callbacks that are run depending on the results of a future:
```java
final ListenableFuture<A> future = getFuture();
FuturesExtra.addCallback(future, System.out::println, Throwable::printStackTrace, executor);
FuturesExtra.
addCallback(future, System.out::println, Throwable::printStackTrace, executor);
```
Alternatively, if you are only interested in either successful or failed results of a future, you can use:
Alternatively, if you are only interested in either successful or failed
results of a future, you can use:
```java
final ListenableFuture<A> future = getFuture();
FuturesExtra.addSuccessCallback(future, System.out::println, executor);
FuturesExtra.
addSuccessCallback(future, System.out::println, executor);
```
```java
final ListenableFuture<B> future = getFuture();
FuturesExtra.addFailureCallback(future, System.out::println, executor);
FuturesExtra.
addFailureCallback(future, System.out::println, executor);
```
#### Concurrency limiting
If you want to fire of a large number of asynchronous requests or jobs,
it can be useful to limit how many will run concurrently.
To help with this, there is a new class called `ConcurrencyLimiter`.
You use it like this:
If you want to fire of a large number of asynchronous requests or jobs, it can be useful to limit how many will run concurrently. To help with this, there is a new class called `ConcurrencyLimiter`. You use it like this:
```java
int maxConcurrency = 10;
int maxQueueSize = 100;
ConcurrencyLimiter<T> limiter = ConcurrencyLimiter.create(maxConcurrency, maxQueueSize);
for (int i = 0; i < 1000; i++) {
ListenableFuture<T> future = limiter.add(() -> createFuture());
for(
int i = 0;
i< 1000;i++){
ListenableFuture<T> future = limiter.add(() -> createFuture());
}
```
The concurrency limiter will ensure that at most 10 futures are created and
incomplete at the same time. All the jobs that are passed into
`ConcurrencyLimiter.add()` will wait in a queue until the concurrency is below
the limit.
The jobs you pass in should not be blocking or be overly CPU intensive.
If that is something you need you should let your ConcurrencyLimiter jobs push
the work on a thread pool.
The concurrency limiter will ensure that at most 10 futures are created and incomplete at the same time. All the jobs that are passed into
`ConcurrencyLimiter.add()` will wait in a queue until the concurrency is below the limit.
The jobs you pass in should not be blocking or be overly CPU intensive. If that is something you need you should let your ConcurrencyLimiter jobs push the work on a thread pool.
The internal queue is bounded and if its limit is reached it, the call to add will return
a failed future of `ConcurrencyLimiter.CapacityReachedException`.
The internal queue is bounded and if its limit is reached it, the call to add will return a failed future of `ConcurrencyLimiter.CapacityReachedException`.
#### Completed futures
In some cases you want to extract the value (or exception) from the future and you know that
the future is completed so it won't be a blocking operation.
In some cases you want to extract the value (or exception) from the future and you know that the future is completed so it won't be a blocking operation.

You could use these methods for that, but they will also block if the future is not complete which may lead to hard to find bugs.

You could use these methods for that, but they will also block if the future is not complete which may lead to
hard to find bugs.
```java
T value = future.get();
T value = Futures.getUnchecked(future);
```

Instead you can use these methods which will never block but instead immediately
throw an exception if the future is not completed. This is typically useful in unit tests
(where futures should be immediate) and in general future callbacks/transforms where you know that a
specific future must be completed for this codepath to be triggered.
Instead you can use these methods which will never block but instead immediately throw an exception if the future is not completed. This is typically useful in unit tests
(where futures should be immediate) and in general future callbacks/transforms where you know that a specific future must be completed for this codepath to be triggered.

```java
T value = FuturesExtra.getCompleted(future);
Throwable exc = FuturesExtra.getException(future);
Expand Down Expand Up @@ -218,9 +209,7 @@ ApiFuture<V> apiFuture = CompletableFuturesExtra.toApiFuture(completable);

## Ownership

The Weaver squad is currently owning this project internally.
We are currently in the evaluating process of the ownership of this and other OSS Java libraries.
The ownership takes into account **ONLY** security maintenance.
The Weaver squad is currently owning this project internally. We are currently in the evaluating process of the ownership of this and other OSS Java libraries. The ownership takes into account **ONLY** security maintenance.

This repo is also co-owned by other people:

Expand Down
38 changes: 1 addition & 37 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<parent>
<groupId>com.spotify</groupId>
<artifactId>foss-root</artifactId>
<version>16</version>
<version>17</version>
</parent>

<scm>
Expand All @@ -20,15 +20,6 @@
<tag>HEAD</tag>
</scm>

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>

<organization>
<name>Spotify AB</name>
<url>http://www.spotify.com</url>
Expand All @@ -48,17 +39,6 @@
</developer>
</developers>

<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-bom -->
Expand Down Expand Up @@ -125,22 +105,6 @@
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<show>private</show>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
Expand Down

0 comments on commit f12c25c

Please sign in to comment.