mirror of https://github.com/longtai-cn/hippo4j
parent
be900bca46
commit
aed2c736ba
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 cn.hippo4j.springboot.starter.core;
|
||||
|
||||
import cn.hippo4j.common.executor.ThreadPoolExecutorHolder;
|
||||
import cn.hippo4j.common.executor.ThreadPoolExecutorRegistry;
|
||||
import cn.hippo4j.common.executor.support.BlockingQueueTypeEnum;
|
||||
import cn.hippo4j.core.executor.DynamicThreadPoolExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Thread-pool rebuild and switch helper.
|
||||
*/
|
||||
@Slf4j
|
||||
public final class ThreadPoolRebuilder {
|
||||
|
||||
private static final Map<String, ReentrantLock> REBUILD_LOCKS = new ConcurrentHashMap<>();
|
||||
|
||||
private ThreadPoolRebuilder() {
|
||||
}
|
||||
|
||||
public static boolean rebuildAndSwitch(ThreadPoolExecutor oldExecutor,
|
||||
Integer newQueueType,
|
||||
Integer capacity,
|
||||
String threadPoolId) {
|
||||
if (oldExecutor == null || newQueueType == null || threadPoolId == null) {
|
||||
return false;
|
||||
}
|
||||
ReentrantLock lock = REBUILD_LOCKS.computeIfAbsent(threadPoolId, k -> new ReentrantLock());
|
||||
if (!lock.tryLock()) {
|
||||
log.warn("Thread pool [{}] is already being rebuilt, skipping concurrent rebuild request.", threadPoolId);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return doRebuildAndSwitch(oldExecutor, newQueueType, capacity, threadPoolId);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean doRebuildAndSwitch(ThreadPoolExecutor oldExecutor,
|
||||
Integer newQueueType,
|
||||
Integer capacity,
|
||||
String threadPoolId) {
|
||||
BlockingQueue<Runnable> newQueue = BlockingQueueTypeEnum.createBlockingQueue(newQueueType, capacity);
|
||||
if (newQueue == null) {
|
||||
log.warn("Rebuild skipped. Unable to create queue by type: {}", newQueueType);
|
||||
return false;
|
||||
}
|
||||
int core = oldExecutor.getCorePoolSize();
|
||||
int max = oldExecutor.getMaximumPoolSize();
|
||||
long keepAlive = oldExecutor.getKeepAliveTime(TimeUnit.SECONDS);
|
||||
ThreadFactory factory = oldExecutor.getThreadFactory();
|
||||
RejectedExecutionHandler rejected = oldExecutor.getRejectedExecutionHandler();
|
||||
boolean allowCoreTimeout = oldExecutor.allowsCoreThreadTimeOut();
|
||||
ThreadPoolExecutor newExecutor;
|
||||
if (oldExecutor instanceof DynamicThreadPoolExecutor) {
|
||||
DynamicThreadPoolExecutor dynOld = (DynamicThreadPoolExecutor) oldExecutor;
|
||||
Long executeTimeout = dynOld.getExecuteTimeOut();
|
||||
newExecutor = new DynamicThreadPoolExecutor(
|
||||
core,
|
||||
max,
|
||||
keepAlive,
|
||||
TimeUnit.SECONDS,
|
||||
executeTimeout == null ? 0L : executeTimeout,
|
||||
false,
|
||||
0L,
|
||||
newQueue,
|
||||
threadPoolId,
|
||||
factory,
|
||||
rejected);
|
||||
} else {
|
||||
newExecutor = new ThreadPoolExecutor(core, max, keepAlive, TimeUnit.SECONDS, newQueue, factory, rejected);
|
||||
}
|
||||
newExecutor.allowCoreThreadTimeOut(allowCoreTimeout);
|
||||
try {
|
||||
newExecutor.prestartAllCoreThreads();
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
ThreadPoolExecutorHolder oldHolder = switchRegistry(threadPoolId, newExecutor);
|
||||
boolean transferSuccess = false;
|
||||
try {
|
||||
transferQueuedTasks(oldExecutor, newExecutor);
|
||||
transferSuccess = true;
|
||||
} catch (Throwable ex) {
|
||||
log.error("Queue transfer failed for thread pool [{}], attempting to rollback.", threadPoolId, ex);
|
||||
}
|
||||
if (!transferSuccess) {
|
||||
if (oldHolder != null) {
|
||||
ThreadPoolExecutorRegistry.putHolder(oldHolder);
|
||||
log.info("Rolled back to old thread pool [{}]", threadPoolId);
|
||||
}
|
||||
safeShutdownNow(newExecutor);
|
||||
return false;
|
||||
}
|
||||
oldExecutor.shutdown();
|
||||
awaitQuietly(oldExecutor, 500, TimeUnit.MILLISECONDS);
|
||||
if (!oldExecutor.isTerminated()) {
|
||||
log.warn("Old thread pool [{}] did not terminate within 500ms, {} tasks may still be running.",
|
||||
threadPoolId, oldExecutor.getActiveCount());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void transferQueuedTasks(ThreadPoolExecutor from, ThreadPoolExecutor to) {
|
||||
BlockingQueue<Runnable> fromQueue = from.getQueue();
|
||||
int transferredCount = 0;
|
||||
int failedCount = 0;
|
||||
try {
|
||||
List<Runnable> batch = new ArrayList<>();
|
||||
fromQueue.drainTo(batch);
|
||||
for (Runnable r : batch) {
|
||||
try {
|
||||
to.execute(r);
|
||||
transferredCount++;
|
||||
} catch (Throwable ex) {
|
||||
failedCount++;
|
||||
log.error("Failed to transfer task during queue switch, may cause task loss.", ex);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
log.error("Failed to drain tasks from old queue.", ex);
|
||||
}
|
||||
Runnable task;
|
||||
while ((task = fromQueue.poll()) != null) {
|
||||
try {
|
||||
to.execute(task);
|
||||
transferredCount++;
|
||||
} catch (Throwable ex) {
|
||||
failedCount++;
|
||||
log.error("Failed to transfer remaining task during queue switch.", ex);
|
||||
}
|
||||
}
|
||||
if (transferredCount > 0 || failedCount > 0) {
|
||||
log.info("Task transfer completed: {} tasks transferred, {} tasks failed.", transferredCount, failedCount);
|
||||
}
|
||||
if (failedCount > 0) {
|
||||
throw new RuntimeException("Task transfer failed: " + failedCount + " tasks could not be transferred.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeShutdownNow(ThreadPoolExecutor executor) {
|
||||
try {
|
||||
List<Runnable> dropped = executor.shutdownNow();
|
||||
if (!dropped.isEmpty()) {
|
||||
log.warn("Dropped {} queued tasks during rollback.", dropped.size());
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitQuietly(ThreadPoolExecutor executor, long time, TimeUnit unit) {
|
||||
try {
|
||||
executor.awaitTermination(time, unit);
|
||||
} catch (InterruptedException ignore) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private static ThreadPoolExecutorHolder switchRegistry(String threadPoolId, ThreadPoolExecutor newExecutor) {
|
||||
Map<String, ThreadPoolExecutorHolder> map = ThreadPoolExecutorRegistry.getHolderMap();
|
||||
ThreadPoolExecutorHolder holder = map.get(threadPoolId);
|
||||
ThreadPoolExecutorHolder newHolder = new ThreadPoolExecutorHolder(threadPoolId, newExecutor,
|
||||
holder == null ? null : holder.getExecutorProperties());
|
||||
ThreadPoolExecutorRegistry.putHolder(newHolder);
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue