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

io.datakernel.eventloop.Eventloop Maven / Gradle / Ivy

Go to download

Efficient non-blocking network and file I/O, for building Node.js-like client/server applications with high performance requirements. It is similar to Event Loop in Node.js. Although Eventloop runs in a single thread, multiple event loops can be launched at the same time allowing for efficient CPU usage.

The newest version!
/*
 * Copyright (C) 2015-2019 SoftIndex LLC.
 *
 * 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 io.datakernel.eventloop;

import io.datakernel.async.callback.Completable;
import io.datakernel.common.Initializable;
import io.datakernel.common.Stopwatch;
import io.datakernel.common.exception.AsyncTimeoutException;
import io.datakernel.common.exception.StacklessException;
import io.datakernel.common.exception.UncheckedException;
import io.datakernel.common.inspector.BaseInspector;
import io.datakernel.common.time.CurrentTimeProvider;
import io.datakernel.common.time.CurrentTimeProviderSystem;
import io.datakernel.eventloop.jmx.EventloopJmxMBeanEx;
import io.datakernel.eventloop.net.DatagramSocketSettings;
import io.datakernel.eventloop.net.ServerSocketSettings;
import io.datakernel.eventloop.util.OptimizedSelectedKeysSet;
import io.datakernel.jmx.api.JmxAttribute;
import io.datakernel.jmx.api.JmxOperation;
import org.jetbrains.annotations.Async;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import static io.datakernel.common.Preconditions.checkArgument;
import static io.datakernel.common.Utils.nullToSupplier;
import static io.datakernel.eventloop.util.ReflectionUtils.isPrivateApiAvailable;
import static io.datakernel.eventloop.util.Utils.tryToOptimizeSelector;
import static java.util.Collections.emptyIterator;

/**
 * It is an internal class for asynchronous programming. In asynchronous
 * programming model, blocking operations (like I/O or long-running computations)
 * in {@code Eventloop} thread must be avoided. Async versions
 * of such operations should be used.
 * 

* Eventloop represents infinite loop with only one blocking operation * {@code selector.select()} which selects a set of keys whose corresponding * channels are ready for I/O operations. *

* With these keys and queues with * tasks, which were added to {@code Eventloop} from the outside, it begins * asynchronous executing from one thread in method {@code run()} which is * overridden because eventloop is an implementation of {@link Runnable}. * Working of this eventloop will be ended when it has no selected keys * and its queues with tasks are empty. */ public final class Eventloop implements Runnable, EventloopExecutor, Scheduler, Initializable, EventloopJmxMBeanEx { public static final Logger logger = LoggerFactory.getLogger(Eventloop.class); public static final boolean jigsawDisabled; static final Duration DEFAULT_SMOOTHING_WINDOW = Duration.ofMinutes(1); static { jigsawDisabled = isPrivateApiAvailable(); } public static final AsyncTimeoutException CONNECT_TIMEOUT = new AsyncTimeoutException(Eventloop.class, "Connection timed out"); public static final StacklessException NOT_CONNECTED = new StacklessException(Eventloop.class, "Connection key was received but the channel was not connected - this is not possible without some bug in Java NIO"); public static final Duration DEFAULT_IDLE_INTERVAL = Duration.ofSeconds(1); @NotNull private static volatile FatalErrorHandler globalFatalErrorHandler = FatalErrorHandlers.ignoreAllErrors(); /** * Collection of local tasks which were added from this thread. */ private final ArrayDeque localTasks = new ArrayDeque<>(); /** * Collection of concurrent tasks which were added from other threads. */ private final ConcurrentLinkedQueue concurrentTasks = new ConcurrentLinkedQueue<>(); /** * Collection of scheduled tasks that are scheduled * to be executed at particular timestamp. */ private final PriorityQueue scheduledTasks = new PriorityQueue<>(); /** * Collection of background tasks, * if eventloop contains only background tasks, it will be closed. */ private final PriorityQueue backgroundTasks = new PriorityQueue<>(); /** * Amount of concurrent operations in other threads, * non-zero value prevents eventloop from termination. */ private final AtomicInteger externalTasksCount = new AtomicInteger(0); private int loop; private int tick; /** * Current time, cached to avoid System.currentTimeMillis() * system calls, and to facilitate unit testing. * It is being refreshed with each eventloop execution. */ private long timestamp; @NotNull private final CurrentTimeProvider timeProvider; /** * The NIO selector which selects a set of keys whose * corresponding channels are ready for I/O operations. */ @Nullable private Selector selector; @Nullable private SelectorProvider selectorProvider; /** * The thread in which eventloop is running. */ @Nullable private Thread eventloopThread; private static final ThreadLocal CURRENT_EVENTLOOP = new ThreadLocal<>(); /** * The desired name of the thread. */ @Nullable private String threadName; private int threadPriority; @Nullable private FatalErrorHandler fatalErrorHandler; private volatile boolean keepAlive; private volatile boolean breakEventloop; private Duration idleInterval = DEFAULT_IDLE_INTERVAL; /** * Amount of selected keys for last Selector.select() */ private int lastSelectedKeys; private int cancelledKeys; private int lastExternalTasksCount; // JMX @Nullable private EventloopInspector inspector; private boolean monitoring = false; // region builders private Eventloop(@NotNull CurrentTimeProvider timeProvider) { this.timeProvider = timeProvider; refreshTimestamp(); } public static Eventloop create() { return create(CurrentTimeProviderSystem.instance()); } public static Eventloop create(@NotNull CurrentTimeProvider currentTimeProvider) { return new Eventloop(currentTimeProvider); } @NotNull public Eventloop withThreadName(@Nullable String threadName) { this.threadName = threadName; return this; } @SuppressWarnings("UnusedReturnValue") @NotNull public Eventloop withThreadPriority(int threadPriority) { this.threadPriority = threadPriority; return this; } @NotNull public Eventloop withInspector(@Nullable EventloopInspector inspector) { this.inspector = inspector; return this; } @NotNull public Eventloop withFatalErrorHandler(@Nullable FatalErrorHandler fatalErrorHandler) { this.fatalErrorHandler = fatalErrorHandler; return this; } @NotNull public Eventloop withSelectorProvider(@Nullable SelectorProvider selectorProvider) { this.selectorProvider = selectorProvider; return this; } @NotNull public Eventloop withIdleInterval(@NotNull Duration idleInterval) { this.idleInterval = idleInterval; return this; } @NotNull public Eventloop withCurrentThread() { CURRENT_EVENTLOOP.set(this); return this; } // endregion @Nullable public Selector getSelector() { return selector; } private static final String NO_CURRENT_EVENTLOOP_ERROR = "Trying to start async operations prior eventloop.run(), or from outside of eventloop.run() \n" + "Possible solutions: " + "1) Eventloop.create().withCurrentThread() ... {your code block} ... eventloop.run() \n" + "2) try_with_resources Eventloop.useCurrentThread() ... {your code block} \n" + "3) refactor application so it starts async operations within eventloop.run(), \n" + " i.e. by implementing EventloopService::start() {your code block} and using ServiceGraphModule"; @NotNull public static Eventloop getCurrentEventloop() { Eventloop eventloop = CURRENT_EVENTLOOP.get(); if (eventloop != null) return eventloop; throw new IllegalStateException(NO_CURRENT_EVENTLOOP_ERROR); } private void openSelector() { if (selector == null) { try { selector = nullToSupplier(selectorProvider, SelectorProvider::provider).openSelector(); } catch (Exception e) { logger.error("Could not open selector", e); throw new RuntimeException(e); } } } /** * Closes the selector if it is opened. */ private void closeSelector() { if (selector != null) { try { selector.close(); selector = null; cancelledKeys = 0; } catch (IOException e) { logger.error("Could not close selector", e); } } } @Nullable public Selector ensureSelector() { if (selector == null) { openSelector(); } return selector; } public void closeChannel(@Nullable SelectableChannel channel, @Nullable SelectionKey key) { checkArgument(channel != null || key == null, "Either channel or key should be not null"); if (channel == null || !channel.isOpen()) return; if (key != null && key.isValid()) { cancelledKeys++; } try { channel.close(); } catch (IOException e) { logger.warn("Failed to close channel {}", channel, e); } } public boolean inEventloopThread() { return eventloopThread == null || eventloopThread == Thread.currentThread(); } /** * Sets the flag which (if set {@code true}) * means that working of this Eventloop will * be continued even if all of the tasks * have been executed and it doesn't have * any selected keys. * * @param keepAlive flag for setting */ public void keepAlive(boolean keepAlive) { this.keepAlive = keepAlive; if (!keepAlive && selector != null) { selector.wakeup(); } } public void breakEventloop() { breakEventloop = true; if (breakEventloop && selector != null) { selector.wakeup(); } } private boolean isAlive() { if (breakEventloop) return false; lastExternalTasksCount = externalTasksCount.get(); return !localTasks.isEmpty() || !scheduledTasks.isEmpty() || !concurrentTasks.isEmpty() || lastExternalTasksCount > 0 || keepAlive || (selector != null && selector.isOpen() && selector.keys().size() - cancelledKeys > 0); } @Nullable public Thread getEventloopThread() { return eventloopThread; } /** * Overridden method from Runnable that executes tasks while this eventloop is alive. */ @Override public void run() { eventloopThread = Thread.currentThread(); if (threadName != null) eventloopThread.setName(threadName); if (threadPriority != 0) eventloopThread.setPriority(threadPriority); CURRENT_EVENTLOOP.set(this); ensureSelector(); assert selector != null; breakEventloop = false; boolean setWasOptimized = jigsawDisabled && tryToOptimizeSelector(selector); long timeAfterSelectorSelect; long timeAfterBusinessLogic = 0; while (isAlive()) { try { long selectTimeout = getSelectTimeout(); if (inspector != null) inspector.onUpdateSelectorSelectTimeout(selectTimeout); if (selectTimeout <= 0) { lastSelectedKeys = selector.selectNow(); } else { lastSelectedKeys = selector.select(selectTimeout); } cancelledKeys = 0; } catch (ClosedChannelException e) { logger.error("Selector is closed, exiting...", e); break; } catch (IOException e) { recordIoError(e, selector); } timeAfterSelectorSelect = refreshTimestampAndGet(); int keys = setWasOptimized ? optimizedProcessSelectedKeys((OptimizedSelectedKeysSet) selector.selectedKeys()) : processSelectedKeys(selector.selectedKeys()); int concurrentTasks = executeConcurrentTasks(); int scheduledTasks = executeScheduledTasks(); int backgroundTasks = executeBackgroundTasks(); int localTasks = executeLocalTasks(); if (inspector != null) { if (timeAfterBusinessLogic != 0) { long selectorSelectTime = timeAfterSelectorSelect - timeAfterBusinessLogic; inspector.onUpdateSelectorSelectTime(selectorSelectTime); } timeAfterBusinessLogic = timestamp; //refreshTimestampAndGet(); boolean taskOrKeyPresent = (keys + concurrentTasks + scheduledTasks + backgroundTasks + localTasks) != 0; boolean externalTaskPresent = lastExternalTasksCount != 0; long businessLogicTime = timeAfterBusinessLogic - timeAfterSelectorSelect; inspector.onUpdateBusinessLogicTime(taskOrKeyPresent, externalTaskPresent, businessLogicTime); } loop++; tick = 0; } logger.info("{} finished", this); eventloopThread = null; if (selector != null && selector.isOpen() && selector.keys().stream().anyMatch(SelectionKey::isValid)) { logger.warn("Selector is still open, because event loop {} has {} keys", this, selector.keys()); return; } closeSelector(); } private long getSelectTimeout() { if (!concurrentTasks.isEmpty() || !localTasks.isEmpty()) return 0L; if (scheduledTasks.isEmpty() && backgroundTasks.isEmpty()) return idleInterval.toMillis(); return Math.min(getTimeBeforeExecution(scheduledTasks), getTimeBeforeExecution(backgroundTasks)); } private long getTimeBeforeExecution(PriorityQueue taskQueue) { while (!taskQueue.isEmpty()) { ScheduledRunnable first = taskQueue.peek(); assert first != null; // unreachable condition if (first.isCancelled()) { taskQueue.poll(); continue; } return first.getTimestamp() - currentTimeMillis(); } return idleInterval.toMillis(); } /** * Processes selected keys related to various I/O events: accept, connect, read, write. * * @param selectedKeys set that contains all selected keys, returned from NIO Selector.select() */ private int processSelectedKeys(@NotNull Set selectedKeys) { long startTimestamp = timestamp; Stopwatch sw = monitoring ? Stopwatch.createUnstarted() : null; int invalidKeys = 0, acceptKeys = 0, connectKeys = 0, readKeys = 0, writeKeys = 0; Iterator iterator = lastSelectedKeys != 0 ? selectedKeys.iterator() : emptyIterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (!key.isValid()) { invalidKeys++; continue; } if (sw != null) { sw.reset(); sw.start(); } if (key.isAcceptable()) { onAccept(key); acceptKeys++; } else if (key.isConnectable()) { onConnect(key); connectKeys++; } else { if (key.isReadable()) { onRead(key); readKeys++; } if (key.isValid()) { if (key.isWritable()) { onWrite(key); writeKeys++; } } else { invalidKeys++; } } if (sw != null && inspector != null) inspector.onUpdateSelectedKeyDuration(sw); } int keys = acceptKeys + connectKeys + readKeys + writeKeys + invalidKeys; if (keys != 0) { long loopTime = refreshTimestampAndGet() - startTimestamp; if (inspector != null) inspector.onUpdateSelectedKeysStats(lastSelectedKeys, invalidKeys, acceptKeys, connectKeys, readKeys, writeKeys, loopTime); } return keys; } private int optimizedProcessSelectedKeys(@NotNull OptimizedSelectedKeysSet selectedKeys) { long startTimestamp = timestamp; Stopwatch sw = monitoring ? Stopwatch.createUnstarted() : null; int invalidKeys = 0, acceptKeys = 0, connectKeys = 0, readKeys = 0, writeKeys = 0; for (int i = 0; i < selectedKeys.size(); i++) { SelectionKey key = selectedKeys.get(i); assert key != null; if (!key.isValid()) { invalidKeys++; continue; } if (sw != null) { sw.reset(); sw.start(); } if (key.isAcceptable()) { onAccept(key); acceptKeys++; } else if (key.isConnectable()) { onConnect(key); connectKeys++; } else { if (key.isReadable()) { onRead(key); readKeys++; } if (key.isValid()) { if (key.isWritable()) { onWrite(key); writeKeys++; } } else { invalidKeys++; } } if (sw != null && inspector != null) inspector.onUpdateSelectedKeyDuration(sw); } selectedKeys.clear(); int keys = acceptKeys + connectKeys + readKeys + writeKeys + invalidKeys; if (keys != 0) { long loopTime = refreshTimestampAndGet() - startTimestamp; if (inspector != null) inspector.onUpdateSelectedKeysStats(lastSelectedKeys, invalidKeys, acceptKeys, connectKeys, readKeys, writeKeys, loopTime); } return keys; } private static void executeTask(@Async.Execute Runnable task) { task.run(); } /** * Executes local tasks which were added from current thread */ private int executeLocalTasks() { long startTimestamp = timestamp; int localTasks = 0; Stopwatch sw = monitoring ? Stopwatch.createUnstarted() : null; while (true) { Runnable runnable = this.localTasks.poll(); if (runnable == null) { break; } if (sw != null) { sw.reset(); sw.start(); } try { executeTask(runnable); tick++; if (sw != null && inspector != null) inspector.onUpdateLocalTaskDuration(runnable, sw); } catch (Throwable e) { recordFatalError(e, runnable); } localTasks++; } if (localTasks != 0) { long loopTime = refreshTimestampAndGet() - startTimestamp; if (inspector != null) inspector.onUpdateLocalTasksStats(localTasks, loopTime); } return localTasks; } /** * Executes concurrent tasks which were added from other threads. */ private int executeConcurrentTasks() { long startTimestamp = timestamp; int concurrentTasks = 0; Stopwatch sw = monitoring ? Stopwatch.createUnstarted() : null; while (true) { Runnable runnable = this.concurrentTasks.poll(); if (runnable == null) { break; } if (sw != null) { sw.reset(); sw.start(); } try { executeTask(runnable); if (sw != null && inspector != null) inspector.onUpdateConcurrentTaskDuration(runnable, sw); } catch (Throwable e) { recordFatalError(e, runnable); } concurrentTasks++; } if (concurrentTasks != 0) { long loopTime = refreshTimestampAndGet() - startTimestamp; if (inspector != null) inspector.onUpdateConcurrentTasksStats(concurrentTasks, loopTime); } return concurrentTasks; } /** * Executes tasks scheduled for execution at particular timestamps */ private int executeScheduledTasks() { return executeScheduledTasks(scheduledTasks); } private int executeBackgroundTasks() { return executeScheduledTasks(backgroundTasks); } private int executeScheduledTasks(PriorityQueue taskQueue) { long startTimestamp = timestamp; boolean background = taskQueue == backgroundTasks; int scheduledTasks = 0; Stopwatch sw = monitoring ? Stopwatch.createUnstarted() : null; for (; ; ) { ScheduledRunnable peeked = taskQueue.peek(); if (peeked == null) break; if (peeked.isCancelled()) { taskQueue.poll(); continue; } if (peeked.getTimestamp() > currentTimeMillis()) { break; } taskQueue.poll(); Runnable runnable = peeked.getRunnable(); if (sw != null) { sw.reset(); sw.start(); } if (monitoring && inspector != null) { int overdue = (int) (System.currentTimeMillis() - peeked.getTimestamp()); inspector.onScheduledTaskOverdue(overdue, background); } try { executeTask(runnable); tick++; peeked.complete(); if (sw != null && inspector != null) inspector.onUpdateScheduledTaskDuration(runnable, sw, background); } catch (Throwable e) { recordFatalError(e, runnable); } scheduledTasks++; } if (scheduledTasks != 0) { long loopTime = refreshTimestampAndGet() - startTimestamp; if (inspector != null) inspector.onUpdateScheduledTasksStats(scheduledTasks, loopTime, background); } return scheduledTasks; } /** * Accepts an incoming socketChannel connections without blocking eventloop thread. * * @param key key of this action. */ private void onAccept(SelectionKey key) { assert inEventloopThread(); ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); if (!serverSocketChannel.isOpen()) { // TODO - remove? key.cancel(); return; } AcceptCallback acceptCallback = (AcceptCallback) key.attachment(); for (; ; ) { SocketChannel channel; try { channel = serverSocketChannel.accept(); if (channel == null) break; channel.configureBlocking(false); } catch (ClosedChannelException e) { break; } catch (IOException e) { recordIoError(e, serverSocketChannel); break; } try { acceptCallback.onAccept(channel); } catch (Throwable e) { recordFatalError(e, acceptCallback); closeChannel(channel, null); } } } /** * Processes newly established TCP connections * without blocking eventloop thread. * * @param key key of this action. */ private void onConnect(SelectionKey key) { assert inEventloopThread(); ConnectCallback cb = (ConnectCallback) key.attachment(); SocketChannel channel = (SocketChannel) key.channel(); boolean connected; try { connected = channel.finishConnect(); } catch (IOException e) { closeChannel(channel, key); cb.onException(e); return; } try { if (connected) { cb.onConnect(channel); } else { cb.onException(NOT_CONNECTED); } } catch (Throwable e) { recordFatalError(e, channel); closeChannel(channel, null); } } /** * Processes socketChannels available for read * without blocking event loop thread. * * @param key key of this action. */ private void onRead(SelectionKey key) { assert inEventloopThread(); NioChannelEventHandler handler = (NioChannelEventHandler) key.attachment(); try { handler.onReadReady(); } catch (Throwable e) { recordFatalError(e, handler); closeChannel(key.channel(), null); } } /** * Processes socketChannels available for write * without blocking thread. * * @param key key of this action. */ private void onWrite(SelectionKey key) { assert inEventloopThread(); NioChannelEventHandler handler = (NioChannelEventHandler) key.attachment(); try { handler.onWriteReady(); } catch (Throwable e) { recordFatalError(e, handler); closeChannel(key.channel(), null); } } /** * Creates {@link ServerSocketChannel} that listens on InetSocketAddress. * * @param address InetSocketAddress that server will listen to * @param serverSocketSettings settings from this server channel * @param acceptCallback callback that is called when new incoming connection is being accepted. It can be called multiple times. * @return server channel * @throws IOException If some I/O error occurs */ @NotNull public ServerSocketChannel listen(@Nullable InetSocketAddress address, @NotNull ServerSocketSettings serverSocketSettings, @NotNull AcceptCallback acceptCallback) throws IOException { assert inEventloopThread(); ServerSocketChannel serverSocketChannel = null; try { serverSocketChannel = ServerSocketChannel.open(); serverSocketSettings.applySettings(serverSocketChannel); serverSocketChannel.configureBlocking(false); serverSocketChannel.bind(address, serverSocketSettings.getBacklog()); serverSocketChannel.register(ensureSelector(), SelectionKey.OP_ACCEPT, acceptCallback); return serverSocketChannel; } catch (IOException e) { if (serverSocketChannel != null) { closeChannel(serverSocketChannel, null); } throw e; } } /** * Registers new UDP connection in this eventloop. * * @param bindAddress address for binding DatagramSocket for this connection. * @return DatagramSocket of this connection * @throws IOException if an I/O error occurs on opening DatagramChannel */ @NotNull public static DatagramChannel createDatagramChannel(DatagramSocketSettings datagramSocketSettings, @Nullable InetSocketAddress bindAddress, @Nullable InetSocketAddress connectAddress) throws IOException { DatagramChannel datagramChannel = null; try { datagramChannel = DatagramChannel.open(); datagramSocketSettings.applySettings(datagramChannel); datagramChannel.configureBlocking(false); datagramChannel.bind(bindAddress); if (connectAddress != null) { datagramChannel.connect(connectAddress); } return datagramChannel; } catch (IOException e) { if (datagramChannel != null) { try { datagramChannel.close(); } catch (Exception nested) { logger.error("Failed closing datagram channel after I/O error", nested); e.addSuppressed(nested); } } throw e; } } /** * Connects to given socket address asynchronously. * * @param address socketChannel's address */ public void connect(SocketAddress address, @NotNull ConnectCallback cb) { connect(address, 0, cb); } public void connect(SocketAddress address, @Nullable Duration timeout, @NotNull ConnectCallback cb) { connect(address, timeout == null ? 0L : timeout.toMillis(), cb); } /** * Connects to given socket address asynchronously with a specified timeout value. * A timeout of zero is interpreted as an default system timeout * * @param address socketChannel's address * @param timeout the timeout value to be used in milliseconds, 0 as default system connection timeout */ public void connect(@NotNull SocketAddress address, long timeout, @NotNull ConnectCallback cb) { assert inEventloopThread(); SocketChannel channel; try { channel = SocketChannel.open(); } catch (IOException e) { try { cb.onException(e); } catch (Throwable e1) { recordFatalError(e1, cb); } return; } try { channel.configureBlocking(false); channel.connect(address); channel.register(ensureSelector(), SelectionKey.OP_CONNECT, timeout == 0 ? cb : new ConnectCallback() { final ScheduledRunnable scheduledTimeout = delay(timeout, () -> { closeChannel(channel, null); cb.onException(CONNECT_TIMEOUT); }); @Override public void onConnect(@NotNull SocketChannel socketChannel) { scheduledTimeout.cancel(); cb.onConnect(socketChannel); } @Override public void onException(@NotNull Throwable e) { scheduledTimeout.cancel(); cb.onException(e); } }); } catch (IOException e) { closeChannel(channel, null); try { cb.onException(e); } catch (Throwable e1) { recordFatalError(e1, cb); } } } public long tick() { assert inEventloopThread(); return (long) loop << 32 | tick; } /** * Posts a new task to the beginning of localTasks. * This method is recommended, since task will be executed * as soon as possible without invalidating CPU cache. * * @param runnable runnable of this task */ public void post(@NotNull @Async.Schedule Runnable runnable) { assert inEventloopThread(); localTasks.addFirst(runnable); } /** * Posts a new task to the end of localTasks. * * @param runnable runnable of this task */ public void postLater(@NotNull @Async.Schedule Runnable runnable) { assert inEventloopThread(); localTasks.addLast(runnable); } /** * Posts a new task from other threads. * This is the preferred method of communicating * with eventloop from other threads. * * @param runnable runnable of this task */ @Override public void execute(@NotNull @Async.Schedule Runnable runnable) { concurrentTasks.offer(runnable); if (selector != null) { selector.wakeup(); } } /** * Schedules new task. Returns {@link ScheduledRunnable} with this runnable. * * @param timestamp timestamp after which task will be ran * @param runnable runnable of this task * @return scheduledRunnable, which could used for cancelling the task */ @NotNull @Override public ScheduledRunnable schedule(long timestamp, @NotNull @Async.Schedule Runnable runnable) { assert inEventloopThread(); return addScheduledTask(timestamp, runnable, false); } /** * Schedules new background task. Returns {@link ScheduledRunnable} with this runnable. *

* If eventloop contains only background tasks, it will be closed * * @param timestamp timestamp after which task will be ran * @param runnable runnable of this task * @return scheduledRunnable, which could used for cancelling the task */ @NotNull @Override public ScheduledRunnable scheduleBackground(long timestamp, @NotNull @Async.Schedule Runnable runnable) { assert inEventloopThread(); return addScheduledTask(timestamp, runnable, true); } @NotNull private ScheduledRunnable addScheduledTask(long timestamp, Runnable runnable, boolean background) { ScheduledRunnable scheduledTask = ScheduledRunnable.create(timestamp, runnable); PriorityQueue taskQueue = background ? backgroundTasks : scheduledTasks; taskQueue.offer(scheduledTask); return scheduledTask; } /** * Notifies the eventloop about concurrent operation in other threads. * Eventloop will not exit until all external tasks are complete. */ public void startExternalTask() { externalTasksCount.incrementAndGet(); } /** * Notifies the eventloop about completion of corresponding operation in other threads. * Failure to call this method will prevent the eventloop from exiting. */ public void completeExternalTask() { externalTasksCount.decrementAndGet(); } public long refreshTimestampAndGet() { refreshTimestamp(); return timestamp; } private void refreshTimestamp() { timestamp = timeProvider.currentTimeMillis(); } /** * Returns current time of this eventloop */ @Override public long currentTimeMillis() { return timestamp; } @NotNull @Override public Eventloop getEventloop() { return this; } /** * Submits {@code Runnable} to eventloop for execution *

{@code Runnable} is executed in the eventloop thread

* * @param computation to be executed * @return {@code CompletableFuture} that completes when runnable completes */ @NotNull @Override public CompletableFuture submit(@NotNull Runnable computation) { CompletableFuture future = new CompletableFuture<>(); execute(() -> { try { computation.run(); } catch (UncheckedException u) { future.completeExceptionally(u.getCause()); return; } future.complete(null); }); return future; } @NotNull @Override public CompletableFuture submit(Supplier> computation) { CompletableFuture future = new CompletableFuture<>(); execute(() -> { try { computation.get().onComplete((result, e) -> { if (e == null) { future.complete(result); } else { future.completeExceptionally(e); } }); } catch (UncheckedException u) { future.completeExceptionally(u.getCause()); } catch (RuntimeException e) { throw e; } catch (Exception e) { future.completeExceptionally(e); } }); return future; } public static void setGlobalFatalErrorHandler(@NotNull FatalErrorHandler handler) { globalFatalErrorHandler = handler; } // JMX @JmxOperation(description = "enable monitoring " + "[ when monitoring is enabled more stats are collected, but it causes more overhead " + "(for example, most of the durationStats are collected only when monitoring is enabled) ]") public void startExtendedMonitoring() { monitoring = true; } @JmxOperation(description = "disable monitoring " + "[ when monitoring is enabled more stats are collected, but it causes more overhead " + "(for example, most of the durationStats are collected only when monitoring is enabled) ]") public void stopExtendedMonitoring() { monitoring = false; } @JmxAttribute(description = "when monitoring is enabled more stats are collected, but it causes more overhead " + "(for example, most of the durationStats are collected only when monitoring is enabled)") public boolean isExtendedMonitoring() { return monitoring; } private void recordIoError(@NotNull Exception e, @Nullable Object context) { logger.warn("IO Error in {}: {}", context, e.toString()); } public void recordFatalError(@NotNull Throwable e, @Nullable Object context) { while (e instanceof UncheckedException) { e = e.getCause(); } logger.error("Fatal Error in " + context, e); if (fatalErrorHandler != null) { handleFatalError(fatalErrorHandler, e, context); } else { handleFatalError(globalFatalErrorHandler, e, context); } if (inspector != null) { if (inEventloopThread()) { inspector.onFatalError(e, context); } else { Throwable finalE = e; execute(() -> inspector.onFatalError(finalE, context)); } } } private void handleFatalError(@NotNull FatalErrorHandler handler, @NotNull Throwable e, @Nullable Object context) { if (inEventloopThread()) { handler.handle(e, context); } else { try { handler.handle(e, context); } catch (Throwable ignored) { } } } @JmxAttribute public int getLoop() { return loop; } @JmxAttribute public long getTick() { return tick; } @Nullable public FatalErrorHandler getFatalErrorHandler() { return fatalErrorHandler; } public int getThreadPriority() { return threadPriority; } @JmxAttribute public boolean getKeepAlive() { return keepAlive; } @Nullable @JmxAttribute(name = "") public EventloopStats getStats() { return BaseInspector.lookup(inspector, EventloopStats.class); } @JmxAttribute public Duration getIdleInterval() { return idleInterval; } @JmxAttribute public void setIdleInterval(Duration idleInterval) { this.idleInterval = idleInterval; } @Override public String toString() { StringBuilder sb = new StringBuilder("Eventloop"); if (threadName != null) { sb.append('(').append(threadName).append(')'); } sb.append("{loop=").append(loop); if (tick != 0) { sb.append(", tick=").append(tick); } if (selector != null && selector.isOpen()) { int selectorKeys = selector.keys().size() - cancelledKeys; if (selectorKeys != 0) { sb.append(", selectorKeys=").append(selectorKeys); } } if (!localTasks.isEmpty()) { sb.append(", localTasks=").append(localTasks.size()); } if (!scheduledTasks.isEmpty()) { sb.append(", scheduledTasks=").append(scheduledTasks.size()); } if (!backgroundTasks.isEmpty()) { sb.append(", backgroundTasks=").append(backgroundTasks.size()); } if (!concurrentTasks.isEmpty()) { sb.append(", concurrentTasks=").append(concurrentTasks.size()); } int externalTasks = externalTasksCount.get(); if (externalTasks != 0) { sb.append(", externalTasks=").append(externalTasks); } return sb.append('}').toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy