
com.oracle.bedrock.deferred.Concurrently Maven / Gradle / Ivy
/*
* File: Concurrently.java
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* The contents of this file are subject to the terms and conditions of
* the Common Development and Distribution License 1.0 (the "License").
*
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the License by consulting the LICENSE.txt file
* distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file LICENSE.txt.
*
* MODIFICATIONS:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*/
package com.oracle.bedrock.deferred;
import com.oracle.bedrock.Option;
import com.oracle.bedrock.OptionsByType;
import com.oracle.bedrock.deferred.options.FailFast;
import com.oracle.bedrock.deferred.options.InitialDelay;
import com.oracle.bedrock.deferred.options.MaximumRetryDelay;
import com.oracle.bedrock.deferred.options.RetryFrequency;
import com.oracle.bedrock.options.Timeout;
import com.oracle.bedrock.runtime.concurrent.RemoteCallable;
import com.oracle.bedrock.runtime.java.JavaApplication;
import org.hamcrest.Matcher;
import static com.oracle.bedrock.deferred.DeferredHelper.eventually;
import static com.oracle.bedrock.deferred.DeferredHelper.valueOf;
/**
* Commonly used "assertThat" methods for the purposes of concurrently asserting
* the state of {@link Deferred} values using the style Concurrently.assertThat(...)
*
*
* try (Concurrent.Assertion assertion = Concurrently.assertThat(invoking(someObject).get(), is(someValue))) {
*
* // ...perform some operations...
*
* assertion.check(); // ensure the assertion still holds
*
* // ...perform some other operations...
*
* assertion.check(); // ensure the assertion still holds
* }
*
*
* Concurrently.assertThat(...)
methods are intended be used within
* try-with-resources blocks. When not, they should always be closed to ensure correct
* clean-up of background resources used for concurrent assertion.
*
* To control "fail-fast" semantics, where by the {@link Concurrent.Assertion} may interrupt the
* {@link Thread} that created the {@link Concurrent.Assertion}, the {@link FailFast} option
* should be specified.
*
* try (Concurrent.Assertion assertion = Concurrently.assertThat(invoking(someObject).get(),
* is(someValue),
* FailFast.enabled())) {
*
* // ...perform some long running operations... (will be interrupted if the assertion fails)
*
* } catch (InterruptedException e) {
* // determine if the InterruptedException was due to AssertionError
* Concurrent.Assertion.check(e);
* }
*
*
* To customize assertion behavior, this class allows extensive use of
* {@link Option}s. For example, the following {@link Option}s may be used
* to customize timeout constraints when asserting {@link Deferred} values;
* {@link Timeout}, {@link MaximumRetryDelay}, Initial {@link InitialDelay} and
* {@link RetryFrequency}.
*
* Copyright (c) 2016. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
*
* @see Eventually
* @see Repetitively
*
* @author Brian Oliver
*/
public class Concurrently
{
/**
* Creates a background thread to repetitively assert that a value satisfies the
* specified {@link Matcher} using the specified {@link Option}s.
*
* Should the value be the result of a call to {@link DeferredHelper#invoking(Deferred)}
* the result is unwrapped into a {@link Deferred} that is then used for the assert.
*
* @param the type of the value
*
* @param value the value
* @param matcher the {@link Matcher} for the value
* @param options the {@link Option}s for the assertion
*
* @return an {@link Concurrent.Assertion} to be used to determine the current success
* of the concurrent assertion
*
* @throws AssertionError if the assertion fails
*/
public static Concurrent.Assertion assertThat(T value,
Matcher super T> matcher,
Option... options) throws AssertionError
{
return assertThat(null, eventually(value), matcher, options);
}
/**
* Creates a background thread to repetitively assert that a value will
* satisfy the specified {@link Matcher}.
*
* Should the value be the result of a call to {@link DeferredHelper#invoking(Deferred)}
* the result is unwrapped into a {@link Deferred} that is then used for the assert.
*
* @param the type of the value
*
* @param message the message for the AssertionError (null
ok)
* @param value the value
* @param matcher the {@link Matcher} for the value
*
* @return an {@link Concurrent.Assertion} to be used to determine the current success
* of the concurrent assertion
*
* @throws AssertionError if the assertion fails
*/
public static Concurrent.Assertion assertThat(String message,
T value,
Matcher super T> matcher) throws AssertionError
{
return assertThat(message, eventually(value), matcher);
}
/**
* Creates a background thread to repetitively assert that a value satisfies the
* specified {@link Matcher} using the provided {@link Option}s.
*
* Should the value be the result of a call to {@link DeferredHelper#invoking(Deferred)}
* the result is unwrapped into a {@link Deferred} that is then used for the assert.
*
* @param the type of the value
*
* @param message the message for the AssertionError (null
ok)
* @param value the value
* @param matcher the {@link Matcher} for the value
* @param options the {@link Option}s
*
* @return an {@link Concurrent.Assertion} to be used to determine the current success
* of the concurrent assertion
*
* @throws AssertionError if the assertion fails
*/
public static Concurrent.Assertion assertThat(String message,
T value,
Matcher super T> matcher,
Option... options) throws AssertionError
{
return assertThat(message, eventually(value), matcher, options);
}
/**
* Creates a background thread to repetitively assert that a {@link Deferred}
* value, when available, satisfies the specified {@link Matcher} using
* constraints defined by the provided {@link Option}s.
*
* @param the type of the value
*
* @param message the message for the AssertionError (null
ok)
* @param deferred the {@link Deferred} value
* @param matcher the {@link Matcher} for the value
* @param options the {@link Option}s
*
* @return an {@link Concurrent.Assertion} providing the ability to check the status and
* control the evaluation of the concurrent {@link Concurrent.Assertion}
*/
public static Concurrent.Assertion assertThat(String message,
Deferred deferred,
Matcher super T> matcher,
Option... options)
{
// create the assertion
ConcurrentAssertion assertion = new ConcurrentAssertion(message, deferred, matcher, options);
// start the assertion in the background
assertion.setDaemon(true);
assertion.setName("ConcurrentAssertion");
assertion.start();
return assertion;
}
/**
* Creates a background thread to repetitively assert that the specified {@link RemoteCallable}
* submitted to the {@link JavaApplication} matches the specified {@link Matcher}.
*
* @param the type of the value
*
* @param application the {@link JavaApplication} to which the {@link RemoteCallable} will be submitted
* @param callable the {@link RemoteCallable}
* @param matcher the {@link Matcher} representing the desire condition to match
*
* @throws AssertionError if the assertion fails
*/
public static void assertThat(JavaApplication application,
RemoteCallable callable,
Matcher super T> matcher) throws AssertionError
{
assertThat(valueOf(new DeferredRemoteExecution(application, callable)), matcher);
}
/**
* Creates a background thread to repetitively assert that the specified {@link RemoteCallable}
* submitted to the {@link JavaApplication} matches the specified {@link Matcher}
* using the specified {@link Option}s.
*
* @param the type of the value
*
* @param application the {@link JavaApplication} to which the {@link RemoteCallable} will be submitted
* @param callable the {@link RemoteCallable}
* @param matcher the {@link Matcher} representing the desire condition to match
* @param options the {@link Option}s
*
* @throws AssertionError if the assertion fails
*/
public static void assertThat(JavaApplication application,
RemoteCallable callable,
Matcher super T> matcher,
Option... options) throws AssertionError
{
assertThat(valueOf(new DeferredRemoteExecution(application, callable)), matcher, options);
}
/**
* An implementation of a concurrent {@link Concurrent.Assertion}.
*
* @param the type of value being asserted
*/
static class ConcurrentAssertion extends Thread implements Concurrent.Assertion
{
/**
* The optional message for the {@link AssertionError}.
*/
private final String message;
/**
* The {@link Deferred} value to assert.
*/
private final Deferred deferred;
/**
* The {@link Matcher}.
*/
private final Matcher super T> matcher;
/**
* The {@link OptionsByType}.
*/
private final OptionsByType optionsByType;
/**
* The {@link Thread} that created this {@link ConcurrentAssertion}.
*/
private final Thread creatingThread;
/**
* The last encountered {@link AssertionError}.
*/
private volatile AssertionError assertionError;
/**
* A flag indicating if the {@link ConcurrentAssertion} is closing.
*/
private volatile boolean closing;
/**
* A flag indicating if the {@link ConcurrentAssertion} is closed.
*/
private volatile boolean closed;
/**
* A flag indicating if the {@link AssertionError} should be thrown
* when closing (in the {@link #close()} method).
*/
private volatile boolean throwAssertionErrorWhenClosing;
/**
* Constructs a {@link ConcurrentAssertion}.
*
* @param message the message for the AssertionError (null
ok)
* @param deferred the {@link Deferred} value
* @param matcher the {@link Matcher} for the value
* @param options the {@link Option}s
*/
public ConcurrentAssertion(String message,
Deferred deferred,
Matcher super T> matcher,
Option... options)
{
this.message = message;
this.deferred = deferred;
this.matcher = matcher;
this.optionsByType = OptionsByType.of(options);
this.creatingThread = Thread.currentThread();
this.assertionError = null;
this.closing = false;
this.closed = false;
this.throwAssertionErrorWhenClosing = true;
}
@Override
public void check() throws AssertionError
{
AssertionError assertionError = this.assertionError;
if (assertionError != null)
{
// as we've checked the exception, we no longer need to throw it
// (to avoid it being thrown twice)
throwAssertionErrorWhenClosing = false;
throw assertionError;
}
}
@Override
public boolean isClosed()
{
return closed;
}
@Override
public void close()
{
// we're now closing
closing = true;
// interrupt ourselves to commence clean up
this.interrupt();
// throw the AssertionError (if we have one and we're throwing them)
// (to allow it to be caught or seen as suppressed)
if (assertionError != null && throwAssertionErrorWhenClosing)
{
throw assertionError;
}
}
@Override
public void run()
{
try
{
while (!closing)
{
Repetitively.assertThat(message, deferred, matcher, optionsByType.asArray());
}
}
catch (AssertionError e)
{
if (e.getCause() instanceof InterruptedException && closing)
{
// we're ok being interrupted while closing!
}
else
{
assertionError = e;
if (optionsByType.get(FailFast.class).isEnabled())
{
// attempt to interrupt the thread that created the ConcurrentAssertion
creatingThread.interrupt();
}
}
}
finally
{
closed = true;
}
}
}
}