com.wl4g.component.common.task.GenericTaskRunner Maven / Gradle / Ivy
/*
* Copyright 2017 ~ 2025 the original author or authors.
*
* 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.wl4g.component.common.task;
import com.wl4g.component.common.log.SmartLogger;
import com.wl4g.component.common.task.SafeScheduledTaskPoolExecutor;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static com.wl4g.component.common.lang.Assert2.notNull;
import static com.wl4g.component.common.lang.Assert2.state;
import static com.wl4g.component.common.log.SmartLoggerFactory.getLogger;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* Generic local scheduler & task runner.
*
* @author Wangl.sir <[email protected]>
* @version v1.0 2019年6月2日
* @since
* @see {@link org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler}
* @see ScheduledThreadPoolExecutor
* Retry task OOM resolution
*/
public abstract class GenericTaskRunner implements Closeable, Runnable {
final protected SmartLogger log = getLogger(getClass());
/** Running state. */
final private AtomicBoolean running = new AtomicBoolean(false);
/** Runner task properties configuration. */
final private C configProperties;
/** Runner boss thread. */
private Thread boss;
/** Runner worker thread group pool. */
private SafeScheduledTaskPoolExecutor worker;
@SuppressWarnings("unchecked")
public GenericTaskRunner() {
this((C) new RunnerProperties());
}
public GenericTaskRunner(C config) {
notNull(config, "GenericTaskRunner properties can't null");
this.configProperties = config;
}
@Override
public void run() {
// Ignore
}
/**
* Note: It is recommended to use the {@link AtomicBoolean} mechanism to
* avoid using synchronized.
* Error example:
*
*
* public abstract class ParentClass implements Closeable, Runnable {
* public synchronized void close() {
* // Some close or release ...
* }
* }
*
* public class SubClass extends ParentClass {
* public synchronized void run() {
* // Long-time jobs ...
*
* // For example:
* // while(true) {
* // ...
* // }
* }
* }
*
*
* At this time, it may lead to deadlock, because SubClass.run() has not
* been executed and is not locked, resulting in the call to
* ParentClass.close() always waiting.
*
*/
@Override
public void close() throws IOException {
if (running.compareAndSet(true, false)) {
// Call pre close
preCloseProperties();
// Close for thread pool worker.
if (!isNull(worker)) {
try {
worker.shutdown();
worker = null;
} catch (Exception e) {
log.error("Runner worker shutdown failed!", e);
}
}
// Close for thread-boss.
try {
if (!isNull(boss)) {
boss.interrupt();
boss = null;
}
} catch (Exception e) {
log.error("Runner boss interrupt failed!", e);
}
// Call post close
postCloseProperties();
}
}
/**
* Auto initialization on startup.
*
* @throws Exception
*/
public void start() throws Exception {
if (running.compareAndSet(false, true)) {
// Call PreStartup
preStartupProperties();
// Create worker(if necessary)
if (configProperties.getConcurrency() > 0) {
// See:https://www.jianshu.com/p/e7ab1ac8eb4c
ThreadFactory tf = new NamedThreadFactory(getClass().getSimpleName().concat("-worker"));
worker = new SafeScheduledTaskPoolExecutor(configProperties.getConcurrency(), configProperties.getKeepAliveTime(),
tf, configProperties.getAcceptQueue(), configProperties.getReject());
} else {
log.warn("No start threads worker, because the number of workthreads is less than 0");
}
// Boss asynchronously execution.(if necessary)
if (configProperties.isAsyncStartup()) {
boss = new NamedThreadFactory(getClass().getSimpleName().concat("-boss")).newThread(this);
boss.start();
} else {
run(); // Sync execution.
}
// Call post startup
postStartupProperties();
} else {
log.warn("Could not startup runner! because already builders are read-only and do not allow task modification");
}
}
/**
* Gets configuration properties.
*
* @return
*/
protected C getConfig() {
return configProperties;
}
/**
* Pre startup properties
*/
protected void preStartupProperties() throws Exception {
// Ignore
}
/**
* Post startup properties
*/
protected void postStartupProperties() throws Exception {
// Ignore
}
/**
* Pre close properties
*/
protected void preCloseProperties() throws IOException {
// Ignore
}
/**
* Post close properties
*/
protected void postCloseProperties() throws IOException {
// Ignore
}
/**
* Is the current runner active.
*
* @return
*/
protected boolean isActive() {
return nonNull(boss) && !boss.isInterrupted() && running.get();
}
/**
* Thread pool executor worker.
*
* @return
*/
public SafeScheduledTaskPoolExecutor getWorker() {
state(nonNull(worker), "Worker thread group is not enabled and can be enabled with concurrency >0");
return worker;
}
/**
* The named thread factory
*/
private class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threads = new AtomicInteger(1);
private final ThreadGroup group;
private final String prefix;
NamedThreadFactory(String prefix) {
SecurityManager s = System.getSecurityManager();
this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
if (isBlank(prefix)) {
prefix = GenericTaskRunner.class.getSimpleName() + "-default";
}
this.prefix = prefix;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, prefix + "-" + threads.getAndIncrement(), 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
}