├── cla.docx ├── .travis.yml ├── .gitignore ├── asyncutil ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── ibm │ │ │ └── asyncutil │ │ │ ├── util │ │ │ ├── package-info.java │ │ │ ├── AsyncCloseable.java │ │ │ ├── Combinators.java │ │ │ └── Either.java │ │ │ ├── locks │ │ │ ├── package-info.java │ │ │ ├── TerminatedEpoch.java │ │ │ ├── FairAsyncNamedLock.java │ │ │ ├── AsyncEpochImpl.java │ │ │ ├── FairAsyncStampedLock.java │ │ │ ├── AsyncLock.java │ │ │ ├── AsyncFunnel.java │ │ │ ├── AsyncNamedLock.java │ │ │ ├── FairAsyncLock.java │ │ │ ├── AsyncEpoch.java │ │ │ ├── PlatformDependent.java │ │ │ ├── AsyncStampedLock.java │ │ │ ├── AsyncReadWriteLock.java │ │ │ ├── AsyncNamedReadWriteLock.java │ │ │ ├── AbstractSimpleEpoch.java │ │ │ └── AsyncSemaphore.java │ │ │ └── iteration │ │ │ ├── package-info.java │ │ │ ├── BoundedAsyncQueue.java │ │ │ └── AsyncQueue.java │ └── test │ │ └── java │ │ └── com │ │ └── ibm │ │ └── asyncutil │ │ ├── locks │ │ ├── AsyncEpochImplTest.java │ │ ├── StripedEpochTest.java │ │ ├── FairAsyncLockTest.java │ │ ├── FairAsyncStampedLockTest.java │ │ ├── FairAsyncReadWriteLockTest.java │ │ ├── FairAsyncNamedReadWriteLockTest.java │ │ ├── FairAsyncSemaphoreTest.java │ │ ├── SyncAsyncSemaphore.java │ │ ├── SyncAsyncStampedLock.java │ │ ├── AbstractAsyncStampedLockTest.java │ │ ├── AsyncFunnelTest.java │ │ ├── AbstractAsyncNamedReadWriteLockTest.java │ │ └── AbstractAsyncLockTest.java │ │ ├── iteration │ │ ├── AsyncQueueTest.java │ │ ├── BoundedAsyncQueueTest.java │ │ ├── AsyncTrampolineTest.java │ │ └── BufferedAsyncQueueTest.java │ │ ├── util │ │ ├── Reference.java │ │ ├── EitherTest.java │ │ ├── StageSupportTest.java │ │ └── TestUtil.java │ │ └── examples │ │ └── nio │ │ ├── Iteration.java │ │ ├── Epochs.java │ │ ├── Locks.java │ │ ├── MultiProducerIteration.java │ │ └── NioBridge.java └── pom.xml ├── CONTRIBUTING.md ├── asyncutil-flow ├── src │ └── test │ │ └── java │ │ └── com │ │ └── ibm │ │ └── asyncutil │ │ └── flow │ │ ├── AdaptedBlackBoxSubscriberVerificationTest.java │ │ ├── AdaptedPublisherVerificationTest.java │ │ ├── AdaptedWhiteBoxSubscriberVerificationTest.java │ │ └── FlowAdapterTest.java ├── README.md └── pom.xml ├── DCO1.1.txt ├── pom.xml └── README.md /cla.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/java-async-util/HEAD/cla.docx -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | dist: trusty 4 | jdk: oraclejdk9 5 | addons: 6 | apt: 7 | packages: 8 | - oracle-java9-installer 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | /target/ 24 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | /** 8 | * Provides general purpose utilities for using {@link java.util.concurrent.CompletionStage}. 9 | */ 10 | package com.ibm.asyncutil.util; 11 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/AsyncEpochImplTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | public class AsyncEpochImplTest extends AbstractAsyncEpochTest { 10 | @Override 11 | AsyncEpoch newEpoch() { 12 | return new AsyncEpochImpl(); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | /** 8 | * Provides asynchronous analogues of synchronization primitives. These primitives use 9 | * {@link java.util.concurrent.CompletionStage} to coordinate instead of blocking. 10 | */ 11 | package com.ibm.asyncutil.locks; 12 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/StripedEpochTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | public class StripedEpochTest extends AbstractAsyncEpochTest { 10 | 11 | @Override 12 | AsyncEpoch newEpoch() { 13 | return new StripedEpoch(); 14 | } 15 | 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/FairAsyncLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | public class FairAsyncLockTest extends AbstractAsyncLockTest.AbstractAsyncLockFairnessTest { 10 | @Override 11 | protected AsyncLock getLock() { 12 | return new FairAsyncLock(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/FairAsyncStampedLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | public class FairAsyncStampedLockTest extends AbstractAsyncStampedLockTest { 10 | 11 | @Override 12 | protected AsyncStampedLock createLock() { 13 | return new FairAsyncStampedLock(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/iteration/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | /** 8 | * Provides support for asynchronous loop constructs. Generally, these loops are used to coordinate 9 | * the production of asynchronous results (delivered via 10 | * {@link java.util.concurrent.CompletionStage}) as well as their asynchronous consumption. 11 | */ 12 | package com.ibm.asyncutil.iteration; 13 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/FairAsyncReadWriteLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | public class FairAsyncReadWriteLockTest 10 | extends AbstractAsyncReadWriteLockTest.AbstractAsyncReadWriteLockFairnessTest { 11 | @Override 12 | protected AsyncReadWriteLock getReadWriteLock() { 13 | return new FairAsyncReadWriteLock(); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/FairAsyncNamedReadWriteLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | public class FairAsyncNamedReadWriteLockTest 10 | extends AbstractAsyncNamedReadWriteLockTest.AbstractAsyncNamedReadWriteLockFairnessTest { 11 | @Override 12 | protected AsyncNamedReadWriteLock getNamedReadWriteLock() { 13 | return new FairAsyncNamedReadWriteLock(); 14 | } 15 | 16 | @Override 17 | protected boolean isEmpty(final AsyncNamedReadWriteLock anrwl) { 18 | return ((FairAsyncNamedReadWriteLock) anrwl).isEmpty(); 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/FairAsyncSemaphoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import org.junit.Test; 10 | 11 | public class FairAsyncSemaphoreTest 12 | extends AbstractAsyncSemaphoreTest.AbstractAsyncSemaphoreFairnessTest { 13 | public FairAsyncSemaphoreTest() { 14 | super(FairAsyncSemaphore.MAX_PERMITS); 15 | } 16 | 17 | @Override 18 | protected AsyncSemaphore createSemaphore(final long initialPermits) { 19 | return new FairAsyncSemaphore(initialPermits); 20 | } 21 | 22 | @Test(expected = IllegalArgumentException.class) 23 | public void testExceededMinConstructor() { 24 | createSemaphore(Long.MIN_VALUE); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/iteration/AsyncQueueTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.iteration; 8 | 9 | import java.util.Optional; 10 | 11 | import org.junit.Before; 12 | 13 | public class AsyncQueueTest extends AbstractAsyncQueueTest { 14 | 15 | private AsyncQueue queue; 16 | 17 | @Before 18 | public void makeQueue() { 19 | this.queue = AsyncQueues.unbounded(); 20 | } 21 | 22 | @Override 23 | boolean send(final Integer c) { 24 | return this.queue.send(c); 25 | } 26 | 27 | @Override 28 | AsyncIterator consumer() { 29 | return this.queue; 30 | } 31 | 32 | @Override 33 | void closeImpl() { 34 | this.queue.terminate(); 35 | } 36 | 37 | @Override 38 | Optional poll() { 39 | return this.queue.poll(); 40 | } 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Reporting bugs 2 | If you are a user and you find a bug, please submit an [issue](https://github.com/IBM/java-async-util/issues). Please try to provide sufficient information for someone else to reproduce the issue. 3 | 4 | # Pull requests 5 | Pull requests for features or bug fixes are welcome. If you are working on a large contribution, please file an issue first. Feel free to ask for help or ask questions about a possible contribution. 6 | 7 | ## Individual Contributions 8 | This project uses the DCO for individual contributions. If you are contributing as an individual, simply include a "Signed-off-by" in the commit message (git commit -s) or in the pull request description. This indicates you agree the commit satisfies the [Developer Certificate of Origin](DCO1.1.txt). 9 | 10 | ## Corporate Contributions 11 | If you are contributing to the project on behalf of a corporation (your company owns the IP you would like to contribute), then we must recieve a signed [CLA](cla.docx) for your company. 12 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/util/Reference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | /** 10 | * sometimes you need a modifiable final variable... 11 | * 12 | * @param 13 | */ 14 | public class Reference { 15 | private T t; 16 | 17 | public Reference(final T t) { 18 | this.t = t; 19 | } 20 | 21 | public T get() { 22 | return this.t; 23 | } 24 | 25 | public void set(final T t) { 26 | this.t = t; 27 | } 28 | 29 | /** 30 | * Set the reference to the given value, returning the previous value of the reference 31 | */ 32 | public T getAndSet(final T t) { 33 | final T get = this.t; 34 | this.t = t; 35 | return get; 36 | } 37 | 38 | /** 39 | * Set the reference to the given value, returning this new value 40 | */ 41 | public T setAndGet(final T t) { 42 | this.t = t; 43 | return t; 44 | } 45 | 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/TerminatedEpoch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | import com.ibm.asyncutil.util.StageSupport; 13 | 14 | /** 15 | * @see AsyncEpoch#newTerminatedEpoch() 16 | * 17 | * @author Renar Narubin 18 | */ 19 | class TerminatedEpoch implements AsyncEpoch { 20 | static final TerminatedEpoch INSTANCE = new TerminatedEpoch(); 21 | 22 | private TerminatedEpoch() {} 23 | 24 | @Override 25 | public Optional enter() { 26 | return Optional.empty(); 27 | } 28 | 29 | @Override 30 | public CompletionStage terminate() { 31 | return StageSupport.completedStage(false); 32 | } 33 | 34 | @Override 35 | public boolean isTerminated() { 36 | return true; 37 | } 38 | 39 | @Override 40 | public CompletionStage awaitCompletion() { 41 | return StageSupport.voidStage(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/FairAsyncNamedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | /** 13 | * An implementation of the {@link AsyncNamedLock} interface which enforces fair ordering in lock 14 | * acquisition. 15 | *

16 | * {@code null} values are not permitted for use as names. 17 | * 18 | * @author Renar Narubin 19 | */ 20 | public class FairAsyncNamedLock implements AsyncNamedLock { 21 | private final FairAsyncNamedReadWriteLock fanrwl = new FairAsyncNamedReadWriteLock<>(); 22 | 23 | @Override 24 | public CompletionStage acquireLock(final T name) { 25 | return this.fanrwl.acquireWriteLock(name).thenApply(writeToken -> writeToken::releaseLock); 26 | } 27 | 28 | @Override 29 | public Optional tryLock(final T name) { 30 | return this.fanrwl.tryWriteLock(name).map(writeToken -> writeToken::releaseLock); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /asyncutil-flow/src/test/java/com/ibm/asyncutil/flow/AdaptedBlackBoxSubscriberVerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.flow; 8 | 9 | 10 | import java.util.concurrent.Flow; 11 | import java.util.concurrent.Flow.Subscriber; 12 | 13 | import org.reactivestreams.tck.TestEnvironment; 14 | import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification; 15 | 16 | public class AdaptedBlackBoxSubscriberVerificationTest 17 | extends FlowSubscriberBlackboxVerification { 18 | 19 | public AdaptedBlackBoxSubscriberVerificationTest() { 20 | super(new TestEnvironment()); 21 | } 22 | 23 | @Override 24 | public Subscriber createFlowSubscriber() { 25 | return new FlowAdapter.SubscribingIterator() { 26 | @Override 27 | public void onSubscribe(final Flow.Subscription subscription) { 28 | super.onSubscribe(subscription); 29 | consume(); 30 | } 31 | }; 32 | } 33 | 34 | @Override 35 | public Integer createElement(final int element) { 36 | return element; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DCO1.1.txt: -------------------------------------------------------------------------------- 1 | Developer's Certificate of Origin 1.1 2 | 3 | By making a contribution to this project, I certify that: 4 | 5 | (a) The contribution was created in whole or in part by me and I 6 | have the right to submit it under the open source license 7 | indicated in the file; or 8 | 9 | (b) The contribution is based upon previous work that, to the best 10 | of my knowledge, is covered under an appropriate open source 11 | license and I have the right under that license to submit that 12 | work with modifications, whether created in whole or in part 13 | by me, under the same open source license (unless I am 14 | permitted to submit under a different license), as indicated 15 | in the file; or 16 | 17 | (c) The contribution was provided directly to me by some other 18 | person who certified (a), (b) or (c) and I have not modified 19 | it. 20 | 21 | (d) I understand and agree that this project and the contribution 22 | are public and that a record of the contribution (including all 23 | personal information I submit with it, including my sign-off) is 24 | maintained indefinitely and may be redistributed consistent with 25 | this project or the open source license(s) involved. 26 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncEpochImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.CompletionStage; 12 | 13 | import com.ibm.asyncutil.util.StageSupport; 14 | 15 | 16 | /** 17 | * @author Renar Narubin 18 | * @see AsyncEpoch 19 | */ 20 | @SuppressWarnings("serial") 21 | class AsyncEpochImpl extends AbstractSimpleEpoch implements AsyncEpoch { 22 | private final CompletableFuture future = new CompletableFuture<>(); 23 | 24 | @Override 25 | public Optional enter() { 26 | return internalEnter() == Status.NORMAL 27 | ? Optional.of(this) 28 | : Optional.empty(); 29 | } 30 | 31 | @Override 32 | public CompletionStage terminate() { 33 | return internalTerminate() == Status.NORMAL 34 | ? this.future 35 | : this.future.thenApply(ignore -> false); 36 | } 37 | 38 | @Override 39 | void onCleared() { 40 | this.future.complete(true); 41 | } 42 | 43 | @Override 44 | public CompletionStage awaitCompletion() { 45 | return StageSupport.voided(this.future); 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /asyncutil-flow/src/test/java/com/ibm/asyncutil/flow/AdaptedPublisherVerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.flow; 8 | 9 | import java.util.concurrent.Flow.Publisher; 10 | 11 | import org.reactivestreams.tck.TestEnvironment; 12 | import org.reactivestreams.tck.flow.FlowPublisherVerification; 13 | 14 | import com.ibm.asyncutil.iteration.AsyncIterator; 15 | 16 | public class AdaptedPublisherVerificationTest extends FlowPublisherVerification { 17 | 18 | public AdaptedPublisherVerificationTest() { 19 | super(new TestEnvironment()); 20 | } 21 | 22 | @Override 23 | public Publisher createFlowPublisher(final long l) { 24 | AsyncIterator it = AsyncIterator.repeat(1); 25 | // infinite on MAX_VALUE 26 | if (l != Long.MAX_VALUE) { 27 | it = it.take(l); 28 | } 29 | return FlowAdapter.toPublisher(it); 30 | } 31 | 32 | @Override 33 | public Publisher createFailedFlowPublisher() { 34 | // return ReactiveStreamsConverter.toPublisher(AsyncIterator.error(new RuntimeException("test 35 | // error"))); 36 | // null ignores these tests. An iterator's error is lazy (requires a request to get an error), 37 | // but there are two tests that test for an error on subscription 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/util/AsyncCloseable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | import com.ibm.asyncutil.locks.AsyncEpoch; 10 | 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.function.Function; 13 | 14 | /** 15 | * An object that may hold resources that must be explicitly released, where the release may be 16 | * performed asynchronously. 17 | * 18 | *

19 | * Examples of such resources are manually managed memory, open file handles, socket descriptors 20 | * etc. While similar to {@link AutoCloseable}, this interface should be used when the resource 21 | * release operation may possibly be async. For example, if an object is thread-safe and has many 22 | * consumers, an implementation may require all current ongoing operations to complete before 23 | * resources are relinquished. A common way to implement this pattern for a thread-safe object with 24 | * asynchronous methods is by using an {@link AsyncEpoch}. 25 | * 26 | *

27 | * May be used with the methods {@link StageSupport#tryWith(AsyncCloseable, Function)}, 28 | * {@link StageSupport#tryComposeWith(AsyncCloseable, Function)} to emulate the behavior of a try 29 | * with resources block. 30 | * 31 | * @author Renar Narubin 32 | * @author Ravi Khadiwala 33 | */ 34 | @FunctionalInterface 35 | public interface AsyncCloseable { 36 | /** 37 | * Relinquishes any resources associated with this object. 38 | * 39 | * @return a {@link CompletionStage} that completes when all resources associated with this object 40 | * have been released, or with an exception if the resources cannot be released. 41 | */ 42 | CompletionStage close(); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /asyncutil-flow/src/test/java/com/ibm/asyncutil/flow/AdaptedWhiteBoxSubscriberVerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.flow; 8 | 9 | import java.util.concurrent.Flow.Subscriber; 10 | import java.util.concurrent.Flow.Subscription; 11 | 12 | import org.reactivestreams.tck.TestEnvironment; 13 | import org.reactivestreams.tck.flow.FlowSubscriberWhiteboxVerification; 14 | 15 | public class AdaptedWhiteBoxSubscriberVerificationTest 16 | extends FlowSubscriberWhiteboxVerification { 17 | public AdaptedWhiteBoxSubscriberVerificationTest() { 18 | super(new TestEnvironment()); 19 | } 20 | 21 | @Override 22 | protected Subscriber createFlowSubscriber( 23 | final WhiteboxSubscriberProbe probe) { 24 | final Subscriber backing = new FlowAdapter.SubscribingIterator<>(); 25 | return new Subscriber() { 26 | @Override 27 | public void onSubscribe(final Subscription s) { 28 | backing.onSubscribe(s); 29 | 30 | probe.registerOnSubscribe(new SubscriberPuppet() { 31 | 32 | @Override 33 | public void triggerRequest(final long elements) { 34 | s.request(elements); 35 | } 36 | 37 | @Override 38 | public void signalCancel() { 39 | s.cancel(); 40 | } 41 | }); 42 | } 43 | 44 | @Override 45 | public void onNext(final Integer integer) { 46 | backing.onNext(integer); 47 | probe.registerOnNext(integer); 48 | } 49 | 50 | @Override 51 | public void onError(final Throwable throwable) { 52 | backing.onError(throwable); 53 | probe.registerOnError(throwable); 54 | } 55 | 56 | @Override 57 | public void onComplete() { 58 | backing.onComplete(); 59 | probe.registerOnComplete(); 60 | } 61 | }; 62 | } 63 | 64 | @Override 65 | public Integer createElement(final int element) { 66 | return element; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/SyncAsyncSemaphore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.CompletionStage; 11 | import java.util.concurrent.Executor; 12 | import java.util.concurrent.Semaphore; 13 | 14 | /** 15 | * An async wrapper over {@link Semaphore} for use in testing 16 | */ 17 | class SyncAsyncSemaphore implements AsyncSemaphore { 18 | private final Semaphore semaphore; 19 | private final Executor executor; 20 | 21 | public SyncAsyncSemaphore(final long permits, final boolean fair) { 22 | this(permits, fair, r -> new Thread(r).start()); 23 | } 24 | 25 | public SyncAsyncSemaphore(final long permits, final boolean fair, final Executor executor) { 26 | this.semaphore = new Semaphore(Math.toIntExact(permits), fair); 27 | this.executor = executor; 28 | } 29 | 30 | @Override 31 | public CompletionStage acquire(final long permits) { 32 | if (permits < 0L) { 33 | throw new IllegalArgumentException(); 34 | } 35 | final int p = Math.toIntExact(permits); 36 | 37 | final CompletableFuture future = new CompletableFuture<>(); 38 | this.executor.execute(() -> { 39 | this.semaphore.acquireUninterruptibly(p); 40 | future.complete(null); 41 | }); 42 | return future; 43 | } 44 | 45 | @Override 46 | public void release(final long permits) { 47 | if (permits < 0L) { 48 | throw new IllegalArgumentException(); 49 | } 50 | this.semaphore.release(Math.toIntExact(permits)); 51 | } 52 | 53 | @Override 54 | public boolean tryAcquire(final long permits) { 55 | if (permits < 0L) { 56 | throw new IllegalArgumentException(); 57 | } 58 | return this.semaphore.tryAcquire(Math.toIntExact(permits)); 59 | } 60 | 61 | @Override 62 | public long drainPermits() { 63 | return this.semaphore.drainPermits(); 64 | } 65 | 66 | @Override 67 | public long getAvailablePermits() { 68 | return this.semaphore.availablePermits(); 69 | } 70 | 71 | @Override 72 | public int getQueueLength() { 73 | return this.semaphore.getQueueLength(); 74 | } 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/FairAsyncStampedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | /** 10 | * An {@link AsyncStampedLock} implementation which provides fair ordering of acquisitions, like 11 | * that of {@link FairAsyncReadWriteLock}. The {@link #tryOptimisticRead()} method also employs fair 12 | * ordering, in that a stamp will not be issued if there are queued writers waiting to acquire the 13 | * lock. {@link Stamp#validate() Validation} of an already issued stamp, however, does not abide by 14 | * this ordering; it will only fail validation once a writer has successfully acquired the lock 15 | * (irrespective of queued writers) 16 | * 17 | * @author Renar Narubin 18 | */ 19 | public class FairAsyncStampedLock extends FairAsyncReadWriteLock implements AsyncStampedLock { 20 | /* 21 | * The precise details of the optimistic read ordering for this implementation result from the 22 | * behavior of the underlying FairAsyncReadWriteLock implementation. Once a writer enters the 23 | * queue, the previously existing state is no longer accessible (in a thread-safe manner) and 24 | * without the previous state it's not possible to say whether readers are permitted to run. 25 | * Circumventing this limitation may be possible, but would likely involve changes to the 26 | * underlying lock structure, at which point an independent implementation would likely be more 27 | * practical than piggy-backing the existing rwlock. 28 | * 29 | * Such a hypothetical improvement would allow optimistic reads to proceed even with queued 30 | * writers, which would be favorable particularly when full read-locks are held for a relatively 31 | * long time, possibly creating many queued writers. 32 | * 33 | * As it stands, this implementation is fairly effective when writer acquisition is rare, which in 34 | * general is a cause for looking into optimistic read strategies to begin with. 35 | */ 36 | 37 | @Override 38 | public Stamp tryOptimisticRead() { 39 | final Node h = this.head; 40 | /* 41 | * in order to issue a stamp, the node must be in read-mode i.e. the read future must be done, 42 | * and the write future must not be done. h.isCleared is equivalent to h.writeFuture.isDone 43 | * without the indirection 44 | */ 45 | return (!h.isCleared()) && h.readFuture.isDone() ? h : null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/iteration/BoundedAsyncQueueTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.iteration; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | import org.junit.Assert; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | public class BoundedAsyncQueueTest extends AbstractAsyncQueueTest { 17 | 18 | private BoundedAsyncQueue queue; 19 | 20 | @Before 21 | public void makeQueue() { 22 | this.queue = AsyncQueues.bounded(); 23 | } 24 | 25 | @Override 26 | boolean send(final Integer c) { 27 | return this.queue.send(c).toCompletableFuture().join(); 28 | } 29 | 30 | @Override 31 | AsyncIterator consumer() { 32 | return this.queue; 33 | } 34 | 35 | @Override 36 | void closeImpl() { 37 | this.queue.terminate(); 38 | } 39 | 40 | @Test 41 | public void asyncCloseContractTest() { 42 | // accepted right away 43 | final CompletableFuture f1 = this.queue.send(1).toCompletableFuture(); 44 | Assert.assertTrue(f1.isDone()); 45 | Assert.assertTrue(f1.join()); 46 | 47 | // waiting 48 | final CompletableFuture f2 = this.queue.send(2).toCompletableFuture(); 49 | Assert.assertFalse(f2.isDone()); 50 | 51 | // terminate 52 | final CompletableFuture closeFuture = this.queue.terminate().toCompletableFuture(); 53 | 54 | Assert.assertFalse(f2.isDone()); 55 | Assert.assertFalse(closeFuture.isDone()); 56 | 57 | // send after terminate 58 | final CompletableFuture f3 = this.queue.send(3).toCompletableFuture(); 59 | 60 | // consume a result 61 | Assert.assertEquals(1, 62 | this.queue.nextStage().toCompletableFuture().join().right().get().intValue()); 63 | 64 | // f2 should be done, and accepted 65 | Assert.assertTrue(f2.isDone()); 66 | Assert.assertTrue(f2.join()); 67 | 68 | Assert.assertEquals(2, 69 | this.queue.nextStage().toCompletableFuture().join().right().get().intValue()); 70 | Assert.assertFalse(this.queue.nextStage().toCompletableFuture().join().isRight()); 71 | 72 | // terminate should be done, f3 should be done and rejected 73 | Assert.assertTrue(closeFuture.isDone()); 74 | Assert.assertTrue(f3.isDone()); 75 | Assert.assertFalse(f3.join()); 76 | } 77 | 78 | @Override 79 | Optional poll() { 80 | return this.queue.poll(); 81 | } 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /asyncutil/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.ibm.async 7 | asyncutil-aggregator 8 | 0.2.0-SNAPSHOT 9 | 10 | 11 | com.ibm.async 12 | asyncutil 13 | 0.2.0-SNAPSHOT 14 | jar 15 | 16 | asyncutil 17 | Utilities for working with CompletionStages 18 | http://github.com/ibm/java-async-util 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | 25 | 26 | 27 | 28 | 29 | Ravi Khadiwala 30 | rkhadiwa@us.ibm.com 31 | IBM 32 | http://www.ibm.com 33 | 34 | 35 | Renar Narubin 36 | rnarubin@us.ibm.com 37 | IBM 38 | http://www.ibm.com 39 | 40 | 41 | 42 | 43 | scm:git:git://github.com/ibm/java-async-util.git 44 | scm:git:ssh://github.com:ibm/java-async-util.git 45 | http://github.com/ibm/java-async-util 46 | 47 | 48 | 49 | UTF-8 50 | 1.8 51 | 1.8 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-javadoc-plugin 59 | 2.10.4 60 | 61 | 62 | 63 | 64 | 65 | 66 | junit 67 | junit 68 | 4.11 69 | test 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /asyncutil-flow/README.md: -------------------------------------------------------------------------------- 1 | # asyncutil-flow 2 | 3 | ## Introduction 4 | 5 | This optional module allows Java 9 users to translate the iteration primitive provided by asyncutil to and from [java.util.Flow constructs](https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html). An asyncutil [AsyncIterator](https://ibm.github.io/java-async-util/apidocs/com/ibm/asyncutil/iteration/AsyncIterator.html) can be used as the source for a `Flow.Publisher`, and a `Flow.Publisher` can be consumed via an `AsyncIterator`. 6 | 7 | ## Downloading 8 | TODO maven instructions 9 | 10 | ## Comparison to other Flow providers 11 | There are now several options for implementations of Flow. Here are some things to consider when comparing `AsyncIterator` to other implementations, especially RxJava. 12 | 13 | ### CompletionStage oriented 14 | asyncutil is strictly a Java 8+ library, and is designed with `CompletionStage` in mind. AsyncIterators are naturally constructed from stage producing APIs and always reduce back into stages when actually evaluated. Moreover, these terminal reduction steps take on the familiar form of `Stream` terminal methods. RxJava doesn't target CompletionStages, and is a better choice when supporting older Java releases. 15 | 16 | ### Iteration vs Reactivity 17 | AsyncIterators excel at being local scope notions for structuring an iterative async computation and getting a `CompletionStage` of the result using a terminal method. It is well suited for concisely expressing an iterative asynchronous computation. Using it to model signals in a reactive style gets messy because you must carefully manage partially evaluating the iterator, which is a more natural fit for RxJava. 18 | 19 | ### Hot/Cold vs Lazy 20 | In the Rx world there are two choices for an `Observable`. Hot observables usually model sources that already "exist" and are always producing. Subscribers join into the existing stream. Cold observables constructa new stream of production when a subscriber is added. 21 | 22 | Instead, an `AsyncIterator` is lazy, which is a constrained form of cold. It can only be consumed once, and production only starts upon consumption. This makes it very natural to model stateful streams, and you can build back to the cold observables model with `Supplier`. Consumption directly includes a notion of completion (consumption is accomplished via terminal methods that return `CompletionStage`), compared to subscription in Rx. AsyncIterators are generally poorly suited for the Hot observable model. 23 | 24 | ### Constrained scope 25 | AsyncIterators exclusively solve the problem of consumer driven sequential iteration, whereas RxJava provides a wealth of features like Schedulers, Plugins, flexible backpressure, etc. If `AsyncIterator` can sufficiently covers your needs, you may find it easier to work with. 26 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/util/EitherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | 14 | public class EitherTest { 15 | 16 | @Test 17 | public void testEitherLeft() { 18 | final Either l = Either.left(5); 19 | 20 | int v = l.fold(Integer::intValue, String::length); 21 | Assert.assertEquals(5, v); 22 | 23 | final Either w = l.map(Long::valueOf, a -> a); 24 | v = w.fold(Long::intValue, String::length); 25 | Assert.assertEquals(5, v); 26 | 27 | final Either r = l.fold( 28 | a -> Either.right(Integer.valueOf(a + 1).toString()), 29 | b -> Either.left(b.length())); 30 | Assert.assertEquals("6", r.fold(a -> a.toString(), b -> b)); 31 | 32 | final AtomicInteger i = new AtomicInteger(); 33 | l.forEach(i::addAndGet, b -> i.addAndGet(b.length())); 34 | Assert.assertEquals(5, i.get()); 35 | } 36 | 37 | @Test 38 | public void testEitherRight() { 39 | final Either l = Either.right("String!"); 40 | 41 | int v = l.fold(Integer::intValue, String::length); 42 | Assert.assertEquals(7, v); 43 | 44 | final Either w = l.map(Long::valueOf, a -> a); 45 | v = w.fold(Long::intValue, String::length); 46 | Assert.assertEquals(7, v); 47 | 48 | final Either r = l.fold( 49 | a -> Either.right(Integer.valueOf(a + 1).toString()), 50 | b -> Either.left(b.length())); 51 | Assert.assertEquals("7", r.fold(a -> a.toString(), b -> b)); 52 | 53 | final AtomicInteger i = new AtomicInteger(); 54 | l.forEach(i::addAndGet, b -> i.addAndGet(b.length())); 55 | Assert.assertEquals(7, i.get()); 56 | } 57 | 58 | @Test 59 | public void testRightLeftEither() { 60 | final Either l = Either.right("String!"); 61 | 62 | int v = l.fold(Integer::intValue, String::length); 63 | Assert.assertEquals(7, v); 64 | 65 | final Either w = l.map(Long::valueOf, a -> a); 66 | v = w.fold(Long::intValue, String::length); 67 | Assert.assertEquals(7, v); 68 | 69 | final Either r = l.fold( 70 | a -> Either.right(Integer.valueOf(a + 1).toString()), 71 | b -> Either.left(b.length())); 72 | Assert.assertEquals("7", r.fold(a -> a.toString(), b -> b)); 73 | 74 | final AtomicInteger i = new AtomicInteger(); 75 | l.forEach(i::addAndGet, b -> i.addAndGet(b.length())); 76 | Assert.assertEquals(7, i.get()); 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | /** 13 | * An asynchronously acquirable mutual exclusion lock. 14 | * 15 | *

16 | * Implementations will specify whether their lock acquisition is fair or not; this interface does 17 | * not define this requirement. 18 | * 19 | * @author Renar Narubin 20 | */ 21 | public interface AsyncLock { 22 | 23 | /** 24 | * Exclusively acquires this lock. The returned stage will complete when the lock is exclusively 25 | * acquired by this caller. The stage may already be complete if the lock was not held. 26 | * 27 | *

28 | * The {@link LockToken} held by the returned stage is used to release the lock after it has been 29 | * acquired and the lock-protected action has completed. 30 | * 31 | * @return A {@link CompletionStage} which will complete with a {@link LockToken} when the lock 32 | * has been exclusively acquired 33 | */ 34 | CompletionStage acquireLock(); 35 | 36 | /** 37 | * Attempts to immediately acquire the lock, returning a populated {@link Optional} if the lock is 38 | * not currently held. 39 | * 40 | * @return An {@link Optional} holding a {@link LockToken} if the lock is not held; otherwise an 41 | * empty Optional 42 | */ 43 | Optional tryLock(); 44 | 45 | /** 46 | * A lock token indicating that the associated lock has been exclusively acquired. Once the 47 | * protected action is completed, the lock may be released by calling 48 | * {@link LockToken#releaseLock()} 49 | */ 50 | interface LockToken extends AutoCloseable { 51 | /** Releases this lock, allowing others to acquire it. */ 52 | void releaseLock(); 53 | 54 | /** 55 | * Releases this lock, allowing others to acquire it. 56 | * 57 | *

58 | * {@inheritDoc} 59 | */ 60 | @Override 61 | default void close() { 62 | releaseLock(); 63 | } 64 | } 65 | 66 | /** 67 | * Creates an {@link AsyncLock} 68 | * 69 | *

70 | * The returned lock is only guaranteed to meet the requirements of {@link AsyncLock}; in 71 | * particular, no guarantee of fairness is provided. 72 | * 73 | * @return a new {@link AsyncLock} 74 | */ 75 | static AsyncLock create() { 76 | // fair for now, may be swapped with a more performant unfair version later 77 | return new FairAsyncLock(); 78 | } 79 | 80 | /** 81 | * Creates a fair {@link AsyncLock} 82 | * 83 | * @return a new {@link AsyncLock} with a fair implementation 84 | */ 85 | static AsyncLock createFair() { 86 | return new FairAsyncLock(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/util/StageSupportTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | import java.util.concurrent.CompletionException; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | 15 | public class StageSupportTest { 16 | private static class TestException extends RuntimeException { 17 | private static final long serialVersionUID = 1L; 18 | } 19 | 20 | @Test 21 | public void testThenComposeOrRecover() { 22 | final CompletionStage error = StageSupport.exceptionalStage(new TestException()); 23 | final CompletionStage success = StageSupport.completedStage(1); 24 | final CompletionStage success2 = StageSupport.completedStage(2); 25 | 26 | // input stage status should only effect function arguments 27 | for (final boolean inputFailed : new boolean[] {false, true}) { 28 | final CompletionStage inputStage = inputFailed ? error : success; 29 | final Integer expectedResult = inputFailed ? null : 1; 30 | 31 | { 32 | // successful compose 33 | final int x = 34 | StageSupport.thenComposeOrRecover( 35 | inputStage, 36 | (result, throwable) -> { 37 | Assert.assertEquals(expectedResult, result); 38 | Assert.assertEquals(inputFailed, throwable != null); 39 | return success2; 40 | }) 41 | .toCompletableFuture() 42 | .join(); 43 | Assert.assertEquals(2, x); 44 | } 45 | 46 | { 47 | // error compose with a thrown exception 48 | assertError( 49 | StageSupport.thenComposeOrRecover( 50 | inputStage, 51 | (result, throwable) -> { 52 | Assert.assertEquals(expectedResult, result); 53 | Assert.assertEquals(inputFailed, throwable != null); 54 | throw new TestException(); 55 | })); 56 | } 57 | 58 | { 59 | // error compose with a stage that completes with error 60 | assertError( 61 | StageSupport.thenComposeOrRecover( 62 | inputStage, 63 | (result, throwable) -> { 64 | Assert.assertEquals(expectedResult, result); 65 | Assert.assertEquals(inputFailed, throwable != null); 66 | return error; 67 | })); 68 | } 69 | } 70 | } 71 | 72 | private void assertError(final CompletionStage stage) { 73 | try { 74 | stage.toCompletableFuture().join(); 75 | Assert.fail("expected exception"); 76 | } catch (final CompletionException e) { 77 | Assert.assertTrue(e.getCause() instanceof TestException); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/SyncAsyncStampedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.concurrent.Executor; 13 | import java.util.concurrent.locks.StampedLock; 14 | 15 | /** 16 | * An {@link AsyncStampedLock} backed by a synchronous {@link StampedLock} used for testing purposes 17 | */ 18 | class SyncAsyncStampedLock implements AsyncStampedLock { 19 | private final StampedLock lock = new StampedLock(); 20 | private final Executor executor; 21 | 22 | public SyncAsyncStampedLock() { 23 | this(r -> new Thread(r).start()); 24 | } 25 | 26 | public SyncAsyncStampedLock(final Executor executor) { 27 | this.executor = executor; 28 | } 29 | 30 | @Override 31 | public CompletionStage acquireReadLock() { 32 | final CompletableFuture future = new CompletableFuture<>(); 33 | this.executor.execute(() -> { 34 | final long stamp = this.lock.readLock(); 35 | future.complete(() -> this.lock.unlockRead(stamp)); 36 | }); 37 | return future; 38 | } 39 | 40 | @Override 41 | public Optional tryReadLock() { 42 | final long stamp = this.lock.tryReadLock(); 43 | return stamp == 0 ? Optional.empty() : Optional.of(() -> this.lock.unlockRead(stamp)); 44 | } 45 | 46 | @Override 47 | public CompletionStage acquireWriteLock() { 48 | final CompletableFuture future = new CompletableFuture<>(); 49 | this.executor.execute(() -> { 50 | final long stamp = this.lock.writeLock(); 51 | future.complete(new StampWriteToken(stamp)); 52 | }); 53 | return future; 54 | } 55 | 56 | @Override 57 | public Optional tryWriteLock() { 58 | final long stamp = this.lock.tryWriteLock(); 59 | return stamp == 0 ? Optional.empty() : Optional.of(new StampWriteToken(stamp)); 60 | } 61 | 62 | @Override 63 | public Stamp tryOptimisticRead() { 64 | final long stamp = this.lock.tryOptimisticRead(); 65 | return stamp == 0 ? null : () -> this.lock.validate(stamp); 66 | } 67 | 68 | private class StampWriteToken implements WriteLockToken { 69 | private final long stamp; 70 | 71 | public StampWriteToken(final long stamp) { 72 | this.stamp = stamp; 73 | } 74 | 75 | @Override 76 | public void releaseLock() { 77 | SyncAsyncStampedLock.this.lock.unlockWrite(this.stamp); 78 | } 79 | 80 | @Override 81 | public ReadLockToken downgradeLock() { 82 | final long readStamp = SyncAsyncStampedLock.this.lock.tryConvertToReadLock(this.stamp); 83 | if (readStamp == 0) { 84 | throw new IllegalStateException("downgraded lock not in locked state"); 85 | } 86 | return () -> SyncAsyncStampedLock.this.lock.unlockRead(readStamp); 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/AbstractAsyncStampedLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import org.junit.AfterClass; 15 | import org.junit.Assert; 16 | import org.junit.BeforeClass; 17 | import org.junit.Test; 18 | 19 | import com.ibm.asyncutil.locks.AsyncStampedLock.Stamp; 20 | import com.ibm.asyncutil.util.TestUtil; 21 | 22 | public abstract class AbstractAsyncStampedLockTest extends AbstractAsyncReadWriteLockTest { 23 | 24 | @Override 25 | protected final AsyncReadWriteLock getReadWriteLock() { 26 | return createLock(); 27 | } 28 | 29 | protected abstract AsyncStampedLock createLock(); 30 | 31 | @Test 32 | public void testOptimism() { 33 | final AsyncStampedLock asl = createLock(); 34 | 35 | final Stamp stamp0 = asl.tryOptimisticRead(); 36 | 37 | Assert.assertNotNull(stamp0); 38 | Assert.assertTrue(stamp0.validate()); 39 | 40 | final AsyncReadWriteLock.ReadLockToken readLockToken = TestUtil.join(asl.acquireReadLock()); 41 | 42 | Assert.assertTrue(stamp0.validate()); 43 | 44 | final Stamp stamp1 = asl.tryOptimisticRead(); 45 | Assert.assertNotNull(stamp1); 46 | Assert.assertTrue(stamp1.validate()); 47 | 48 | final CompletableFuture writeLock = 49 | asl.acquireWriteLock().toCompletableFuture(); 50 | 51 | // stamps are still valid because the write lock has not yet acquired 52 | Assert.assertTrue(stamp0.validate()); 53 | Assert.assertTrue(stamp1.validate()); 54 | 55 | readLockToken.releaseLock(); 56 | final AsyncReadWriteLock.WriteLockToken writeLockToken = TestUtil.join(writeLock); 57 | 58 | Assert.assertFalse(stamp0.validate()); 59 | Assert.assertFalse(stamp1.validate()); 60 | final Stamp stamp2 = asl.tryOptimisticRead(); 61 | Assert.assertNull(stamp2); 62 | 63 | writeLockToken.releaseLock(); 64 | 65 | final Stamp stamp3 = asl.tryOptimisticRead(); 66 | Assert.assertNotNull(stamp3); 67 | Assert.assertTrue(stamp3.validate()); 68 | 69 | Assert.assertFalse(stamp0.validate()); 70 | Assert.assertFalse(stamp1.validate()); 71 | } 72 | 73 | 74 | public static class SyncAsyncStampedLockTest extends AbstractAsyncStampedLockTest { 75 | @Override 76 | protected boolean isActuallyAsync() { 77 | return false; 78 | } 79 | 80 | private static ExecutorService pool; 81 | 82 | @BeforeClass 83 | public static void setupPool() { 84 | pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 85 | } 86 | 87 | @AfterClass 88 | public static void shutdownPool() throws InterruptedException { 89 | pool.shutdown(); 90 | pool.awaitTermination(10, TimeUnit.SECONDS); 91 | } 92 | 93 | @Override 94 | protected SyncAsyncStampedLock createLock() { 95 | return new SyncAsyncStampedLock(pool); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/examples/nio/Iteration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.examples.nio; 8 | 9 | import static com.ibm.asyncutil.examples.nio.NioBridge.accept; 10 | import static com.ibm.asyncutil.examples.nio.NioBridge.connect; 11 | import static com.ibm.asyncutil.examples.nio.NioBridge.readInt; 12 | import static com.ibm.asyncutil.examples.nio.NioBridge.writeInt; 13 | 14 | import java.io.IOException; 15 | import java.net.SocketAddress; 16 | import java.nio.channels.AsynchronousServerSocketChannel; 17 | import java.nio.channels.AsynchronousSocketChannel; 18 | import java.util.List; 19 | import java.util.concurrent.CompletionStage; 20 | import java.util.concurrent.ThreadLocalRandom; 21 | import java.util.stream.Collectors; 22 | 23 | import com.ibm.asyncutil.iteration.AsyncIterator; 24 | import com.ibm.asyncutil.util.StageSupport; 25 | 26 | public class Iteration { 27 | 28 | /** 29 | * Write 100 random integers into {@code channel}, then write -1 30 | * 31 | * @param channel An {@link AsynchronousSocketChannel} that will be written into 32 | * @return A stage that completes when we've finished writing into the channel 33 | */ 34 | static CompletionStage write100Randoms(final AsynchronousSocketChannel channel) { 35 | final CompletionStage allItemsWritten = AsyncIterator 36 | .generate(() -> StageSupport.completedStage(ThreadLocalRandom.current().nextInt(0, 100))) 37 | .thenCompose(i -> writeInt(channel, i)) 38 | .take(100) 39 | .consume(); 40 | return allItemsWritten.thenCompose(ignore -> writeInt(channel, -1)); 41 | } 42 | 43 | /** 44 | * Read one int from the channel at a time. 45 | * 46 | * @param channel An {@link AsynchronousSocketChannel} that will be read from 47 | * @return A stage that completes when we read a -1 off of the channel 48 | */ 49 | static CompletionStage> readUntilStopped( 50 | final AsynchronousSocketChannel channel) { 51 | return AsyncIterator 52 | .generate(() -> readInt(channel)) 53 | .takeWhile(i -> i != -1) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | public static void main(final String[] args) throws IOException { 58 | final AsynchronousServerSocketChannel server = 59 | AsynchronousServerSocketChannel.open().bind(null); 60 | 61 | final CompletionStage acceptStage = accept(server); 62 | 63 | final SocketAddress addr = server.getLocalAddress(); 64 | final CompletionStage connectStage = connect(addr); 65 | 66 | // after connecting, write 100 random integers, then write -1 67 | final CompletionStage writeStage = 68 | connectStage.thenCompose(channel -> write100Randoms(channel)); 69 | 70 | final CompletionStage> readStage = 71 | acceptStage.thenCompose(Iteration::readUntilStopped); 72 | 73 | // wait for the write and the read to complete, print read results 74 | writeStage.toCompletableFuture().join(); 75 | System.out.println(readStage.toCompletableFuture().join()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncFunnel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.CompletionStage; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | import java.util.function.Supplier; 13 | 14 | /** 15 | * Enforces that a single asynchronous computation occurs at a time, allowing others to observe it. 16 | * 17 | *

18 | * Used when many clients need the result of an expensive asynchronous operation but only one should 19 | * be ongoing at any given time. If {@link AsyncFunnel#doOrGet} is called while an operation is 20 | * ongoing, the future result of that ongoing task is returned. After the operation completes, a new 21 | * task can be created. 22 | * 23 | *

24 | * For example, imagine we have many threads that will perform a computation on a potentially cached 25 | * value. If the value is not in the cache, they will have to do an expensive operation to populate 26 | * the cache. Many threads may discover the value needs to be computed at the same time, but only 27 | * one thread needs to actually compute it. 28 | * 29 | *

30 |  * {@code
31 |  * AsyncFunnel readFunnel = new AsyncFunnel();
32 |  * for (int i = 0; i < numThreads; i++) {
33 |  *    CompletableFuture.supplyAsync(() -> {
34 |  *
35 |  *        val = readFromCache();
36 |  *        if (val != null) {
37 |  *          return StageSupport.completedStage(val);
38 |  *        }
39 |  *        // populate the cache if we're first, or just listen for the result on the thread
40 |  *        // already doing the computation if we're not
41 |  *        return readFunnel.doOrGet(() -> {
42 |  *            return expensiveOperation().thenApply(computedVal -> {
43 |  *                updateCache(readVal);
44 |  *                return readVal;
45 |  *            });
46 |  *        });
47 |  *    })
48 |  * }
49 |  * }
50 |  * 
51 | * 52 | * @author Ravi Khadiwala 53 | * @author Renar Narubin 54 | */ 55 | public class AsyncFunnel { 56 | private final AtomicReference> current = new AtomicReference<>(null); 57 | 58 | /** 59 | * Runs the provided action, or returns an existing {@link CompletionStage} if an action is 60 | * already running. After the returned stage is completed, the first subsequent doOrGet call will 61 | * start a new action 62 | * 63 | * @param action Supplier of a {@link CompletionStage} of T that only runs if no action is 64 | * currently running 65 | * @return A {@link CompletionStage} produced by action or one that was previously 66 | * produced 67 | */ 68 | public CompletionStage doOrGet(final Supplier> action) { 69 | CompletableFuture newFuture; 70 | do { 71 | final CompletionStage oldFuture = this.current.get(); 72 | if (oldFuture != null) { 73 | return oldFuture; 74 | } 75 | } while (!this.current.compareAndSet(null, newFuture = new CompletableFuture<>())); 76 | 77 | final CompletableFuture finalRef = newFuture; 78 | return action 79 | .get() 80 | .whenComplete( 81 | (t, ex) -> { 82 | this.current.set(null); 83 | if (ex != null) { 84 | finalRef.completeExceptionally(ex); 85 | } else { 86 | finalRef.complete(t); 87 | } 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncNamedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | /** 13 | * A mechanism used to acquire mutual exclusion locks shared by a common name. Acquisitions for a 14 | * given name will share exclusivity with other acquisitions of the same name, based on 15 | * {@link Object#equals(Object) object equality}. 16 | * 17 | *

18 | * Implementations will specify whether their lock acquisition is fair or not; this interface does 19 | * not define this requirement. 20 | * 21 | *

22 | * Note that implementations will generally employ an underlying {@link java.util.Map}; as such, the 23 | * same precautions must be taken regarding mutability of keys (names). Name objects should not 24 | * change from the time of acquisition to the time of release, with respect to their 25 | * {@link Object#equals(Object) equality} and {@link Object#hashCode() hash code} semantics. The 26 | * release methods of the returned {@link AsyncLock.LockToken} may throw a 27 | * {@link java.util.ConcurrentModificationException} if such a modification is detected. 28 | * 29 | * @author Renar Narubin 30 | * @param the type of named objects used to identify locks 31 | */ 32 | public interface AsyncNamedLock { 33 | 34 | /** 35 | * Acquires the lock associated with the given name. The returned stage will complete when the 36 | * lock is exclusively acquired by this caller. The stage may already be complete if the lock was 37 | * not held. 38 | * 39 | *

40 | * The {@link AsyncLock.LockToken} held by the returned stage is used to release the lock after it 41 | * has been acquired and the lock-protected action has completed. 42 | * 43 | * @param name to acquire exclusive access for 44 | * @return A {@link CompletionStage} which will complete with a {@link AsyncLock.LockToken} when 45 | * the lock associated with {@code name} has been exclusively acquired 46 | */ 47 | CompletionStage acquireLock(final T name); 48 | 49 | /** 50 | * Attempts to immediately acquire the lock associated with the given name, returning a populated 51 | * {@link Optional} if the lock is not currently held. 52 | * 53 | *

54 | * The {@link AsyncLock.LockToken} held by the returned Optional is used to release the lock after 55 | * it has been acquired and the lock-protected action has completed. 56 | * 57 | * @param name to acquire exclusive access for 58 | * @return An {@link Optional} holding a {@link AsyncLock.LockToken} if the lock associated with 59 | * {@code name} is not held; otherwise an empty Optional 60 | */ 61 | Optional tryLock(final T name); 62 | 63 | /** 64 | * Creates an {@link AsyncNamedLock} 65 | * 66 | *

67 | * The returned lock is only guaranteed to meet the requirements of {@link AsyncNamedLock}; in 68 | * particular, no guarantee of fairness is provided. 69 | * 70 | * @return a new {@link AsyncNamedLock} 71 | */ 72 | static AsyncNamedLock create() { 73 | // fair for now, may be swapped with a more performant unfair version later 74 | return new FairAsyncNamedLock<>(); 75 | } 76 | 77 | /** 78 | * Creates a fair {@link AsyncNamedLock} 79 | * 80 | * @return a new {@link AsyncNamedLock} with a fair implementation 81 | */ 82 | static AsyncNamedLock createFair() { 83 | return new FairAsyncNamedLock<>(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/examples/nio/Epochs.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.examples.nio; 8 | 9 | import java.io.IOException; 10 | import java.net.SocketAddress; 11 | import java.nio.channels.AsynchronousSocketChannel; 12 | import java.util.Optional; 13 | import java.util.concurrent.CompletionException; 14 | import java.util.concurrent.CompletionStage; 15 | 16 | import com.ibm.asyncutil.locks.AsyncEpoch; 17 | import com.ibm.asyncutil.util.AsyncCloseable; 18 | 19 | /** 20 | * Example using {@link AsyncEpoch} to wait for ongoing tasks to finish before cleaning up 21 | * resources 22 | */ 23 | public class Epochs { 24 | 25 | /** 26 | * Wrapper over a {@link com.ibm.asyncutil.examples.nio.Locks.Requester} that allows clients to 27 | * make {@link #intRequest(int) intRequests} until {@link #close()} is called 28 | */ 29 | static class CloseableRequester implements AsyncCloseable { 30 | private final AsyncEpoch epoch; 31 | private final Locks.Requester requester; 32 | private final AsynchronousSocketChannel channel; 33 | 34 | CloseableRequester(final AsynchronousSocketChannel channel) { 35 | this.channel = channel; 36 | this.requester = new Locks.Requester(channel); 37 | this.epoch = AsyncEpoch.newEpoch(); 38 | } 39 | 40 | /** 41 | * Send a request to the server if {@code this} requester has not yet been closed 42 | * 43 | * @param i the request to send 44 | * @return a present stage that will complete with the server's response, or empty if the 45 | * requester has already been closed 46 | */ 47 | Optional> intRequest(final int i) { 48 | return this.epoch 49 | .enter() 50 | .map(epochToken -> this.requester 51 | .intRequest(i) 52 | .whenComplete((r, ex) -> epochToken.close())); 53 | } 54 | 55 | /** 56 | * Forbid any new {@link #intRequest(int)} from starting 57 | * 58 | * @return A stage that will complete when all currently outstanding {@link #intRequest(int) 59 | * intRequests} have completed. 60 | */ 61 | @Override 62 | public CompletionStage close() { 63 | return this.epoch.terminate().thenAccept(closeSuccess -> { 64 | if (!closeSuccess) { 65 | System.out.println("close called multiple times"); 66 | return; 67 | } 68 | try { 69 | this.channel.close(); 70 | } catch (final IOException e) { 71 | throw new CompletionException(e); 72 | } 73 | }); 74 | } 75 | } 76 | 77 | public static void main(final String[] args) throws IOException { 78 | final SocketAddress addr = Locks.setupServer(); 79 | 80 | NioBridge 81 | .connect(addr) 82 | .thenCompose(connectedChannel -> { 83 | CloseableRequester requester = new CloseableRequester(connectedChannel); 84 | 85 | // send a request while the requester is open 86 | requester.intRequest(1).get().thenAccept(response -> { 87 | System.out.println(response); 88 | }); 89 | 90 | // close the requester 91 | CompletionStage closeStage = requester.close(); 92 | 93 | // won't be able to send any more requests 94 | if (requester.intRequest(2).isPresent()) { 95 | throw new IllegalStateException("impossible!"); 96 | } 97 | 98 | return closeStage; 99 | }) 100 | 101 | // wait for close to finish 102 | .toCompletableFuture().join(); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/FairAsyncLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 13 | 14 | 15 | /** 16 | * An {@link AsyncLock} which enforces fairness in its acquisition ordering 17 | * 18 | * @author Renar Narubin 19 | */ 20 | public class FairAsyncLock implements AsyncLock { 21 | /* 22 | * Each attempt to acquire the lock is assigned a unique node in the backing FIFO ordered queue. 23 | * The head node is always unclaimed; when a participant attempts to acquire the lock, it installs 24 | * a new node to the head and claims the node that was just replaced by this process. Each node 25 | * holds a future which is triggered when the preceding node's `releaseLock` method has been 26 | * called (excluding the initial node, whose future is complete during construction of the lock). 27 | * 28 | * The current holder of the lock -- the assignee of the node with a complete future and not yet 29 | * called `releaseLock` -- holds the implicit tail of the queue. When release is called on this 30 | * node, the node unlinks itself from the queue and triggers its successor's future, allowing the 31 | * next waiter to proceed 32 | */ 33 | 34 | private static final Node NULL_PREDECESSOR = new Node(); 35 | private static final AtomicReferenceFieldUpdater UPDATER = 36 | AtomicReferenceFieldUpdater.newUpdater(FairAsyncLock.class, Node.class, "head"); 37 | private volatile Node head; 38 | 39 | public FairAsyncLock() { 40 | final Node n = new Node(); 41 | n.prev = NULL_PREDECESSOR; 42 | 43 | // the first lock is immediately available 44 | n.complete(n); 45 | 46 | this.head = n; 47 | } 48 | 49 | private static final class Node extends CompletableFuture 50 | implements LockToken { 51 | Node next; 52 | Node prev; 53 | private Thread releaseThread; 54 | 55 | @Override 56 | public void releaseLock() { 57 | // see FairAsyncReadWriteLock for an explanation of the unrolling code 58 | if (this.next == null) { 59 | throw new IllegalStateException("released lock not in locked state"); 60 | } 61 | 62 | this.releaseThread = Thread.currentThread(); 63 | if (this.releaseThread.equals(this.prev.releaseThread)) { 64 | // unlink nested node from queue 65 | this.prev.next = this.next; 66 | this.next.prev = this.prev; 67 | this.next = null; // break chain 68 | } else { 69 | do { 70 | final Node n = this.next; 71 | this.next = null; 72 | n.complete(n); 73 | // recursive calls will set our next to non-null 74 | } while (this.next != null); 75 | } 76 | 77 | this.releaseThread = null; 78 | this.prev = null; // break chains for gc purposes 79 | } 80 | 81 | } 82 | 83 | @Override 84 | public CompletionStage acquireLock() { 85 | final Node newHead = new Node(); 86 | final Node oldHead = UPDATER.getAndSet(this, newHead); 87 | 88 | newHead.prev = oldHead; 89 | oldHead.next = newHead; 90 | return oldHead; 91 | } 92 | 93 | // Node is AutoCloseable, but here is not acquired so should not be released 94 | @SuppressWarnings("resource") 95 | @Override 96 | public Optional tryLock() { 97 | final Node oldHead = this.head; 98 | final Node newHead; 99 | // if the head is unlocked (future is done) 100 | // and no one else locks (the CAS succeeds) 101 | if (oldHead.isDone() && UPDATER.compareAndSet(this, oldHead, newHead = new Node())) { 102 | newHead.prev = oldHead; 103 | oldHead.next = newHead; 104 | return Optional.of(oldHead); 105 | } else { 106 | return Optional.empty(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/examples/nio/Locks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.examples.nio; 8 | 9 | import java.io.IOException; 10 | import java.net.SocketAddress; 11 | import java.nio.channels.AsynchronousServerSocketChannel; 12 | import java.nio.channels.AsynchronousSocketChannel; 13 | import java.util.Collection; 14 | import java.util.List; 15 | import java.util.concurrent.CompletionStage; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.IntStream; 18 | 19 | import com.ibm.asyncutil.iteration.AsyncIterator; 20 | import com.ibm.asyncutil.locks.AsyncLock; 21 | import com.ibm.asyncutil.util.Combinators; 22 | import com.ibm.asyncutil.util.StageSupport; 23 | 24 | /** 25 | * Example showing usage for {@link AsyncLock} 26 | */ 27 | public class Locks { 28 | 29 | /** 30 | * An object that can be used to make {@link #intRequest(int) intRequests} into an 31 | * {@link AsynchronousSocketChannel}, ensuring that subsequent requests are not sent until the 32 | * response for the previous request was received 33 | */ 34 | static class Requester { 35 | private final AsyncLock lock; 36 | private final AsynchronousSocketChannel connectedChannel; 37 | 38 | Requester(final AsynchronousSocketChannel channel) { 39 | this.lock = AsyncLock.create(); 40 | this.connectedChannel = channel; 41 | } 42 | 43 | /** 44 | * Send a request to the server 45 | * 46 | * @param i the request to send 47 | * @return a stage that will complete with the server's response 48 | */ 49 | CompletionStage intRequest(final int i) { 50 | // acquire the lock, unconditionally closing the token after the stage we produced completes 51 | return StageSupport.tryComposeWith(this.lock.acquireLock(), token -> NioBridge 52 | 53 | // write the int into the channel 54 | .writeInt(this.connectedChannel, i) 55 | 56 | // read the int response from the server 57 | .thenCompose(ignore -> NioBridge.readInt(this.connectedChannel))); 58 | } 59 | } 60 | 61 | /** 62 | * Setup a server that will accept a connection from a single client, and then respond to every 63 | * request sent by the client by incrementing the request by one. 64 | * 65 | * @return the {@link SocketAddress} of the created server 66 | * @throws IOException 67 | */ 68 | static SocketAddress setupServer() throws IOException { 69 | final AsynchronousServerSocketChannel server = 70 | AsynchronousServerSocketChannel.open().bind(null); 71 | 72 | final SocketAddress addr = server.getLocalAddress(); 73 | 74 | NioBridge.accept(server).thenAccept(channel -> { 75 | AsyncIterator 76 | .generate(() -> NioBridge.readInt(channel)) 77 | .thenCompose(clientIntRequest -> NioBridge.writeInt(channel, clientIntRequest + 1)) 78 | .consume() 79 | .whenComplete((ignore, ex) -> { 80 | System.out.println("connection closed, " + ex.getMessage()); 81 | }); 82 | }); 83 | return addr; 84 | } 85 | 86 | public static void main(final String[] args) throws IOException { 87 | final SocketAddress addr = setupServer(); 88 | 89 | final Collection responses = NioBridge.connect(addr) 90 | .thenCompose(connectedChannel -> { 91 | final Requester requester = new Requester(connectedChannel); 92 | 93 | // send 5 requests using the requester object. The requester object will internally make 94 | // sure that these are sent sequentially and request/responses are not mixed. 95 | final List> stages = 96 | IntStream 97 | .range(0, 5) 98 | .mapToObj(i -> requester.intRequest(i)) 99 | .collect(Collectors.toList()); 100 | return Combinators.collect(stages); 101 | }).toCompletableFuture().join(); 102 | 103 | System.out.println(responses); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /asyncutil-flow/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.ibm.async 7 | asyncutil-aggregator 8 | 0.2.0-SNAPSHOT 9 | 10 | 11 | com.ibm.async 12 | asyncutil-flow 13 | 0.2.0-SNAPSHOT 14 | jar 15 | 16 | asyncutil-flow 17 | Utilities for working with asyncutil and Flow 18 | http://github.com/ibm/java-async-util 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | 25 | 26 | 27 | 28 | 29 | Ravi Khadiwala 30 | rkhadiwa@us.ibm.com 31 | IBM 32 | http://www.ibm.com 33 | 34 | 35 | Renar Narubin 36 | rnarubin@us.ibm.com 37 | IBM 38 | http://www.ibm.com 39 | 40 | 41 | 42 | 43 | scm:git:git://github.com/ibm/java-async-util.git 44 | scm:git:ssh://github.com:ibm/java-async-util.git 45 | http://github.com/ibm/java-async-util 46 | 47 | 48 | 49 | UTF-8 50 | 1.9 51 | 1.9 52 | 53 | 54 | 55 | 56 | 57 | maven-surefire-plugin 58 | 59 | 1 60 | 61 | 2.20.1 62 | 63 | 64 | org.apache.maven.surefire 65 | surefire-junit47 66 | 2.20.1 67 | 68 | 69 | org.apache.maven.surefire 70 | surefire-testng 71 | 2.20.1 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-javadoc-plugin 83 | 2.10.4 84 | 85 | 86 | 87 | 88 | 89 | 90 | com.ibm.async 91 | asyncutil 92 | 0.2.0-SNAPSHOT 93 | 94 | 95 | junit 96 | junit 97 | 4.11 98 | test 99 | 100 | 101 | org.testng 102 | testng 103 | 6.8 104 | test 105 | 106 | 107 | org.reactivestreams 108 | reactive-streams-tck-flow 109 | 1.0.2 110 | test 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/examples/nio/MultiProducerIteration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.examples.nio; 8 | 9 | import static com.ibm.asyncutil.examples.nio.NioBridge.accept; 10 | import static com.ibm.asyncutil.examples.nio.NioBridge.connect; 11 | import static com.ibm.asyncutil.examples.nio.NioBridge.readInt; 12 | 13 | import java.io.IOException; 14 | import java.net.SocketAddress; 15 | import java.nio.channels.AsynchronousServerSocketChannel; 16 | import java.nio.channels.AsynchronousSocketChannel; 17 | import java.util.concurrent.CompletionStage; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.IntStream; 20 | 21 | import com.ibm.asyncutil.iteration.AsyncIterator; 22 | import com.ibm.asyncutil.iteration.AsyncQueue; 23 | import com.ibm.asyncutil.iteration.AsyncQueues; 24 | import com.ibm.asyncutil.util.Combinators; 25 | 26 | /** 27 | * Example showing the use of {@link AsyncIterator multi-producer-single-consumer async queues} 28 | */ 29 | public class MultiProducerIteration { 30 | 31 | /** 32 | * Given an AsyncIterator of {@link AsynchronousSocketChannel} representing connected clients, 33 | * return an AsyncIterator of messages from those clients, in whatever order they happen to arrive 34 | * 35 | * @param clientConnections An {@link com.ibm.asyncutil.iteration.AsyncIterator} of connected 36 | * {@link AsynchronousSocketChannel sockets} 37 | * @return An {@link AsyncIterator} of messages from all clients in {@code clientConnections} 38 | */ 39 | static AsyncIterator routeClientMessages( 40 | final AsyncIterator clientConnections) { 41 | 42 | // we'll collect the results of all connections into this queue 43 | final AsyncQueue results = AsyncQueues.unbounded(); 44 | 45 | clientConnections 46 | .thenApply(socketChannel -> AsyncIterator 47 | 48 | // read ints from client one at a time 49 | .generate(() -> readInt(socketChannel)) 50 | 51 | // stop when the client sends -1 52 | .takeWhile(i -> i != -1) 53 | 54 | // put each result into results as they arrive 55 | .forEach(results::send)) 56 | 57 | // get a stage that completes with stages for each connection's routing task 58 | .collect(Collectors.toList()) 59 | 60 | // returns a stage that completes when -1 has been returned on all connections 61 | .thenCompose(fillingCompleteStages -> Combinators.allOf(fillingCompleteStages)) 62 | 63 | // when we've connected to 4 clients and either read to -1 or hit an IOException on all 4 of 64 | // them, terminate our results queue 65 | .whenComplete((t, ex) -> results.terminate()); 66 | 67 | return results; 68 | 69 | } 70 | 71 | public static void main(final String[] args) throws IOException { 72 | final AsynchronousServerSocketChannel server = 73 | AsynchronousServerSocketChannel.open().bind(null); 74 | 75 | final SocketAddress addr = server.getLocalAddress(); 76 | 77 | // on the client side, concurrently connect to addr 4 times, and write 100 random integers on 78 | // each connection 79 | final CompletionStage writeStage = Combinators.allOf(IntStream 80 | .range(0, 4) 81 | .mapToObj(i -> connect(addr) 82 | .thenComposeAsync(channel -> Iteration.write100Randoms(channel))) 83 | .collect(Collectors.toList())) 84 | .thenApply(ig -> null); 85 | 86 | 87 | // on the server side, we'd like to accept 4 connections and route their messages into a single 88 | // place we can consume 89 | final AsyncIterator clientConnections = AsyncIterator 90 | 91 | // listen for next connection 92 | .generate(() -> accept(server)) 93 | 94 | // only will take 4 connections 95 | .take(4); 96 | final AsyncIterator results = routeClientMessages(clientConnections); 97 | 98 | 99 | // do something with the results! - print each result as it comes from each client 100 | final CompletionStage printStage = results.forEach(i -> System.out.println(i)); 101 | 102 | // wait for both the clients and the server/printing to complete 103 | writeStage.thenAcceptBoth(printStage, (ig1, ig2) -> { 104 | System.out.println("completed successfully"); 105 | }); 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.ibm.async 6 | asyncutil-aggregator 7 | 0.2.0-SNAPSHOT 8 | pom 9 | 10 | asyncutil-aggregator 11 | Utilities for working with CompletionStages 12 | http://github.com/ibm/java-async-util 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | 23 | Ravi Khadiwala 24 | rkhadiwa@us.ibm.com 25 | IBM 26 | http://www.ibm.com 27 | 28 | 29 | Renar Narubin 30 | rnarubin@us.ibm.com 31 | IBM 32 | http://www.ibm.com 33 | 34 | 35 | 36 | 37 | scm:git:git://github.com/ibm/java-async-util.git 38 | scm:git:ssh://github.com:ibm/java-async-util.git 39 | http://github.com/ibm/java-async-util 40 | 41 | 42 | 43 | 44 | ossrh 45 | https://oss.sonatype.org/content/repositories/snapshots 46 | 47 | 48 | ossrh 49 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 50 | 51 | 52 | 53 | 54 | 55 | release 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-source-plugin 61 | 2.2.1 62 | 63 | 64 | attach-sources 65 | 66 | jar-no-fork 67 | 68 | 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-javadoc-plugin 74 | 2.10.4 75 | 76 | 77 | attach-javadocs 78 | 79 | jar 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-gpg-plugin 87 | 1.5 88 | 89 | 90 | sign-artifacts 91 | verify 92 | 93 | sign 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-javadoc-plugin 108 | 2.10.4 109 | 110 | 111 | 112 | 113 | 114 | asyncutil 115 | asyncutil-flow 116 | 117 | 118 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncEpoch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | /** 13 | * A concurrency mechanism which maintains a period of activity -- an epoch -- during which 14 | * participants may successfully enter it, that later can be terminated. After an epoch is 15 | * terminated, new entrants are rejected from entering but the terminator waits for any remaining 16 | * participants to exit. 17 | * 18 | * For example, to allow updates to a protected resource until the resource is closed: 19 | * 20 | *

 21 |  * {@code
 22 |  * AsyncEpoch epoch = AsyncEpoch.newEpoch();
 23 |  * Resource r = ...
 24 |  *
 25 |  * // returns true if write succeeded
 26 |  * boolean write(Data data) {
 27 |  *     epoch.enter().map(token -> {
 28 |  *         try {
 29 |  *             r.write(data);
 30 |  *             return true;
 31 |  *         } finally {
 32 |  *             token.close();
 33 |  *         }
 34 |  *     }).orElse(false);
 35 |  * }
 36 |  *
 37 |  * // stage completes when all outstanding writes are finished
 38 |  * CompletionStage close() { return epoch.terminate(); }
 39 |  * }
 40 |  * 
41 | * 42 | * @author Renar Narubin 43 | */ 44 | public interface AsyncEpoch { 45 | 46 | /** 47 | * Attempts to secure a position in the active epoch, failing if it has already been terminated. 48 | * 49 | * @return an {@link Optional} populated with an {@link EpochToken} associated with the active 50 | * epoch if it has not been terminated. Otherwise, returns an empty Optional 51 | */ 52 | Optional enter(); 53 | 54 | /** 55 | * Atomically ends the active epoch, preventing new entrants from successfully entering and 56 | * returning a {@link CompletionStage} that triggers once all active participants have exited. 57 | * 58 | * @return a {@link CompletionStage}, which will complete after the last open {@link EpochToken} 59 | * has been closed. The returned stage will complete with {@code false} if this epoch was 60 | * already terminated, otherwise {@code true} for the single call that terminates this 61 | * epoch 62 | */ 63 | CompletionStage terminate(); 64 | 65 | /** 66 | * Returns {@code true} if this epoch has been terminated. This boolean does not indicate 67 | * whether all active participants have exited the epoch, only whether the {@link #terminate()} 68 | * method has been called and subsequent entrants will be rejected 69 | * 70 | * @return true iff this epoch has been terminated 71 | */ 72 | boolean isTerminated(); 73 | 74 | /** 75 | * Waits for the epoch to complete, returning a stage that completes after the epoch has been 76 | * {@link #terminate() terminated} and all participants have exited. Note that this method does 77 | * not terminate the epoch itself -- new entrants may enter and exit freely after this 78 | * method is called, and a separate call to {@link #terminate()} must be made before this returned 79 | * stage completes 80 | * 81 | * @return a {@link CompletionStage} which will complete after a call to {@link #terminate()} has 82 | * been made, and the last open {@link EpochToken} has been closed. 83 | */ 84 | CompletionStage awaitCompletion(); 85 | 86 | /** 87 | * An epoch that works well when most accesses come from the same thread. 88 | * 89 | * @return an epoch with a lightweight memory footprint 90 | */ 91 | static AsyncEpoch newUncontendedEpoch() { 92 | return new AsyncEpochImpl(); 93 | } 94 | 95 | /** 96 | * @return an epoch that works well with many concurrent calls to {@link #enter()} 97 | */ 98 | static AsyncEpoch newContendedEpoch() { 99 | return new StripedEpoch(); 100 | } 101 | 102 | /** 103 | * @return a new {@link AsyncEpoch} instance 104 | */ 105 | static AsyncEpoch newEpoch() { 106 | return new StripedEpoch(); 107 | } 108 | 109 | /** 110 | * @return a new {@link AsyncEpoch} instance which is already terminated. 111 | */ 112 | static AsyncEpoch newTerminatedEpoch() { 113 | return TerminatedEpoch.INSTANCE; 114 | } 115 | 116 | /** 117 | * A token signifying successful entry in the active epoch. This token must be 118 | * {@link EpochToken#close() closed} when its work has completed in order to exit the epoch. 119 | * 120 | * @see AsyncEpoch 121 | */ 122 | public interface EpochToken extends AutoCloseable { 123 | /** 124 | * Exits the epoch that was previously entered. 125 | */ 126 | @Override 127 | void close(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/util/TestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.CompletionException; 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.concurrent.ExecutionException; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.TimeoutException; 15 | import java.util.function.Function; 16 | import java.util.function.Supplier; 17 | import java.util.stream.Stream; 18 | 19 | public class TestUtil { 20 | 21 | public static T join(final CompletionStage future) { 22 | return future.toCompletableFuture().join(); 23 | } 24 | 25 | public static T join(final CompletionStage future, final long time, 26 | final TimeUnit timeUnit) throws TimeoutException { 27 | try { 28 | return future.toCompletableFuture().get(time, timeUnit); 29 | } catch (InterruptedException | ExecutionException e) { 30 | throw new CompletionException(e); 31 | } 32 | } 33 | 34 | /** 35 | * A {@link CompletionStage} wrapping interface which allows completing, and querying the result 36 | * of, a completion stage. 37 | *

38 | * This interface is useful in unit tests to generalize over both {@link CompletableFuture} and 39 | * the validation-oriented {@link SimpleCompletionStage} 40 | * 41 | * @param 42 | */ 43 | public interface CompletableStage extends CompletionStage { 44 | boolean complete(T t); 45 | 46 | boolean completeExceptionally(Throwable t); 47 | 48 | boolean isDone(); 49 | 50 | default T join() { 51 | return toCompletableFuture().join(); 52 | } 53 | 54 | default T join(final long time, final TimeUnit timeUnit) throws TimeoutException { 55 | try { 56 | return toCompletableFuture().get(time, timeUnit); 57 | } catch (InterruptedException | ExecutionException e) { 58 | throw new CompletionException(e); 59 | } 60 | } 61 | } 62 | 63 | private static class CompletableFutureAsCompletableStage extends CompletableFuture 64 | implements CompletableStage { 65 | } 66 | 67 | /** 68 | * @return suppliers of the possible implementations of {@link CompletableStage} 69 | */ 70 | public static Stream>> stageFactories() { 71 | return Stream.of( 72 | new Supplier>() { 73 | @Override 74 | public CompletableStage get() { 75 | return new CompletableFutureAsCompletableStage<>(); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "CompletableFuture factory"; 81 | } 82 | }, 83 | new Supplier>() { 84 | 85 | @Override 86 | public CompletableStage get() { 87 | return new SimpleCompletionStage<>(); 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "SimpleCompletionStage factory"; 93 | } 94 | }); 95 | } 96 | 97 | /** 98 | * @return functions of the possible implementations of already-completed {@link CompletionStage} 99 | */ 100 | public static Stream, CompletionStage>> doneStageFactories() { 101 | return Stream., CompletionStage>>concat( 102 | Stream.of( 103 | new Function, CompletionStage>() { 104 | 105 | @Override 106 | public CompletionStage apply(final Either et) { 107 | return et.fold(StageSupport::exceptionalStage, StageSupport::completedStage); 108 | } 109 | 110 | @Override 111 | public String toString() { 112 | return "StageSupport factory"; 113 | } 114 | }), 115 | TestUtil.stageFactories() 116 | ., CompletionStage>>map( 117 | factory -> new Function, CompletionStage>() { 118 | @Override 119 | public CompletionStage apply(final Either et) { 120 | final CompletableStage c = factory.get(); 121 | et.forEach(c::completeExceptionally, c::complete); 122 | return c; 123 | } 124 | 125 | @Override 126 | public String toString() { 127 | return "completed " + factory.toString(); 128 | } 129 | })); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/iteration/AsyncTrampolineTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.iteration; 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.CompletionException; 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.concurrent.Executor; 13 | import java.util.concurrent.Executors; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | import java.util.stream.IntStream; 17 | 18 | import org.junit.Assert; 19 | import org.junit.Test; 20 | 21 | import com.ibm.asyncutil.util.StageSupport; 22 | 23 | 24 | public class AsyncTrampolineTest { 25 | 26 | @Test 27 | public void testExceptionalStackUnroll() { 28 | final AtomicInteger sum = new AtomicInteger(); 29 | final int breakPoint = 1000000; // enough to cause StackOverflow if broken 30 | 31 | final CompletionStage sumFuture = AsyncTrampoline.asyncWhile( 32 | c -> c < breakPoint, 33 | c -> { 34 | sum.addAndGet(c); 35 | return StageSupport.completedStage(c + 1); 36 | }, 37 | 0); 38 | final int expected = IntStream.range(0, breakPoint).sum(); 39 | sumFuture.toCompletableFuture().join(); 40 | 41 | Assert.assertEquals(expected, sum.get()); 42 | } 43 | 44 | @Test 45 | public void testThreadsABA() throws Exception { 46 | final Executor execA = Executors.newSingleThreadExecutor(); 47 | final Thread threadA = CompletableFuture.supplyAsync(() -> Thread.currentThread(), execA).get(); 48 | 49 | final Executor execB = Executors.newSingleThreadExecutor(); 50 | final Thread threadB = CompletableFuture.supplyAsync(() -> Thread.currentThread(), execB).get(); 51 | 52 | final int sum = CompletableFuture.supplyAsync( 53 | () -> AsyncTrampoline.asyncWhile( 54 | c -> c < 3, 55 | c -> { 56 | final CompletableFuture future = new CompletableFuture<>(); 57 | final Thread currentThread = Thread.currentThread(); 58 | if (currentThread.equals(threadA)) { 59 | // switch to B 60 | execB.execute(() -> future.complete(c + 1)); 61 | } else { 62 | Assert.assertEquals(threadB, currentThread); 63 | // switch back to A 64 | execA.execute(() -> future.complete(c + 1)); 65 | } 66 | return future; 67 | }, 68 | 0), 69 | execA).get(10, TimeUnit.SECONDS).toCompletableFuture().get(10, TimeUnit.SECONDS); 70 | 71 | Assert.assertEquals(3, sum); 72 | } 73 | 74 | @Test 75 | public void testThreadsAA() throws Exception { 76 | final Executor execA = Executors.newSingleThreadExecutor(); 77 | 78 | final int sum = CompletableFuture.supplyAsync( 79 | () -> AsyncTrampoline.asyncWhile( 80 | c -> c < 3, 81 | c -> { 82 | final CompletableFuture future = new CompletableFuture<>(); 83 | execA.execute(() -> future.complete(c + 1)); 84 | return future; 85 | }, 86 | 0), 87 | execA).get(5, TimeUnit.SECONDS).toCompletableFuture().get(5, TimeUnit.SECONDS); 88 | 89 | Assert.assertEquals(3, sum); 90 | } 91 | 92 | @Test 93 | public void testVoidUnroll() { 94 | final AtomicInteger count = new AtomicInteger(); 95 | Assert.assertEquals( 96 | null, 97 | AsyncTrampoline.asyncWhile( 98 | ig -> count.incrementAndGet() < 5, 99 | ig -> StageSupport.voidStage(), 100 | null).toCompletableFuture().join()); 101 | } 102 | 103 | @Test 104 | public void testNullIntermediateValue() { 105 | Assert.assertEquals( 106 | AsyncTrampoline.asyncWhile( 107 | i -> i != null && i < 10, 108 | i -> { 109 | Integer next; 110 | if (i == null) { 111 | next = 5; 112 | } else if (i == 4) { 113 | next = null; 114 | } else { 115 | next = i + 1; 116 | } 117 | return StageSupport.completedStage(next); 118 | }, 119 | 0).toCompletableFuture().join(), 120 | null); 121 | } 122 | 123 | @Test 124 | public void testNullInitialValue() { 125 | Assert.assertEquals( 126 | AsyncTrampoline.asyncWhile( 127 | i -> i == null || i < 5, 128 | i -> StageSupport.completedStage(i == null ? 0 : i + 1), 129 | null).toCompletableFuture().join().intValue(), 130 | 5); 131 | } 132 | 133 | @Test(expected = NullPointerException.class) 134 | public void testNullBooleanThrowsException() throws Throwable { 135 | try { 136 | AsyncTrampoline.asyncWhile(() -> null).toCompletableFuture().join(); 137 | } catch (CompletionException e) { 138 | throw e.getCause(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/PlatformDependent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.lang.reflect.Field; 10 | import java.security.AccessController; 11 | import java.security.PrivilegedAction; 12 | 13 | import sun.misc.Unsafe; 14 | 15 | /** 16 | * A utility class which provides certain functionality that may not be portable (e.g. 17 | * {@link sun.misc.Unsafe}). Provides automatic fallback to portable alternatives when necessary 18 | * 19 | * @author Renar Narubin 20 | */ 21 | @SuppressWarnings("restriction") 22 | final class PlatformDependent { 23 | private PlatformDependent() {} 24 | 25 | // TODO(java9) VarHandles are supposed to provide fences without accessing Unsafe 26 | 27 | /** 28 | * @see sun.misc.Unsafe#loadFence() 29 | * @return true iff this method is supported 30 | */ 31 | public static boolean loadFence() { 32 | return AlternativeHolder.UNSAFE_PROVIDER.loadFence(); 33 | } 34 | 35 | /** 36 | * @see sun.misc.Unsafe#storeFence() 37 | * @return true iff this method is supported 38 | */ 39 | public static boolean storeFence() { 40 | return AlternativeHolder.UNSAFE_PROVIDER.storeFence(); 41 | } 42 | 43 | /** 44 | * @see sun.misc.Unsafe#fullFence() 45 | * @return true iff this method is supported 46 | */ 47 | public static boolean fullFence() { 48 | return AlternativeHolder.UNSAFE_PROVIDER.fullFence(); 49 | } 50 | 51 | private interface UnsafeProvider { 52 | public boolean loadFence(); 53 | 54 | public boolean storeFence(); 55 | 56 | public boolean fullFence(); 57 | } 58 | 59 | private static UnsafeProvider safeJavaImpl() { 60 | return AlternativeHolder.PureJavaAlternative.INSTANCE; 61 | } 62 | 63 | /** 64 | * Provides alternatives implementations of a particular platform dependent facility. Uses 65 | * reflection to gracefully fall back to the portable Java implementation if the respective 66 | * platform-dependent one isn't available. 67 | */ 68 | private static class AlternativeHolder { 69 | static final String UNSAFE_ALTERNATIVE_NAME = AlternativeHolder.class.getName() 70 | + "$UnsafeAlternative"; 71 | 72 | /** 73 | * The provider for methods which use {@link sun.misc.Unsafe} (or java fallbacks) 74 | */ 75 | static final UnsafeProvider UNSAFE_PROVIDER = AlternativeHolder.getUnsafeProvider(); 76 | 77 | /** 78 | * Returns the Unsafe-using Comparer, or falls back to the pure-Java implementation if unable to 79 | * do so. 80 | */ 81 | static UnsafeProvider getUnsafeProvider() { 82 | try { 83 | final Class theClass = Class.forName(UNSAFE_ALTERNATIVE_NAME); 84 | 85 | final UnsafeProvider comparer = (UnsafeProvider) theClass.getEnumConstants()[0]; 86 | return comparer; 87 | } catch (final Throwable t) { // ensure we really catch *everything* 88 | return PlatformDependent.safeJavaImpl(); 89 | } 90 | } 91 | 92 | private enum PureJavaAlternative 93 | implements UnsafeProvider { 94 | INSTANCE; 95 | 96 | @Override 97 | public boolean loadFence() { 98 | return false; 99 | } 100 | 101 | @Override 102 | public boolean storeFence() { 103 | return false; 104 | } 105 | 106 | @Override 107 | public boolean fullFence() { 108 | return false; 109 | } 110 | } 111 | 112 | @SuppressWarnings({"unused"}) 113 | // used via reflection 114 | private enum UnsafeAlternative 115 | implements UnsafeProvider { 116 | INSTANCE; 117 | 118 | private static final Unsafe unsafe; 119 | 120 | static { 121 | unsafe = (Unsafe) AccessController.doPrivileged(new PrivilegedAction() { 122 | @Override 123 | public Object run() { 124 | try { 125 | final Field f = Unsafe.class.getDeclaredField("theUnsafe"); 126 | f.setAccessible(true); 127 | return f.get(null); 128 | } catch (final NoSuchFieldException e) { 129 | // It doesn't matter what we throw; 130 | // it's swallowed in getBestComparer(). 131 | throw new Error(e); 132 | } catch (final IllegalAccessException e) { 133 | throw new Error(e); 134 | } 135 | } 136 | }); 137 | 138 | // sanity check - this should never fail 139 | if (unsafe.arrayIndexScale(byte[].class) != 1) { 140 | throw new AssertionError(); 141 | } 142 | } 143 | 144 | @Override 145 | public boolean loadFence() { 146 | unsafe.loadFence(); 147 | return true; 148 | } 149 | 150 | @Override 151 | public boolean storeFence() { 152 | unsafe.storeFence(); 153 | return true; 154 | } 155 | 156 | @Override 157 | public boolean fullFence() { 158 | unsafe.fullFence(); 159 | return true; 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncStampedLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | /** 10 | * An asynchronously acquirable read-write lock which additionally provides an optimistic read mode. 11 | * Optimistic read mode can be thought of as a weak reader mode which does not prevent writers from 12 | * acquiring the lock. 13 | * 14 | *

15 | * In practice, optimistic reads of brief read-only sections can reduce memory contention. Great 16 | * care must be taken when reading mutable variables, however, as they can change at any point and 17 | * knowledge of the underlying accessed structures is necessary to ensure a consistent view of the 18 | * variables. 19 | * 20 | *

21 | * Consider the following example. The BitSet class is not inherently thread-safe, so we need a lock 22 | * to protect it. Further, our hypothetical use case exhibits only rare modifications but extremely 23 | * frequent queries, which may justify an optimistic read design: 24 | * 25 | *

 26 |  * {@code
 27 |  * BitSet bits = new BitSet();
 28 |  *
 29 |  * CompletionStage isSet(int idx) {
 30 |  *   // first attempt to optimistically read; if it fails,
 31 |  *   // fall back to full read lock
 32 |  *   final Stamp stamp = this.lock.tryOptimisticRead();
 33 |  *
 34 |  *   // lock may already be held, first check the stamp
 35 |  *   if (stamp != null) {
 36 |  *
 37 |  *     // BitSet is internally "safe" to concurrent modification during reads
 38 |  *     // i.e. structures cannot be corrupted, but results may be inconsistent
 39 |  *     // for example these individual bits might be separately modified
 40 |  *     final boolean optimistic = this.bits.get(idx) && this.bits.get(idx+1);
 41 |  *
 42 |  *     // the read value can only be considered correct if
 43 |  *     // the write lock was not acquired in the interim
 44 |  *     if (stamp.validate()) {
 45 |  *       return StageSupport.completedStage(optimistic);
 46 |  *     }
 47 |  *   }
 48 |  *
 49 |  *   // otherwise, if the write lock was already held, or acquired afterwards,
 50 |  *   // acquire the full read lock for a consistent result (fall back from optimism)
 51 |  *   return this.lock.acquireReadLock().thenApply(lockToken -> {
 52 |  *     try {
 53 |  *       return this.bits.get(idx) && this.bits.get(idx+1);
 54 |  *     } finally {
 55 |  *       lockToken.releaseLock();
 56 |  *     }
 57 |  *   }
 58 |  * }
 59 |  * }
 60 |  * 
61 | * 62 | *

63 | * This interface draws inspiration from the standard library's 64 | * {@link java.util.concurrent.locks.StampedLock} but certain implementation details may differ. 65 | * Identical behavior should not be expected from both locking facilities beyond what is explicitly 66 | * documented. 67 | * 68 | *

69 | * Implementations will specify whether their lock acquisition is fair or not; this interface does 70 | * not define this requirement. 71 | * 72 | * @author Renar Narubin 73 | * @see AsyncReadWriteLock 74 | * @see java.util.concurrent.locks.StampedLock 75 | */ 76 | public interface AsyncStampedLock extends AsyncReadWriteLock { 77 | 78 | /** 79 | * Attempts to acquire a {@link Stamp} in optimistic-read mode if the lock is not already 80 | * write-locked. The stamp may subsequently be {@link Stamp#validate() validated} to check whether 81 | * the write lock has been acquired 82 | * 83 | * @return a non-null Stamp if the lock is not currently write-locked. Otherwise, returns {@code 84 | * null} 85 | */ 86 | // API note: why a nullable instead of an Optional like the similar try*Lock methods? 87 | // a few reasons: 88 | // 89 | // - the common use case of tryOptimistic -> check null -> validate -> fall back to readlock is 90 | // cumbersome with Optional chaining compared to if-blocks 91 | // - tryOptimisticRead is fundamentally intended for high-performance areas; an additional 92 | // object wrapper is undesirable 93 | Stamp tryOptimisticRead(); 94 | 95 | /** 96 | * An object indicating a successful optimistic read attempt. 97 | * 98 | * @see AsyncStampedLock#tryOptimisticRead() 99 | */ 100 | interface Stamp { 101 | 102 | /** 103 | * Checks whether the associated lock's write mode has been acquired in the time after this stamp 104 | * was issued. 105 | * 106 | * @return true iff the stamp is still valid i.e. write lock has not been acquired since this 107 | * stamp was issued 108 | */ 109 | boolean validate(); 110 | } 111 | 112 | /** 113 | * Creates an {@link AsyncStampedLock} 114 | * 115 | *

116 | * The returned lock is only guaranteed to meet the requirements of {@link AsyncStampedLock}; in 117 | * particular, no guarantee of fairness is provided. 118 | * 119 | * @return a new {@link AsyncStampedLock} 120 | */ 121 | static AsyncStampedLock create() { 122 | // fair for now, may be swapped with a more performant unfair version later 123 | return new FairAsyncStampedLock(); 124 | } 125 | 126 | /** 127 | * Creates a fair {@link AsyncStampedLock} 128 | * 129 | * @return a new {@link AsyncStampedLock} with a fair implementation 130 | */ 131 | static AsyncStampedLock createFair() { 132 | return new FairAsyncStampedLock(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncReadWriteLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | import com.ibm.asyncutil.locks.AsyncLock.LockToken; 13 | 14 | /** 15 | * An asynchronously acquirable read-write lock. 16 | *

17 | * Implementations will specify whether their lock acquisition is fair or not; this interface does 18 | * not define this requirement. 19 | * 20 | * @author Renar Narubin 21 | */ 22 | public interface AsyncReadWriteLock { 23 | 24 | /** 25 | * Acquires this read lock. The returned stage will complete when the lock is no longer 26 | * exclusively held by a writer, and the read lock has been acquired. The stage may already be 27 | * complete if the write lock is not currently held. 28 | *

29 | * The {@link ReadLockToken} held by the returned stage is used to release the read lock after it 30 | * has been acquired and the read-lock-protected action has completed. 31 | * 32 | * @return A {@link CompletionStage} which will complete with a {@link ReadLockToken} when the 33 | * read lock has been acquired 34 | */ 35 | CompletionStage acquireReadLock(); 36 | 37 | /** 38 | * Attempts to immediately acquire the read lock, returning a populated {@link Optional} if the 39 | * lock is not currently held by a writer. 40 | *

41 | * Implementations may define whether this attempt can succeed while a writer is waiting to 42 | * acquire ("barging"). This interface only requires that the attempt will succeed if all writers 43 | * have released and no new writers are acquiring. 44 | * 45 | * @return An {@link Optional} holding a {@link ReadLockToken} if the lock is not held by a 46 | * writer; otherwise an empty Optional 47 | */ 48 | Optional tryReadLock(); 49 | 50 | 51 | /** 52 | * Exclusively acquires this write lock. The returned stage will complete when the lock is no 53 | * longer held by any readers or another writer, and the write lock has been exclusively acquired. 54 | * The stage may already be complete if no locks are currently held. 55 | *

56 | * The {@link WriteLockToken} held by the returned stage is used to release the write lock after 57 | * it has been acquired and the write-lock-protected action has completed. 58 | * 59 | * @return A {@link CompletionStage} which will complete with a {@link WriteLockToken} when the 60 | * write lock has been exclusively acquired 61 | */ 62 | CompletionStage acquireWriteLock(); 63 | 64 | /** 65 | * Attempts to immediately acquire the write lock, returning a populated {@link Optional} if the 66 | * lock is not currently held by a writer or any readers. 67 | * 68 | * @return An {@link Optional} holding a {@link WriteLockToken} if the lock is not held by a 69 | * writer or any readers; otherwise an empty Optional 70 | */ 71 | Optional tryWriteLock(); 72 | 73 | /** 74 | * A lock token indicating that the associated lock has been acquired for reader access. Once the 75 | * protected action is completed, the lock may be released by calling 76 | * {@link ReadLockToken#releaseLock()} 77 | */ 78 | interface ReadLockToken extends LockToken { 79 | /** 80 | * Releases this read lock, possibly allowing writers to enter once all read locks have been 81 | * released. 82 | */ 83 | @Override 84 | void releaseLock(); 85 | } 86 | 87 | /** 88 | * A lock token indicating that the associated lock has been exclusively acquired for writer 89 | * access. Once the protected action is completed, the lock may be released by calling 90 | * {@link WriteLockToken#releaseLock()} 91 | */ 92 | interface WriteLockToken extends LockToken { 93 | /** 94 | * Releases this write lock, allowing readers or other writers to acquire it. 95 | */ 96 | @Override 97 | void releaseLock(); 98 | 99 | /** 100 | * Downgrades this write lock acquisition to a read lock acquisition without any intermediate 101 | * release. This may allow other waiting readers to proceed with their acquisitions. Other 102 | * writers, however, may not proceed until the returned read lock token (and any others that 103 | * become acquired) is released. 104 | *

105 | * The returned {@link ReadLockToken} becomes the principle token for this acquisition; this 106 | * calling WriteLockToken should not be released afterwards, and may be abandoned freely. 107 | * 108 | * @return a ReadLockToken representing read lock exclusivity on the lock 109 | */ 110 | ReadLockToken downgradeLock(); 111 | } 112 | 113 | /** 114 | * Creates an {@link AsyncReadWriteLock} 115 | * 116 | *

117 | * The returned lock is only guaranteed to meet the requirements of {@link AsyncReadWriteLock}; in 118 | * particular, no guarantee of fairness is provided. 119 | * 120 | * @return a new {@link AsyncReadWriteLock} 121 | */ 122 | static AsyncReadWriteLock create() { 123 | // fair for now, may be swapped with a more performant unfair version later 124 | return new FairAsyncReadWriteLock(); 125 | } 126 | 127 | /** 128 | * Creates a fair {@link AsyncReadWriteLock} 129 | * 130 | * @return a new {@link AsyncReadWriteLock} with a fair implementation 131 | */ 132 | static AsyncReadWriteLock createFair() { 133 | return new FairAsyncReadWriteLock(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/examples/nio/NioBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.examples.nio; 8 | 9 | import java.io.IOException; 10 | import java.net.SocketAddress; 11 | import java.nio.ByteBuffer; 12 | import java.nio.channels.AsynchronousServerSocketChannel; 13 | import java.nio.channels.AsynchronousSocketChannel; 14 | import java.nio.channels.CompletionHandler; 15 | import java.util.concurrent.CompletableFuture; 16 | import java.util.concurrent.CompletionStage; 17 | 18 | import com.ibm.asyncutil.util.StageSupport; 19 | 20 | /** 21 | * Static methods to convert the callback based {@link java.nio.channels.AsynchronousChannel} API to 22 | * one based on {@link CompletionStage} 23 | */ 24 | public class NioBridge { 25 | 26 | static CompletionStage accept( 27 | final AsynchronousServerSocketChannel server) { 28 | final CompletableFuture channelFuture = new CompletableFuture<>(); 29 | server.accept(channelFuture, 30 | new CompletionHandler>() { 31 | @Override 32 | public void completed(final AsynchronousSocketChannel result, 33 | final CompletableFuture attachment) { 34 | attachment.complete(result); 35 | } 36 | 37 | @Override 38 | public void failed(final Throwable exc, 39 | final CompletableFuture attachment) { 40 | attachment.completeExceptionally(exc); 41 | } 42 | }); 43 | return channelFuture; 44 | } 45 | 46 | static CompletionStage connect(final SocketAddress addr) { 47 | try { 48 | final AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(); 49 | return connect(channel, addr).thenApply(ig -> channel); 50 | } catch (final IOException e) { 51 | return StageSupport.exceptionalStage(e); 52 | } 53 | } 54 | 55 | static CompletionStage connect(final AsynchronousSocketChannel channel, 56 | final SocketAddress addr) { 57 | final CompletableFuture connectFuture = new CompletableFuture<>(); 58 | channel.connect(addr, connectFuture, new CompletionHandler>() { 59 | @Override 60 | public void completed(final Void result, final CompletableFuture attachment) { 61 | attachment.complete(null); 62 | } 63 | 64 | @Override 65 | public void failed(final Throwable exc, final CompletableFuture attachment) { 66 | attachment.completeExceptionally(exc); 67 | } 68 | }); 69 | return connectFuture; 70 | } 71 | 72 | static CompletionStage writeInt(final AsynchronousSocketChannel channel, 73 | final int toWrite) { 74 | final ByteBuffer buffer = ByteBuffer.allocate(4); 75 | buffer.putInt(toWrite); 76 | buffer.flip(); 77 | final CompletableFuture writeFuture = new CompletableFuture<>(); 78 | channel.write(buffer, writeFuture, 79 | new CompletionHandler>() { 80 | @Override 81 | public void completed(final Integer result, final CompletableFuture attachment) { 82 | if (result != 4) 83 | attachment.completeExceptionally(new IOException("write interrupted")); 84 | else 85 | attachment.complete(null); 86 | } 87 | 88 | @Override 89 | public void failed(final Throwable exc, final CompletableFuture attachment) { 90 | attachment.completeExceptionally(exc); 91 | } 92 | }); 93 | return writeFuture; 94 | } 95 | 96 | static CompletionStage readInt(final AsynchronousSocketChannel channel) { 97 | final ByteBuffer buf = ByteBuffer.allocate(4); 98 | final CompletableFuture intFuture = new CompletableFuture<>(); 99 | channel.read(buf, buf, new CompletionHandler() { 100 | @Override 101 | public void completed(final Integer result, final ByteBuffer attachment) { 102 | attachment.flip(); 103 | intFuture.complete(attachment.getInt()); 104 | } 105 | 106 | @Override 107 | public void failed(final Throwable exc, final ByteBuffer attachment) { 108 | intFuture.completeExceptionally(exc); 109 | } 110 | }); 111 | return intFuture; 112 | } 113 | 114 | public static void main(final String[] args) throws IOException { 115 | final AsynchronousServerSocketChannel server = 116 | AsynchronousServerSocketChannel.open().bind(null); 117 | 118 | final CompletionStage acceptStage = accept(server); 119 | final SocketAddress addr = server.getLocalAddress(); 120 | final CompletionStage connectStage = connect(addr); 121 | 122 | // after connecting, write the integer 42 to the server 123 | final CompletionStage writeStage = 124 | connectStage.thenAccept(channel -> writeInt(channel, 42)); 125 | 126 | final CompletionStage readStage = acceptStage 127 | // after accepting, read an int from the socket 128 | .thenCompose(NioBridge::readInt) 129 | // print the result 130 | .thenAccept(System.out::println); 131 | 132 | // wait for the write and the read to complete 133 | writeStage.toCompletableFuture().join(); 134 | readStage.toCompletableFuture().join(); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/iteration/BufferedAsyncQueueTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.iteration; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.CompletionStage; 13 | import java.util.concurrent.Future; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.IntStream; 16 | 17 | import org.junit.Assert; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | 21 | import com.ibm.asyncutil.iteration.AsyncIterator.End; 22 | import com.ibm.asyncutil.util.Combinators; 23 | import com.ibm.asyncutil.util.Either; 24 | 25 | public class BufferedAsyncQueueTest extends AbstractAsyncQueueTest { 26 | private final static int BUFFER = 5; 27 | private BoundedAsyncQueue queue; 28 | 29 | @Before 30 | public void makeQueue() { 31 | this.queue = AsyncQueues.buffered(BUFFER); 32 | } 33 | 34 | @Override 35 | boolean send(final Integer c) { 36 | return this.queue.send(c).toCompletableFuture().join(); 37 | } 38 | 39 | @Override 40 | AsyncIterator consumer() { 41 | return this.queue; 42 | } 43 | 44 | @Override 45 | void closeImpl() { 46 | this.queue.terminate(); 47 | } 48 | 49 | 50 | @Test 51 | public void bufferedTest() { 52 | // first five futures should be done immediately 53 | IntStream.range(0, BUFFER) 54 | .mapToObj(i -> this.queue.send(i).toCompletableFuture()) 55 | .forEach(f -> { 56 | Assert.assertTrue(f.isDone()); 57 | Assert.assertTrue(f.join()); 58 | }); 59 | 60 | // next 5 should all wait 61 | final List> collect = IntStream.range(0, BUFFER) 62 | .mapToObj(this.queue::send) 63 | .map(CompletionStage::toCompletableFuture) 64 | .map(f -> { 65 | Assert.assertFalse(f.isDone()); 66 | return f; 67 | }) 68 | .collect(Collectors.toList()); 69 | 70 | for (int i = 0; i < BUFFER; i++) { 71 | final CompletableFuture> fut = 72 | this.queue.nextStage().toCompletableFuture(); 73 | 74 | // could change with impl, but with a full queue, futures should already be completed 75 | Assert.assertTrue(fut.isDone()); 76 | // not closed 77 | Assert.assertTrue(fut.join().isRight()); 78 | 79 | // impl supports fairness (for now), every release, the next waiting future should complete 80 | for (int j = 0; j < BUFFER; j++) { 81 | Assert.assertTrue(collect.get(j).isDone() == (j <= i)); 82 | } 83 | } 84 | 85 | this.queue.terminate(); 86 | for (int i = 0; i < BUFFER * 5; i++) { 87 | if (i % 2 == 0) { 88 | this.queue.terminate(); 89 | } else { 90 | this.queue.send(1); 91 | } 92 | } 93 | this.queue.consume().toCompletableFuture().join(); 94 | } 95 | 96 | @Test 97 | public void asyncCloseContractTest() { 98 | // accepted right away 99 | final List> immediate = IntStream 100 | .range(0, BUFFER) 101 | .mapToObj(this.queue::send) 102 | .map(CompletionStage::toCompletableFuture) 103 | .collect(Collectors.toList()); 104 | 105 | Assert.assertTrue( 106 | Combinators.collect(immediate).toCompletableFuture().join().stream().allMatch(b -> b)); 107 | 108 | final List> delayeds = IntStream 109 | .range(0, BUFFER) 110 | .mapToObj(i -> this.queue.send(i + BUFFER)) 111 | .map(CompletionStage::toCompletableFuture) 112 | .collect(Collectors.toList()); 113 | Assert.assertFalse(delayeds.stream().map(CompletableFuture::isDone).anyMatch(b -> b)); 114 | 115 | // terminate 116 | final CompletableFuture closeFuture = this.queue.terminate().toCompletableFuture(); 117 | 118 | Assert.assertFalse(delayeds.stream().map(Future::isDone).anyMatch(b -> b)); 119 | Assert.assertFalse(closeFuture.isDone()); 120 | 121 | // send after terminate 122 | final CompletableFuture rejected = this.queue.send(3).toCompletableFuture(); 123 | 124 | for (int i = 0; i < BUFFER; i++) { 125 | // consume one item 126 | Assert.assertEquals(i, 127 | this.queue.nextStage().toCompletableFuture().join().right().get().intValue()); 128 | // delayeds less than item should be done 129 | Assert.assertTrue(delayeds.stream().limit(i + 1).map(Future::isDone).allMatch(b -> b)); 130 | Assert 131 | .assertTrue(delayeds.stream().limit(i + 1).map(CompletableFuture::join).allMatch(b -> b)); 132 | // delayeds more than item should be pending 133 | Assert.assertFalse(delayeds.stream().skip(i + 1).map(Future::isDone).anyMatch(b -> b)); 134 | // terminate should not be done until all delayeds are done 135 | 136 | // according to the contract, the terminate future could be done when the last delayed is 137 | // accepted, however it is not required. only check that if there is outstanding acceptable 138 | // work, we don't finish terminate 139 | if (i == BUFFER - 1) { 140 | Assert.assertFalse(closeFuture.isDone()); 141 | } 142 | } 143 | 144 | // consume delayed results 145 | for (int i = BUFFER; i < 2 * BUFFER; i++) { 146 | Assert.assertEquals(i, 147 | this.queue.nextStage().toCompletableFuture().join().right().get().intValue()); 148 | } 149 | Assert.assertFalse(this.queue.nextStage().toCompletableFuture().join().isRight()); 150 | Assert.assertTrue(closeFuture.isDone()); 151 | Assert.assertTrue(rejected.isDone()); 152 | Assert.assertFalse(rejected.join()); 153 | } 154 | 155 | @Override 156 | Optional poll() { 157 | return this.queue.poll(); 158 | } 159 | 160 | } 161 | 162 | 163 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncNamedReadWriteLock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | /** 13 | * A mechanism used to acquire read-write locks shared by a common name. Acquisitions for a given 14 | * name will share exclusivity with other acquisitions of the same name, based on 15 | * {@link Object#equals(Object) object equality}. Acquisitions of different names may proceed 16 | * unobstructed. 17 | *

18 | * Implementations will specify whether their lock acquisition is fair or not; this interface does 19 | * not define this requirement. 20 | *

21 | * Note that implementations will generally employ an underlying {@link java.util.Map}; as such, the 22 | * same precautions must be taken regarding mutability of keys (names). Name objects should not 23 | * change from the time of acquisition to the time of release, with respect to their 24 | * {@link Object#equals(Object) equality} and {@link Object#hashCode() hash code} semantics. The 25 | * release methods of the returned {@link AsyncReadWriteLock.ReadLockToken} and 26 | * {@link AsyncReadWriteLock.WriteLockToken} may throw a 27 | * {@link java.util.ConcurrentModificationException} if such a modification is detected. 28 | * 29 | * @author Renar Narubin 30 | * @param the type of named objects used to identify read-write locks 31 | */ 32 | public interface AsyncNamedReadWriteLock { 33 | 34 | /** 35 | * Acquires the read lock associated with the given name. The returned stage will complete when 36 | * the lock is no longer exclusively held by a writer, and the read lock has been acquired. The 37 | * stage may already be complete if the write lock for the given name is not currently held. 38 | *

39 | * The {@link AsyncReadWriteLock.ReadLockToken} held by the returned stage is used to release the 40 | * read lock after it has been acquired and the read-lock-protected action has completed. 41 | * 42 | * @param name to acquire read access for 43 | * @return A {@link CompletionStage} which will complete with a 44 | * {@link AsyncReadWriteLock.ReadLockToken} when the read lock associated with 45 | * {@code name} has been acquired 46 | */ 47 | CompletionStage acquireReadLock(T name); 48 | 49 | /** 50 | * Exclusively acquires the write lock associated with the given name. The returned stage will 51 | * complete when the lock is no longer held by any readers or another writer, and the write lock 52 | * has been exclusively acquired. The stage may already be complete if no locks for the given name 53 | * are currently held. 54 | *

55 | * The {@link AsyncReadWriteLock.WriteLockToken} held by the returned stage is used to release the 56 | * write lock after it has been acquired and the write-lock-protected action has completed. 57 | * 58 | * @param name to acquire exclusive write access for 59 | * @return A {@link CompletionStage} which will complete with a 60 | * {@link AsyncReadWriteLock.WriteLockToken} when the lock associated with {@code name} 61 | * has been exclusively acquired 62 | */ 63 | CompletionStage acquireWriteLock(T name); 64 | 65 | /** 66 | * Attempts to acquire the read lock associated with the given name. If the associated write lock 67 | * is not currently held, the returned Optional will hold a ReadLockToken representing this 68 | * acquisition. Otherwise, the returned Optional will be empty. 69 | *

70 | * The {@link AsyncReadWriteLock.ReadLockToken} held by the returned optional is used to release 71 | * the read lock after it has been acquired and the read-lock-protected action has completed. 72 | * 73 | * @param name to acquire read access for 74 | * @return An {@link Optional} holding a {@link AsyncReadWriteLock.ReadLockToken} if the write 75 | * lock associated with {@code name} is not held; otherwise an empty Optional 76 | */ 77 | Optional tryReadLock(T name); 78 | 79 | /** 80 | * Attempts to acquire the write lock associated with the given name. If the associated write lock 81 | * or read lock is not currently held, the returned Optional will hold a WriteLockToken 82 | * representing this acquisition. Otherwise, the returned Optional will be empty. 83 | *

84 | * The {@link AsyncReadWriteLock.WriteLockToken} held by the returned optional is used to release 85 | * the write lock after it has been acquired and the write-lock-protected action has completed. 86 | * 87 | * @param name to acquire exclusive write access for 88 | * @return An {@link Optional} holding a {@link AsyncReadWriteLock.WriteLockToken} if the lock 89 | * associated with {@code name} is not held by a writer or any readers; otherwise an empty 90 | * Optional 91 | */ 92 | Optional tryWriteLock(T name); 93 | 94 | /** 95 | * Creates an {@link AsyncNamedReadWriteLock} 96 | * 97 | *

98 | * The returned lock is only guaranteed to meet the requirements of 99 | * {@link AsyncNamedReadWriteLock}; in particular, no guarantee of fairness is provided. 100 | * 101 | * @return a new {@link AsyncNamedReadWriteLock} 102 | */ 103 | static AsyncNamedReadWriteLock create() { 104 | // fair for now, may be swapped with a more performant unfair version later 105 | return new FairAsyncNamedReadWriteLock<>(); 106 | } 107 | 108 | /** 109 | * Creates a fair {@link AsyncNamedReadWriteLock} 110 | * 111 | * @return a new {@link AsyncNamedReadWriteLock} with a fair implementation 112 | */ 113 | static AsyncNamedReadWriteLock createFair() { 114 | return new FairAsyncNamedReadWriteLock<>(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/AsyncFunnelTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.CompletionException; 12 | import java.util.concurrent.CompletionStage; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.ExecutionException; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.TimeoutException; 17 | import java.util.concurrent.atomic.AtomicBoolean; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.IntStream; 21 | 22 | import org.junit.Assert; 23 | import org.junit.Test; 24 | 25 | import com.ibm.asyncutil.util.StageSupport; 26 | import com.ibm.asyncutil.util.Combinators; 27 | 28 | public class AsyncFunnelTest { 29 | 30 | private static class TestException extends Exception { 31 | private static final long serialVersionUID = 1L; 32 | private final int i; 33 | 34 | public TestException(final int i) { 35 | this.i = i; 36 | } 37 | 38 | public int get() { 39 | return this.i; 40 | } 41 | } 42 | 43 | 44 | @Test 45 | public void testNullify() { 46 | final AsyncFunnel c = new AsyncFunnel<>(); 47 | for (int k = 0; k < 5; k++) { 48 | final int finalK = k; 49 | final CompletionStage second = c.doOrGet(() -> { 50 | final CompletableFuture result = new CompletableFuture<>(); 51 | if (finalK % 2 == 0) { 52 | result.complete(finalK); 53 | } else { 54 | result.completeExceptionally(new TestException(finalK)); 55 | } 56 | return result; 57 | }); 58 | int j; 59 | try { 60 | j = second.toCompletableFuture().join(); 61 | } catch (final CompletionException e) { 62 | j = ((TestException) e.getCause()).get(); 63 | } 64 | Assert.assertEquals(j, k); 65 | } 66 | } 67 | 68 | @Test 69 | public void funnelTest() throws InterruptedException, ExecutionException, TimeoutException { 70 | final AsyncFunnel c = new AsyncFunnel<>(); 71 | final CountDownLatch latch1 = new CountDownLatch(1); 72 | c.doOrGet(() -> CompletableFuture.supplyAsync(() -> { 73 | try { 74 | latch1.await(); 75 | } catch (final InterruptedException e) { 76 | } 77 | return 1; 78 | })); 79 | 80 | // this should not be called 81 | final CompletableFuture fail = 82 | c.doOrGet(() -> StageSupport.completedStage(-1)).toCompletableFuture(); 83 | Assert.assertFalse(fail.isDone()); 84 | 85 | // let the first future finish 86 | latch1.countDown(); 87 | Assert.assertEquals(1, fail.get(1, TimeUnit.SECONDS).intValue()); 88 | 89 | // this can be accepted immediately 90 | final CompletableFuture second = 91 | c.doOrGet(() -> StageSupport.completedStage(2)).toCompletableFuture(); 92 | Assert.assertTrue(second.isDone()); 93 | Assert.assertEquals(2, second.join().intValue()); 94 | 95 | // so can this 96 | final CompletableFuture third = 97 | c.doOrGet(() -> { 98 | final CompletableFuture x = new CompletableFuture<>(); 99 | x.completeExceptionally(new Exception()); 100 | return x; 101 | }).toCompletableFuture(); 102 | Assert.assertTrue(third.isDone()); 103 | Assert.assertTrue(third.isCompletedExceptionally()); 104 | } 105 | 106 | @Test 107 | public void concurrentFunnelTest() 108 | throws InterruptedException, ExecutionException, TimeoutException { 109 | final int NUM_THREADS = 3; 110 | final AsyncFunnel c = new AsyncFunnel<>(); 111 | final AtomicInteger count = new AtomicInteger(0); 112 | final CountDownLatch latch1 = new CountDownLatch(1); 113 | final CountDownLatch latch2 = new CountDownLatch(1); 114 | final CountDownLatch secondSubmitted = new CountDownLatch(1); 115 | final AtomicBoolean running = new AtomicBoolean(true); 116 | c.doOrGet(() -> CompletableFuture.supplyAsync(() -> { 117 | final int next = count.incrementAndGet(); 118 | try { 119 | latch1.await(); 120 | } catch (final InterruptedException e) { 121 | } 122 | return next; 123 | })); 124 | final List> futures = IntStream.range(0, NUM_THREADS).mapToObj(i -> { 125 | return CompletableFuture.runAsync(() -> { 126 | while (running.get()) { 127 | c.doOrGet(() -> CompletableFuture.supplyAsync(() -> { 128 | final int next = count.incrementAndGet(); 129 | secondSubmitted.countDown(); 130 | try { 131 | latch2.await(); 132 | } catch (final InterruptedException e) { 133 | } 134 | return next; 135 | })); 136 | } 137 | }); 138 | }).collect(Collectors.toList()); 139 | 140 | Assert.assertEquals(count.get(), 1); 141 | final CompletableFuture first = 142 | c.doOrGet(() -> StageSupport.completedStage(-1)).toCompletableFuture(); 143 | Assert.assertFalse(first.isDone()); 144 | Assert.assertEquals(1, secondSubmitted.getCount()); 145 | latch1.countDown(); 146 | Assert.assertEquals(1, first.get(1, TimeUnit.SECONDS).intValue()); 147 | 148 | // let any of the threads get the next future in 149 | Assert.assertTrue(secondSubmitted.await(1, TimeUnit.SECONDS)); 150 | 151 | Assert.assertEquals(count.get(), 2); 152 | final CompletableFuture second = 153 | c.doOrGet(() -> StageSupport.completedStage(-1)).toCompletableFuture(); 154 | Assert.assertFalse(second.isDone()); 155 | latch2.countDown(); 156 | Assert.assertEquals(2, second.get(1, TimeUnit.SECONDS).intValue()); 157 | 158 | running.set(false); 159 | Combinators.allOf(futures).toCompletableFuture().get(1, TimeUnit.SECONDS); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AbstractSimpleEpoch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | /** 12 | * A base epoch implementation used to construct various epoch mechanisms, e.g. {@link AsyncEpoch} 13 | * 14 | * @author Renar Narubin 15 | */ 16 | @SuppressWarnings("serial") 17 | abstract class AbstractSimpleEpoch 18 | // opportunistically extend AtomicInteger for state 19 | extends AtomicInteger implements AsyncEpoch.EpochToken { 20 | 21 | /* 22 | * Epoch state is maintained by an atomically adjusted integer. Termination sets the high bit of 23 | * the state; any participants attempting to enter will fail if they find the high bit is set. 24 | * Otherwise, successful entrants increment the state, thus maintaining a participant count in the 25 | * remaining bits. On exiting the epoch, participants then decrement this count. If a decrement 26 | * ever finds that the new count is zero with the terminated bit set, the whenCleared method is 27 | * run. Similarly, if the terminating CAS finds that the count was previously zero, the 28 | * whenCleared method is called directly. 29 | * 30 | * This implementation serves as its own epoch token as all operations are performed on the same 31 | * state integer. 32 | */ 33 | static final int TERMINATED = 0x80000000; 34 | static final int ENTRANT_MASK = ~TERMINATED; 35 | static final int MAX_COUNT = ENTRANT_MASK; 36 | 37 | /** 38 | * Status codes used to differentiate possible return values of {@link #internalEnter} and 39 | * {@link #internalTerminate} 40 | */ 41 | enum Status { 42 | NORMAL, TERMINATED, SPECIAL 43 | } 44 | 45 | /** 46 | * Method to call when an epoch has been terminated and all participants have exited. May be 47 | * called from the last exiting participant's thread, or the terminating thread if there are no 48 | * active participants 49 | *

50 | * This method must be overridden to define the epoch's terminal operation 51 | */ 52 | abstract void onCleared(); 53 | 54 | /** 55 | * Method to call when an entrant has {@link #close() exited} the epoch. The given argument is the 56 | * state of the epoch after this exit; masked with ENTRANT_MASK this will yield the current count 57 | * of entrants. 58 | *

59 | * This method may be overridden to inject some operation after each exit 60 | */ 61 | void onExit(final int state) {} 62 | 63 | /** 64 | * Method to call before terminators attempt to terminate the epoch. The given argument is the 65 | * state of the epoch before that termination is counted. If the returned boolean is {@code true}, 66 | * the termination attempt will not take effect and {@link Status#SPECIAL} will be returned by 67 | * that call to {@link #internalTerminate()} 68 | *

69 | * This method may be overridden to reject terminations when the current state meets some criteria 70 | */ 71 | boolean isSpecialTerminateState(final int state) { 72 | return false; 73 | } 74 | 75 | /** 76 | * Method to call before every entrant enters the epoch. The given argument is the state of the 77 | * epoch before this entrant is counted. If the returned boolean is {@code true}, the entrant will 78 | * not enter the epoch and {@link Status#SPECIAL} will be returned by that call to 79 | * {@link #internalEnter()} 80 | *

81 | * This method may be overridden to reject new entrants when the current state meets some criteria 82 | */ 83 | boolean isSpecialEnterState(final int state) { 84 | return false; 85 | } 86 | 87 | final Status internalEnter() { 88 | int state; 89 | do { 90 | state = get(); 91 | 92 | if (isSpecialEnterState(state)) { 93 | return Status.SPECIAL; 94 | } 95 | 96 | if ((state & TERMINATED) != 0) { 97 | return Status.TERMINATED; 98 | } 99 | 100 | if (state == MAX_COUNT) { 101 | throw new IllegalStateException("maximum epoch entrants exceeded"); 102 | } 103 | 104 | } while (!compareAndSet(state, state + 1)); 105 | 106 | return Status.NORMAL; 107 | } 108 | 109 | /** 110 | * @return {@link Status#SPECIAL} if the current state before this call meets the 111 | * {@link #isSpecialTerminateState(int)} criteria. Otherwise returns 112 | * {@link Status#TERMINATED} if the epoch was already terminated (not by this 113 | * call). Otherwise returns {@link Status#NORMAL} (i.e. uniquely terminated by this call) 114 | */ 115 | final Status internalTerminate() { 116 | int state, newState; 117 | do { 118 | state = get(); 119 | 120 | if (isSpecialTerminateState(state)) { 121 | return Status.SPECIAL; 122 | } 123 | 124 | if ((state & TERMINATED) != 0) { 125 | // somebody else claimed the termination 126 | return Status.TERMINATED; 127 | } 128 | 129 | } while (!compareAndSet(state, newState = (state | TERMINATED))); 130 | 131 | if (newState == TERMINATED) { 132 | // there weren't any active participants 133 | onCleared(); 134 | } 135 | 136 | return Status.NORMAL; 137 | } 138 | 139 | public final boolean isTerminated() { 140 | return (get() & TERMINATED) != 0; 141 | } 142 | 143 | final boolean isCleared() { 144 | return get() == TERMINATED; 145 | } 146 | 147 | @Override 148 | public final void close() { 149 | int state, newState; 150 | do { 151 | state = get(); 152 | 153 | if ((state & ENTRANT_MASK) == 0) { 154 | throw new IllegalStateException("excessively closed epoch"); 155 | } 156 | 157 | } while (!compareAndSet(state, newState = (state - 1))); 158 | 159 | onExit(newState); 160 | 161 | if (newState == TERMINATED) { 162 | onCleared(); 163 | } 164 | } 165 | 166 | @Override 167 | public String toString() { 168 | final int state = get(); 169 | return "Epoch [isTerminated=" + ((state & TERMINATED) != 0) + ", entrants=" 170 | + (state & ENTRANT_MASK) + "]"; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/locks/AsyncSemaphore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.concurrent.CompletionStage; 10 | 11 | /** 12 | * An asynchronously acquirable counting semaphore 13 | *

14 | * Implementations will specify whether their permit acquisition and release is fair or not; this 15 | * interface does not define this requirement. 16 | * 17 | * @author Renar Narubin 18 | */ 19 | public interface AsyncSemaphore { 20 | 21 | /** 22 | * Acquires the given number of permits from the semaphore, returning a stage which will complete 23 | * when all of the permits are exclusively acquired. The stage may already be complete if the 24 | * permits are available immediately. 25 | *

26 | * If the permits are not available immediately, the acquisition will enter a queue and an 27 | * incomplete stage will be returned. Semantics of the waiter queue, including ordering policies, 28 | * are implementation specific and will be defined by the given implementing class. The returned 29 | * stage will complete when sufficient permits have been {@link #release(long) released} and 30 | * assigned to this acquisition by the governing queue policy. 31 | * 32 | * @param permits A non-negative number of permits to acquire from the semaphore 33 | * @return a {@link CompletionStage} which will be completed when all {@code permits} have been 34 | * acquired 35 | * @throws IllegalArgumentException if the requested permits are negative, or exceed any 36 | * restrictions enforced by the given implementation 37 | */ 38 | CompletionStage acquire(long permits); 39 | 40 | /** 41 | * Releases the given number of permits to the semaphore. 42 | *

43 | * If there are unfulfilled acquires pending, this method will release permits to the waiting 44 | * acquisitions based on the implementation's release and acquire policies. Consequently, this 45 | * method may complete a number of waiting stages and execute the corresponding observers. 46 | * 47 | * @param permits A non-negative number of permits to release to the semaphore 48 | * @throws IllegalArgumentException if the released permits are negative, or exceed any 49 | * restrictions enforced by the given implementation 50 | */ 51 | void release(long permits); 52 | 53 | /** 54 | * Attempts to acquire the given number of permits from the semaphore, returning a boolean 55 | * indicating whether all of the permits were immediately available and have been exclusively 56 | * acquired. 57 | *

58 | * Implementations may define precise behavior of this method with respect to competing 59 | * acquisitions, e.g. whether permits may be acquired while other acquisitions are waiting. This 60 | * interface only requires that this method will succeed when the given permits are available and 61 | * there are no other acquisitions queued. 62 | * 63 | * @param permits A non-negative number of permits to acquire from the semaphore 64 | * @return true iff all of the requested permits are available, and have been immediately acquired 65 | * @throws IllegalArgumentException if the requested permits are negative, or exceed any 66 | * restrictions enforced by the given implementation 67 | */ 68 | boolean tryAcquire(long permits); 69 | 70 | /** 71 | * Acquires all permits that are immediately available. 72 | *

73 | * After this call -- provided there are no intermediate {@link #release(long) releases} -- any 74 | * attempt to {@link #acquire(long) acquire} will queue and any {@link #tryAcquire(long) 75 | * tryAcquire} will fail. 76 | * 77 | * @return the number of permits that were available and have been drained 78 | */ 79 | long drainPermits(); 80 | 81 | /** 82 | * Gets the number of currently available permits. 83 | *

84 | * The bounds of the returned value are not defined; an implementation may, for example, choose to 85 | * represent waiting acquisitions as holding negative permits, and thus the value returned by this 86 | * method could be negative. Furthermore, a positive number of permits returned by this method may 87 | * not indicate that such permits are acquirable, as the waiter-queue policy may prohibit 88 | * fulfilling further acquisitions. 89 | *

90 | * This value is produced on a best-effort basis, and should not be used for any control logic. 91 | * Generally it is only useful in testing, debugging, or statistics purposes. 92 | * 93 | * @return the number of currently available permits 94 | */ 95 | long getAvailablePermits(); 96 | 97 | /** 98 | * Gets the number of unfulfilled acquisitions waiting on this semaphore's permits. 99 | *

100 | * This value is produced on a best-effort basis, and should not be used for any control logic. 101 | * Generally it is only useful in testing, debugging, or statistics purposes. 102 | * 103 | * @return the number of unfulfilled acquisitions waiting on this semaphore's permits 104 | */ 105 | int getQueueLength(); 106 | 107 | /** 108 | * Acquires 1 permit from the semaphore as if by calling {@link #acquire(long)} with an argument 109 | * of 1. 110 | * 111 | * @return a {@link CompletionStage} which will complete when 1 permit has been successfully 112 | * acquired 113 | * @see #acquire(long) 114 | */ 115 | default CompletionStage acquire() { 116 | return acquire(1L); 117 | } 118 | 119 | /** 120 | * Releases 1 permit from the semaphore as if by calling {@link #release(long)} with an argument 121 | * of 1. 122 | * 123 | * @see #release(long) 124 | */ 125 | default void release() { 126 | release(1L); 127 | } 128 | 129 | /** 130 | * Attempts to acquire 1 permit from the semaphore as if by calling {@link #tryAcquire(long)} with 131 | * an argument of 1. 132 | * 133 | * @return true iff the single request permit is available, and has been immediately acquired 134 | * @see #tryAcquire(long) 135 | */ 136 | default boolean tryAcquire() { 137 | return tryAcquire(1L); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/iteration/BoundedAsyncQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.iteration; 8 | 9 | import java.util.Optional; 10 | import java.util.concurrent.CompletionStage; 11 | 12 | /** 13 | * A version of {@link AsyncQueue} that provides a mechanism for backpressure. 14 | * 15 | *

16 | * The documentation from {@link AsyncQueue} largely applies here. Backpressure refers to the signal 17 | * sent to senders that the queue is "full" and the sender should stop sending values for some 18 | * period of time. Typically a queue becomes "full" because values are being sent into the queue 19 | * faster than the consumer is capable of consuming them. Without backpressure, the senders could 20 | * cause an out of memory condition if they eventually sent too many messages into the queue. Users 21 | * are expected to respect backpressure by refraining from making a subsequent call to {@link #send} 22 | * until the {@link CompletionStage} returned by the previous call completes. 23 | * 24 | *

25 | * Currently you can produce a bounded queue with {@link AsyncQueues#bounded()} or 26 | * {@link AsyncQueues#buffered(int)}. 27 | * 28 | *

29 | * Consider this example implemented without backpressure 30 | * 31 | *

 32 |  * {@code
 33 |  * AsyncIterator produce() {
 34 |  *   AsyncQueue queue = AsyncQueues.unbounded();
 35 |  *   pool.submit(() -> {
 36 |  *     while (keepGoing) {
 37 |  *       queue.send(i++);
 38 |  *     }
 39 |  *     queue.terminate();
 40 |  *   });
 41 |  * }
 42 |  * produce().forEach(i -> {
 43 |  *   slowWriteToDisk(i);
 44 |  * });
 45 |  * }
 46 |  * 
47 | * 48 | * Because generating work is a cheap in-memory operation but consuming it is a slow IO operation, 49 | * the sender will dramatically outpace the consumer in this case. Soon, the process will run out of 50 | * memory, as the sender continues to queue ints for the consumer to write. Instead we can use a 51 | * bounded queue: 52 | * 53 | *
 54 |  * {@code
 55 |  * AsyncIterator produce() {
 56 |  *   final AsyncQueue queue = AsyncQueues.bounded();
 57 |  *
 58 |  *   //blocking sends on pool
 59 |  *   pool.submit(() -> {
 60 |  *     while (shouldContinue()) {
 61 |  *       queue.send(i++).toCompletableFuture().join();
 62 |  *     }
 63 |  *     queue.terminate();
 64 |  *   });
 65 |  *   return queue;
 66 |  *}
 67 |  *
 68 |  *   // consumer doesn't know or care queue is bounded
 69 |  * produce().forEach(i -> {
 70 |  *   slowWriteToDisk(i);
 71 |  * });
 72 |  *}
 73 |  * 
74 | * 75 | * Senders of course can be implemented without blocking while still respecting backpressure: 76 | * 77 | *
 78 |  * {@code
 79 |  * AsyncIterator produce() {
 80 |  *   final AsyncQueue queue = AsyncQueues.bounded();
 81 |  *
 82 |  *   // alternative approach to sending: async sender
 83 |  *   AsyncIterators
 84 |  *       .iterate(i -> i + 1)
 85 |  *       // send to queue
 86 |  *       .thenApply(i -> queue.send(i))
 87 |  *       // consumes futures one by one
 88 |  *       .takeWhile(ig -> shouldContinue())
 89 |  *       .consume()
 90 |  *       // finished, terminate queue
 91 |  *       .thenRun(() -> queue.terminate());
 92 |  *
 93 |  *   return queue;
 94 |  *}
 95 |  *}
 96 |  * 
97 | * 98 | *

99 | * An important point is that trying to send is the only way to be notified that the queue is full. 100 | * In practice, this means that if your number of senders is very large you can still consume too 101 | * much memory even if you are respecting the send interface. 102 | * 103 | * @author Ravi Khadiwala 104 | * @param the type of the items sent and consumed from this queue 105 | * @see AsyncIterator 106 | * @see AsyncQueue 107 | * @see AsyncQueues 108 | */ 109 | public interface BoundedAsyncQueue extends AsyncIterator { 110 | 111 | /** 112 | * Sends a value into this queue that can be consumed via the {@link AsyncIterator} interface. 113 | * 114 | *

115 | * This method is thread safe - multiple threads can send values into this queue concurrently. 116 | * This queue is bounded, so after a call to {@code send} a {@link CompletionStage} is returned to 117 | * the sender. When the stage finishes, consumption has progressed enough that the queue is again 118 | * willing to accept messages. The implementation decides when a queue is writable: it could 119 | * require that all outstanding values are consumed by the consumer, it could allow a certain 120 | * number of values to be buffered before applying back pressure, or it could use some out-of-band 121 | * metric to decide. 122 | * 123 | * @param item element to send into the queue 124 | * @return a {@link CompletionStage} that completes when the queue is ready to accept another 125 | * message. It completes with true if the item was accepted, false if it was rejected 126 | * because the queue has already been terminated. 127 | * @see AsyncQueue#send 128 | */ 129 | CompletionStage send(T item); 130 | 131 | /** 132 | * Terminates the queue. After termination subsequent attempts to {@link #send} into the queue will 133 | * fail. 134 | * 135 | *

136 | * After the queue is terminated, all subsequent sends will return stages that will complete with 137 | * false. After the consumer consumes whatever was sent before the terminate, the consumer will 138 | * receive an {@link AsyncIterator.End} marker. When the {@link CompletionStage} returned by this 139 | * method completes, no more messages will ever make it into the queue. Equivalently, all stages 140 | * generated by {@link #send} that will complete with {@code true} will have been completed by the 141 | * time the returned stage completes. 142 | * 143 | * @return a {@link CompletionStage} that indicates when all sends that were sent before the 144 | * terminate have made it into the queue 145 | * @see AsyncQueue#terminate() 146 | */ 147 | CompletionStage terminate(); 148 | 149 | /** 150 | * Gets a result from the queue if one is immediately available. 151 | * 152 | *

153 | * This method consumes parts of the queue, so like the consumption methods on 154 | * {@link AsyncIterator}, this method is not thread-safe and should be used in a single threaded 155 | * fashion. After {@link #terminate()} is called and all outstanding results are consumed, poll 156 | * will always return empty. This method should not be used if there are null values in 157 | * the queue.
158 | * Notice that the queue being closed is indistinguishable from the queue being transiently empty. 159 | * To discover that no more results will ever be available, you must use the normal means on 160 | * {@link AsyncIterator}: either calling {@link #nextStage()} and seeing if the result indicates 161 | * an end of iteration when the stage completes, or using one of the consumer methods that only 162 | * complete once the queue has been closed. 163 | * 164 | * @throws NullPointerException if the polled result is null 165 | * @return a present T value if there was one immediately available in the queue, empty if the 166 | * queue is currently empty 167 | * @see AsyncQueue#poll() 168 | */ 169 | Optional poll(); 170 | } 171 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/iteration/AsyncQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.iteration; 8 | 9 | import java.util.Optional; 10 | 11 | /** 12 | * An unbounded async multi-producer-single-consumer queue. 13 | * 14 | *

15 | * This class provides a queue abstraction that allows multiple senders to place values into the 16 | * queue synchronously, and a single consumer to consume values as they become available 17 | * asynchronously. You can construct an {@link AsyncQueue} with the static methods on 18 | * {@link AsyncQueues}. 19 | * 20 | *

21 | * This interface represents an unbounded queue, meaning there is no mechanism to notify 22 | * the sender that the queue is "full" (nor is there a notion of the queue being full to begin 23 | * with). The queue will continue to accept values as fast as the senders can {@link #send} them, 24 | * regardless of the rate at which the values are being consumed. If senders produce a lot of values 25 | * much faster than the consumption rate, it will lead to an out of memory error, so users are 26 | * responsible for enforcing that the queue does not grow too large. If you would like a queue 27 | * abstraction that provides backpressure, see {@link BoundedAsyncQueue}. 28 | * 29 | *

30 | * This queue can be terminated by someone calling {@link #terminate()}, which can be called by 31 | * consumers or senders. It is strongly recommended that all instances of this class eventually be 32 | * terminated. Most terminal operations on {@link AsyncIterator} return 33 | * {@link java.util.concurrent.CompletionStage CompletionStages} whose stage will not complete until 34 | * the queue is terminated. After the queue is terminated, subsequent {@link #send}s are rejected, 35 | * though consumers of the queue will still receive any values that were sent before the 36 | * termination. 37 | * 38 | *

39 | * Typically you'll want to use a queue when you have some "source" of items, and want to consume 40 | * them asynchronously as the become available. Some examples of sources could be a collection of 41 | * {@link java.util.concurrent.CompletionStage CompletionStages}, bytes off of a socket, results 42 | * produced by dedicated worker threads, etc. Suppose you had a scenario where you had many threads 43 | * doing some CPU intensive computation, and you'd send their answers off to some server one at a 44 | * time. 45 | * 46 | *

 47 |  * {@code
 48 |  * AsyncQueue queue = AsyncQueues.unbounded();
 49 |  * for (i = 0; i < numThreads; i++) {
 50 |  *   // spawn threads that send results to queue
 51 |  *   threadpool.submit(() -> {
 52 |  *      while (canStillCompute) {
 53 |  *        int num = computeReallyExpensiveThing();
 54 |  *        queue.send(num);
 55 |  *      }
 56 |  *    });
 57 |  * }
 58 |  *
 59 |  * //consumer of queue, sending numbers to a server one at a time
 60 |  * queue
 61 |  *   // lazily map numbers to send
 62 |  *   .thenCompose(number -> sendToServer(number))
 63 |  *   // consume all values
 64 |  *   .consume()
 65 |  *   // iteration stopped (meaning queue was terminated)
 66 |  *   .thenAccept(ig -> sendToServer("no more numbers!");
 67 |  *
 68 |  * threadpool.awaitTermination();
 69 |  * // terminate the queue, done computing
 70 |  * queue.terminate();
 71 |  *
 72 |  * }
 73 |  * 
74 | * 75 | *

76 | * It is also convenient to use a queue to merge many {@link AsyncIterator}s together. Consider the 77 | * destination server in the previous example, now with many compute servers sending the numbers 78 | * they were computing. If we used {@link AsyncIterator#concat} in the following example, we would 79 | * wait until we got all the work from the first iterator to move onto the next. With a queue we 80 | * instead process each number as soon as it becomes available. 81 | * 82 | *

 83 |  * {@code
 84 |  * AsyncIterator getNumbersFrom(ServerLocation ip);
 85 |  * AsyncQueue queue = AsyncQueues.unbounded();
 86 |  * futures = ips.stream()
 87 |  *
 88 |  *    // get an AsyncIterator of numbers from each server
 89 |  *   .map(this::getNumbersFrom)
 90 |  *
 91 |  *    // send each number on each iterator into the queue as they arrive
 92 |  *   .forEach(asyncIterator -> asyncIterator.forEach(t -> queue.send(t)))
 93 |  *
 94 |  *   // bundle futures into a list
 95 |  *   .collect(Collectors.toList());
 96 |  *
 97 |  * // terminate the queue whenever we're done sending
 98 |  * Combinators.allOf(futures).thenAccept(ignore -> queue.terminate());
 99 |  *
100 |  * // prints each number returned by servers as they arrive
101 |  * queue
102 |  *   .forEach(num -> System.out.println(num))
103 |  *   .thenAccept(ig -> System.out.println("finished getting all numbers")));
104 |  * }
105 |  * 
106 | * 107 | *

108 | * A reminder, all topics addressed in the documentation of {@link AsyncIterator} apply to this 109 | * interface as well. Most importantly this means: 110 | * 111 | *

    112 | *
  • Consumption of an AsyncIterator is not thread safe 113 | *
  • Lazy methods on AsyncIterator like thenApply/thenCompose don't consume anything. Make sure 114 | * you actually use a consumption operation somewhere, otherwise no one will ever read what was sent 115 | *
116 | * 117 | * @author Ravi Khadiwala 118 | * @param The type of the elements in this queue 119 | * @see AsyncQueues 120 | * @see BoundedAsyncQueue 121 | */ 122 | public interface AsyncQueue extends AsyncIterator { 123 | /** 124 | * Sends a value into this queue that can be consumed via the {@link AsyncIterator} interface. 125 | * 126 | *

127 | * This method is thread safe - multiple threads can send values into this queue concurrently. 128 | * This queue is unbounded, so it will continue to accept new items immediately and store them in 129 | * memory until they can be consumed. If you are sending work faster than you can consume it, this 130 | * can easily lead to an out of memory condition. 131 | * 132 | * @param item the item to be sent into the queue 133 | * @return true if the item was accepted, false if it was rejected because the queue has already 134 | * been terminated 135 | */ 136 | boolean send(T item); 137 | 138 | /** 139 | * Terminates the queue, disabling {@link #send}. 140 | * 141 | *

142 | * After the queue is terminated all subsequent sends will be rejected, returning false. After the 143 | * consumer consumes whatever was sent before the terminate, the consumer will receive an end of 144 | * iteration notification. 145 | * 146 | *

147 | * This method is thread-safe, and can be called multiple times. An attempt to terminate after 148 | * termination has already occurred is a no-op. 149 | */ 150 | void terminate(); 151 | 152 | /** 153 | * Gets a result from the queue if one is immediately available. 154 | * 155 | *

156 | * This method consumes parts of the queue, so like the consumption methods on 157 | * {@link AsyncIterator}, this method is not thread-safe and should be used in a single threaded 158 | * fashion. After {@link #terminate()} is called and all outstanding results are consumed, poll 159 | * will always return empty. This method should not be used if there are null values in 160 | * the queue.
161 | * Notice that the queue being closed is indistinguishable from the queue being transiently empty. 162 | * To discover that no more results will ever be available, you must use the normal means on 163 | * {@link AsyncIterator}: either calling {@link #nextStage()} and seeing if the result indicates 164 | * an end of iteration when the future completes, or using one of the consumer methods that only 165 | * complete once the queue has been closed. 166 | * 167 | * @throws NullPointerException if the polled result is null 168 | * @return a present T value if there was one immediately available in the queue, otherwise empty 169 | * if the queue is currently empty 170 | */ 171 | Optional poll(); 172 | } 173 | -------------------------------------------------------------------------------- /asyncutil-flow/src/test/java/com/ibm/asyncutil/flow/FlowAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.flow; 8 | 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.concurrent.CompletionException; 14 | import java.util.concurrent.CompletionStage; 15 | import java.util.concurrent.Flow; 16 | import java.util.concurrent.Flow.Publisher; 17 | import java.util.concurrent.Flow.Subscription; 18 | import java.util.concurrent.TimeoutException; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | 23 | import com.ibm.asyncutil.iteration.AsyncIterator; 24 | import com.ibm.asyncutil.util.Either; 25 | import com.ibm.asyncutil.util.StageSupport; 26 | 27 | /** 28 | * For AsyncIterator specific tests that TCK may not cover 29 | */ 30 | public class FlowAdapterTest { 31 | 32 | @Test(expected = IOException.class) 33 | public void testJustError() throws Throwable { 34 | FlowAdapterTest.consume(new ExceptionalIterator(5, new IOException(), null)); 35 | } 36 | 37 | @Test(expected = IOException.class) 38 | public void testJustCloseError() throws Throwable { 39 | FlowAdapterTest.consume(new ExceptionalIterator(5, null, new IOException())); 40 | } 41 | 42 | @Test 43 | public void testCloseErrorSuppressed() throws Throwable { 44 | try { 45 | FlowAdapterTest 46 | .consume(new ExceptionalIterator(5, new IOException(), new TimeoutException())); 47 | Assert.fail("iterator should have thrown exception"); 48 | } catch (final IOException e) { 49 | final Throwable[] arr = e.getSuppressed(); 50 | Assert.assertTrue(arr.length == 1); 51 | Assert.assertTrue(arr[0] instanceof TimeoutException); 52 | } 53 | } 54 | 55 | @Test(expected = IOException.class) 56 | public void testCancelCloseException() throws Throwable { 57 | final ConsumingSubscriber subscriber = new ConsumingSubscriber() { 58 | @Override 59 | public void onNext(final Long arg0) {} 60 | }; 61 | final Publisher publisher = 62 | FlowAdapter.toPublisher(new ExceptionalIterator(10, null, new IOException())); 63 | publisher.subscribe(subscriber); 64 | subscriber.request(); 65 | subscriber.request(); 66 | Assert.assertFalse(subscriber.isDone()); 67 | 68 | subscriber.cancel(); 69 | Assert.assertTrue(subscriber.isDone()); 70 | FlowAdapterTest.unwrap(subscriber); 71 | } 72 | 73 | @Test 74 | public void testMultiSubscribeInSeries() { 75 | final List its = new ArrayList<>(); 76 | final Publisher p = FlowAdapter.toPublisher(() -> { 77 | final CloseTrackingIt it = new CloseTrackingIt(5); 78 | its.add(it); 79 | return it; 80 | }); 81 | 82 | // subscribe twice in series 83 | final int firstSum = 84 | FlowAdapter.toAsyncIterator(p).fold(0, (i, j) -> i + j).toCompletableFuture().join(); 85 | final int secondSum = 86 | FlowAdapter.toAsyncIterator(p).fold(0, (i, j) -> i + j).toCompletableFuture().join(); 87 | 88 | Assert.assertEquals(2, its.size()); 89 | Assert.assertTrue(its.stream().allMatch(it -> it.closed)); 90 | Assert.assertEquals(15, secondSum); 91 | Assert.assertEquals(15, firstSum); 92 | } 93 | 94 | @Test 95 | public void testMultiSubscribeInParallel() { 96 | final List its = new ArrayList<>(); 97 | final Publisher p = FlowAdapter.toPublisher(() -> { 98 | final CloseTrackingIt it = new CloseTrackingIt(5); 99 | its.add(it); 100 | return it; 101 | }); 102 | 103 | // subscribe twice in parallel 104 | final AsyncIterator it1 = FlowAdapter.toAsyncIterator(p); 105 | Assert.assertEquals(0, it1.nextStage().toCompletableFuture().join().right().get().intValue()); 106 | 107 | final AsyncIterator it2 = FlowAdapter.toAsyncIterator(p); 108 | Assert.assertEquals(0, it2.nextStage().toCompletableFuture().join().right().get().intValue()); 109 | 110 | final int firstSum = it1.fold(0, (i, j) -> i + j).toCompletableFuture().join(); 111 | final int secondSum = it2.fold(0, (i, j) -> i + j).toCompletableFuture().join(); 112 | 113 | Assert.assertEquals(2, its.size()); 114 | Assert.assertTrue(its.stream().allMatch(it -> it.closed)); 115 | Assert.assertEquals(15, secondSum); 116 | Assert.assertEquals(15, firstSum); 117 | } 118 | 119 | private static class CloseTrackingIt implements AsyncIterator { 120 | final int max; 121 | int i = 0; 122 | boolean closed = false; 123 | 124 | private CloseTrackingIt(final int max) { 125 | this.max = max; 126 | } 127 | 128 | @Override 129 | public CompletionStage> nextStage() { 130 | final int ret = this.i++; 131 | return StageSupport.completedStage(ret > this.max ? End.end() : Either.right(ret)); 132 | } 133 | 134 | @Override 135 | public CompletionStage close() { 136 | this.closed = true; 137 | return StageSupport.voidStage(); 138 | } 139 | } 140 | 141 | @Test(expected = IllegalStateException.class) 142 | public void testDoubleSubscription() throws Throwable { 143 | final Flow.Publisher publisher = FlowAdapter.toPublisher(AsyncIterator.range(0, 5)); 144 | final ConsumingSubscriber s = new ConsumingSubscriber<>(); 145 | publisher.subscribe(s); 146 | s.join(); 147 | 148 | final ConsumingSubscriber s2 = new ConsumingSubscriber<>(); 149 | try { 150 | publisher.subscribe(s2); 151 | } catch (final Throwable e) { 152 | Assert.fail("failure should be notified via onError, got: " + e); 153 | } 154 | FlowAdapterTest.unwrap(s2); 155 | } 156 | 157 | private static void consume(final AsyncIterator it) throws Throwable { 158 | final Publisher publisher = FlowAdapter.toPublisher(it); 159 | final ConsumingSubscriber stage = new ConsumingSubscriber<>(); 160 | publisher.subscribe(stage); 161 | FlowAdapterTest.unwrap(stage); 162 | 163 | } 164 | 165 | private static class ExceptionalIterator implements AsyncIterator { 166 | private long count = 0; 167 | private final long numElements; 168 | private final Throwable iterationException; 169 | private final Throwable closeException; 170 | 171 | ExceptionalIterator(final long numElements, final Throwable iterationException, 172 | final Throwable closeException) { 173 | this.numElements = numElements; 174 | this.iterationException = iterationException; 175 | this.closeException = closeException; 176 | 177 | } 178 | 179 | @Override 180 | public CompletionStage> nextStage() { 181 | final long nxt = this.count++; 182 | if (nxt >= this.numElements) { 183 | if (this.iterationException != null) { 184 | return StageSupport.exceptionalStage(this.iterationException); 185 | } 186 | return End.endStage(); 187 | } 188 | return StageSupport.completedStage(Either.right(nxt)); 189 | } 190 | 191 | @Override 192 | public CompletionStage close() { 193 | if (this.closeException != null) { 194 | return StageSupport.exceptionalStage(this.closeException); 195 | } 196 | return StageSupport.voidStage(); 197 | } 198 | 199 | } 200 | 201 | private static void unwrap(final CompletableFuture future) throws Throwable { 202 | try { 203 | future.join(); 204 | } catch (final CompletionException e) { 205 | throw e.getCause(); 206 | } 207 | } 208 | 209 | private static class ConsumingSubscriber extends CompletableFuture 210 | implements Flow.Subscriber { 211 | 212 | private Subscription subscription; 213 | 214 | @Override 215 | public void onComplete() { 216 | complete(null); 217 | 218 | } 219 | 220 | @Override 221 | public void onError(final Throwable arg0) { 222 | completeExceptionally(arg0); 223 | 224 | } 225 | 226 | @Override 227 | public void onNext(final T arg0) { 228 | this.subscription.request(1); 229 | } 230 | 231 | @Override 232 | public void onSubscribe(final Subscription arg0) { 233 | this.subscription = arg0; 234 | this.subscription.request(1); 235 | } 236 | 237 | void cancel() { 238 | this.subscription.cancel(); 239 | } 240 | 241 | void request() { 242 | this.subscription.request(1); 243 | } 244 | } 245 | } 246 | 247 | 248 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/util/Combinators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.Iterator; 12 | import java.util.Map; 13 | import java.util.concurrent.CompletionStage; 14 | import java.util.function.BiConsumer; 15 | import java.util.stream.Collector; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * Utility methods for combining more than one {@link CompletionStage} into a single 20 | * {@link CompletionStage} 21 | * 22 | * @author Ravi Khadiwala 23 | * @author Renar Narubin 24 | */ 25 | public class Combinators { 26 | private Combinators() {} 27 | 28 | /* 29 | * The maximum allowed size of a chain of dependants on a stage. In particular, allOf and collect 30 | * may set up a single linear dependency chain. If a CompletionStage implementation does not 31 | * support trampolining the notification of dependent stages, this can cause a StackOverflow on 32 | * notification 33 | */ 34 | private final static int MAX_DEPENDANT_DEPTH = 256; 35 | 36 | /** 37 | * Given a collection of stages, returns a new {@link CompletionStage} that is completed when all 38 | * input stages are complete. If any stage completes exceptionally, the returned stage will 39 | * complete exceptionally. 40 | * 41 | * @param stages a Collection of {@link CompletionStage} 42 | * @return a {@link CompletionStage} which will complete after every stage in {@code stages} 43 | * completes 44 | * @throws NullPointerException if {@code stages} or any of its elements are null 45 | */ 46 | @SuppressWarnings("unchecked") 47 | public static CompletionStage allOf( 48 | final Collection> stages) { 49 | 50 | final Iterator> backingIt = stages.iterator(); 51 | final int size = stages.size(); 52 | final Iterator> it = 53 | size > MAX_DEPENDANT_DEPTH 54 | ? new Iterator>() { 55 | @Override 56 | public boolean hasNext() { 57 | return backingIt.hasNext(); 58 | } 59 | 60 | @Override 61 | public CompletionStage next() { 62 | return backingIt.next().toCompletableFuture(); 63 | } 64 | } 65 | : backingIt; 66 | return allOfImpl(it); 67 | } 68 | 69 | /* 70 | * Put every stage in the collection into a single dependant chain. CompletableFuture trampolines 71 | * the notification of the chain, so it is safe - for other implementations this may cause a 72 | * StackOverflow 73 | */ 74 | private static CompletionStage allOfImpl( 75 | final Iterator> it) { 76 | CompletionStage accumulator = StageSupport.voidStage(); 77 | while (it.hasNext()) { 78 | accumulator = accumulator.thenCombine(it.next(), (l, r) -> null); 79 | } 80 | return accumulator; 81 | } 82 | 83 | /** 84 | * Given a collection of stages all of the same type, returns a new {@link CompletionStage} that 85 | * is completed with a collection of the results of all input stages when all stages complete. If 86 | * the input collection has a defined order, the order will be preserved in the returned 87 | * collection. If an element of {@code stages} completes exceptionally, so too will the 88 | * CompletionStage returned by this method. 89 | * 90 | * @param stages a Collection of {@link CompletionStage} all of type T 91 | * @return a {@link CompletionStage} which will complete with a collection of the elements 92 | * produced by {@code stages} when all stages complete 93 | * @throws NullPointerException if {@code stages} or any of its elements are null 94 | */ 95 | @SuppressWarnings("unchecked") 96 | public static CompletionStage> collect( 97 | final Collection> stages) { 98 | return collect(stages, Collectors.toCollection(() -> new ArrayList<>(stages.size()))); 99 | } 100 | 101 | /* 102 | * Put every stage in the collection into a single dependant chain, accumulating the next element 103 | * as each stage completes (from left to right). CompletableFuture trampolines the notification of 104 | * the chain, so it is safe - for other implementations this may cause a StackOverflow 105 | */ 106 | @SuppressWarnings("unchecked") 107 | private static CompletionStage collectImpl( 108 | final Iterator> it, 109 | final Collector collector) { 110 | 111 | CompletionStage acc = StageSupport.completedStage(collector.supplier().get()); 112 | final BiConsumer accFun = collector.accumulator(); 113 | 114 | while (it.hasNext()) { 115 | /* 116 | * each additional combination step runs only after all previous steps have completed 117 | */ 118 | acc = acc.thenCombine(it.next(), (a, t) -> { 119 | accFun.accept(a, t); 120 | return a; 121 | }); 122 | } 123 | return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH) 124 | ? (CompletionStage) acc 125 | : acc.thenApply(collector.finisher()); 126 | 127 | } 128 | 129 | /** 130 | * Applies a collector to the results of all {@code stages} after all complete, returning a 131 | * {@link CompletionStage} of the collected result. There is no need nor benefit for the Collector 132 | * to have the {@link java.util.stream.Collector.Characteristics CONCURRENT characteristic}, the 133 | * {@code collector} will be applied in a single thread. If any of the input stages completes 134 | * exceptionally, so too will the CompletionStage returned by this method. 135 | * 136 | * @param stages a Collection of stages all of type T 137 | * @param collector a {@link Collector} which will be applied to the results of {@code stages} to 138 | * produce the final R result. 139 | * @param The type of the elements in {@code stages} which will be collected by {@code 140 | * collector} 141 | * @param The intermediate collection type 142 | * @param The final type returned by {@code collector} 143 | * @return a {@link CompletionStage} which will complete with the R typed object that is produced 144 | * by {@code collector} when all input {@code stages} have completed. 145 | * @throws NullPointerException if {@code stages} or any of its elements are null 146 | */ 147 | @SuppressWarnings("unchecked") 148 | public static CompletionStage collect( 149 | final Collection> stages, 150 | final Collector collector) { 151 | final int size = stages.size(); 152 | final Iterator> backingIt = stages.iterator(); 153 | final Iterator> it = 154 | size > MAX_DEPENDANT_DEPTH 155 | ? new Iterator>() { 156 | @Override 157 | public boolean hasNext() { 158 | return backingIt.hasNext(); 159 | } 160 | 161 | @Override 162 | public CompletionStage next() { 163 | return backingIt.next().toCompletableFuture(); 164 | } 165 | } 166 | : backingIt; 167 | return collectImpl(it, collector); 168 | } 169 | 170 | /** 171 | * Given a Map from some key type K to {@link CompletionStage CompletionStages} of values, returns 172 | * a {@link CompletionStage} which completes with a {@code Map} when all the 173 | * CompletionStages in the input map have completed. For example, if we have an asynchronous 174 | * method to lookup student grade point averages. 175 | * 176 | *

177 |    * {@code
178 |    * Map> gpaFutures =
179 |    *  students
180 |    *      .stream()
181 |    *      .collect(Collectors.toMap(Functions.identity(), student -> getGpaAsync(student));
182 |    * Map studentGpas = keyedAll(gpaFutures).toCompletableFuture().join();
183 |    * }
184 |    * 
185 | * 186 | * If a value in {@code stageMap} completes exceptionally, so too will the CompletionStage 187 | * returned by this method. 188 | * 189 | * @param stageMap a Map with keys of type K and {@link CompletionStage CompletionStages} of type 190 | * V for values 191 | * @param the input and output key type 192 | * @param the value type for the map that will be produced by the returned 193 | * {@link CompletionStage} 194 | * @throws NullPointerException if {@code stageMap} or any of its values are null 195 | * @return a {@link CompletionStage} that will be completed with a map mapping keys of type K to 196 | * the values returned by the CompletionStages in {@code stageMap} 197 | */ 198 | @SuppressWarnings("unchecked") 199 | public static CompletionStage> keyedAll( 200 | final Map> stageMap) { 201 | return Combinators 202 | .allOf(stageMap.values()) 203 | .thenApply(ignore -> stageMap.entrySet().stream() 204 | .collect(Collectors.toMap( 205 | e -> e.getKey(), 206 | e -> e.getValue().toCompletableFuture().join()))); 207 | } 208 | 209 | 210 | } 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-util 2 | 3 | ## Introduction 4 | async-util is a library for working with Java 8 [CompletionStages](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html). Its primary goal is to provide tools for asynchronous coordination, including iterative production/consumption of CompletionStages and non-blocking asynchronous mutual exclusion support. 5 | 6 | The library is broken up into three packages: 7 | * [Locks](#locks) 8 | * [Iteration](#iteration) 9 | * [Util](#util) 10 | 11 | To get started, you can browse the [javadocs](https://ibm.github.io/java-async-util/apidocs/overview-summary.html) or walk through some [example code](asyncutil/src/test/java/com/ibm/asyncutil/examples/nio/nio.md). 12 | 13 | ## Downloading 14 | 15 | To add a dependency on asyncutil 16 | ```xml 17 | 18 | com.ibm.async 19 | asyncutil 20 | 0.1.0 21 | 22 | ``` 23 | 24 | To get support for [Flow](https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html) (JDK9+ only) 25 | ```xml 26 | 27 | com.ibm.async 28 | asyncutil-flow 29 | 0.1.0 30 | 31 | ``` 32 | 33 | ## Locks 34 | The locks package provides asynchronous analogs of familiar synchronization primitives, all with efficient non-blocking implementations. Imagine we again have some source of asynchronity (say asynchronous network requests), and we'd like to implement an asynchronous method that makes a request and generates a result based on the request's response and some state that requires access under mutual exclusion. 35 | ```java 36 | class MyAsyncClass { 37 | // not thread safe 38 | private MutableState mutableState; 39 | 40 | private CompletionStage asyncNetworkOperation(Request request) {...} 41 | 42 | 43 | CompletionStage makeRequest(Request request) { 44 | return asyncNetworkOperation(request) 45 | .thenApply(response -> { 46 | // unsafe! 47 | mutableState.update(response); 48 | return mutableState.produceResult(); 49 | }); 50 | } 51 | } 52 | ``` 53 | If we wrap the `mutableState` operations in a `synchronized` block, we'll end up blocking the thread pool that runs our network operations. This is especially undesirable if this threadpool is possibly serving other interests in our application. We could solve that by creating our own thread pool just to do the locking + state manipulation using `thenApplyAsync` but that has a number of downsides 54 | * We've added more threads to our application for little benefit 55 | * If there's lock contention, we'll also be incurring a lot of additional context switching on these threads 56 | * If many locations in our application solve similar problems, they'll also have to create their own thread pools which is not scalable. 57 | 58 | Instead we can use `AsyncLock` to provide exclusive access to the `MutableState`. We will try to acquire the lock on the thread that completes the network operation stage, and if it is not available we'll receive a CompletionStage that will notify us when it becomes available. 59 | 60 | ```java 61 | ... 62 | private AsyncLock lock = AsyncLock.create(); 63 | 64 | CompletionStage makeRequest(Request request) { 65 | return asyncNetworkOperation(request) 66 | .thenCompose(response -> 67 | lock.acquireLock().thenApply(token -> { 68 | try { 69 | mutableState.update(response); 70 | return mutableState.produceResult(); 71 | } finally { 72 | token.release(); 73 | } 74 | }) 75 | }); 76 | } 77 | ``` 78 | for cleanliness, we can use `StageSupport.tryWith` for try-with-resources emulation: 79 | ```java 80 | CompletionStage makeRequest(Request request) { 81 | return asyncNetworkOperation(request) 82 | .thenCompose(response -> 83 | StageSupport.tryWith(lock.acquireLock(), ignored -> { 84 | mutableState.update(response); 85 | return mutableState.produceResult(); 86 | }) 87 | ); 88 | } 89 | ``` 90 | The package provides asynchronous versions of read/write locks, stamped locks, semaphores and named locks. The full [locks javadoc](https://ibm.github.io/java-async-util/apidocs/com/ibm/asyncutil/locks/package-summary.html) contains more information. 91 | 92 | ## Iteration 93 | The classes in this package provide ways to generate and consume results asynchronously. The main mechanism is `AsyncIterator` interface, which can be considered an asynchronous analog of the Stream API. The full [iteration javadocs](https://ibm.github.io/java-async-util/apidocs/com/ibm/asyncutil/iteration/package-summary.html) contains more information on `AsyncIterator` as well as other asynchronous iteration constructs. 94 | 95 | Consider the following example from the `Stream` documentation 96 | ```java 97 | int sum = widgets.stream() 98 | .filter(w -> w.getColor() == RED) 99 | .mapToInt(w -> w.getWeight()) 100 | .sum(); 101 | ``` 102 | Say widgets was not a concrete collection, but instead generating a widget involved an asynchronous network request (or an expensive CPU computation, etc). If we instead make the source of widgets an `AsyncIterator` we can asynchronously apply the pipeline every time a widget becomes available, and return a CompletionStage which will be complete when the pipeline has finished. In this example, let's say we are only interested in the first 100 red widgets. 103 | ```java 104 | // make an asynchronous network request that yields a widget 105 | CompletionStage getWidget(); 106 | 107 | CompletionStage sum = AsyncIterator 108 | .generate(() -> getWidget()) 109 | .filter(w -> w.getColor() == RED) 110 | .take(100) 111 | .thenApply(w -> w.getWeight()) 112 | .collect(Collectors.summingInt(i -> i)); 113 | ``` 114 | This will make one `getWidget` request at a time, running the rest of the pipeline operations each time a widget is generated on whatever thread processes the response required to generate the widget. When the widget stream is finished (in this case, after receiving 100 red widgets), the CompletionStage `sum` will complete with the result of the reduction operation. `AsyncIterators` have many other capabilities; if getting the weight required asynchronity we could use `thenCompose` instead of `thenApply`, if we needed to collect the weights into a collection we could use `collect(Collector)`, etc. 115 | 116 | It's often limiting to only be able to produce results for consumption iteratively. `AsyncQueue` provides ways to produce these values in parallel without any blocking synchronization: 117 | ```java 118 | // implements AsyncIterator 119 | AsyncQueue widgets = AsyncQueues.unbounded(); 120 | 121 | // dedicate NUM_THREADS threads to producing widgets 122 | for (int i = 0; i < NUM_THREADS; i++) { 123 | executor.submit(() -> { 124 | // send returns whether the queue is still accepting values 125 | while (widgets.send(expensiveComputeWidget()); 126 | }); 127 | } 128 | 129 | // create a pipeline the same way as before 130 | CompletionStage sum = widgets.filter(...)...; 131 | 132 | // once we get our sum, we can terminate the queue, stopping widget production 133 | sum.thenRun(() -> widgets.terminate()); 134 | ``` 135 | 136 | ## Util 137 | The util package contains interfaces and static functions for commonly reimplemented `CompletionStage` patterns. The best way to discover them all is to browse [the javadoc](https://ibm.github.io/java-async-util/apidocs/com/ibm/asyncutil/util/package-summary.html). 138 | 139 | ### `StageSupport` 140 | StageSupport contains miscellaneous utility functions, including methods to create already completed exceptional stages, common transformations, and methods for working with resources that need to be asynchronously released. 141 | 142 | ### `AsyncCloseable` 143 | An interface analogous to [AutoCloseable](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html) for objects that hold resources that must be relinquished asynchronously. By implementing `AsyncCloseable` with your own objects, you can take advantage of the `try*` methods on `StageSupport` to safely relinquish resources after performing asynchronous actions. 144 | 145 | ### `Combinators` 146 | Static functions for combining collections of stages into a single result stage. The Java standard library provides two such methods with [CompletableFuture.allOf](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#allOf-java.util.concurrent.CompletableFuture...-) / [CompletableFuture.anyOf](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#anyOf-java.util.concurrent.CompletableFuture...-) . Unfortunately, these only work with arrays of `CompletableFuture`, not `CompletionStage`, so you must first convert using `toCompletableFuture` if you wish to use these methods. Collections must be converted to arrays well. The methods on `Combinators` work on `CompletionStage` and collections directly, furthermore several additional useful combinators are added. For example, to get the results of multiple stages with `CompletableFuture.allOf`: 147 | ```java 148 | CompletableFuture[] arr = ... 149 | CompletionStage> listFuture = CompletableFuture.allOf(arr).thenApply(ignore -> { 150 | final List ints = new ArrayList<>(); 151 | for (CompletableFuture stage : arr) { 152 | return ints.add(stage.join()); 153 | } 154 | return ints; 155 | } 156 | ``` 157 | Instead, you can use `collect` on `Combinators`: 158 | ```java 159 | Collection> stages = ... 160 | CompletionStage> listFuture = Combinators.collect(stages, Collectors.toList()); 161 | ``` 162 | 163 | ## Contributing 164 | Contributions welcome! See [Contributing](CONTRIBUTING.md) for details. 165 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/AbstractAsyncNamedReadWriteLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.Arrays; 10 | import java.util.ConcurrentModificationException; 11 | import java.util.Optional; 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.concurrent.CompletionStage; 14 | 15 | import org.junit.Assert; 16 | import org.junit.Test; 17 | 18 | import com.ibm.asyncutil.util.Combinators; 19 | import com.ibm.asyncutil.util.TestUtil; 20 | 21 | public abstract class AbstractAsyncNamedReadWriteLockTest extends AbstractAsyncReadWriteLockTest { 22 | 23 | protected abstract AsyncNamedReadWriteLock getNamedReadWriteLock(); 24 | 25 | protected abstract boolean isEmpty(AsyncNamedReadWriteLock narwls); 26 | 27 | @Override 28 | protected final AsyncReadWriteLock getReadWriteLock() { 29 | return new AsyncNamedRWLockAsRWLock(getNamedReadWriteLock()); 30 | } 31 | 32 | @Test 33 | public final void testExclusivity() throws Exception { 34 | final AsyncNamedReadWriteLock narwls = getNamedReadWriteLock(); 35 | final String s1 = "abc"; 36 | final String s2 = "123"; 37 | 38 | Assert.assertTrue(isEmpty(narwls)); 39 | final CompletableFuture read1_1 = 40 | narwls.acquireReadLock(s1).toCompletableFuture(); 41 | Assert.assertFalse(isEmpty(narwls)); 42 | Assert.assertTrue(read1_1.isDone()); 43 | 44 | final CompletableFuture read1_2 = 45 | narwls.acquireReadLock(s1).toCompletableFuture(); 46 | Assert.assertTrue(read1_2.isDone()); 47 | 48 | final CompletableFuture write1_1 = 49 | narwls.acquireWriteLock(s1).toCompletableFuture(); 50 | Assert.assertFalse(write1_1.isDone()); 51 | 52 | TestUtil.join(read1_2).releaseLock(); 53 | Assert.assertFalse(write1_1.isDone()); 54 | 55 | // s2 can read or write while s1 is occupied 56 | Assert.assertTrue(narwls.acquireReadLock(s2) 57 | .thenAccept(readLock -> readLock.releaseLock()).toCompletableFuture().isDone()); 58 | Assert.assertTrue(narwls.acquireWriteLock(s2) 59 | .thenAccept(writeLock -> writeLock.releaseLock()).toCompletableFuture().isDone()); 60 | Assert.assertTrue(narwls.acquireReadLock(s2) 61 | .thenAccept(readLock -> readLock.releaseLock()).toCompletableFuture().isDone()); 62 | Assert.assertFalse(isEmpty(narwls)); 63 | 64 | TestUtil.join(read1_1).releaseLock(); 65 | Assert.assertTrue(write1_1.isDone()); 66 | 67 | Assert.assertFalse(narwls.acquireReadLock(s1) 68 | .thenAccept(readLock -> readLock.releaseLock()).toCompletableFuture().isDone()); 69 | 70 | TestUtil.join(write1_1).releaseLock(); 71 | Assert.assertTrue(isEmpty(narwls)); 72 | } 73 | 74 | private static class MutableKey { 75 | int value = 0; 76 | 77 | @Override 78 | public int hashCode() { 79 | return this.value; 80 | } 81 | } 82 | 83 | @Test(expected = ConcurrentModificationException.class) 84 | public final void testReadNameModificationException() { 85 | final AsyncNamedReadWriteLock lock = getNamedReadWriteLock(); 86 | final MutableKey key = new MutableKey(); 87 | final AsyncReadWriteLock.ReadLockToken readToken = TestUtil.join(lock.acquireReadLock(key)); 88 | key.value = 1; 89 | readToken.releaseLock(); 90 | } 91 | 92 | @Test(expected = ConcurrentModificationException.class) 93 | public final void testWriteNameModificationException() { 94 | final AsyncNamedReadWriteLock lock = getNamedReadWriteLock(); 95 | final MutableKey key = new MutableKey(); 96 | final AsyncReadWriteLock.WriteLockToken writeToken = TestUtil.join(lock.acquireWriteLock(key)); 97 | key.value = 1; 98 | writeToken.releaseLock(); 99 | } 100 | 101 | public static abstract class AbstractAsyncNamedReadWriteLockFairnessTest 102 | extends AbstractAsyncNamedReadWriteLockTest { 103 | @Test 104 | public void testFairness() throws Exception { 105 | final AsyncNamedReadWriteLock narwls = getNamedReadWriteLock(); 106 | final String s = "abc"; 107 | 108 | final CompletableFuture write1 = 109 | narwls.acquireWriteLock(s).toCompletableFuture(); 110 | Assert.assertTrue(write1.isDone()); 111 | 112 | // under write lock 113 | final CompletableFuture read1 = 114 | narwls.acquireReadLock(s).toCompletableFuture(); 115 | final CompletableFuture read2 = 116 | narwls.acquireReadLock(s).toCompletableFuture(); 117 | final CompletableFuture read3 = 118 | narwls.acquireReadLock(s).toCompletableFuture(); 119 | Assert.assertFalse(read1.isDone()); 120 | Assert.assertFalse(read2.isDone()); 121 | Assert.assertFalse(read3.isDone()); 122 | 123 | final CompletableFuture write2 = 124 | narwls.acquireWriteLock(s).toCompletableFuture(); 125 | Assert.assertFalse(write2.isDone()); 126 | 127 | final CompletableFuture read4 = 128 | narwls.acquireReadLock(s).toCompletableFuture(); 129 | final CompletableFuture read5 = 130 | narwls.acquireReadLock(s).toCompletableFuture(); 131 | final CompletableFuture read6 = 132 | narwls.acquireReadLock(s).toCompletableFuture(); 133 | Assert.assertFalse(read4.isDone()); 134 | Assert.assertFalse(read5.isDone()); 135 | Assert.assertFalse(read6.isDone()); 136 | 137 | final CompletableFuture write3 = 138 | narwls.acquireWriteLock(s).toCompletableFuture(); 139 | Assert.assertFalse(write3.isDone()); 140 | 141 | TestUtil.join(write1).releaseLock(); 142 | Assert.assertTrue(read1.isDone()); 143 | Assert.assertTrue(read2.isDone()); 144 | Assert.assertTrue(read3.isDone()); 145 | Assert.assertFalse(write2.isDone()); 146 | Assert.assertFalse(read4.isDone()); 147 | Assert.assertFalse(read5.isDone()); 148 | Assert.assertFalse(read6.isDone()); 149 | Assert.assertFalse(write3.isDone()); 150 | 151 | TestUtil.join(read1).releaseLock(); 152 | TestUtil.join(read2).releaseLock(); 153 | Assert.assertTrue(read3.isDone()); 154 | Assert.assertFalse(write2.isDone()); 155 | Assert.assertFalse(read4.isDone()); 156 | Assert.assertFalse(read5.isDone()); 157 | Assert.assertFalse(read6.isDone()); 158 | Assert.assertFalse(write3.isDone()); 159 | 160 | // now under read lock (read3 still active) 161 | final CompletableFuture read7 = 162 | narwls.acquireReadLock(s).toCompletableFuture(); 163 | final CompletableFuture write4 = 164 | narwls.acquireWriteLock(s).toCompletableFuture(); 165 | 166 | Assert.assertTrue(read3.isDone()); 167 | Assert.assertFalse(write2.isDone()); 168 | Assert.assertFalse(read4.isDone()); 169 | Assert.assertFalse(read5.isDone()); 170 | Assert.assertFalse(read6.isDone()); 171 | Assert.assertFalse(write3.isDone()); 172 | Assert.assertFalse(read7.isDone()); 173 | Assert.assertFalse(write4.isDone()); 174 | 175 | TestUtil.join(read3).releaseLock(); 176 | Assert.assertTrue(write2.isDone()); 177 | Assert.assertFalse(read4.isDone()); 178 | Assert.assertFalse(read5.isDone()); 179 | Assert.assertFalse(read6.isDone()); 180 | Assert.assertFalse(write3.isDone()); 181 | Assert.assertFalse(read7.isDone()); 182 | Assert.assertFalse(write4.isDone()); 183 | 184 | TestUtil.join(write2).releaseLock(); 185 | Assert.assertTrue(read4.isDone()); 186 | Assert.assertTrue(read5.isDone()); 187 | Assert.assertTrue(read6.isDone()); 188 | Assert.assertFalse(write3.isDone()); 189 | Assert.assertFalse(read7.isDone()); 190 | Assert.assertFalse(write4.isDone()); 191 | 192 | TestUtil.join(Combinators.collect(Arrays.asList(read4, read5, read6))) 193 | .forEach(readLock -> readLock.releaseLock()); 194 | Assert.assertTrue(write3.isDone()); 195 | Assert.assertFalse(read7.isDone()); 196 | Assert.assertFalse(write4.isDone()); 197 | 198 | TestUtil.join(write3).releaseLock(); 199 | Assert.assertTrue(read7.isDone()); 200 | Assert.assertFalse(write4.isDone()); 201 | 202 | TestUtil.join(read7).releaseLock(); 203 | Assert.assertTrue(write4.isDone()); 204 | 205 | TestUtil.join(write4).releaseLock(); 206 | Assert.assertTrue(isEmpty(narwls)); 207 | } 208 | } 209 | } 210 | 211 | 212 | class AsyncNamedRWLockAsRWLock implements AsyncReadWriteLock { 213 | private static final Object KEY = new Object(); 214 | private final AsyncNamedReadWriteLock lock; 215 | 216 | public AsyncNamedRWLockAsRWLock(final AsyncNamedReadWriteLock lock) { 217 | this.lock = lock; 218 | } 219 | 220 | @Override 221 | public CompletionStage acquireReadLock() { 222 | return this.lock.acquireReadLock(KEY); 223 | } 224 | 225 | @Override 226 | public CompletionStage acquireWriteLock() { 227 | return this.lock.acquireWriteLock(KEY); 228 | } 229 | 230 | @Override 231 | public Optional tryReadLock() { 232 | return this.lock.tryReadLock(KEY); 233 | } 234 | 235 | @Override 236 | public Optional tryWriteLock() { 237 | return this.lock.tryWriteLock(KEY); 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /asyncutil/src/test/java/com/ibm/asyncutil/locks/AbstractAsyncLockTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.locks; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.concurrent.Executor; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.TimeoutException; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | import java.util.function.Function; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.IntStream; 21 | 22 | import org.junit.AfterClass; 23 | import org.junit.Assert; 24 | import org.junit.Assume; 25 | import org.junit.BeforeClass; 26 | import org.junit.Test; 27 | 28 | import com.ibm.asyncutil.util.Reference; 29 | import com.ibm.asyncutil.util.TestUtil; 30 | 31 | public abstract class AbstractAsyncLockTest { 32 | 33 | protected abstract AsyncLock getLock(); 34 | 35 | /** 36 | * Stack overflow tests tend to fail (or require huge thread growth) when using sanity stdlib sync 37 | * backed tests 38 | */ 39 | protected boolean isActuallyAsync() { 40 | return true; 41 | } 42 | 43 | @Test 44 | public final void testMutex() { 45 | final AsyncLock lock = getLock(); 46 | 47 | final AtomicInteger participants = new AtomicInteger(); 48 | final Function mutexCheck = input -> { 49 | if (participants.getAndIncrement() != 0) { 50 | throw new AssertionError("lock not mutually exclusive"); 51 | } 52 | try { 53 | Thread.sleep(20); 54 | } catch (final InterruptedException e) { 55 | throw new RuntimeException(e); 56 | } 57 | if (participants.decrementAndGet() != 0) { 58 | throw new AssertionError("lock not mutually exclusive"); 59 | } 60 | return input; 61 | }; 62 | 63 | final int parallel = 20; 64 | final List> acqs = IntStream.range(0, parallel) 65 | // start acquisitions in separate threads, but collect the futures back together whether 66 | // they're done or not 67 | .mapToObj( 68 | i -> CompletableFuture.supplyAsync(() -> lock.acquireLock().thenApply(mutexCheck))) 69 | .map(CompletableFuture::join).collect(Collectors.toList()); 70 | 71 | TestUtil.join(CompletableFuture.allOf(acqs.stream() 72 | .map(f -> f.thenAccept(AsyncLock.LockToken::releaseLock)) 73 | .map(CompletionStage::toCompletableFuture) 74 | .toArray(CompletableFuture[]::new))); 75 | 76 | } 77 | 78 | @Test 79 | public final void testStackOverflow() throws Exception { 80 | Assume.assumeTrue(isActuallyAsync()); 81 | 82 | // test no overflow 83 | { 84 | final AsyncLock lock = getLock(); 85 | final CompletionStage firstLock = lock.acquireLock(); 86 | final Reference> lastLock = new Reference<>(null); 87 | 88 | CompletableFuture.runAsync(() -> { 89 | // chain futures on the single write lock 90 | for (int i = 0; i < 100_000; i++) { 91 | lock.acquireLock().thenAccept(AsyncLock.LockToken::releaseLock); 92 | } 93 | lastLock.set(lock.acquireLock().thenApply(token -> { 94 | token.releaseLock(); 95 | return token; 96 | })); 97 | }).join(); 98 | 99 | TestUtil.join(firstLock).releaseLock(); 100 | TestUtil.join(lastLock.get(), 2, TimeUnit.SECONDS); 101 | } 102 | 103 | // test threading ABA for unroll 104 | { 105 | final Executor execA = Executors.newSingleThreadExecutor(); 106 | final Executor execB = Executors.newSingleThreadExecutor(); 107 | 108 | final AsyncLock lock = getLock(); 109 | final AsyncLock.LockToken acq1 = TestUtil.join(lock.acquireLock()); 110 | 111 | // acquires primed to release on threads: main, A, B, A 112 | final CompletableFuture acq2 = 113 | lock.acquireLock().thenAcceptAsync(AsyncLock.LockToken::releaseLock, execA) 114 | .toCompletableFuture(); 115 | final CompletableFuture acq3 = 116 | lock.acquireLock().thenAcceptAsync(AsyncLock.LockToken::releaseLock, execB) 117 | .toCompletableFuture(); 118 | final CompletableFuture acq4 = 119 | lock.acquireLock().thenAcceptAsync(AsyncLock.LockToken::releaseLock, execA) 120 | .toCompletableFuture(); 121 | 122 | 123 | acq1.releaseLock(); 124 | // wait for everybody to do everything 125 | TestUtil.join(CompletableFuture.allOf(acq2, acq3, acq4)); 126 | TestUtil.join(lock.acquireLock().thenAccept(AsyncLock.LockToken::releaseLock), 2, 127 | TimeUnit.SECONDS); 128 | } 129 | 130 | // test threading AA for unroll (release on same thread, but separate stacks) 131 | { 132 | final Executor execA = Executors.newSingleThreadExecutor(); 133 | 134 | final AsyncLock lock = getLock(); 135 | final AsyncLock.LockToken acq1 = TestUtil.join(lock.acquireLock()); 136 | 137 | // acqrs primed to release on threads: main, A, A 138 | final CompletableFuture acq2 = 139 | lock.acquireLock().thenAcceptAsync(AsyncLock.LockToken::releaseLock, execA) 140 | .toCompletableFuture(); 141 | final CompletableFuture acq3 = 142 | lock.acquireLock().thenAcceptAsync(AsyncLock.LockToken::releaseLock, execA) 143 | .toCompletableFuture(); 144 | 145 | 146 | acq1.releaseLock(); 147 | TestUtil.join(CompletableFuture.allOf(acq2, acq3)); 148 | TestUtil.join(lock.acquireLock().thenAccept(AsyncLock.LockToken::releaseLock), 2, 149 | TimeUnit.SECONDS); 150 | } 151 | } 152 | 153 | @SuppressWarnings("serial") 154 | private static class LockStateException extends RuntimeException { 155 | } 156 | 157 | @Test(expected = LockStateException.class) 158 | public final void testReleaseException() { 159 | final AsyncLock.LockToken token = TestUtil.join(getLock().acquireLock()); 160 | 161 | token.releaseLock(); 162 | try { 163 | token.releaseLock(); 164 | } catch (IllegalStateException | IllegalMonitorStateException expected) { 165 | throw new LockStateException(); 166 | } catch (final Error e) { 167 | // j.u.c.Semaphore throws an Error with this message 168 | if (e.getMessage().equals("Maximum permit count exceeded")) { 169 | throw new LockStateException(); 170 | } else { 171 | throw e; 172 | } 173 | } 174 | } 175 | 176 | @Test 177 | public final void testTryLock() throws TimeoutException { 178 | final AsyncLock lock = getLock(); 179 | // first lock should succeed 180 | final AsyncLock.LockToken acq1 = lock.tryLock().orElseThrow(AssertionError::new); 181 | // other lock should fail 182 | Assert.assertFalse(lock.tryLock().isPresent()); 183 | 184 | final CompletableFuture acq2 = lock.acquireLock().toCompletableFuture(); 185 | Assert.assertFalse(acq2.isDone()); 186 | 187 | acq1.releaseLock(); 188 | final AsyncLock.LockToken acq2Token = TestUtil.join(acq2, 500, TimeUnit.MILLISECONDS); 189 | 190 | Assert.assertFalse(lock.tryLock().isPresent()); 191 | acq2Token.releaseLock(); 192 | 193 | lock.tryLock().orElseThrow(AssertionError::new).releaseLock(); 194 | } 195 | 196 | public static abstract class AbstractAsyncLockFairnessTest extends AbstractAsyncLockTest { 197 | @Test 198 | public final void testFairness() throws Exception { 199 | final AsyncLock lock = getLock(); 200 | 201 | final CompletableFuture acq1 = lock.acquireLock().toCompletableFuture(); 202 | final CompletableFuture acq2 = lock.acquireLock().toCompletableFuture(); 203 | final CompletableFuture acq3 = lock.acquireLock().toCompletableFuture(); 204 | 205 | final AsyncLock.LockToken token1 = TestUtil.join(acq1, 500, TimeUnit.MILLISECONDS); 206 | Assert.assertFalse(acq2.isDone()); 207 | Assert.assertFalse(acq3.isDone()); 208 | 209 | token1.releaseLock(); 210 | 211 | final CompletableFuture acq4 = lock.acquireLock().toCompletableFuture(); 212 | final AsyncLock.LockToken token2 = TestUtil.join(acq2, 500, TimeUnit.MILLISECONDS); 213 | Assert.assertFalse(acq3.isDone()); 214 | Assert.assertFalse(acq4.isDone()); 215 | 216 | token2.releaseLock(); 217 | 218 | final AsyncLock.LockToken token3 = TestUtil.join(acq3, 500, TimeUnit.MILLISECONDS); 219 | Assert.assertFalse(acq4.isDone()); 220 | 221 | token3.releaseLock(); 222 | 223 | TestUtil.join(acq4, 500, TimeUnit.MILLISECONDS).releaseLock(); 224 | } 225 | } 226 | 227 | public static class SyncSemaphoreAsyncLockTest extends AbstractAsyncLockFairnessTest { 228 | @Override 229 | protected boolean isActuallyAsync() { 230 | return false; 231 | } 232 | 233 | private static ExecutorService pool; 234 | 235 | @BeforeClass 236 | public static void setupPool() { 237 | pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 238 | } 239 | 240 | @AfterClass 241 | public static void shutdownPool() throws InterruptedException { 242 | pool.shutdown(); 243 | pool.awaitTermination(10, TimeUnit.SECONDS); 244 | } 245 | 246 | @Override 247 | protected AsyncLock getLock() { 248 | return new RWLockAsAsyncLock(new SemaphoreAsAsyncReadWriteLock( 249 | permits -> new SyncAsyncSemaphore(permits, true, pool), Integer.MAX_VALUE)); 250 | } 251 | } 252 | } 253 | 254 | -------------------------------------------------------------------------------- /asyncutil/src/main/java/com/ibm/asyncutil/util/Either.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) IBM Corporation 2017. All Rights Reserved. 3 | * Project name: java-async-util 4 | * This project is licensed under the Apache License 2.0, see LICENSE. 5 | */ 6 | 7 | package com.ibm.asyncutil.util; 8 | 9 | import java.util.Optional; 10 | import java.util.function.Consumer; 11 | import java.util.function.Function; 12 | 13 | /** 14 | * A container type that contains either an L or an R type object. An Either indicating a left value 15 | * can be constructed with {@link #left() Either.left(value)}, a right with {@link #right() 16 | * Either.right(value)}. 17 | * 18 | *

19 | * By convention, if using this class to represent a result that may be an error or a success, the 20 | * error type should be in the left position and the success type should be in the right position 21 | * (mnemonic: "right" also means correct). As a result, this class implements monadic methods 22 | * (similar to those on {@link Optional}, {@link java.util.stream.Stream}, etc) for working on the R 23 | * type. 24 | * 25 | *

 26 |  * {@code
 27 |  * Either tryGetString();
 28 |  * Either tryParse(String s);
 29 |  * Double convert(Integer i);
 30 |  *
 31 |  * // if any intermediate step fails, will just be an Either.left(exception).
 32 |  * Either eitherDouble = tryGetString() // Either
 33 |  *  .flatMap(tryParse) // Either
 34 |  *  .map(convert); // Either
 35 |  *
 36 |  * }
 37 |  * 
38 | * 39 | * @author Ravi Khadiwala 40 | * @param The left type, by convention the error type if being used to represent possible errors 41 | * @param The right type, by convention the success type if being used to represent possible 42 | * errors 43 | */ 44 | public interface Either { 45 | 46 | /** 47 | * Whether this container holds an L type 48 | * 49 | * @return true if this is a Left, false otherwise 50 | */ 51 | default boolean isLeft() { 52 | return fold(left -> true, right -> false); 53 | } 54 | 55 | /** 56 | * Whether this container holds an R type 57 | * 58 | * @return true if this is a Right, false otherwise 59 | */ 60 | default boolean isRight() { 61 | return fold(left -> false, right -> true); 62 | } 63 | 64 | /** 65 | * Applies exactly one of the two provided functions to produce a value of type {@code V}. For 66 | * example, applying an int function or defaulting to zero on error: 67 | * 68 | *
 69 |    * {@code
 70 |    * {
 71 |    *   Either either = tryGetInteger();
 72 |    *   int halvedOrZero = either.fold(e -> 0, i -> i / 2);
 73 |    * }}
 74 |    * 
75 | * 76 | * @param leftFn a function the produces a V from an L, only applied if {@code this} contains an L 77 | * type 78 | * @param rightFn a function the produces a V from an R, only applied if {@code this} contains an 79 | * R type 80 | * @param the return type 81 | * @return a {@code V} value produced by {@code leftFn} if {@code this} contained an L, produced 82 | * by {@code rightFn} otherwise. 83 | * @throws NullPointerException if the function to be applied is null 84 | */ 85 | V fold( 86 | final Function leftFn, 87 | final Function rightFn); 88 | 89 | /** 90 | * Calls exactly one of the two provided functions with an L or an R 91 | * 92 | * @param leftConsumer a function that consumes an L, only applied if {@code this} contains an L 93 | * type 94 | * @param rightConsumer a function the consumes an R, only applied if {@code this} contains an R 95 | * type 96 | * @throws NullPointerException if the function to be applied is null 97 | */ 98 | default void forEach( 99 | final Consumer leftConsumer, final Consumer rightConsumer) { 100 | fold( 101 | left -> { 102 | leftConsumer.accept(left); 103 | return null; 104 | }, 105 | right -> { 106 | rightConsumer.accept(right); 107 | return null; 108 | }); 109 | } 110 | 111 | /** 112 | * Creates a new Either possibly of two new and distinct types, by applying the provided 113 | * transformation functions. 114 | * 115 | *
116 |    * {@code
117 |    * Either result;
118 |    * Either x = result.map(d -> d.toString(), i -> i % 2 == 0)
119 |    * }
120 |    * 
121 | * 122 | * @param leftFn a function that takes an L and produces an A, to be applied if {@code this} 123 | * contains an L 124 | * @param rightFn a function that takes an R and produces and B, to be applied if {@code this} 125 | * contains an R 126 | * @param the left type of the returned Either 127 | * @param the right type of the returned Either 128 | * @return a new Either, containing an A resulting from the application of leftFn if {@code this} 129 | * contained an L, or containing a B resulting from the application of rightFn otherwise 130 | * @throws NullPointerException if the function to be applied is null 131 | */ 132 | default Either map( 133 | final Function leftFn, 134 | final Function rightFn) { 135 | return fold( 136 | left -> Either.left(leftFn.apply(left)), right -> Either.right(rightFn.apply(right))); 137 | } 138 | 139 | /** 140 | * Transforms the right type of {@code this}, producing an Either of the transformed value if 141 | * {@code this} contained right, or an Either of the original left value otherwise. For example, 142 | * if we have either a Double or an error, convert it into an Integer if it's a Double, then 143 | * convert it to a String if it's an Integer: 144 | * 145 | *
146 |    * {@code
147 |    * Either eitherDouble;
148 |    * Either eitherString = eitherDouble.map(Double::intValue).map(Integer::toString)
149 |    * }
150 |    * 
151 | * 152 | * @param fn function that takes an R value and produces a V value, to be applied if {@code this} 153 | * contains an R 154 | * @param the right type of the returned Either 155 | * @return an Either containing the transformed value produced by {@code fn} if {@code this} 156 | * contained an R, or the original L value otherwise. 157 | */ 158 | default Either map(final Function fn) { 159 | return fold(Either::left, r -> Either.right(fn.apply(r))); 160 | } 161 | 162 | /** 163 | * Transforms the right type of {@code this}, producing a right Either of type {@code V} if {@code 164 | * this} was right and {@code f} produced a right Either, or a left Either otherwise. For 165 | * example, if we have either a String or an error, attempt to parse into an Integer (which could 166 | * potentially itself produce an error): 167 | * 168 | *
169 |    * {@code
170 |    * Either tryParse(String s);
171 |    * Either eitherString;
172 |    *
173 |    * // will be an exception if eitherString was an exception
174 |    * // or if tryParse failed, otherwise will be an Integer
175 |    * Either e = eitherString.flatMap(tryParse);
176 |    *
177 |    * }
178 |    * 
179 | * 180 | * @param f a function that takes an R value and produces an Either of a L value or a V value 181 | * @param the right type of the returned Either 182 | * @return an Either containing the right value produced by {@code f} if both {@code this} and the 183 | * produced Either were right, the left value of the produced Either if {@code this} was 184 | * right but the produced value wasn't, or the original left value if {@code this} was 185 | * left. 186 | */ 187 | default Either flatMap(final Function> f) { 188 | return fold(Either::left, f); 189 | } 190 | 191 | /** 192 | * Optionally gets the left value of {@code this} if it exists 193 | * 194 | * @return a present {@link Optional} of an L if {@code this} contains an L, an empty one 195 | * otherwise. 196 | * @throws NullPointerException if the left value of {@code this} is present and null 197 | */ 198 | default Optional left() { 199 | return fold(Optional::of, r -> Optional.empty()); 200 | } 201 | 202 | /** 203 | * Optionally gets the right value of {@code this} if it exists 204 | * 205 | * @return a present {@link Optional} of an R if {@code this} contains an R, an empty one 206 | * otherwise. 207 | * @throws NullPointerException if the right value of {@code this} is present and null 208 | */ 209 | default Optional right() { 210 | return fold(l -> Optional.empty(), Optional::of); 211 | } 212 | 213 | /** 214 | * Constructs an Either with a left value 215 | * 216 | * @param a the left element of the new Either 217 | * @return an Either with a left value 218 | */ 219 | static Either left(final A a) { 220 | return new Either() { 221 | 222 | @Override 223 | public V fold( 224 | final Function leftFn, 225 | final Function rightFn) { 226 | return leftFn.apply(a); 227 | } 228 | 229 | @Override 230 | public String toString() { 231 | if (a == null) { 232 | return "Left (null type): null"; 233 | } else { 234 | return String.format("Left (%s): %s", a.getClass(), a); 235 | } 236 | } 237 | }; 238 | } 239 | 240 | /** 241 | * Constructs an Either with a right value 242 | * 243 | * @param b the right element of the new Either 244 | * @return An Either with a right value 245 | */ 246 | static Either right(final B b) { 247 | return new Either() { 248 | 249 | @Override 250 | public V fold( 251 | final Function leftFn, 252 | final Function rightFn) { 253 | return rightFn.apply(b); 254 | } 255 | 256 | @Override 257 | public String toString() { 258 | if (b == null) { 259 | return "Right (null type): null"; 260 | } else { 261 | return String.format("Right (%s): %s", b.getClass(), b); 262 | } 263 | } 264 | }; 265 | } 266 | } 267 | --------------------------------------------------------------------------------