
org.minidns.source.async.AsyncNetworkDataSource Maven / Gradle / Ivy
/*
* Copyright 2015-2024 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.source.async;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.minidns.MiniDnsFuture;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsqueryresult.DnsQueryResult;
import org.minidns.source.AbstractDnsDataSource;
/**
* A DNS data sources that resolves requests via the network asynchronously.
*/
public class AsyncNetworkDataSource extends AbstractDnsDataSource {
/**
* The logger of this data source.
*/
protected static final Logger LOGGER = Logger.getLogger(AsyncNetworkDataSource.class.getName());
private static final int REACTOR_THREAD_COUNT = 1;
private static final Queue INCOMING_REQUESTS = new ConcurrentLinkedQueue<>();
private static final Selector SELECTOR;
private static final Lock REGISTRATION_LOCK = new ReentrantLock();
private static final Queue PENDING_SELECTION_KEYS = new ConcurrentLinkedQueue<>();
private static final Thread[] REACTOR_THREADS = new Thread[REACTOR_THREAD_COUNT];
private static final PriorityQueue DEADLINE_QUEUE = new PriorityQueue<>(16, new Comparator() {
@Override
public int compare(AsyncDnsRequest o1, AsyncDnsRequest o2) {
if (o1.deadline > o2.deadline) {
return 1;
} else if (o1.deadline < o2.deadline) {
return -1;
}
return 0;
}
});
static {
try {
SELECTOR = Selector.open();
} catch (IOException e) {
throw new IllegalStateException(e);
}
for (int i = 0; i < REACTOR_THREAD_COUNT; i++) {
Thread reactorThread = new Thread(new Reactor());
reactorThread.setDaemon(true);
reactorThread.setName("MiniDNS Reactor Thread #" + i);
reactorThread.start();
REACTOR_THREADS[i] = reactorThread;
}
}
@Override
public MiniDnsFuture queryAsync(DnsMessage message, InetAddress address, int port, OnResponseCallback onResponseCallback) {
AsyncDnsRequest asyncDnsRequest = new AsyncDnsRequest(message, address, port, udpPayloadSize, this, onResponseCallback);
INCOMING_REQUESTS.add(asyncDnsRequest);
synchronized (DEADLINE_QUEUE) {
DEADLINE_QUEUE.add(asyncDnsRequest);
}
SELECTOR.wakeup();
return asyncDnsRequest.getFuture();
}
@Override
public DnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException {
MiniDnsFuture future = queryAsync(message, address, port, null);
try {
return future.get();
} catch (InterruptedException e) {
// This should never happen.
throw new AssertionError(e);
} catch (ExecutionException e) {
Throwable wrappedThrowable = e.getCause();
if (wrappedThrowable instanceof IOException) {
throw (IOException) wrappedThrowable;
}
// This should never happen.
throw new AssertionError(e);
}
}
SelectionKey registerWithSelector(SelectableChannel channel, int ops, Object attachment) throws ClosedChannelException {
REGISTRATION_LOCK.lock();
try {
SELECTOR.wakeup();
return channel.register(SELECTOR, ops, attachment);
} finally {
REGISTRATION_LOCK.unlock();
}
}
void finished(AsyncDnsRequest asyncDnsRequest) {
synchronized (DEADLINE_QUEUE) {
DEADLINE_QUEUE.remove(asyncDnsRequest);
}
}
void cancelled(AsyncDnsRequest asyncDnsRequest) {
finished(asyncDnsRequest);
// Wakeup since the async DNS request was removed from the deadline queue.
SELECTOR.wakeup();
}
private static final class Reactor implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) {
Collection mySelectedKeys = performSelect();
handleSelectedKeys(mySelectedKeys);
handlePendingSelectionKeys();
handleIncomingRequests();
}
}
private static void handleSelectedKeys(Collection selectedKeys) {
for (SelectionKey selectionKey : selectedKeys) {
ChannelSelectedHandler channelSelectedHandler = (ChannelSelectedHandler) selectionKey.attachment();
SelectableChannel channel = selectionKey.channel();
channelSelectedHandler.handleChannelSelected(channel, selectionKey);
}
}
@SuppressWarnings({"LockNotBeforeTry", "MixedMutabilityReturnType"})
private static Collection performSelect() {
AsyncDnsRequest nearestDeadline = null;
AsyncDnsRequest nextInQueue;
synchronized (DEADLINE_QUEUE) {
while ((nextInQueue = DEADLINE_QUEUE.peek()) != null) {
if (nextInQueue.wasDeadlineMissedAndFutureNotified()) {
// We notified the future, associated with the AsyncDnsRequest nearestDeadline,
// that the deadline has passed, hence remove it from the queue.
DEADLINE_QUEUE.poll();
} else {
// We found a nearest deadline that has not yet passed, break out of the loop.
nearestDeadline = nextInQueue;
break;
}
}
}
long selectWait;
if (nearestDeadline == null) {
// There is no deadline, wait indefinitely in select().
selectWait = 0;
} else {
// There is a deadline in the future, only block in select() until the deadline.
selectWait = nextInQueue.deadline - System.currentTimeMillis();
if (selectWait < 0) {
// We already have a missed deadline. Do not call select() and handle the tasks which are past their
// deadline.
return Collections.emptyList();
}
}
List selectedKeys;
int newSelectedKeysCount;
synchronized (SELECTOR) {
// Ensure that a wakeup() in registerWithSelector() gives the corresponding
// register() in the same method the chance to actually register the channel. In
// other words: This construct ensure that there is never another select()
// between a corresponding wakeup() and register() calls.
// See also https://stackoverflow.com/a/1112809/194894
REGISTRATION_LOCK.lock();
REGISTRATION_LOCK.unlock();
try {
newSelectedKeysCount = SELECTOR.select(selectWait);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "IOException while using select()", e);
return Collections.emptyList();
}
if (newSelectedKeysCount == 0) {
return Collections.emptyList();
}
Set selectedKeySet = SELECTOR.selectedKeys();
for (SelectionKey selectionKey : selectedKeySet) {
selectionKey.interestOps(0);
}
selectedKeys = new ArrayList<>(selectedKeySet.size());
selectedKeys.addAll(selectedKeySet);
selectedKeySet.clear();
}
int selectedKeysCount = selectedKeys.size();
final Level LOG_LEVEL = Level.FINER;
if (LOGGER.isLoggable(LOG_LEVEL)) {
LOGGER.log(LOG_LEVEL, "New selected key count: " + newSelectedKeysCount + ". Total selected key count "
+ selectedKeysCount);
}
int myKeyCount = selectedKeysCount / REACTOR_THREAD_COUNT;
Collection mySelectedKeys = new ArrayList<>(myKeyCount);
Iterator it = selectedKeys.iterator();
for (int i = 0; i < myKeyCount; i++) {
SelectionKey selectionKey = it.next();
mySelectedKeys.add(selectionKey);
}
while (it.hasNext()) {
// Drain to PENDING_SELECTION_KEYS
SelectionKey selectionKey = it.next();
PENDING_SELECTION_KEYS.add(selectionKey);
}
return mySelectedKeys;
}
private static void handlePendingSelectionKeys() {
int pendingSelectionKeysSize = PENDING_SELECTION_KEYS.size();
if (pendingSelectionKeysSize == 0) {
return;
}
int myKeyCount = pendingSelectionKeysSize / REACTOR_THREAD_COUNT;
Collection selectedKeys = new ArrayList<>(myKeyCount);
for (int i = 0; i < myKeyCount; i++) {
SelectionKey selectionKey = PENDING_SELECTION_KEYS.poll();
if (selectionKey == null) {
// We lost a race :)
break;
}
selectedKeys.add(selectionKey);
}
if (!PENDING_SELECTION_KEYS.isEmpty()) {
// There is more work in the pending selection keys queue, wakeup another thread to handle it.
SELECTOR.wakeup();
}
handleSelectedKeys(selectedKeys);
}
private static void handleIncomingRequests() {
int incomingRequestsSize = INCOMING_REQUESTS.size();
if (incomingRequestsSize == 0) {
return;
}
int myRequestsCount = incomingRequestsSize / REACTOR_THREAD_COUNT;
// The division could result in myRequestCount being zero despite pending incoming
// requests. Therefore, ensure this thread tries to get at least one incoming
// request by invoking poll(). Otherwise, we might end up in a busy loop
// where myRequestCount is zero, and this thread invokes a selector.wakeup() below
// because incomingRequestsSize is not empty, but the woken-up reactor thread
// will end up with myRequestCount being zero again, restarting the busy-loop cycle.
if (myRequestsCount == 0) myRequestsCount = 1;
Collection requests = new ArrayList<>(myRequestsCount);
for (int i = 0; i < myRequestsCount; i++) {
AsyncDnsRequest asyncDnsRequest = INCOMING_REQUESTS.poll();
if (asyncDnsRequest == null) {
// We lost a race :)
break;
}
requests.add(asyncDnsRequest);
}
if (!INCOMING_REQUESTS.isEmpty()) {
SELECTOR.wakeup();
}
for (AsyncDnsRequest asyncDnsRequest : requests) {
asyncDnsRequest.startHandling();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy