A simple example of Deferred class code

Keywords: Big Data Attribute

A simple example of Deferred class code in openTSDB source code details

1. example 1

1.1 code
    /**
	 * simplest with only 1 defer
     * In the simplest case, there is only one defer
	 */
	public static void test1() {
		try {
            //Deferred deferred = new deferred(); - > no parameters are passed, and direct new is OK
            //01. Its generic type is String, indicating that the return value type is String
            // The Constructor. Will set state to PENDING;
            Deferred<String> dfd = new Deferred<String>();

			//02. Define a Callback whose return value type is String and input type parameter is String
			Callback<String, String> cb = new MyCallback();

			//Add a Callback object cb for the Deferred object dfd
			dfd.addCallback(cb);

			System.out.println("test");
			Thread.sleep(4000);

			//callback: Starts running the callback chain
			dfd.callback("callback data after 4 seconds aaa");
		} catch (Exception e) {}
	}	
1.2 operation process details
 /** Constructor.  */
  public Deferred() {
    state = PENDING;
  }

The state property value is set as follows:

 /**
   * The current state of this Deferred.
	 The state of the current Deferred object
   
   * All state transitions must be done atomically.  Since this attribute is
   * volatile, a single assignment is atomic.  But if you need to do a state
   * transition with a test first ("if state is A, move to state B") then you
   * must use {@link #casState} to do an atomic CAS (Compare And Swap).
   All state transitions must be set atomically. Because this property is volatile, an assignment is atomic.
   But if you need to do A state transition (use A test statement (set the state to B if the state is A), then you have to use casState
   To make a CAS(compare an swap)
   
   01.The state is decorated with volatile
   */
  private volatile int state;

After constructing the Deferred object, the attribute values of the dfd object are as follows:

Deferred@578866604(state=PENDING, result=null, 
callback=SimpleDeferExample.MyCallback@1517365b, errback=passthrough)

The object properties obtained during debug ging are as follows:

new is a MyCallback() object, and cb points to it. debug gets the following values:

The reference cb is then added to the dfd as a Callback. The code is as follows:

/**
   * Registers a callback.
     Pay attention to a callback
   * <p>
   * If the deferred result is already available and isn't an exception, the
   * callback is executed immediately from this thread.
   * If the deferred result is already available and is an exception, the
   * callback is discarded.
    If the delayed result is available and is not an exception, the call chain is executed immediately by the current thread.
	If the delayed result is available but an exception, the callback will be discarded.
   
   * If the deferred result is not available, this callback is queued and will
   * be invoked from whichever thread gives this deferred its initial result
   * by calling {@link #callback}.
    If the delayed result is not available, the callback will be queued and an initial value will be given to the delayed object
	The thread that initializes the deferred object by calling the callback method.
	
	
   * @param cb The callback to register.
   * @return {@code this} with an "updated" type.
   */
  public <R> Deferred<R> addCallback(final Callback<R, T> cb) {
    return addCallbacks(cb, Callback.PASSTHROUGH);
  }

The PASSTHROUGH is a static final method, as follows:

 /** The identity function (returns its argument).  */
  public static final Callback<Object, Object> PASSTHROUGH =
    new Callback<Object, Object>() {
      public Object call(final Object arg) {
        return arg;
      }
      public String toString() {
        return "passthrough";
      }
    };

This PASSTHROUGH is actually used to generate an error back (its function is nothing more than that).
But the addCallback( )The following methods are called:

public <R, R2, E> Deferred<R> addCallbacks(final Callback<R, T> cb,
                                             final Callback<R2, E> eb) {...}
 public <R, R2, E> Deferred<R> addCallbacks(final Callback<R, T> cb,
                                             final Callback<R2, E> eb) {
    if (cb == null) {
      throw new NullPointerException("null callback");
    } else if (eb == null) {
      throw new NullPointerException("null errback");
    }
    // We need to synchronize on `this' first before the CAS, to prevent
    // runCallbacks from switching our state from RUNNING to DONE right
    // before we add another callback.
    synchronized (this) {
      // If we're DONE, switch to RUNNING atomically.
      
	  //At the beginning, the state! = done, and the value of state is PENDING
	if (state == DONE) {
        // This "check-then-act" sequence is safe as this is the only code
        // path that transitions from DONE to RUNNING and it's synchronized.
        state = RUNNING;
      	} else {
        // We get here if weren't DONE (most common code path)
        //  -or-
        // if we were DONE and another thread raced with us to change the
        // state and we lost the race (uncommon).
        if (callbacks == null) {
        //Go to the following initialization callbacks. The init call chain size value is 4
          callbacks = new Callback[INIT_CALLBACK_CHAIN_SIZE];
        }
        // Do we need to grow the array?
        else if (last_callback == callbacks.length) {
          final int oldlen = callbacks.length;
          if (oldlen == MAX_CALLBACK_CHAIN_LENGTH * 2) {
            throw new CallbackOverflowError("Too many callbacks in " + this
              + " (size=" + (oldlen / 2) + ") when attempting to add cb="
              + cb + '@' + cb.hashCode() + ", eb=" + eb + '@' + eb.hashCode());
          }
          final int len = Math.min(oldlen * 2, MAX_CALLBACK_CHAIN_LENGTH * 2);
          final Callback[] newcbs = new Callback[len];
          System.arraycopy(callbacks, next_callback,  // Outstanding callbacks.
                           newcbs, 0,            // Move them to the beginning.
                           last_callback - next_callback);  // Number of items.
          last_callback -= next_callback;
          next_callback = 0;
          callbacks = newcbs;
        }		
	//Add two callbacks to the callbacks array
        callbacks[last_callback++] = cb;
        callbacks[last_callback++] = eb;
        return (Deferred<R>) ((Deferred) this);
      }
    }  // end of synchronized block

    if (!doCall(result instanceof Exception ? eb : cb)) {
      // While we were executing the callback, another thread could have
      // added more callbacks.  If doCall returned true, it means we're
      // PAUSED, so we won't reach this point, because the Deferred we're
      // waiting on will call us back later.  But if we're still in state
      // RUNNING, we'll get to here, and we must check to see if any new
      // callbacks were added while we were executing doCall, because if
      // there are, we must execute them immediately, because no one else
      // is going to execute them for us otherwise.
      boolean more;
      synchronized (this) {
        more = callbacks != null && next_callback != last_callback;
      }
      if (more) {
        runCallbacks();  // Will put us back either in DONE or in PAUSED.
      } else {
        state = DONE;
      }
    }
    return (Deferred<R>) ((Object) this);
  }

The definition process of last call is as follows:

  /**
   * Index in {@link #callbacks} past the last callback to invoke.
   Index of the last callbacks in callbacks
   
   * Invariants:
   *   - When entering DONE, this value is reset to 0. When you enter the DONE state, this value will be reset to 0
   *   - All the callbacks at and after this index are null.  The callbacks at and after this index are null
   *   - This value might be equal to {@code callbacks.length}.  This value may be the same as callbacks.length
   *   - All accesses to this value must be done while synchronizing on `this'.  All access to this value must be in synchronized(this)
   
   */
  private short last_callback;

This method not only registers a cb, but also registers an eb. Generate a callbacks at the same time.
Finally, call the callback(final Object initresult) method. As follows:

/**
   * Starts running the callback chain.
		Start to run the callback chain
   * <p>
   * This posts the initial result that will be passed to the first callback
   * in the callback chain.  If the argument is an {@link Exception} then
   * the "errback" chain will be triggered instead.
   This publishes the initial result, which will be passed to the first callback function in the callback chain. If parameters
   Is an Exception, then the errback chain will trigger.
   
   * <p>
   * This method will not let any {@link Exception} thrown by a callback
   * propagate.  You shouldn't try to catch any {@link RuntimeException} when
   * you call this method, as this is unnecessary.
   This method does not let any Exception throw be propagated by a callback. You should not try to catch RuntimeException
   When you call this method, it's unnecessary.
   
   * @param initresult The initial result with which to start the 1st callback.
   * The following must be true:
   * {@code initresult instanceof T || initresult instanceof }{@link Exception}
	Start the initial result of the first callback.
	The following will be true:
	initresult instanceof T || initresult instanceof Exception
   * @throws AssertionError if this method was already called on this instance.
   * @throws AssertionError if {@code initresult == this}.
   */
  public void callback(final Object initresult) {
    if (!casState(PENDING, RUNNING)) {
      throw new AssertionError("This Deferred was already called!"
        + "  New result=" + initresult + ", this=" + this);
    }
    result = initresult;
    if (initresult instanceof Deferred) {
      // Twisted doesn't allow a callback chain to start with another Deferred
      // but I don't see any reason.  Maybe it was to help prevent people from
      // creating recursive callback chains that would never terminate?  We
      // already check for the obvious in handleContinuation by preventing
      // this Deferred from depending on itself, but there's no way to prevent
      // people from building mutually dependant Deferreds or complex cyclic
      // chains of Deferreds, unless we keep track in a set of all the
      // Deferreds we go through while executing a callback chain, which seems
      // like an unnecessary complication for uncommon cases (bad code). Plus,
      // when that actually happens and people write buggy code that creates
      // cyclic chains, they will quickly get a CallbackOverflowError.
      final Deferred d = (Deferred) initresult;
      if (this == d) {
        throw new AssertionError("A Deferred cannot be given to itself"
                                 + " as a result.  this=" + this);
      }
      handleContinuation(d, null);
    }
    runCallbacks();
  }  

Where 'casState( ) as follows:

  /**
   * Atomically compares and swaps the state of this Deferred.
     Atomically compare and exchange the state of the Deferred object
   
   * @param cmp The expected state to compare against.
   * @param val The new state to transition to if the comparison is successful. If successful, transition to a new state
   * @return {@code true} if the CAS succeeded, {@code false} otherwise.If CAS succeeds, it returns true; otherwise, it returns false.
   
   */
  private boolean casState(final int cmp, final int val) {
    return stateUpdater.compareAndSet(this, cmp, val);
  }

Where hanging = 0, running = 1

It will enter the method runCallbacks(), as follows:

/**
   * Executes all the callbacks in the current chain.
   Execute all the callback s in the current chain
   */
  private void runCallbacks() {
    while (true) {//Note that this is a dead loop. You can exit through break only in the if() condition
	  //Define a normal Callback
      Callback cb = null;
	  //Then define an error Callback
      Callback eb = null;
	  
	  //Lock the current object using the synchronization method
      synchronized (this) {
        // Read those into local variables so we can call doCall and invoke the
        // callbacks without holding the lock on `this', which would cause a
        // deadlock if we try to addCallbacks to `this' while a callback is
        // running.
        if (callbacks != null && next_callback != last_callback) {
          cb = callbacks[next_callback++];
          eb = callbacks[next_callback++];
        }
        // Also, we may need to atomically change the state to DONE.
        // Otherwise if another thread is blocked in addCallbacks right before
        // we're done processing the last element, we'd enter state DONE and
        // leave this method, and then addCallbacks would add callbacks that
        // would never get called.
        else {
          state = DONE;
          callbacks = null;
          next_callback = last_callback = 0;
          break;
        }
      }

      //final long start = System.nanoTime();
      //LOG.debug("START >>>>>>>>>>>>>>>>>> doCall(" + cb + ", " + eb + ')');
	  //If result is of type Exception, eb is called, / otherwise cb is called	  
      if (doCall(result instanceof Exception ? eb : cb)) {
        //LOG.debug("PAUSE ================== doCall(" + cb + ", " + eb
        //          + ") in " + (System.nanoTime() - start) / 1000 + "us");
        break;
      }
      //LOG.debug("DONE  <<<<<<<<<<<<<<<<<< doCall(" + cb + ", " + eb
      //          + "), result=" + result
      //          + " in " + (System.nanoTime() - start) / 1000 + "us");
    }
  }
/**
   * Executes a single callback, handling continuation if it returns a Deferred.
    Executes a single callback, and continues processing if it returns a Deferred object.
   
   * @param cb The callback to execute.  callback to be executed
   * @return {@code true} if the callback returned a Deferred and we switched to
   * PAUSED, {@code false} otherwise and we didn't change state.   
	Returns true if callback returns a Deferred and we switch to PAUSED, or false if we cannot switch states
   */
  @SuppressWarnings("unchecked")
  private boolean doCall(final Callback cb) {
    try {
      //LOG.debug("doCall(" + cb + '@' + cb.hashCode() + ')' + super.hashCode());
	  
      result = cb.call(result);
    } catch (Exception e) {
      result = e;
    }

    if (result instanceof Deferred) {
      handleContinuation((Deferred) result, cb);
      return true;
    }
    return false;
  }  

This call() method will call the method in its implementation class. In this case, it uses the call(String arg) method in MyCallback class

	public String call(String arg) throws Exception {
		System.out.println("mycb is called with: " + arg);
		return arg;
	}

If the returned result is still of type Deferred, enter the if branch and call handleContinuation((Deferred) result,cb); otherwise, return false directly.
When false is returned, if(doCall(result instanceof Exception? eb:cb)){break} will be used
It will not enter the if code block, so it exits normally.

Posted by popsiclesph on Sun, 08 Dec 2019 18:55:11 -0800