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

org.eclipse.jetty.util.IteratingCallback Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.util;

import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 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 inactive, ready to iterate. */ INACTIVE, /** * This callback is iterating and {@link #process()} has scheduled an * asynchronous operation by returning {@link Action#SCHEDULED}, but * the operation is still undergoing. */ ACTIVE, /** * This callback is iterating and {@link #process()} has been called * but not returned yet. */ ITERATING, /** * While this callback was iterating, another request for iteration * has been issued, so the iteration must continue even if a previous * call to {@link #process()} returned {@link Action#IDLE}. */ ITERATE_AGAIN, /** * The overall job has succeeded. */ SUCCEEDED, /** * The overall job has failed. */ 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 final AtomicReference _state; protected IteratingCallback() { _state = new AtomicReference<>(State.INACTIVE); } protected IteratingCallback(boolean needReset) { _state = new AtomicReference<>(needReset ? State.SUCCEEDED : State.INACTIVE); } /** * 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
  • *
* * @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. * * @see #onCompleteSuccess() */ protected void onCompleteFailure(Throwable x) { } /** * This method must be invoked by applications to start the processing * of sub tasks. *

* If {@link #process()} returns {@link Action#IDLE}, then this method * should be called again to restart processing. * It is safe to call iterate multiple times from multiple threads since only * the first thread to move the state out of INACTIVE will actually do any iteration * and processing. */ public void iterate() { try { while (true) { switch (_state.get()) { case INACTIVE: { if (processIterations()) return; break; } case ITERATING: { if (_state.compareAndSet(State.ITERATING, State.ITERATE_AGAIN)) return; break; } default: { return; } } } } catch (Throwable x) { failed(x); } } private boolean processIterations() throws Exception { // Keeps iterating as long as succeeded() is called during process(). // If we are in INACTIVE state, either this is the first iteration or // succeeded()/failed() were called already. while (_state.compareAndSet(State.INACTIVE, State.ITERATING)) { // Method process() can only be called by one thread at a time because // it is guarded by the CaS above. However, the case blocks below may // be executed concurrently in this case: T1 calls process() which // executes the asynchronous sub task, which calls succeeded(), which // moves the state into INACTIVE, then returns SCHEDULED; T2 calls // iterate(), state is now INACTIVE and process() is called again and // returns another action. Now we have 2 threads that may execute the // action case blocks below concurrently; therefore each case block // has to be prepared to fail the CaS it's doing. Action action = process(); switch (action) { case IDLE: { // No more progress can be made. if (_state.compareAndSet(State.ITERATING, State.INACTIVE)) return true; // Was iterate() called again since we already decided to go INACTIVE ? // If so, try another iteration as more work may have been added // while the previous call to process() was returning. if (_state.compareAndSet(State.ITERATE_AGAIN, State.INACTIVE)) continue; // State may have changed concurrently, try again. continue; } case SCHEDULED: { // The sub task is executing, and the callback for it may or // may not have already been called yet, which we figure out below. // Can double CaS here because state never changes directly ITERATING_AGAIN --> ITERATE. if (_state.compareAndSet(State.ITERATING, State.ACTIVE) || _state.compareAndSet(State.ITERATE_AGAIN, State.ACTIVE)) // Not called back yet, so wait. return true; // Call back must have happened, so iterate. continue; } case SUCCEEDED: { // The overall job has completed. while (true) { State current = _state.get(); switch(current) { case SUCCEEDED: case FAILED: // Already complete!. return true; case CLOSED: throw new IllegalStateException(); default: if (_state.compareAndSet(current, State.SUCCEEDED)) { onCompleteSuccess(); return true; } } } } default: { throw new IllegalStateException(toString()); } } } return false; } /** * Invoked when the sub task succeeds. * Subclasses that override this method must always remember to call * {@code super.succeeded()}. */ @Override public void succeeded() { while (true) { State current = _state.get(); switch (current) { case ITERATE_AGAIN: case ITERATING: { if (_state.compareAndSet(current, State.INACTIVE)) return; continue; } case ACTIVE: { // If we can move from ACTIVE to INACTIVE // then we are responsible to call iterate(). if (_state.compareAndSet(current, State.INACTIVE)) iterate(); // If we can't CaS, then failed() must have been // called, and we just return. return; } case INACTIVE: { // Support the case where the callback is scheduled // externally without a call to iterate(). iterate(); return; } case CLOSED: { // Too late! return; } default: { throw new IllegalStateException(toString()); } } } } /** * 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) { while (true) { State current = _state.get(); switch (current) { case SUCCEEDED: case FAILED: case INACTIVE: case CLOSED: { // Already complete!. return; } default: { if (_state.compareAndSet(current, State.FAILED)) { onCompleteFailure(x); return; } } } } } public void close() { while (true) { State current = _state.get(); switch (current) { case INACTIVE: case SUCCEEDED: case FAILED: { if (_state.compareAndSet(current, State.CLOSED)) return; break; } case CLOSED: { return; } default: { if (_state.compareAndSet(current, State.CLOSED)) { onCompleteFailure(new ClosedChannelException()); return; } } } } } /* * only for testing * @return whether this callback is idle and {@link #iterate()} needs to be called */ boolean isIdle() { return _state.get() == State.INACTIVE; } public boolean isClosed() { return _state.get() == State.CLOSED; } /** * @return whether this callback has failed */ public boolean isFailed() { return _state.get() == State.FAILED; } /** * @return whether this callback has succeeded */ public boolean isSucceeded() { return _state.get() == State.SUCCEEDED; } /** * Resets this callback. *

* A callback can only be reset to INACTIVE from the * SUCCEEDED or FAILED states or if it is already INACTIVE. * * @return true if the reset was successful */ public boolean reset() { while (true) { switch(_state.get()) { case INACTIVE: return true; case SUCCEEDED: if (_state.compareAndSet(State.SUCCEEDED, State.INACTIVE)) return true; break; case FAILED: if (_state.compareAndSet(State.FAILED, State.INACTIVE)) return true; break; default: return false; } } } @Override public String toString() { return String.format("%s[%s]", super.toString(), _state); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy