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

org.teamapps.util.threading.CompletableFutureChainSequentialExecutorFactory Maven / Gradle / Ivy

There is a newer version: 0.9.194
Show newest version
/*-
 * ========================LICENSE_START=================================
 * TeamApps
 * ---
 * Copyright (C) 2014 - 2024 TeamApps.org
 * ---
 * 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.
 * =========================LICENSE_END==================================
 */
package org.teamapps.util.threading;

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

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static org.teamapps.common.util.ExceptionUtil.softenExceptions;

public class CompletableFutureChainSequentialExecutorFactory implements SequentialExecutorFactory {

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

	private final AtomicReference delayStats = new AtomicReference<>(new MinMaxAverageStats());
	private final AtomicReference executionTimeStats = new AtomicReference<>(new MinMaxAverageStats());

	private final ExecutorService pool;

	public CompletableFutureChainSequentialExecutorFactory(int nThreads) {
		this(Executors.newFixedThreadPool(nThreads));
	}

	public CompletableFutureChainSequentialExecutorFactory(ExecutorService executorService) {
		pool = executorService;

		ScheduledExecutorService statsLogExecutorService = Executors.newSingleThreadScheduledExecutor();
		statsLogExecutorService.scheduleAtFixedRate(() -> {
			MinMaxAverageStats delayStats = this.delayStats.getAndSet(new MinMaxAverageStats());
			MinMaxAverageStats executionTimeStats = this.executionTimeStats.getAndSet(new MinMaxAverageStats());
			if (delayStats.getMax() > 3000) {
				LOGGER.warn("Delays critical: min: {}, max: {}, avg: {}, count: {}", delayStats.getMin(), delayStats.getMax(), delayStats.getAvg(), delayStats.getCount());
			}
			if (executionTimeStats.getMax() > 1000) {
				LOGGER.warn("Execution times critical: min: {}, max: {}, avg: {}, count: {}", executionTimeStats.getMin(), executionTimeStats.getMax(), executionTimeStats.getAvg(), executionTimeStats.getCount());
			}
		}, 1, 1, TimeUnit.SECONDS);
	}

	public CloseableExecutor createExecutor() {
		return createExecutor("unnamed");
	}

	public CloseableExecutor createExecutor(String name) {
		return new SequentialExecutor(name);
	}

	public class SequentialExecutor implements CloseableExecutor {
		private final String name;
		private CompletableFuture lastFuture = CompletableFuture.completedFuture(null);
		private final AtomicInteger queueSize = new AtomicInteger(0);

		public SequentialExecutor(String name) {
			this.name = name;
		}

		@Override
		public synchronized void execute(Runnable command) {
			int queueSize = this.queueSize.incrementAndGet();
			LOGGER.trace("{}: Queue size: {}", name, queueSize);
			if (queueSize >= 500 && queueSize % 10 == 0) { // the queue gets quite long when destroying a session, since there are very many listeners to the destroyed event
				LOGGER.warn("{}: Queue is very long: {}", name, queueSize);
			}
			long submitTime = System.currentTimeMillis();
			lastFuture = lastFuture.thenApplyAsync(o -> {
				long executionStartTime = System.currentTimeMillis();
				long delay = executionStartTime - submitTime;
				delayStats.getAndUpdate(minMaxAverageStats -> minMaxAverageStats.push(delay));
				Object result = softenExceptions(() -> {
					command.run();
					return null;
				});
				long executionTime = System.currentTimeMillis() - executionStartTime;
				executionTimeStats.getAndUpdate(minMaxAverageStats -> minMaxAverageStats.push(executionTime));
				this.queueSize.decrementAndGet();
				return result;
			}, pool).exceptionally(throwable -> {
				LOGGER.error("{}: Error while executing: ", name, throwable);
				return null; // do not interrupt the execution chain!!
			});
		}

		@Override
		public void close() {
			// nothing to do here
		}
	}

	public static class SequentialExecutorClosedException extends RuntimeException {
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy