Package org.jboss.cache.loader

Source Code of org.jboss.cache.loader.AsyncCacheLoader$AsyncProcessor

/**
*
*/
package org.jboss.cache.loader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.Modification;
import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
import org.jboss.cache.util.MapCopy;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
* The AsyncCacheLoader is a delegating cache loader that extends
* AbstractDelegatingCacheLoader overriding methods to that should not
* just delegate the operation to the underlying cache loader.
* <p/>
* Read operations are done synchronously, while write (CRUD - Create, Remove,
* Update, Delete) operations are done asynchronously.  There is no provision
* for exception handling at the moment for problems encountered with the
* underlying CacheLoader during a CRUD operation, and the exception is just
* logged.
* <p/>
* When configuring the CacheLoader, use the following attribute:
* <p/>
* <code>
* &lt;attribute name="CacheLoaderAsynchronous"&gt;true&lt;/attribute&gt;
* </code>
* <p/>
* to define whether cache loader operations are to be asynchronous.  If not
* specified, a cache loader operation is assumed synchronous.
* <p/>
* <p/>
* The following additional parameters are available:
* <dl>
* <dt>cache.async.batchSize</dt>
* <dd>Number of modifications to commit in one transaction, default is
* 100. The minimum batch size is 1.</dd>
* <dt>cache.async.pollWait</dt>
* <dd>How long to wait before processing an incomplete batch, in
* milliseconds.  Default is 100.  Set this to 0 to not wait before processing
* available records.</dd>
* <dt>cache.async.returnOld</dt>
* <dd>If <code>true</code>, this loader returns the old values from {@link
* #put} and {@link #remove} methods.  Otherwise, these methods always return
* null.  Default is true.  <code>false</code> improves the performance of these
* operations.</dd>
* <dt>cache.async.queueSize</dt>
* <dd>Maximum number of entries to enqueue for asynchronous processing.
* Lowering this size may help prevent out-of-memory conditions.  It also may
* help to prevent less records lost in the case of JVM failure.  Default is
* 10,000 operations.</dd>
* <dt>cache.async.put</dt>
* <dd>If set to false, all {@link #put} operations will be processed
* synchronously, and then only the {@link #remove} operations will be
* processed asynchronously. This mode may be useful for processing
* expiration of messages within a separate thread and keeping other
* operations synchronous for reliability.
* </dd>
* </dl>
* For increased performance for many smaller transactions, use higher values
* for <code>cache.async.batchSize</code> and
* <code>cache.async.pollWait</code>.  For larger sized records, use a smaller
* value for <code>cache.async.queueSize</code>.
*
* @author Manik Surtani (manik.surtani@jboss.com)
*/
public class AsyncCacheLoader extends AbstractDelegatingCacheLoader
{

   private static final Log log = LogFactory.getLog(AsyncCacheLoader.class);

   private static AtomicInteger threadId = new AtomicInteger(0);

   /**
    * Default limit on entries to process asynchronously.
    */
   public static final int DEFAULT_QUEUE_SIZE = 10000;

   private AsyncCacheLoaderConfig config;
   private AsyncProcessor processor;
   private AtomicBoolean stopped = new AtomicBoolean(true);
   private BlockingQueue<Modification> queue = new ArrayBlockingQueue<Modification>(DEFAULT_QUEUE_SIZE);

   public AsyncCacheLoader()
   {
      super(null);
   }

   public AsyncCacheLoader(CacheLoader cacheLoader)
   {
      super(cacheLoader);
   }

   public void setConfig(IndividualCacheLoaderConfig base)
   {
      if (base instanceof AsyncCacheLoaderConfig)
      {
         config = (AsyncCacheLoaderConfig) base;
      }
      else
      {
         config = new AsyncCacheLoaderConfig(base);
      }

      if (config.getQueueSize() > 0)
      {
         queue = new ArrayBlockingQueue<Modification>(config.getQueueSize());
      }

      super.setConfig(base);
   }

   public Map get(Fqn name) throws Exception
   {
      try
      {
         return super.get(name);
      }
      catch (IOException e)
      {
         // FileCacheLoader sometimes does this apparently
         log.trace(e);
         return new HashMap(); // ?
      }
   }

   Object get(Fqn name, Object key) throws Exception
   {
      if (config.getReturnOld())
      {
         try
         {
            Map map = super.get(name);
            if (map != null)
            {
               return map.get(key);
            }
         }
         catch (IOException e)
         {
            // FileCacheLoader sometimes does this apparently
            log.trace(e);
         }
      }
      return null;
   }

   public Object put(Fqn name, Object key, Object value) throws Exception
   {
      if (config.getUseAsyncPut())
      {
         Object oldValue = get(name, key);
         Modification mod = new Modification(Modification.ModificationType.PUT_KEY_VALUE, name, key, value);
         enqueue(mod);
         return oldValue;
      }
      else
      {
         return super.put(name, key, value);
      }
   }

   public void put(Fqn name, Map attributes) throws Exception
   {
      if (config.getUseAsyncPut())
      {
         // JBCACHE-769 -- make a defensive copy
         Map attrs = (attributes == null ? null : new MapCopy(attributes));
         Modification mod = new Modification(Modification.ModificationType.PUT_DATA, name, attrs);
         enqueue(mod);
      }
      else
      {
         super.put(name, attributes); // Let delegate make its own defensive copy
      }
   }

   public void put(List<Modification> modifications) throws Exception
   {
      if (config.getUseAsyncPut())
      {
         for (Modification modification : modifications)
         {
            enqueue(modification);
         }
      }
      else
      {
         super.put(modifications);
      }
   }

   public Object remove(Fqn name, Object key) throws Exception
   {
      Object oldValue = get(name, key);
      Modification mod = new Modification(Modification.ModificationType.REMOVE_KEY_VALUE, name, key);
      enqueue(mod);
      return oldValue;
   }

   public void remove(Fqn name) throws Exception
   {
      Modification mod = new Modification(Modification.ModificationType.REMOVE_NODE, name);
      enqueue(mod);
   }

   public void removeData(Fqn name) throws Exception
   {
      Modification mod = new Modification(Modification.ModificationType.REMOVE_DATA, name);
      enqueue(mod);
   }

   public void start() throws Exception
   {
      if (log.isInfoEnabled()) log.info("Async cache loader starting: " + this);
      stopped.set(false);
      super.start();
      processor = new AsyncProcessor();
      processor.start();
   }

   public void stop()
   {
      stopped.set(true);
      if (processor != null)
      {
         processor.stop();
      }
      super.stop();
   }

   private void enqueue(Modification mod)
           throws CacheException, InterruptedException
   {
      if (stopped.get())
      {
         throw new CacheException("AsyncCacheLoader stopped; no longer accepting more entries.");
      }
      if (log.isTraceEnabled())
      {
         log.trace("Enqueuing modification " + mod);
      }
      queue.put(mod);
   }

   /**
    * Processes (by batch if possible) a queue of {@link Modification}s.
    *
    * @author manik surtani
    */
   private class AsyncProcessor implements Runnable
   {
      private Thread t;

      // Modifications to process as a single put
      private final List<Modification> mods = new ArrayList<Modification>(config.getBatchSize());

      public void start()
      {
         if (t == null || !t.isAlive())
         {
            t = new Thread(this, "AsyncCacheLoader-" + threadId.getAndIncrement());
            t.setDaemon(true);
            t.start();
         }
      }

      public void stop()
      {
         if (t != null)
         {
            t.interrupt();
            try
            {
               t.join();
            }
            catch (InterruptedException e)
            {
            }
         }
         if (!queue.isEmpty())
         {
            log.warn("Async queue not yet empty, possibly interrupted");
         }
      }

      public void run()
      {
         while (!Thread.interrupted())
         {
            try
            {
               run0();
            }
            catch (InterruptedException e)
            {
               break;
            }
         }

         try
         {
            if (log.isTraceEnabled()) log.trace("process remaining batch " + mods.size());
            put(mods);
            if (log.isTraceEnabled()) log.trace("process remaining queued " + queue.size());
            while (!queue.isEmpty())
            {
               run0();
            }
         }
         catch (InterruptedException e)
         {
            log.trace("remaining interrupted");
         }
      }

      private void run0() throws InterruptedException
      {
         log.trace("Checking for modifications");
         int i = queue.drainTo(mods, config.getBatchSize());
         if (i == 0)
         {
            Modification m = queue.take();
            mods.add(m);
         }

         if (log.isTraceEnabled())
         {
            log.trace("Calling put(List) with " + mods.size() + " modifications");
         }
         put(mods);
         mods.clear();
      }

      private void put(List<Modification> mods)
      {
         try
         {
            AsyncCacheLoader.super.put(mods);
         }
         catch (Exception e)
         {
            if (log.isWarnEnabled()) log.warn("Failed to process async modifications: " + e);
            log.debug("Exception: ", e);
         }
      }

      public String toString()
      {
         return "TQ t=" + t;
      }

   }

   public String toString()
   {
      return super.toString() +
              " delegate=[" + super.getCacheLoader() + "]" +
              " processor=" + processor +
              " stopped=" + stopped +
              " batchSize=" + config.getBatchSize() +
              " returnOld=" + config.getReturnOld() +
              " asyncPut=" + config.getUseAsyncPut() +
              " queue.remainingCapacity()=" + queue.remainingCapacity() +
              " queue.peek()=" + queue.peek();
   }

}
TOP

Related Classes of org.jboss.cache.loader.AsyncCacheLoader$AsyncProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.