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

com.cedarsoft.async.AsyncCallSupport Maven / Gradle / Ivy

package com.cedarsoft.async;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.Override;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * An asynchronous call.
 *
 * @param  the type of calls
 */
public class AsyncCallSupport {
  @NonNls
  @NotNull
  private static final Log log = LogFactory.getLog( AsyncCallSupport.class );

  @NotNull
  @NonNls
  public static final String NAME_PREFIX = "AsyncWorkerThread ";

  /**
   * The call queue contains all calls
   */
  @NotNull
  private final Queue callbacksQueue = new LinkedList();
  @NotNull
  private final Map returnValues = new HashMap();
  @NotNull
  private final Set acks = new HashSet();

  private boolean initialized;


  //The worker thread that might have been started
  private transient Thread workerThread;

  public void initializeWorker( @NotNull CallbackCaller callbackCaller ) {
    log.debug( "initializeWorker " + callbackCaller );
    workerThread = new Thread( new AsyncWorker( this, callbackCaller ), NAME_PREFIX + callbackCaller.getDescription() + ' ' + hashCode() );
    workerThread.start();
    initialized = true;
  }

  private void initialize() {//todo remove
  }

  public void shutdown() {
    workerThread.interrupt();
  }

  /**
   * Invokes a callback
   *
   * @param callback the callback
   * @return the return value of the callback
   */
  @NotNull
  public  R invoke( T callback ) throws AsynchroniousInvocationException {
    R value = this.invokeNullable( callback );
    if ( value == null ) {
      throw new IllegalStateException( "Return values was null. Expected not null." );
    }
    return value;
  }

  /**
   * Invoke a nullable callback
   *
   * @param callback the callback
   * @return the return value (may be null)
   */
  @Nullable
  public  R invokeNullable( @NotNull T callback ) throws AsynchroniousInvocationException {
    long start = System.currentTimeMillis();
    log.debug( "invoking Nullable " + callback );
    log.debug( "Is initialized: " + initialized );
    if ( !isInitialized() ) {
      initialize();
    }

    synchronized ( callbacksQueue ) {
      log.debug( "got Lock on callbacksQueue" );
      callbacksQueue.add( callback );
      log.debug( "added callback" );
      callbacksQueue.notifyAll();
      log.debug( "notified callbacks queue (added callback with hash code " + callback.hashCode() + ')' );
    }

    synchronized ( acks ) {
      log.debug( "got log on acks" );
      while ( !acks.remove( callback ) ) {
        try {
          log.debug( "waiting for acks - nothing found. " + acks.size() );
          acks.wait();
        } catch ( InterruptedException e ) {
          throw new AsynchroniousInvocationException( "Interrupted", e );
        }
      }
    }

    synchronized ( returnValues ) {
      log.debug( "got lock on return values" );

      Object returnValue = returnValues.remove( callback );

      long end = System.currentTimeMillis();
      log.info( "Database action took " + ( end - start ) + " ms - returning <" + returnValue + "> for " + callback );

      /**
       * If it is an exception --> throw it
       */
      if ( returnValue instanceof Throwable ) {
        log.warn( "Wrapping Throwable: " + returnValue );
        throw new AsynchroniousInvocationException( ( Throwable ) returnValue );
      }

      //No exception, simple return value
      log.debug( "Returning " + returnValue );
      //noinspection unchecked
      return ( R ) returnValue;
    }
  }

  /**
   * Invokes a void callback
   *
   * @param callback the callback
   */
  public void invokeVoid( @NotNull T callback ) throws AsynchroniousInvocationException {
    invokeNullable( callback );
  }

  public boolean isInitialized() {
    return initialized;
  }

  /**
   * Returns the next action
   *
   * @return the next action from the queue
   */
  @NotNull
  protected T waitForNextCallback() throws InterruptedException {
    T callback;
    synchronized ( callbacksQueue ) {
      log.debug( "got lock on callbacks queue" );
      while ( callbacksQueue.isEmpty() ) {
        log.debug( "nothing in callbacksqueue - waiting" );
        callbacksQueue.wait();
      }
      callback = callbacksQueue.poll();
    }
    log.debug( "new callback added - waiting successfull. Returning " + callback );
    return callback;
  }

  /**
   * Acknowledges the asynchronous dao that an action has been executed
   *
   * @param action      the action that has been executed
   * @param returnValue the return value (or exception)
   */
  protected void acknowledge( @NotNull T action, @Nullable Object returnValue ) {
    log.debug( "acknowleding " + action.hashCode() + " with return value " + returnValue );
    synchronized ( returnValues ) {
      returnValues.put( action, returnValue );
    }
    log.debug( "Added return value" );
    synchronized ( acks ) {
      log.debug( "Got Lock on acks" );
      int oldSize = acks.size();
      acks.add( action );
      if ( oldSize == acks.size() ) {
        throw new IllegalStateException( "You must not reuse the action " + action + " with return value " + returnValue );
      }
      acks.notifyAll();
      log.debug( "notified acks" );
    }
  }

  /**
   * Verifies that no worker threads have been left
   *
   * @throws InterruptedException
   */
  public static void verifyNoWorkerThreadsLeft() throws InterruptedException {
    Thread.sleep( 1000 );

    ThreadGroup group = Thread.currentThread().getThreadGroup();
    Thread[] threads = new Thread[group.activeCount()];
    group.enumerate( threads );

    StringBuilder found = new StringBuilder();

    for ( Thread thread : threads ) {
      if ( thread.getName().contains( NAME_PREFIX ) ) {
        System.out.println( "---> Uups, found one! " + thread );
        found.append( "Found remaing thread " + thread + "\n" );
      }
    }

    if ( found.length() > 0 ) {
      throw new IllegalThreadStateException( found.toString() );
    }
  }

  public static int countWorkerThreadsLeft() throws InterruptedException {
    Thread.sleep( 1000 );

    ThreadGroup group = Thread.currentThread().getThreadGroup();
    Thread[] threads = new Thread[group.activeCount()];
    group.enumerate( threads );

    int count = 0;

    for ( Thread thread : threads ) {
      if ( thread.getName().contains( NAME_PREFIX ) ) {
        count++;
      }
    }

    return count;
  }


  public static void shutdownWorkerThreads() throws InterruptedException {
    ThreadGroup group = Thread.currentThread().getThreadGroup();
    Thread[] threads = new Thread[group.activeCount()];
    group.enumerate( threads );

    for ( Thread thread : threads ) {
      if ( thread.getName().contains( NAME_PREFIX ) ) {
        thread.interrupt();
        thread.stop();
      }
    }

    verifyNoWorkerThreadsLeft();
  }

  static class AsyncWorker implements Runnable {
    private static final Log log = LogFactory.getLog( AsyncCallSupport.AsyncWorker.class );

    @NotNull
    private final AsyncCallSupport support;
    @NotNull
    private final CallbackCaller callbackCaller;

    AsyncWorker( @NotNull AsyncCallSupport support, @NotNull CallbackCaller callbackCaller ) {
      this.support = support;
      this.callbackCaller = callbackCaller;
    }

    @Override
    public void run() {
      log.debug( "up and running" );
      //noinspection InfiniteLoopStatement
      while ( true ) {
        if ( Thread.currentThread().isInterrupted() ) {
          log.debug( "Thread has been interrupted --> exiting" );
          return;
        }

        try {
          log.debug( "waiting for next callback" );
          T callback = support.waitForNextCallback();
          log.debug( "got callback" );

          Object returnValue;
          synchronized ( support ) {
            returnValue = execute( callback );
          }
          log.debug( "executed" );
          support.acknowledge( callback, returnValue );
          log.debug( "acknowledged" );
        } catch ( InterruptedException ignore ) {
          log.debug( "Thread has been interrupted --> exiting" );
          return;
        }
      }
    }

    @Nullable
    protected Object execute( @NotNull T callback ) {
      long start = System.currentTimeMillis();
      Object returnValue;
      //noinspection CatchGenericClass
      try {
        returnValue = callbackCaller.call( callback );
      } catch ( Throwable e ) {
        log.warn( "Got an exception", e );
        returnValue = e;
      }

      long end = System.currentTimeMillis();
      log.info( "Executing callback took " + ( end - start ) + "ms" );

      return returnValue;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy