org.apache.iotdb.session.subscription.consumer.SubscriptionExecutorServiceManager Maven / Gradle / Ivy
The newest version!
/*
* 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 org.apache.iotdb.session.subscription.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public final class SubscriptionExecutorServiceManager {
private static final Logger LOGGER =
LoggerFactory.getLogger(SubscriptionExecutorServiceManager.class);
private static final long AWAIT_TERMINATION_TIMEOUT_MS = 15_000L;
private static final String CONTROL_FLOW_EXECUTOR_NAME = "SubscriptionControlFlowExecutor";
private static final String UPSTREAM_DATA_FLOW_EXECUTOR_NAME =
"SubscriptionUpstreamDataFlowExecutor";
private static final String DOWNSTREAM_DATA_FLOW_EXECUTOR_NAME =
"SubscriptionDownstreamDataFlowExecutor";
/**
* Control Flow Executor: execute heartbeat worker and endpoints syncer for {@link
* SubscriptionConsumer}
*/
private static final SubscriptionExecutorService CONTROL_FLOW_EXECUTOR =
new SubscriptionExecutorService(
CONTROL_FLOW_EXECUTOR_NAME, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1));
/**
* Upstream Data Flow Executor: execute auto commit worker and async commit worker for {@link
* SubscriptionPullConsumer}
*/
private static final SubscriptionExecutorService UPSTREAM_DATA_FLOW_EXECUTOR =
new SubscriptionExecutorService(
UPSTREAM_DATA_FLOW_EXECUTOR_NAME,
Math.max(Runtime.getRuntime().availableProcessors() / 2, 1));
/**
* Downstream Data Flow Executor: execute auto poll worker for {@link SubscriptionPushConsumer}
*/
private static final SubscriptionExecutorService DOWNSTREAM_DATA_FLOW_EXECUTOR =
new SubscriptionExecutorService(
DOWNSTREAM_DATA_FLOW_EXECUTOR_NAME,
Math.max(Runtime.getRuntime().availableProcessors(), 1));
/////////////////////////////// set core pool size ///////////////////////////////
public static void setControlFlowExecutorCorePoolSize(final int corePoolSize) {
CONTROL_FLOW_EXECUTOR.setCorePoolSize(corePoolSize);
}
public static void setUpstreamDataFlowExecutorCorePoolSize(final int corePoolSize) {
UPSTREAM_DATA_FLOW_EXECUTOR.setCorePoolSize(corePoolSize);
}
public static void setDownstreamDataFlowExecutorCorePoolSize(final int corePoolSize) {
DOWNSTREAM_DATA_FLOW_EXECUTOR.setCorePoolSize(corePoolSize);
}
/////////////////////////////// shutdown hook ///////////////////////////////
static {
// register shutdown hook
Runtime.getRuntime()
.addShutdownHook(
new Thread(
new SubscriptionExecutorServiceShutdownHook(),
"SubscriptionExecutorServiceShutdownHook"));
}
private static class SubscriptionExecutorServiceShutdownHook implements Runnable {
@Override
public void run() {
// shutdown executors
CONTROL_FLOW_EXECUTOR.shutdown();
UPSTREAM_DATA_FLOW_EXECUTOR.shutdown();
DOWNSTREAM_DATA_FLOW_EXECUTOR.shutdown();
}
}
/////////////////////////////// submitter ///////////////////////////////
@SuppressWarnings("unsafeThreadSchedule")
public static ScheduledFuture> submitHeartbeatWorker(
final Runnable task, final long heartbeatIntervalMs) {
CONTROL_FLOW_EXECUTOR.launchIfNeeded();
return CONTROL_FLOW_EXECUTOR.scheduleWithFixedDelay(
task,
generateRandomInitialDelayMs(heartbeatIntervalMs),
heartbeatIntervalMs,
TimeUnit.MILLISECONDS);
}
@SuppressWarnings("unsafeThreadSchedule")
public static ScheduledFuture> submitEndpointsSyncer(
final Runnable task, final long endpointsSyncIntervalMs) {
CONTROL_FLOW_EXECUTOR.launchIfNeeded();
return CONTROL_FLOW_EXECUTOR.scheduleWithFixedDelay(
task,
generateRandomInitialDelayMs(endpointsSyncIntervalMs),
endpointsSyncIntervalMs,
TimeUnit.MILLISECONDS);
}
@SuppressWarnings("unsafeThreadSchedule")
public static ScheduledFuture> submitAutoCommitWorker(
final Runnable task, final long autoCommitIntervalMs) {
UPSTREAM_DATA_FLOW_EXECUTOR.launchIfNeeded();
return UPSTREAM_DATA_FLOW_EXECUTOR.scheduleWithFixedDelay(
task,
generateRandomInitialDelayMs(autoCommitIntervalMs),
autoCommitIntervalMs,
TimeUnit.MILLISECONDS);
}
public static void submitAsyncCommitWorker(final Runnable task) {
UPSTREAM_DATA_FLOW_EXECUTOR.launchIfNeeded();
UPSTREAM_DATA_FLOW_EXECUTOR.submit(task);
}
@SuppressWarnings("unsafeThreadSchedule")
public static ScheduledFuture> submitAutoPollWorker(
final Runnable task, final long autoPollIntervalMs) {
DOWNSTREAM_DATA_FLOW_EXECUTOR.launchIfNeeded();
return DOWNSTREAM_DATA_FLOW_EXECUTOR.scheduleWithFixedDelay(
task,
generateRandomInitialDelayMs(autoPollIntervalMs),
autoPollIntervalMs,
TimeUnit.MILLISECONDS);
}
/////////////////////////////// subscription executor service ///////////////////////////////
private static class SubscriptionExecutorService {
String name;
volatile int corePoolSize;
volatile ScheduledExecutorService executor;
SubscriptionExecutorService(final String name, final int corePoolSize) {
this.name = name;
this.corePoolSize = corePoolSize;
}
boolean isShutdown() {
return Objects.isNull(this.executor);
}
void setCorePoolSize(final int corePoolSize) {
if (isShutdown()) {
synchronized (this) {
if (isShutdown()) {
this.corePoolSize = corePoolSize;
return;
}
}
}
LOGGER.warn(
"{} has been launched, set core pool size to {} will be ignored",
this.name,
corePoolSize);
}
void launchIfNeeded() {
if (isShutdown()) {
synchronized (this) {
if (isShutdown()) {
LOGGER.info("Launching {} with core pool size {}...", this.name, this.corePoolSize);
this.executor =
Executors.newScheduledThreadPool(
this.corePoolSize,
r -> {
final Thread t =
new Thread(Thread.currentThread().getThreadGroup(), r, this.name, 0);
if (!t.isDaemon()) {
t.setDaemon(true);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
});
}
}
}
}
void shutdown() {
if (!isShutdown()) {
synchronized (this) {
if (!isShutdown()) {
LOGGER.info("Shutting down {}...", this.name);
this.executor.shutdown();
try {
if (!this.executor.awaitTermination(
AWAIT_TERMINATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
this.executor.shutdownNow();
LOGGER.warn(
"Interrupt the worker, which may cause some task inconsistent. Please check the biz logs.");
if (!this.executor.awaitTermination(
AWAIT_TERMINATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
LOGGER.error(
"Thread pool can't be shutdown even with interrupting worker threads, which may cause some task inconsistent. Please check the biz logs.");
}
}
} catch (final InterruptedException e) {
this.executor.shutdownNow();
LOGGER.error(
"The current thread is interrupted when it is trying to stop the worker threads. This may leave an inconsistent state. Please check the biz logs.");
Thread.currentThread().interrupt();
}
this.executor = null;
}
}
}
}
@SuppressWarnings("unsafeThreadSchedule")
ScheduledFuture> scheduleWithFixedDelay(
final Runnable task, final long initialDelay, final long delay, final TimeUnit unit) {
if (!isShutdown()) {
synchronized (this) {
if (!isShutdown()) {
return this.executor.scheduleWithFixedDelay(task, initialDelay, delay, unit);
}
}
}
LOGGER.warn("{} has not been launched, ignore scheduleWithFixedDelay for task", this.name);
return null;
}
Future> submit(final Runnable task) {
if (!isShutdown()) {
synchronized (this) {
if (!isShutdown()) {
return this.executor.submit(task);
}
}
}
LOGGER.warn("{} has not been launched, ignore submit task", this.name);
return null;
}
}
/////////////////////////////// utility ///////////////////////////////
private static long generateRandomInitialDelayMs(final long maxMs) {
return (long) (Math.random() * maxMs);
}
}