
oracle.kv.impl.async.AsyncIterationHandleImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oracle-nosql-server Show documentation
Show all versions of oracle-nosql-server Show documentation
NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.
The newest version!
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.async;
import static oracle.kv.impl.util.ObjectUtil.checkNull;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import oracle.kv.IterationSubscription;
import oracle.kv.stats.DetailedMetrics;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
/**
* A class that generates subscriptions, to implement {@link Publisher} for
* async iterations.
*
* @param the type of the iteration element
*/
public class AsyncIterationHandleImpl implements IterationHandleNotifier {
/** The number of currently open subscriptions. */
private static final AtomicInteger openSubscriptions = new AtomicInteger();
/**
* A thread local variable that records whether a call to the request
* method is already underway in the current thread.
*/
private final ThreadLocal inRequest = new ThreadLocal();
/**
* The logger.
*/
private final Logger logger;
/**
* The object used for locking, to avoid interference from application code
* that could lock this instance. Non-final fields should only be accessed
* by using synchronization on this lock, either from within a synchronized
* block, in a code block that is running exclusively because it set
* notifyingSubscriber while synchronized, or, for fields that are only set
* once to a non-null value, after checking for a non-null value while
* synchronized. The newNotify field is special since it is set by
* non-notifying threads: that field should only be read while in a
* synchronized block.
*/
private final Object lock = new Object();
/**
* The async iterator that supplies locally available values and transmits
* close requests, or null if not initialized. Set only once.
*/
private AsyncTableIterator asyncIterator;
/** The subscriber, or null if either not initialized or complete. */
private Subscriber super E> subscriber;
/**
* If the subscriber is newly supplied, meaning that onSubscribe should be
* called.
*/
private boolean newSubscriber;
/** The number of iteration items requested. */
private long requests;
/**
* Used to make sure only one thread makes calls to the subscriber at a
* time.
*/
private boolean notifyingSubscriber;
/**
* Set when notifyNext is called while another thread is notifying the
* subscriber. If true, the notifying thread will check again for a next
* element or whether the iteration is closed before exiting.
*/
private boolean newNotify;
/** Whether Subscriber.onComplete or onError has been called. */
private boolean complete;
/** Whether cancel has been called. Set only once. */
private boolean cancelCalled;
/** If non-null, a throwable to deliver to Subscriber.onError. */
private Throwable deliverException;
/**
* Creates an instance of this class.
*/
public AsyncIterationHandleImpl(Logger logger) {
this.logger = checkNull("logger", logger);
}
/**
* Sets the iterator that the handle should use to obtain elements. This
* method is typically called in the iterator constructor, and must be
* called before the iterate method is called.
*
* @param asyncIterator the iterator
* @throws IllegalStateException if the iterator has already been specified
*/
public void setIterator(AsyncTableIterator asyncIterator) {
synchronized (lock) {
if (this.asyncIterator != null) {
throw new AssertionError(
"Iterator has already been specified");
}
this.asyncIterator = checkNull("asyncIterator", asyncIterator);
}
}
public void subscribe(Subscriber super E> s) {
checkNull("s", s);
synchronized (lock) {
if (asyncIterator == null) {
throw new AssertionError("No iterator");
}
if (this.subscriber != null) {
throw new AssertionError("Subscribe has already been called");
}
subscriber = s;
newSubscriber = true;
}
notifyNext();
}
void request(long n) {
synchronized (lock) {
if (asyncIterator == null) {
throw new AssertionError("No iterator");
}
if (n < 1) {
deliverException = new IllegalArgumentException(
"Request value must be greater than zero");
} else {
requests += n;
/* Check for overflow */
if (requests <= 0) {
requests = Long.MAX_VALUE;
}
}
}
/*
* If we are already within a nested call to the request method, then
* the top level call will deliver the results. A recursive call can
* happen if the application calls request, request calls notifyNext,
* that calls the subscriber's onNext method, and that method calls
* request again.
*/
if (inRequest.get() != null) {
return;
}
inRequest.set(Boolean.TRUE);
try {
notifyNext();
} finally {
inRequest.remove();
}
}
void cancel() {
synchronized (lock) {
if (asyncIterator == null) {
throw new AssertionError("No iterator");
}
if (cancelCalled) {
return;
}
cancelCalled = true;
}
notifyNext();
}
List getPartitionMetrics() {
synchronized (lock) {
if (asyncIterator == null) {
return Collections.emptyList();
}
}
return asyncIterator.getPartitionMetrics();
}
List getShardMetrics() {
synchronized (lock) {
if (asyncIterator == null) {
return Collections.emptyList();
}
}
return asyncIterator.getShardMetrics();
}
/* -- Implement IterationHandleNotifier -- */
@Override
public void notifyNext() {
if (Thread.holdsLock(lock)) {
throw new AssertionError(
"Already holding lock in call to notifyNext");
}
final boolean doOnSubscribe;
synchronized (lock) {
/* Note new notify so notify thread checks again */
if (notifyingSubscriber) {
newNotify = true;
logger.finest("notifyNext newNotify=true");
return;
}
/* Subscriber isn't initialized yet */
if (subscriber == null) {
return;
}
/* Subscriber was already notified that the iteration is done */
if (complete) {
return;
}
/* Note if we should call onSubscribe, then clear */
doOnSubscribe = newSubscriber;
newSubscriber = false;
/*
* Mark that we are delivering notifications and clear newNotify so
* that we notice newer changes
*/
notifyingSubscriber = true;
newNotify = false;
logger.finest("notifyNext");
}
if (doOnSubscribe) {
final Subscription subscription = createSubscription();
try {
subscriber.onSubscribe(subscription);
} catch (Throwable e) {
/*
* Reactive Streams Publisher Rule 2.13 suggests that we should
* not call onError in this case, since the subscriber is
* suspect. Just log.
*/
logger.warning("Problem calling onSubscribe on subscriber: " +
subscriber + ", exception from subscriber: " +
e);
synchronized (lock) {
notifyingSubscriber = false;
subscriber = null;
}
return;
}
noteOpeningSubscription();
}
Throwable exception = null;
try {
/*
* Return local results to the app until:
* - There are no more local results
* - We have returned all the results requested by the app
* - The iteration was canceled by the app
* - The iteration was closed due to an error
*/
while (true) {
if (notifyOneNext()) {
break;
}
}
} catch (RuntimeException e) {
exception = e;
throw e;
} catch (Error e) {
exception = e;
throw e;
} finally {
/*
* Make certain that notifyingSubscriber is cleared if there is an
* unexpected exception -- it will have already been cleared
* otherwise
*/
if (exception != null) {
synchronized (lock) {
notifyingSubscriber = false;
}
logger.log(Level.WARNING, "Unexpected exception: " + exception,
exception);
}
}
}
protected Subscription createSubscription() {
return new IterationSubscriptionImpl();
}
protected class IterationSubscriptionImpl
implements IterationSubscription {
@Override
public void cancel() {
AsyncIterationHandleImpl.this.cancel();
}
@Override
public void request(long n) {
AsyncIterationHandleImpl.this.request(n);
}
@Override
public List getPartitionMetrics() {
return AsyncIterationHandleImpl.this.getPartitionMetrics();
}
@Override
public List getShardMetrics() {
return AsyncIterationHandleImpl.this.getShardMetrics();
}
}
private void noteOpeningSubscription() {
openSubscriptions.incrementAndGet();
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Opening subscription, subscriber: " + subscriber +
", count: " + openSubscriptions.get());
}
}
private void noteClosingSubscription(Subscriber> forSubscriber) {
openSubscriptions.decrementAndGet();
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Closing subscription," +
" subscriber: " + forSubscriber +
", count: " + openSubscriptions.get());
}
}
/**
* Check for a single next element, a closed iterator, a cancel call, or an
* exception to deliver, returning true if notifications are done because
* there are no more elements or the iterator is closed. This method is
* called after setting notifyingSubscriber with the lock held, so it can
* read most fields without synchronization, but still needs to synchronize
* for updates.
*/
private boolean notifyOneNext() {
assert !complete;
/* Get next element and closed status */
E next = null;
Throwable closeException = null;
if (cancelCalled) {
asyncIterator.close();
} else if (deliverException != null) {
closeException = deliverException;
asyncIterator.close();
} else if (requests > 0 && !asyncIterator.isClosed()) {
/*
* The isClosed() call above is to cover the case where the iterator
* was closed due to an exception thrown by the remote request.
*/
try {
next = asyncIterator.nextLocal();
} catch (Throwable e) {
closeException = e;
}
}
if (next != null) {
closeException = onNext(next);
/* Delivering next failed: terminate the iteration */
if (closeException != null) {
asyncIterator.close();
next = null;
}
}
final boolean makeComplete =
(closeException != null) ? true :
(next != null) ? false :
asyncIterator.isClosed();
final Subscriber super E> savedSubscriber = subscriber;
final long originalRequests = requests;
final boolean done;
synchronized (lock) {
/* Decrement requests if we delivered a next element */
if (next != null) {
assert requests > 0;
requests--;
}
if (makeComplete) {
/* Iteration is complete */
complete = true;
/*
* Reactive Streams Subscription Rule 3.13 says that canceling
* a subscription must result in dropping references to the
* subscriber.
*/
subscriber = null;
notifyingSubscriber = false;
done = true;
} else if (newNotify) {
/* Clear and try again */
newNotify = false;
done = false;
} else if (next == null) {
/* No next and no new notifications, so we're done */
notifyingSubscriber = false;
done = true;
} else {
/* Check for more elements */
done = false;
}
}
if (logger.isLoggable(Level.FINEST)) {
logger.finest("notifyNext next=" + next +
" makeComplete=" + makeComplete +
" newNotify=" + newNotify +
" originalRequests=" + originalRequests +
(closeException != null ?
" closeException=" + closeException :
"") +
" done=" + done);
}
if (makeComplete) {
if (closeException == null) {
closeException = asyncIterator.getCloseException();
}
onComplete(savedSubscriber, closeException);
}
return done;
}
/**
* Deliver a next iteration result, returning any exception thrown during
* delivery, or null if the call completes normally.
*/
private Throwable onNext(E next) {
try {
subscriber.onNext(next);
return null;
} catch (Throwable t) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest(
"Problem delivering result to subscriber: " +
subscriber +
" result: " + next +
" exception from subscriber: " + t);
}
return t;
}
}
/**
* Deliver the completion result.
*/
private void onComplete(Subscriber> forSubscriber, Throwable exception) {
noteClosingSubscription(forSubscriber);
try {
if (exception == null) {
forSubscriber.onComplete();
} else {
forSubscriber.onError(exception);
}
} catch (Throwable t) {
logger.warning("Problem calling " +
((exception != null) ? "onError" : "onComplete") +
" on subscriber: " + forSubscriber +
((exception != null) ?
", exception being delivered: " + exception :
"") +
", exception from subscriber: " + t);
}
}
/**
* Close the iteration if the handle is garbage collected without being
* closed.
*/
@Override
protected void finalize() {
cancel();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy