
com.landawn.abacus.core.AsyncBatchExecutor Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2015 HaiYang Li
*
* 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 com.landawn.abacus.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.landawn.abacus.EntityManager;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.util.ExceptionUtil;
import com.landawn.abacus.util.MoreExecutors;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Options;
// TODO: Auto-generated Javadoc
/**
*
* @author Haiyang Li
* @param
* @since 0.8
*/
public final class AsyncBatchExecutor {
private static final Logger logger = LoggerFactory.getLogger(AsyncBatchExecutor.class);
public static final int DEFAULT_CAPACITY = 8192;
public static final int DEFAULT_EVICT_DELAY = 3000;
/**
* The Constant DEFAULT_BATCH_SIZE.
*
* @see Options#DEFAULT_BATCH_SIZE
*/
public static final int DEFAULT_BATCH_SIZE = Options.DEFAULT_BATCH_SIZE;
static final ScheduledExecutorService scheduledExecutor;
static {
final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(16);
executor.setKeepAliveTime(180, TimeUnit.SECONDS);
executor.allowCoreThreadTimeOut(true);
executor.setRemoveOnCancelPolicy(true);
scheduledExecutor = MoreExecutors.getExitingScheduledExecutorService(executor);
}
private final ScheduledFuture> scheduleFuture;
protected final EntityManager em;
protected final Map options;
protected final List addQueue;
protected final List updateQueue;
protected final List deleteQueue;
/** Unit is millisecond. */
private final int capacity;
private final long evictDelay;
private final int batchSize;
private boolean isClosed = false;
public AsyncBatchExecutor(final EntityManager em) {
this(em, DEFAULT_CAPACITY, DEFAULT_EVICT_DELAY, DEFAULT_BATCH_SIZE);
}
public AsyncBatchExecutor(final EntityManager em, final int capacity, final long evictDelay, final int batchSize) {
if ((em == null) || (evictDelay <= 0) || (batchSize <= 0) || (capacity <= 0)) {
throw new IllegalArgumentException();
}
this.em = em;
this.addQueue = new ArrayList<>(capacity);
this.updateQueue = new ArrayList<>(capacity);
this.deleteQueue = new ArrayList<>(capacity);
this.evictDelay = evictDelay;
this.batchSize = batchSize;
this.capacity = capacity;
this.options = N.asProps(Options.BATCH_SIZE, batchSize);
// start evict process.
final Runnable commitTask = new Runnable() {
@Override
public void run() {
if ((addQueue.size() > 0) || (updateQueue.size() > 0) || (deleteQueue.size() > 0)) {
commit();
}
}
};
scheduleFuture = scheduledExecutor.scheduleWithFixedDelay(commitTask, evictDelay, evictDelay, TimeUnit.MILLISECONDS);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
logger.warn("Starting to shutdown task in AsyncBatchExecutor");
try {
close();
} finally {
logger.warn("Completed to shutdown task in AsyncBatchExecutor");
}
}
});
}
/**
* Gets the evict delay.
*
* @return
*/
public long getEvictDelay() {
return evictDelay;
}
/**
* Gets the batch size.
*
* @return
*/
public int getBatchSize() {
return batchSize;
}
/**
* Gets the capacity.
*
* @return
*/
public int getCapacity() {
return capacity;
}
/**
*
* @param e
*/
public void add(E e) {
assertNotClosed();
synchronized (addQueue) {
addElement(addQueue, e);
}
}
/**
*
* @param e
*/
public void update(E e) {
assertNotClosed();
synchronized (updateQueue) {
addElement(updateQueue, e);
}
}
/**
*
* @param e
*/
public void delete(E e) {
assertNotClosed();
synchronized (deleteQueue) {
addElement(deleteQueue, e);
}
}
/**
* Commit.
*/
@SuppressWarnings("unchecked")
public void commit() {
assertNotClosed();
internalCommit();
}
/**
* Internal commit.
*/
private void internalCommit() {
if (addQueue.size() > 0) {
List entities = new ArrayList<>();
synchronized (addQueue) {
entities.addAll(addQueue);
addQueue.clear();
}
if (logger.isInfoEnabled()) {
logger.info("START-BATCH-ADD[" + entities.size() + "]");
}
for (int size = entities.size(), from = 0, to = Math.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
try {
em.addAll(entities.subList(from, to), options);
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("FAILED-BATCH-ADD[" + entities.size() + "]. " + ExceptionUtil.getMessage(e));
}
}
}
if (logger.isInfoEnabled()) {
logger.info("END-BATCH-ADD[" + entities.size() + "]");
}
}
if (updateQueue.size() > 0) {
List entities = new ArrayList<>();
synchronized (updateQueue) {
entities.addAll(updateQueue);
updateQueue.clear();
}
if (logger.isInfoEnabled()) {
logger.info("START-BATCH-UPDATE[" + entities.size() + "]");
}
for (int size = entities.size(), from = 0, to = Math.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
try {
em.updateAll(entities.subList(from, to), options);
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("FAILED-BATCH-UPDATE[" + entities.size() + "]. " + ExceptionUtil.getMessage(e));
}
}
}
if (logger.isInfoEnabled()) {
logger.info("END-BATCH-UPDATE[" + entities.size() + "]");
}
}
if (deleteQueue.size() > 0) {
List entities = new ArrayList<>();
synchronized (deleteQueue) {
entities.addAll(deleteQueue);
deleteQueue.clear();
}
if (logger.isInfoEnabled()) {
logger.info("START-BATCH-DELETE[" + entities.size() + "]");
}
for (int size = entities.size(), from = 0, to = Math.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
try {
em.deleteAll(entities.subList(from, to), options);
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("FAILED-BATCH-DELETE[" + entities.size() + "]. " + ExceptionUtil.getMessage(e));
}
}
}
if (logger.isInfoEnabled()) {
logger.info("END-BATCH-DELETE[" + entities.size() + "]");
}
}
}
/**
* Adds the element.
*
* @param queue
* @param e
*/
protected void addElement(List queue, E e) {
if ((addQueue.size() + updateQueue.size() + deleteQueue.size()) > capacity) {
String msg = "Queue is full. The capacity is " + capacity;
throw new RuntimeException(msg);
}
queue.add(e);
}
/**
* Close.
*/
public void close() {
if (isClosed) {
return;
}
isClosed = true;
try {
if (scheduleFuture != null) {
scheduleFuture.cancel(true);
}
} finally {
internalCommit();
}
}
/**
* Assert not closed.
*/
protected void assertNotClosed() {
if (isClosed) {
throw new RuntimeException("This object pool has been closed");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy