All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.firefly.utils.concurrent.IteratingCallback Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
package com.firefly.utils.concurrent;

import java.nio.channels.ClosedChannelException;

/**
 * This specialized callback implements a pattern that allows a large job to be
 * broken into smaller tasks using iteration rather than recursion.
 * 

* A typical example is the write of a large content to a socket, divided in * chunks. Chunk C1 is written by thread T1, which also invokes the callback, * which writes chunk C2, which invokes the callback again, which writes chunk * C3, and so forth. *

*

* The problem with the example is that if the callback thread is the same that * performs the I/O operation, then the process is recursive and may result in a * stack overflow. To avoid the stack overflow, a thread dispatch must be * performed, causing context switching and cache misses, affecting performance. *

*

* To avoid this issue, this callback uses an AtomicReference to record whether * success callback has been called during the processing of a sub task, and if * so then the processing iterates rather than recurring. *

*

* Subclasses must implement method {@link #process()} where the sub task is * executed and a suitable {@link IteratingCallback.Action} is returned to this * callback to indicate the overall progress of the job. This callback is passed * to the asynchronous execution of each sub task and a call the * {@link #succeeded()} on this callback represents the completion of the sub * task. *

*/ public abstract class IteratingCallback implements Callback { /** * The internal states of this callback */ private enum State { /** * This callback is IDLE, ready to iterate. */ IDLE, /** * This callback is iterating calls to {@link #process()} and is dealing * with the returns. To get into processing state, it much of held the * lock state and set iterating to true. */ PROCESSING, /** * Waiting for a schedule callback */ PENDING, /** * Called by a schedule callback */ CALLED, /** * The overall job has succeeded as indicated by a * {@link Action#SUCCEEDED} return from * {@link IteratingCallback#process()} */ SUCCEEDED, /** * The overall job has failed as indicated by a call to * {@link IteratingCallback#failed(Throwable)} */ FAILED, /** * This callback has been closed and cannot be reset. */ CLOSED } /** * The indication of the overall progress of the overall job that * implementations of {@link #process()} must return. */ protected enum Action { /** * Indicates that {@link #process()} has no more work to do, but the * overall job is not completed yet, probably waiting for additional * events to trigger more work. */ IDLE, /** * Indicates that {@link #process()} is executing asynchronously a sub * task, where the execution has started but the callback may have not * yet been invoked. */ SCHEDULED, /** * Indicates that {@link #process()} has completed the overall job. */ SUCCEEDED } private Locker _locker = new Locker(); private State _state; private boolean _iterate; protected IteratingCallback() { _state = State.IDLE; } protected IteratingCallback(boolean needReset) { _state = needReset ? State.SUCCEEDED : State.IDLE; } /** * Method called by {@link #iterate()} to process the sub task. *

* Implementations must start the asynchronous execution of the sub task (if * any) and return an appropriate action: *

*
    *
  • {@link Action#IDLE} when no sub tasks are available for execution but * the overall job is not completed yet
  • *
  • {@link Action#SCHEDULED} when the sub task asynchronous execution has * been started
  • *
  • {@link Action#SUCCEEDED} when the overall job is completed
  • *
* * @return the appropriate Action * * @throws Exception * if the sub task processing throws */ protected abstract Action process() throws Exception; /** * Invoked when the overall task has completed successfully. * * @see #onCompleteFailure(Throwable) */ protected void onCompleteSuccess() { } /** * Invoked when the overall task has completed with a failure. * * @param cause * the throwable to indicate cause of failure * * @see #onCompleteSuccess() */ protected void onCompleteFailure(Throwable cause) { } /** * This method must be invoked by applications to start the processing of * sub tasks. It can be called at any time by any thread, and it's contract * is that when called, then the {@link #process()} method will be called * during or soon after, either by the calling thread or by another thread. */ public void iterate() { boolean process = false; loop: while (true) { try (Locker.Lock lock = _locker.lock()) { switch (_state) { case PENDING: case CALLED: // process will be called when callback is handled break loop; case IDLE: _state = State.PROCESSING; process = true; break loop; case PROCESSING: _iterate = true; break loop; case FAILED: case SUCCEEDED: break loop; case CLOSED: default: throw new IllegalStateException(toString()); } } } if (process) processing(); } private void processing() { // This should only ever be called when in processing state, however a // failed or close call // may happen concurrently, so state is not assumed. boolean on_complete_success = false; // While we are processing processing: while (true) { // Call process to get the action that we have to take. Action action; try { action = process(); } catch (Throwable x) { failed(x); break processing; } // acted on the action we have just received try (Locker.Lock lock = _locker.lock()) { switch (_state) { case PROCESSING: { switch (action) { case IDLE: { // Has iterate been called while we were processing? if (_iterate) { // yes, so skip idle and keep processing _iterate = false; _state = State.PROCESSING; continue processing; } // No, so we can go idle _state = State.IDLE; break processing; } case SCHEDULED: { // we won the race against the callback, so the callback // has to process and we can break processing _state = State.PENDING; break processing; } case SUCCEEDED: { // we lost the race against the callback, _iterate = false; _state = State.SUCCEEDED; on_complete_success = true; break processing; } default: throw new IllegalStateException(String.format("%s[action=%s]", this, action)); } } case CALLED: { switch (action) { case SCHEDULED: { // we lost the race, so we have to keep processing _state = State.PROCESSING; continue processing; } default: throw new IllegalStateException(String.format("%s[action=%s]", this, action)); } } case SUCCEEDED: case FAILED: case CLOSED: break processing; case IDLE: case PENDING: default: throw new IllegalStateException(String.format("%s[action=%s]", this, action)); } } } if (on_complete_success) onCompleteSuccess(); } /** * Invoked when the sub task succeeds. Subclasses that override this method * must always remember to call {@code super.succeeded()}. */ @Override public void succeeded() { boolean process = false; try (Locker.Lock lock = _locker.lock()) { switch (_state) { case PROCESSING: { _state = State.CALLED; break; } case PENDING: { _state = State.PROCESSING; process = true; break; } case CLOSED: case FAILED: { // Too late! break; } default: { throw new IllegalStateException(toString()); } } } if (process) processing(); } /** * Invoked when the sub task fails. Subclasses that override this method * must always remember to call {@code super.failed(Throwable)}. */ @Override public void failed(Throwable x) { boolean failure = false; try (Locker.Lock lock = _locker.lock()) { switch (_state) { case SUCCEEDED: case FAILED: case IDLE: case CLOSED: case CALLED: // too late!. break; case PENDING: case PROCESSING: { _state = State.FAILED; failure = true; break; } default: throw new IllegalStateException(toString()); } } if (failure) onCompleteFailure(x); } public void close() { boolean failure = false; try (Locker.Lock lock = _locker.lock()) { switch (_state) { case IDLE: case SUCCEEDED: case FAILED: _state = State.CLOSED; break; case CLOSED: break; default: _state = State.CLOSED; failure = true; } } if (failure) onCompleteFailure(new ClosedChannelException()); } /* * only for testing * * @return whether this callback is idle and {@link #iterate()} needs to be * called */ boolean isIdle() { try (Locker.Lock lock = _locker.lock()) { return _state == State.IDLE; } } public boolean isClosed() { try (Locker.Lock lock = _locker.lock()) { return _state == State.CLOSED; } } /** * @return whether this callback has failed */ public boolean isFailed() { try (Locker.Lock lock = _locker.lock()) { return _state == State.FAILED; } } /** * @return whether this callback has succeeded */ public boolean isSucceeded() { try (Locker.Lock lock = _locker.lock()) { return _state == State.SUCCEEDED; } } /** * Resets this callback. *

* A callback can only be reset to IDLE from the SUCCEEDED or FAILED states * or if it is already IDLE. *

* * @return true if the reset was successful */ public boolean reset() { try (Locker.Lock lock = _locker.lock()) { switch (_state) { case IDLE: return true; case SUCCEEDED: case FAILED: _iterate = false; _state = State.IDLE; return true; default: return false; } } } @Override public String toString() { return String.format("%s[%s]", super.toString(), _state); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy