com.tangosol.internal.net.topic.impl.paged.BatchingOperationsQueue Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
package com.tangosol.internal.net.topic.impl.paged;
import com.oracle.coherence.common.base.Logger;
import com.oracle.coherence.common.base.NonBlocking;
import com.tangosol.internal.net.DebouncedFlowControl;
import com.tangosol.internal.util.DaemonPool;
import com.tangosol.util.Gate;
import com.tangosol.util.LongArray;
import com.tangosol.util.NullImplementation;
import com.tangosol.util.TaskDaemon;
import com.tangosol.util.ThreadGateLite;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* A {@link BatchingOperationsQueue} is a queue that values can be added to for later processing.
* When values are added a function is triggered to process a batch of values.
*
* When values are added to the queue the {@link Consumer} function will be called with a batch size.
*
* @param the type of value added to the queue
* @param the type of result returned by the {@link CompletableFuture} as values are added
*
* @author jk 2015.12.17
* @since Coherence 14.1.1
*/
public class BatchingOperationsQueue
{
// ----- constructors ---------------------------------------------------
/**
* Create a new {@link BatchingOperationsQueue} that will call the specified
* {@link Consumer} function to process a batch of operations.
*
* @param functionBatch the {@link Consumer} to call to process batches of operations
* @param cbInitialBatch the size of the initial batch of operations
*/
public BatchingOperationsQueue(Consumer functionBatch, int cbInitialBatch)
{
this ((q, i) -> functionBatch.accept(i),
cbInitialBatch,
new DebouncedFlowControl(cbInitialBatch, Integer.MAX_VALUE),
v -> 1,
Runnable::run);
}
/**
* Create a new {@link BatchingOperationsQueue} that will call the specified
* {@link Consumer} function to process a batch of operations.
*
* This constructor takes a {@link Consumer} to use to complete futures. This allows us
* to, for example, optionally use a daemon pool to complete futures that may otherwise
* complete on a service thread. If the {code completer} parameter is {@code null} futures
* will complete on the calling thread.
*
* @param functionBatch the {@link Consumer} to call to process batches of operations
* @param cbInitialBatch the size of the initial batch of operations
* @param backlog the governing FlowControl object
* @param backlogCalculator a function that calculates a backlog from a value
* @param executor an {@link Executor} that will execute completion tasks for futures
*/
public BatchingOperationsQueue(Consumer functionBatch, int cbInitialBatch, DebouncedFlowControl backlog,
ToLongFunction backlogCalculator, Executor executor)
{
this((q, i) -> functionBatch.accept(i), cbInitialBatch, backlog, backlogCalculator, executor);
}
/**
* Create a new {@link BatchingOperationsQueue} that will call the specified
* {@link Consumer} function to process a batch of operations.
*
* @param functionBatch the {@link Consumer} to call to process batches of operations
* @param cbInitialBatch the size of the initial batch of operations
* @param backlog the governing FlowControl object
* @param backlogCalculator a function that calculates a backlog from a value
* @param executor an {@link Executor} that will execute completion tasks for futures
*/
public BatchingOperationsQueue(BiConsumer, Integer> functionBatch, int cbInitialBatch,
DebouncedFlowControl backlog, ToLongFunction backlogCalculator, Executor executor)
{
f_functionBatch = functionBatch;
f_cbInitialBatch = cbInitialBatch;
f_queuePending = new ConcurrentLinkedDeque<>();
f_queueCurrentBatch = new ConcurrentLinkedDeque<>();
f_gate = new ThreadGateLite<>();
f_backlog = backlog;
f_backlogCalculator = backlogCalculator == null ? v -> 1 : backlogCalculator;
f_executor = executor == null ? Executor.sameThread() : executor;
resetTrigger();
}
// ----- BatchingOperationsQueue methods --------------------------------
/**
* Add the specified value to the pending operations queue.
*
* @param value the value to add to the queue
*
* @return a future which will complete once the supplied value has been published to the topic.
*/
public CompletableFuture add(V value)
{
return add(value, /* fFirst */ false);
}
/**
* Add the specified value to the head of the pending operations queue.
*
* @param value the value to add to the head of the queue
*
* @return a future which will complete once the supplied value has been published to the topic.
*/
public CompletableFuture addFirst(V value)
{
return add(value, /* fFirst */ true);
}
/**
* Close this {@link BatchingOperationsQueue}.
* This {@link BatchingOperationsQueue} will no longer accept values
* but the pending values can continue to be processed.
*/
public void close()
{
// Close the gate so no more add can be made while we are closing
Gate> gate = getGate();
gate.close(-1);
try
{
m_fActive = false;
}
finally
{
// Now we can open the gate, nothing can be added as we have deactivated
gate.open();
}
}
/**
* Obtain a {@link CompletableFuture} that will be complete when
* all the currently outstanding operations complete.
* The returned {@link CompletableFuture} will always complete
* normally, even if the outstanding operations complete exceptionally.
*
* @return a {@link CompletableFuture} that will be completed when
* all the currently outstanding operations are complete
*/
public CompletableFuture flush()
{
// Close the gate so no more add can be made while we are
// working out the outstanding set of operations
Gate> gate = getGate();
gate.close(-1);
try
{
Queue queueCurrent = getCurrentBatch();
Deque queuePending = getPending();
// Collect the outstanding futures from the current batch and pending queues
CompletableFuture>[] aFutures = Stream.concat(queueCurrent.stream(), queuePending.stream())
.map(Element::getFuture)
.filter((future) -> !future.isDone())
.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(aFutures).handle((_void, throwable) -> null);
}
finally
{
// Now we can open the gate
gate.open();
}
}
/**
* Obtain the values from the current batch to process.
*
* @return the values from the current batch to process
*/
public LinkedList getCurrentBatchValues()
{
return getCurrentBatch().stream()
.filter(((Predicate) BatchingOperationsQueue.Element::isDone).negate())
.map(BatchingOperationsQueue.Element::getValue)
.collect(Collectors.toCollection(LinkedList::new));
}
/**
* Return true if the current batch is complete.
*
* @return true if the current batch is complete
*/
public boolean isBatchComplete()
{
purgeCurrentBatch();
return getCurrentBatchValues().isEmpty();
}
/**
* Return the combined size of the current batch and pending queues.
*
* @return the combined size of the current batch and pending queues
*/
public int size()
{
return getCurrentBatchSize() + getPendingSize();
}
/**
* Return the size of the current batch queue.
*
* @return the size of the current batch queue
*/
public int getCurrentBatchSize()
{
return f_queueCurrentBatch.size();
}
/**
* Return the size of the pending queue.
*
* @return the size of the pending queue
*/
public int getPendingSize()
{
return f_queuePending.size();
}
/**
* Handle the error that occurred processing the current batch.
*
* @param function the function to create the actual error to complete the future with
* @param action the action to take to handle the error
*/
public void handleError(BiFunction function, OnErrorAction action)
{
Gate> gate = getGate();
gate.close(-1);
try
{
boolean fClose = false;
if (action == null)
{
action = OnErrorAction.CompleteWithException;
}
switch(action)
{
case Retry:
// Move uncompleted elements in the current batch back to the
// front of the pending queue - in the same order
Deque queueCurrent = getCurrentBatch();
while(!queueCurrent.isEmpty())
{
Element element = queueCurrent.pollLast();
long cb = f_backlogCalculator.applyAsLong(element.getValue());
m_cbCurrentBatch -= cb;
if (!element.isDone())
{
f_backlog.adjustBacklog(cb);
getPending().offerFirst(element);
}
}
// reset the trigger
resetTrigger();
triggerOperations(f_cbInitialBatch);
break;
case CompleteAndClose:
fClose = true;
case Complete:
// Complete all the futures
doErrorAction(e -> e.complete(null, null), fClose);
break;
case CompleteWithExceptionAndClose:
fClose = true;
case CompleteWithException:
// Complete exceptionally all the futures in the current queue
doErrorAction(e -> e.completeExceptionally(null, function), fClose);
break;
case CancelAndClose:
fClose = true;
case Cancel:
// Cancel all the futures in the current queue
doErrorAction(e -> e.cancel(function, null), fClose);
break;
}
}
finally
{
gate.open();
}
}
/**
* Cancel all requests and close the queue.
*/
public void cancelAllAndClose(String sReason, BiFunction function)
{
Gate> gate = getGate();
gate.close(-1);
try
{
doErrorAction(e -> e.cancel(function, sReason), true);
}
finally
{
gate.open();
}
}
/**
* Specifies whether the publisher is active.
*
* @return true if the publisher is active; false otherwise
*/
public boolean isActive()
{
return m_fActive;
}
/**
* Create an {@link Element} containing the specified value.
*
* @param value the value to use
*
* @return a new {@link Element} containing the specified value
*/
protected Element createElement(V value)
{
return new Element(value);
}
/**
* Fill the current batch queue with {@link Element}s.
*
* The queue will be filled up to a maximum of the specified
* number of elements, or less if the pending queue has fewer
* elements than required.
*
* @param cbMaxElements the maximum byte limit to fill the current batch queue
*
* @return true if the current batch queue has elements,
* false if the current batch queue is empty.
*/
public boolean fillCurrentBatch(int cbMaxElements)
{
if (m_cbCurrentBatch >= cbMaxElements)
{
return true;
}
// Shut the gate so that no more offers come
// into the queue while we remove some elements
Gate> gate = getGate();
gate.close(-1);
try
{
// Pull elements from the pending queue into the current
// batch queue until either the pending queue is empty or
// we have lMaxElements in the current batch queue
Queue queueCurrent = getCurrentBatch();
Queue queuePending = getPending();
Element element = queuePending.poll();
while(element != null)
{
V value = element.getValue();
long lSize = f_backlogCalculator.applyAsLong(value);
element.setSize(lSize);
try (@SuppressWarnings("unused") NonBlocking nb = new NonBlocking())
{
f_backlog.adjustBacklog(-lSize);
}
if (!element.isDone())
{
queueCurrent.add(element);
long cbBatch = m_cbCurrentBatch += lSize;
if (cbBatch >= cbMaxElements)
{
// page will be filled
break;
}
}
element = queuePending.poll();
}
// We might not have pulled anything from the queue
// if, for example, the application has cancelled all
// the queued futures
if (queueCurrent.isEmpty())
{
// While the gate is shut create a new future to trigger
// a round of adds when this set is done and more values
// are added to the queue
resetTrigger();
return false;
}
return true;
}
finally
{
// Don't forget to open the gate
gate.open();
}
}
/**
* Reset the operations trigger so that a new batch operation
* will be triggered on another add;
*/
protected void resetTrigger()
{
getTrigger().set(TRIGGER_OPEN);
}
/**
* Pause the queue.
*/
protected void pause()
{
getTrigger().set(TRIGGER_WAIT);
}
public boolean resume()
{
return getTrigger().compareAndSet(TRIGGER_WAIT, TRIGGER_CLOSED);
}
/**
* If a batch of operations is not already in progress then
* trigger a new batch of operations.
*/
protected void triggerOperations()
{
triggerOperations(Math.max(f_cbInitialBatch, 1));
}
/**
* If a batch of operations is not already in progress then
* trigger a new batch of operations using the specified
* batch size.
*
* @param cBatchSize the batch size
*/
protected void triggerOperations(int cBatchSize)
{
AtomicInteger trigger = getTrigger();
if (trigger.get() == TRIGGER_OPEN && trigger.compareAndSet(TRIGGER_OPEN, TRIGGER_CLOSED))
{
f_functionBatch.accept(this, cBatchSize);
}
}
/**
* Complete the first {@link Element Element} in the current batch.
*
* @param oValue the value to use to complete the elements
* @param onComplete an optional {@link Consumer} to call when requests are completed
*/
@SuppressWarnings("unchecked")
public void completeElement(Object oValue, BiFunction function, Consumer onComplete)
{
completeElements(1, NullImplementation.getLongArray(), LongArray.singleton((R) oValue), function, onComplete);
}
/**
* Complete the first n {@link Element Elements} in the current batch.
*
* @param cComplete the number of {@link Element}s to complete
* @param aValues the values to use to complete the elements
* @param onComplete an optional {@link Consumer} to call when requests are completed
*/
public void completeElements(int cComplete, LongArray aValues, BiFunction function, Consumer onComplete)
{
completeElements(cComplete, NullImplementation.getLongArray(), aValues, function, onComplete);
}
/**
* Complete the first n {@link Element Elements} in the current batch.
*
* If any element in the current batch has a corresponding error
* in the errors array then it will be completed exceptionally.
*
* @param cComplete the number of {@link Element}s to complete
* @param aErrors the errors related to individual elements (could be null)
* @param aValues the values to use to complete the elements
* @param onComplete an optional {@link Consumer} to call when requests are completed
*/
public void completeElements(int cComplete, LongArray aErrors, LongArray aValues, BiFunction errFunction, Consumer onComplete)
{
Queue queueCurrent = getCurrentBatch();
// Loop over the number of completed elements
for (int i=0; i getGate()
{
return f_gate;
}
/**
* Return the {@link Deque} containing the current batch of {@link Element}s.
*
* @return the {@link Deque} containing the current batch of {@link Element}s
*/
protected Deque getCurrentBatch()
{
return f_queueCurrentBatch;
}
/**
* Return the {@link Deque} containing the pending {@link Element}s.
*
* @return the {@link Deque} containing the pending {@link Element}s
*/
protected Deque getPending()
{
return f_queuePending;
}
protected AtomicInteger getTrigger()
{
return f_lockTrigger;
}
// ----- object methods -------------------------------------------------
@Override
public String toString()
{
return "BatchingOperationsQueue(" +
"current=" + getCurrentBatch().size() +
", pending=" + getPending().size() +
", trigger=" + triggerToString(getTrigger().get()) +
", backlog=" + f_backlog +
')';
}
// ----- helper methods -------------------------------------------------
/**
* Add the specified value to the pending operations queue.
*
* @param value the value to add to the queue
*
* @return a future which will complete once the supplied value has been published to the topic.
*/
private CompletableFuture add(V value, boolean fFirst)
{
Element element = createElement(value);
Gate> gate = getGate();
// Wait to enter the gate
gate.enter(-1);
try
{
assertActive();
// Add the new element containing the value and the future to the offer queue
if (fFirst)
{
getPending().addFirst(element);
}
else
{
getPending().add(element);
}
}
finally
{
// and finally exit from the gate
gate.exit();
}
// This will cause the batch operation to be triggered if required.
triggerOperations(f_cbInitialBatch);
f_backlog.adjustBacklog(f_backlogCalculator.applyAsLong(value));
return element.getFuture();
}
/**
* Assert that this {@link BatchingOperationsQueue} is active.
*
* @throws com.tangosol.util.AssertionException if
* this {@link BatchingOperationsQueue} is not active.
*/
protected void assertActive()
{
if (!isActive())
{
throw new IllegalStateException("This batching queue is no longer active");
}
}
/**
* Poll all the elements from the queue and pass any non-done elements
* to the consumer.
*
* @param action the consumer to process non-done elements
* @param fClose {@code true} to close the queues
*/
protected void doErrorAction(Consumer action, boolean fClose)
{
Deque current = getCurrentBatch();
Deque pending = getPending();
if (!current.isEmpty() || !pending.isEmpty())
{
Stream.concat(current.stream(), pending.stream())
.forEach(element ->
{
if (!element.isDone())
{
action.accept(element);
}
});
m_cbCurrentBatch = 0;
try (@SuppressWarnings("unused") NonBlocking nb = new NonBlocking())
{
// must do this in a non-blocking try block in case we're backlogged
long lBacklog = pending.stream()
.map(element -> f_backlogCalculator.applyAsLong(element.getValue()))
.mapToLong(Long::longValue)
.sum();
f_backlog.adjustBacklog(-lBacklog);
}
}
if (fClose)
{
close();
}
else
{
resetTrigger();
}
}
private String triggerToString(int n)
{
switch (n)
{
case TRIGGER_OPEN:
return "TRIGGER_OPEN";
case TRIGGER_CLOSED:
return "TRIGGER_CLOSED";
case TRIGGER_WAIT:
return "TRIGGER_WAIT";
}
return "TRIGGER_UNKNOWN";
}
/**
* Remove any completed elements from the current batch.
*
* @return {@code true} if the current batch is empty
*/
private boolean purgeCurrentBatch()
{
if (f_queueCurrentBatch.isEmpty())
{
return true;
}
// Shut the gate so that no more offers come
// into the queue while we remove some elements
Gate> gate = getGate();
gate.close(-1);
try
{
Iterator iterator = f_queueCurrentBatch.iterator();
long cbSize = m_cbCurrentBatch;
while (iterator.hasNext())
{
Element element = iterator.next();
if (element.isDone())
{
iterator.remove();
cbSize -= element.getSize();
}
}
m_cbCurrentBatch = cbSize;
}
finally
{
// Don't forget to open the gate
gate.open();
}
return getCurrentBatchValues().isEmpty();
}
// ----- inner class: Element -------------------------------------------
/**
* A class holding a value and a {@link CompletableFuture} that will be
* completed when async operation on this element completes
*/
public class Element
{
/**
* Create an Element with the specified value and future.
*
* @param value the value for this element
*/
public Element(V value)
{
f_value = value;
f_future = new CompletableFuture<>();
// ensure we're set to done if the future is completed
f_future.handle((r, error) ->
{
m_fCancelled = error instanceof CancellationException;
m_fDone = true;
return null;
});
}
/**
* Obtain the value to add to the topic.
*
* @return the value to add to the topic
*/
public V getValue()
{
return f_value;
}
/**
* Obtain the {@link CompletableFuture} to complete when the
* value has been added to the topic.
*
* @return the {@link CompletableFuture} to complete when
* the value has been added to the topic.
*/
public CompletableFuture getFuture()
{
return f_future;
}
/**
* Determine whether this element's add operations has completed
* either successfully or exceptionally, or has been cancelled.
*
* @return true if this element's add operation has completed
*/
public boolean isDone()
{
return m_fDone || f_future.isDone();
}
/**
* Determine whether this element's add operations has been cancelled.
*
* @return true if this element's add operation has been cancelled
*/
public boolean isCancelled()
{
return m_fCancelled || f_future.isCancelled();
}
/**
* Complete this element's {@link CompletableFuture}.
*
* @param result the value to use to complete the future
* @param onComplete an optional {@link Consumer} to call when the future is actually completed
*/
public void complete(R result, Consumer onComplete)
{
if (!m_fDone)
{
m_fDone = true;
f_executor.complete(f_future, result, onComplete);
}
}
/**
* Complete exceptionally this element's {@link CompletableFuture}.
*
* @param throwable the error that occurred
* @param function the function to use to create the actual error to complete the future with
*/
public void completeExceptionally(Throwable throwable, BiFunction function)
{
if (!m_fDone)
{
m_fDone = true;
f_executor.completeExceptionally(f_future, function.apply(throwable, f_value));
}
}
/**
* Cancel this element's {@link CompletableFuture}.
*
* @param function a function to create an exception
* @param sReason an optional cancellation reason
*/
public void cancel(BiFunction function, String sReason)
{
if (!m_fDone)
{
CancellationException exception = sReason != null && !sReason.isEmpty()
? new CancellationException(sReason)
: new CancellationException();
Throwable throwable = function == null ? exception : function.apply(exception, f_value);
f_executor.completeExceptionally(f_future, throwable);
}
}
public long getSize()
{
return m_cbSize;
}
public void setSize(long cbSize)
{
m_cbSize = cbSize;
}
// ----- data members -------------------------------------------
/**
* The {@link CompletableFuture} that will be completed when this element's value
* has been added to the topic.
*/
private final CompletableFuture f_future;
/**
* The value for this element.
*/
private final V f_value;
/**
* A flag indicating whether this element has completed.
*/
private volatile boolean m_fDone = false;
/**
* A flag indicating whether this element has been cancelled.
*/
private volatile boolean m_fCancelled = false;
/**
* The size of this element;
*/
private long m_cbSize;
}
// ----- inner class: OnErrorAction -------------------------------------------
/**
* An enum of possible actions to take when an error occurs during
* asynchronous topic operations.
*/
public enum OnErrorAction
{
/**
* Retry the failed operation.
*/
Retry,
/**
* Complete the {@link CompletableFuture}s associated with the
* failed operation and all outstanding pending futures.
*/
Complete,
/**
* Complete the {@link CompletableFuture}s associated with the
* failed operation and all outstanding pending futures and close
* this {@link BatchingOperationsQueue}.
*/
CompleteAndClose,
/**
* Complete exceptionally the {@link CompletableFuture}s associated
* with the failed operation and all outstanding pending futures.
*/
CompleteWithException,
/**
* Complete exceptionally the {@link CompletableFuture}s associated
* with the failed operation and all outstanding pending futures and
* close this {@link BatchingOperationsQueue}.
*/
CompleteWithExceptionAndClose,
/**
* Cancel the {@link CompletableFuture}s associated with the
* failed operation and all outstanding pending futures.
*/
Cancel,
/**
* Cancel the {@link CompletableFuture}s associated with the
* failed operation and all outstanding pending futures and
* close this {@link BatchingOperationsQueue}.
*/
CancelAndClose,
}
// ----- inner interface Executor ---------------------------------------
/**
* A simple executor that will be used to invoke completion tasks on
* {@link CompletableFuture} instances.
*/
public interface Executor
{
/**
* Execute the completion task.
*
* @param runnable the task to complete.
*/
void execute(Runnable runnable);
/**
* Complete the future with a value.
*
* @param future the {@link CompletableFuture} to complete
* @param oValue the value to use to complete the future
* @param onComplete an optional {@link Consumer} to call when the future is actually completed
*
* @param the type of the future's value
*/
default void complete(CompletableFuture future, R oValue, Consumer onComplete)
{
execute(() ->
{
if (onComplete != null)
{
try
{
onComplete.accept(oValue);
}
catch (Throwable t)
{
Logger.err(t);
}
}
future.complete(oValue);
});
}
/**
* Complete the future with an error.
*
* @param future the {@link CompletableFuture} to complete
* @param t the error to use to complete the future
*/
default void completeExceptionally(CompletableFuture> future, Throwable t)
{
execute(() -> future.completeExceptionally(t));
}
/**
* Return an {@link Executor} that completes futures
* on the calling thread.
*
* @return an {@link Executor} that completes futures
* on the calling thread
*/
static Executor sameThread()
{
return Runnable::run;
}
/**
* Return an {@link Executor} that completes futures
* using a {@link DaemonPool}.
*
* @param daemon the {@link TaskDaemon} that executes the tasks
*
* @return an {@link Executor} that completes futures
* using a {@link TaskDaemon}.
*/
static Executor fromTaskDaemon(TaskDaemon daemon)
{
return daemon::executeTask;
}
}
// ----- constants ------------------------------------------------------
/**
* Trigger state indicating that there is no request in progress.
*/
public static final int TRIGGER_OPEN = 0;
/**
* Trigger state indicating that a request is in progress.
*/
public static final int TRIGGER_CLOSED = 1;
/**
* Trigger state indicating that no request is in progress but requests are deferred
* pending notification from the topic.
*/
public static final int TRIGGER_WAIT = 2;
// ----- data members ---------------------------------------------------
/**
* The {@link Consumer} to call to process batches of operations.
*/
private final BiConsumer, Integer> f_functionBatch;
/**
* The initial batch size to use.
*/
private final int f_cbInitialBatch;
/**
* The {@link Deque} of {@link Element}s waiting to be offered to the topic.
*/
private final Deque f_queuePending;
/**
* The {@link Deque} of currently in-flight operations.
*/
private final Deque f_queueCurrentBatch;
/**
* The cumulative size of the current batch.
*/
private long m_cbCurrentBatch;
/**
* The {@link Gate} controlling access to the {@link #f_queuePending} queue
*/
private final Gate> f_gate;
/**
* The lock for submitting operations.
*/
private final AtomicInteger f_lockTrigger = new AtomicInteger(TRIGGER_OPEN);
/**
* The FlowControl object.
*/
private final DebouncedFlowControl f_backlog;
/**
* The function used to calculate backlog length from a value.
*/
private final ToLongFunction f_backlogCalculator;
/**
* The executor to use to complete the {@link Element} futures.
*/
private final Executor f_executor;
/**
* A flag indicating whether this {@link PagedTopicPublisher} is active.
*/
private boolean m_fActive = true;
}