/* Copyright (c) 2011 Danish Maritime Authority.
*
* 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 net.maritimecloud.internal.mms.client;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import net.maritimecloud.internal.net.util.DefaultEndpointInvocationFuture;
import net.maritimecloud.internal.util.concurrent.CompletableFuture;
import net.maritimecloud.internal.util.concurrent.ConcurrentWeakHashSet;
import net.maritimecloud.net.mms.MmsClientClosedException;
import net.maritimecloud.util.Binary;
import org.cakeframework.container.lifecycle.RunOnStart;
import org.cakeframework.container.lifecycle.RunOnStop;
/**
* A central place for all threads that are spawned within the MMS Client.
* <p>
* This does not include the threads used for handling websocket connections.
*
* @author Kasper Nielsen
*/
public class MmsThreadManager {
/** The prefix of each thread created by the client. */
static final String THREAD_PREFIX = "MMSClient";
/** An {@link ExecutorService} for running various tasks. */
final ThreadPoolExecutor es = new ThreadPoolExecutor(0, 100, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), new DefaultThreadFactory("GeneralPool", Executors.defaultThreadFactory()));
/** A list of all outstanding futures. Is used to cancel each future in case of shutdown. */
final ConcurrentWeakHashSet<DefaultEndpointInvocationFuture<?>> futures = new ConcurrentWeakHashSet<>();
/** A {@link ScheduledExecutorService} for scheduling various tasks. */
final ScheduledThreadPoolExecutor ses = new ScheduledThreadPoolExecutor(2, new DefaultThreadFactory("Scheduler",
Executors.defaultThreadFactory()));
public <T> DefaultEndpointInvocationFuture<T> create(Binary messageId) {
DefaultEndpointInvocationFuture<T> t = new DefaultEndpointInvocationFuture<>(new CompletableFuture<T>(),
messageId);
futures.add(t);
return t;
}
public void broadcastReceived(Runnable r) {
es.execute(r);
}
@RunOnStart
public void start() {
// Clean up weak references
ses.schedule(new Runnable() {
public void run() {
futures.cleanup();
}
}, 1, TimeUnit.MINUTES);
}
/**
* @param runnable
*/
public void startCloseThread(Runnable runnable) {
runDaemonThread(THREAD_PREFIX + "-ClosingThread", runnable);
}
public void startConnectingManager(Runnable runnable) {
runDaemonThread(THREAD_PREFIX + "-ConnectionManager", runnable);
}
public void startConnectingThread(Runnable runnable) {
runDaemonThread(THREAD_PREFIX + "-ConnectingThread", runnable);
}
public void startDisconnectingThread(Runnable runnable) {
runDaemonThread(THREAD_PREFIX + "-DisconnectingThread", runnable);
}
public void startWorkerThread(Runnable runnable) {
runDaemonThread(THREAD_PREFIX + "-MessageProcessor", runnable);
}
void runDaemonThread(String name, Runnable runnable) {
Thread t = new Thread(runnable);
t.setName(name);
t.setDaemon(true);
t.start();
}
@RunOnStop
public void stop() {
es.shutdown();
ses.shutdown();
for (DefaultEndpointInvocationFuture<?> f : futures) {
if (!f.isDone()) {
f.completeExceptionally(new MmsClientClosedException("OOps"));
}
}
for (Runnable r : ses.getQueue()) {
ScheduledFuture<?> sf = (ScheduledFuture<?>) r;
sf.cancel(false);
}
ses.purge(); // remove all the tasks we just cancelled
try {
ses.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
es.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
static class DefaultThreadFactory implements ThreadFactory {
private final ThreadFactory delegate;
private final String prefix;
private final AtomicInteger threadNumber = new AtomicInteger(1);
DefaultThreadFactory(String prefix, ThreadFactory delegate) {
this.delegate = requireNonNull(delegate);
this.prefix = prefix;
}
public Thread newThread(Runnable r) {
Thread t = delegate.newThread(r);
t.setDaemon(true);
t.setName(THREAD_PREFIX + "-" + prefix + "-" + threadNumber.getAndIncrement());
return t;
}
}
}