Package org.eclipse.jgit.storage.dht.spi.util

Source Code of org.eclipse.jgit.storage.dht.spi.util.AbstractWriteBuffer$WrappedCallback

/*
* Copyright (C) 2011, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
*   notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
*   copyright notice, this list of conditions and the following
*   disclaimer in the documentation and/or other materials provided
*   with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
*   names of its contributors may be used to endorse or promote
*   products derived from this software without specific prior
*   written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.storage.dht.spi.util;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.eclipse.jgit.storage.dht.AsyncCallback;
import org.eclipse.jgit.storage.dht.DhtException;
import org.eclipse.jgit.storage.dht.DhtTimeoutException;
import org.eclipse.jgit.storage.dht.spi.WriteBuffer;

/**
* Abstract buffer service built on top of an ExecutorService.
* <p>
* Writes are combined together into batches, to reduce RPC overhead when there
* are many small writes occurring. Batches are sent asynchronously when they
* reach 512 KiB worth of key/column/value data. The calling application is
* throttled when the outstanding writes are equal to the buffer size, waiting
* until the cluster has replied with success or failure.
* <p>
* This buffer implementation is not thread-safe, it assumes only one thread
* will use the buffer instance. (It does however correctly synchronize with the
* background tasks it spawns.)
*/
public abstract class AbstractWriteBuffer implements WriteBuffer {
  private final static int AUTO_FLUSH_SIZE = 512 * 1024;

  private final ExecutorService executor;

  private final int bufferSize;

  private final List<Future<?>> running;

  private final Object runningLock;

  private final Semaphore spaceAvailable;

  private int queuedCount;

  private boolean flushing;

  private Callable<?> finalTask;

  /**
   * Initialize a buffer with a backing executor service.
   *
   * @param executor
   *            service to run mutation tasks on.
   * @param bufferSize
   *            maximum number of bytes to have pending at once.
   */
  protected AbstractWriteBuffer(ExecutorService executor, int bufferSize) {
    this.executor = executor;
    this.bufferSize = bufferSize;
    this.running = new LinkedList<Future<?>>();
    this.runningLock = new Object();
    this.spaceAvailable = new Semaphore(bufferSize);
  }

  /**
   * Notify the buffer data is being added onto it.
   * <p>
   * This method waits until the buffer has sufficient space for the requested
   * data, thereby throttling the calling application code. It returns true if
   * its recommendation is for the buffer subclass to copy the data onto its
   * internal buffer and defer starting until later. It returns false if the
   * recommendation is to start the operation immediately, due to the large
   * size of the request.
   * <p>
   * Buffer implementors should keep in mind that the return value is offered
   * as advice only, they may choose to implement different behavior.
   *
   * @param size
   *            an estimated number of bytes that the buffer will be
   *            responsible for until the operation completes. This should
   *            include the row keys and column headers, in addition to the
   *            data values.
   * @return true to enqueue the operation; false to start it right away.
   * @throws DhtException
   *             the current thread was interrupted before space became
   *             available in the buffer.
   */
  protected boolean add(int size) throws DhtException {
    acquireSpace(size);
    return size < AUTO_FLUSH_SIZE;
  }

  /**
   * Notify the buffer bytes were enqueued.
   *
   * @param size
   *            the estimated number of bytes that were enqueued.
   * @throws DhtException
   *             a previously started operation completed and failed.
   */
  protected void queued(int size) throws DhtException {
    queuedCount += size;

    if (AUTO_FLUSH_SIZE < queuedCount) {
      startQueuedOperations(queuedCount);
      queuedCount = 0;
    }
  }

  /**
   * Start all queued operations.
   * <p>
   * This method is invoked by {@link #queued(int)} or by {@link #flush()}
   * when there is a non-zero number of bytes already enqueued as a result of
   * prior {@link #add(int)} and {#link {@link #queued(int)} calls.
   * <p>
   * Implementors should use {@link #start(Callable, int)} to begin their
   * mutation tasks in the background.
   *
   * @param bufferedByteCount
   *            number of bytes that were already enqueued. This count should
   *            be passed to {@link #start(Callable, int)}.
   * @throws DhtException
   *             a previously started operation completed and failed.
   */
  protected abstract void startQueuedOperations(int bufferedByteCount)
      throws DhtException;

  public void flush() throws DhtException {
    try {
      flushing = true;

      if (0 < queuedCount) {
        startQueuedOperations(queuedCount);
        queuedCount = 0;
      }

      // If a task was created above, try to use the current thread
      // instead of burning an executor thread for the final work.

      if (finalTask != null) {
        try {
          waitFor(finalTask);
        } finally {
          finalTask = null;
        }
      }

      synchronized (runningLock) {
        checkRunningTasks(true);
      }
    } finally {
      flushing = false;
    }
  }

  public void abort() throws DhtException {
    synchronized (runningLock) {
      checkRunningTasks(true);
    }
  }

  private void acquireSpace(int sz) throws DhtException {
    try {
      final int permits = permitsForSize(sz);
      if (spaceAvailable.tryAcquire(permits))
        return;

      if (0 < queuedCount) {
        startQueuedOperations(queuedCount);
        queuedCount = 0;
      }

      spaceAvailable.acquire(permits);
    } catch (InterruptedException e) {
      throw new DhtTimeoutException(e);
    }
  }

  private int permitsForSize(int size) {
    // Do not acquire more than the configured buffer size,
    // even if the actual write size is larger. Trying to
    // acquire more would never succeed.

    if (size <= 0)
      size = 1;
    return Math.min(size, bufferSize);
  }

  /**
   * Start a mutation task.
   *
   * @param <T>
   *            any type the task might return.
   * @param task
   *            the mutation task. The result of the task is discarded, so
   *            callers should perform result validation within the task.
   * @param size
   *            number of bytes that are buffered within the task.
   * @throws DhtException
   *             a prior task has completed, and failed.
   */
  protected <T> void start(final Callable<T> task, int size)
      throws DhtException {
    final int permits = permitsForSize(size);
    final Callable<T> op = new Callable<T>() {
      public T call() throws Exception {
        try {
          return task.call();
        } finally {
          spaceAvailable.release(permits);
        }
      }
    };

    if (flushing && finalTask == null) {
      // If invoked by flush(), don't start on an executor.
      //
      finalTask = op;
      return;
    }

    synchronized (runningLock) {
      if (!flushing)
        checkRunningTasks(false);
      running.add(executor.submit(op));
    }
  }

  /**
   * Wrap a callback to update the buffer.
   * <p>
   * Flushing the buffer will wait for the returned callback to complete.
   *
   * @param <T>
   *            any type the task might return.
   * @param callback
   *            callback invoked when the task has finished.
   * @param size
   *            number of bytes that are buffered within the task.
   * @return wrapped callback that will update the buffer state when the
   *         callback is invoked.
   * @throws DhtException
   *             a prior task has completed, and failed.
   */
  protected <T> AsyncCallback<T> wrap(final AsyncCallback<T> callback,
      int size) throws DhtException {
    int permits = permitsForSize(size);
    WrappedCallback<T> op = new WrappedCallback<T>(callback, permits);
    synchronized (runningLock) {
      checkRunningTasks(false);
      running.add(op);
    }
    return op;
  }

  private void checkRunningTasks(boolean wait) throws DhtException {
    if (running.isEmpty())
      return;

    Iterator<Future<?>> itr = running.iterator();
    while (itr.hasNext()) {
      Future<?> task = itr.next();
      if (task.isDone() || wait) {
        itr.remove();
        waitFor(task);
      }
    }
  }

  private static void waitFor(Callable<?> task) throws DhtException {
    try {
      task.call();
    } catch (DhtException err) {
      throw err;
    } catch (Exception err) {
      throw new DhtException(err);
    }
  }

  private static void waitFor(Future<?> task) throws DhtException {
    try {
      task.get();

    } catch (InterruptedException e) {
      throw new DhtTimeoutException(e);

    } catch (ExecutionException err) {

      Throwable t = err;
      while (t != null) {
        if (t instanceof DhtException)
          throw (DhtException) t;
        t = t.getCause();
      }

      throw new DhtException(err);
    }
  }

  private final class WrappedCallback<T> implements AsyncCallback<T>,
      Future<T> {
    private final AsyncCallback<T> callback;

    private final int permits;

    private final CountDownLatch sync;

    private volatile boolean done;

    WrappedCallback(AsyncCallback<T> callback, int permits) {
      this.callback = callback;
      this.permits = permits;
      this.sync = new CountDownLatch(1);
    }

    public void onSuccess(T result) {
      try {
        callback.onSuccess(result);
      } finally {
        done();
      }
    }

    public void onFailure(DhtException error) {
      try {
        callback.onFailure(error);
      } finally {
        done();
      }
    }

    private void done() {
      spaceAvailable.release(permits);
      done = true;
      sync.countDown();
    }

    public boolean cancel(boolean mayInterrupt) {
      return false;
    }

    public T get() throws InterruptedException, ExecutionException {
      sync.await();
      return null;
    }

    public T get(long time, TimeUnit unit) throws InterruptedException,
        ExecutionException, TimeoutException {
      sync.await(time, unit);
      return null;
    }

    public boolean isCancelled() {
      return false;
    }

    public boolean isDone() {
      return done;
    }
  }
}
TOP

Related Classes of org.eclipse.jgit.storage.dht.spi.util.AbstractWriteBuffer$WrappedCallback

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.