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

org.ehcache.impl.internal.loaderwriter.writebehind.BatchingLocalHeapWriteBehindQueue Maven / Gradle / Ivy

There is a newer version: 3.10.8
Show newest version
/*
 * Copyright Terracotta, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ehcache.impl.internal.loaderwriter.writebehind;

import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.impl.internal.loaderwriter.writebehind.operations.BatchOperation;
import org.ehcache.impl.internal.loaderwriter.writebehind.operations.DeleteOperation;
import org.ehcache.impl.internal.loaderwriter.writebehind.operations.DeleteAllOperation;
import org.ehcache.impl.internal.loaderwriter.writebehind.operations.SingleOperation;
import org.ehcache.impl.internal.loaderwriter.writebehind.operations.WriteOperation;
import org.ehcache.impl.internal.loaderwriter.writebehind.operations.WriteAllOperation;
import org.ehcache.spi.loaderwriter.CacheLoaderWriter;
import org.ehcache.spi.loaderwriter.WriteBehindConfiguration;
import org.ehcache.spi.loaderwriter.WriteBehindConfiguration.BatchingConfiguration;
import org.ehcache.core.spi.service.ExecutionService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.ehcache.impl.internal.executor.ExecutorUtil.shutdown;
import static org.ehcache.impl.internal.executor.ExecutorUtil.shutdownNow;
import static org.ehcache.impl.internal.executor.ExecutorUtil.waitFor;

/**
 *
 * @author cdennis
 */
public class BatchingLocalHeapWriteBehindQueue extends AbstractWriteBehind {

  private static final Logger LOGGER = LoggerFactory.getLogger(BatchingLocalHeapWriteBehindQueue.class);

  private final CacheLoaderWriter cacheLoaderWriter;

  private final ConcurrentMap> latest = new ConcurrentHashMap<>();

  private final BlockingQueue executorQueue;
  private final ExecutorService executor;
  private final ScheduledExecutorService scheduledExecutor;

  private final long maxWriteDelayMs;
  private final int batchSize;
  private final boolean coalescing;

  private volatile Batch openBatch;

  public BatchingLocalHeapWriteBehindQueue(ExecutionService executionService, String defaultThreadPool, WriteBehindConfiguration config, CacheLoaderWriter cacheLoaderWriter) {
    super(cacheLoaderWriter);
    this.cacheLoaderWriter = cacheLoaderWriter;
    BatchingConfiguration batchingConfig = config.getBatchingConfiguration();
    this.maxWriteDelayMs = batchingConfig.getMaxDelayUnit().toMillis(batchingConfig.getMaxDelay());
    this.batchSize = batchingConfig.getBatchSize();
    this.coalescing = batchingConfig.isCoalescing();
    this.executorQueue = new LinkedBlockingQueue<>(config.getMaxQueueSize() / batchSize);
    if (config.getThreadPoolAlias() == null) {
      this.executor = executionService.getOrderedExecutor(defaultThreadPool, executorQueue);
    } else {
      this.executor = executionService.getOrderedExecutor(config.getThreadPoolAlias(), executorQueue);
    }
    if (config.getThreadPoolAlias() == null) {
      this.scheduledExecutor = executionService.getScheduledExecutor(defaultThreadPool);
    } else {
      this.scheduledExecutor = executionService.getScheduledExecutor(config.getThreadPoolAlias());
    }
  }

  @Override
  protected SingleOperation getOperation(K key) {
    return latest.get(key);
  }

  @Override
  protected void addOperation(SingleOperation operation) {
    latest.put(operation.getKey(), operation);

    synchronized (this) {
      if (openBatch == null) {
        openBatch = newBatch();
      }
      if (openBatch.add(operation)) {
        submit(openBatch);
        openBatch = null;
      }
    }
  }

  @Override
  public void start() {
    //no-op
  }

  @Override
  public void stop() {
    try {
      synchronized (this) {
        if (openBatch != null) {
          waitFor(submit(openBatch));
          openBatch = null;
        }
      }
    } catch (ExecutionException e) {
      LOGGER.error("Exception running batch on shutdown", e);
    } finally {
      /*
       * The scheduled executor should only contain cancelled tasks, but these
       * can stall a regular shutdown for up to max-write-delay.  So we just
       * kill it now.
       */
      shutdownNow(scheduledExecutor);
      shutdown(executor);
    }
  }

  private Batch newBatch() {
    if (coalescing) {
      return new CoalescingBatch(batchSize);
    } else {
      return new SimpleBatch(batchSize);
    }
  }

  private Future submit(Batch batch) {
    return executor.submit(batch);
  }

  /**
   * Gets the best estimate for items in the queue still awaiting processing.
   * Since the value returned is a rough estimate, it can sometimes be more than
   * the number of items actually in the queue but not less.
   *
   * @return the amount of elements still awaiting processing.
   */
  @Override
  public long getQueueSize() {
    Batch snapshot = openBatch;
    return executorQueue.size() * batchSize + (snapshot == null ? 0 : snapshot.size());
  }

  abstract class Batch implements Runnable {

    private final int batchSize;
    private final ScheduledFuture expireTask;

    Batch(int size) {
      this.batchSize = size;
      this.expireTask = scheduledExecutor.schedule(() -> {
        synchronized (BatchingLocalHeapWriteBehindQueue.this) {
          if (openBatch == Batch.this) {
            submit(openBatch);
            openBatch = null;
          }
        }
      }, maxWriteDelayMs, MILLISECONDS);
    }

    public boolean add(SingleOperation operation) {
      internalAdd(operation);
      return size() >= batchSize;
    }

    @Override
    public void run() {
      try {
        List> batches = createMonomorphicBatches(operations());
        // execute the batch operations
        for (BatchOperation batch : batches) {
          try {
            batch.performOperation(cacheLoaderWriter);
          } catch (Exception e) {
            LOGGER.warn("Exception while bulk processing in write behind queue", e);
          }
        }
      } finally {
        try {
          for (SingleOperation op : operations()) {
            latest.remove(op.getKey(), op);
          }
        } finally {
          LOGGER.debug("Cancelling batch expiry task");
          expireTask.cancel(false);
        }
      }
    }

    protected abstract void internalAdd(SingleOperation operation);

    protected abstract Iterable> operations();

    protected abstract int size();
  }

  private class SimpleBatch extends Batch {

    private final List> operations;

    SimpleBatch(int size) {
      super(size);
      this.operations = new ArrayList<>(size);
    }

    @Override
    public void internalAdd(SingleOperation operation) {
      operations.add(operation);
    }

    @Override
    protected List> operations() {
      return operations;
    }

    @Override
    protected int size() {
      return operations.size();
    }
  }

  private class CoalescingBatch extends Batch {

    private final LinkedHashMap> operations;

    public CoalescingBatch(int size) {
      super(size);
      this.operations = new LinkedHashMap<>(size);
    }

    @Override
    public void internalAdd(SingleOperation operation) {
      operations.put(operation.getKey(), operation);
    }

    @Override
    protected Iterable> operations() {
      return operations.values();
    }

    @Override
    protected int size() {
      return operations.size();
    }
  }

  private static  List> createMonomorphicBatches(Iterable> batch) {
    final List> closedBatches = new ArrayList<>();

    Set activeDeleteKeys = new HashSet<>();
    Set activeWrittenKeys = new HashSet<>();
    List activeDeleteBatch = new ArrayList<>();
    List> activeWriteBatch = new ArrayList<>();

    for (SingleOperation item : batch) {
      if (item instanceof WriteOperation) {
        if (activeDeleteKeys.contains(item.getKey())) {
          //close the current delete batch
          closedBatches.add(new DeleteAllOperation<>(activeDeleteBatch));
          activeDeleteBatch = new ArrayList<>();
          activeDeleteKeys = new HashSet<>();
        }
        activeWriteBatch.add(new SimpleEntry<>(item.getKey(), ((WriteOperation) item).getValue()));
        activeWrittenKeys.add(item.getKey());
      } else if (item instanceof DeleteOperation) {
        if (activeWrittenKeys.contains(item.getKey())) {
          //close the current write batch
          closedBatches.add(new WriteAllOperation<>(activeWriteBatch));
          activeWriteBatch = new ArrayList<>();
          activeWrittenKeys = new HashSet<>();
        }
        activeDeleteBatch.add(item.getKey());
        activeDeleteKeys.add(item.getKey());
      } else {
        throw new AssertionError();
      }
    }

    if (!activeWriteBatch.isEmpty()) {
      closedBatches.add(new WriteAllOperation<>(activeWriteBatch));
    }
    if (!activeDeleteBatch.isEmpty()) {
      closedBatches.add(new DeleteAllOperation<>(activeDeleteBatch));
    }
    return closedBatches;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy