/*
 * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.nio.ch;

import java.nio.channels.*;
import java.nio.channels.spi.AsynchronousChannelProvider;
import java.io.Closeable;
import java.io.IOException;
import java.io.FileDescriptor;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import jdk.internal.misc.Unsafe;

Windows implementation of AsynchronousChannelGroup encapsulating an I/O completion port.
/** * Windows implementation of AsynchronousChannelGroup encapsulating an I/O * completion port. */
class Iocp extends AsynchronousChannelGroupImpl { private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long INVALID_HANDLE_VALUE = -1L; // maps completion key to channel private final ReadWriteLock keyToChannelLock = new ReentrantReadWriteLock(); private final Map<Integer,OverlappedChannel> keyToChannel = new HashMap<Integer,OverlappedChannel>(); private int nextCompletionKey; // handle to completion port private final long port; // true if port has been closed private boolean closed; // the set of "stale" OVERLAPPED structures. These OVERLAPPED structures // relate to I/O operations where the completion notification was not // received in a timely manner after the channel is closed. private final Set<Long> staleIoSet = new HashSet<Long>(); Iocp(AsynchronousChannelProvider provider, ThreadPool pool) throws IOException { super(provider, pool); this.port = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, fixedThreadCount()); this.nextCompletionKey = 1; } Iocp start() { startThreads(new EventHandlerTask()); return this; } /* * Channels implements this interface support overlapped I/O and can be * associated with a completion port. */ static interface OverlappedChannel extends Closeable {
Returns a reference to the pending I/O result.
/** * Returns a reference to the pending I/O result. */
<V,A> PendingFuture<V,A> getByOverlapped(long overlapped); } // release all resources void implClose() { synchronized (this) { if (closed) return; closed = true; } close0(port); synchronized (staleIoSet) { for (Long ov: staleIoSet) { unsafe.freeMemory(ov); } staleIoSet.clear(); } } @Override boolean isEmpty() { keyToChannelLock.writeLock().lock(); try { return keyToChannel.isEmpty(); } finally { keyToChannelLock.writeLock().unlock(); } } @Override final Object attachForeignChannel(final Channel channel, FileDescriptor fdObj) throws IOException { int key = associate(new OverlappedChannel() { public <V,A> PendingFuture<V,A> getByOverlapped(long overlapped) { return null; } public void close() throws IOException { channel.close(); } }, 0L); return Integer.valueOf(key); } @Override final void detachForeignChannel(Object key) { disassociate((Integer)key); } @Override void closeAllChannels() { /** * On Windows the close operation will close the socket/file handle * and then wait until all outstanding I/O operations have aborted. * This is necessary as each channel's cache of OVERLAPPED structures * can only be freed once all I/O operations have completed. As I/O * completion requires a lookup of the keyToChannel then we must close * the channels when not holding the write lock. */ final int MAX_BATCH_SIZE = 32; OverlappedChannel channels[] = new OverlappedChannel[MAX_BATCH_SIZE]; int count; do { // grab a batch of up to 32 channels keyToChannelLock.writeLock().lock(); count = 0; try { for (Integer key: keyToChannel.keySet()) { channels[count++] = keyToChannel.get(key); if (count >= MAX_BATCH_SIZE) break; } } finally { keyToChannelLock.writeLock().unlock(); } // close them for (int i=0; i<count; i++) { try { channels[i].close(); } catch (IOException ignore) { } } } while (count > 0); } private void wakeup() { try { postQueuedCompletionStatus(port, 0); } catch (IOException e) { // should not happen throw new AssertionError(e); } } @Override void executeOnHandlerTask(Runnable task) { synchronized (this) { if (closed) throw new RejectedExecutionException(); offerTask(task); wakeup(); } } @Override void shutdownHandlerTasks() { // shutdown all handler threads int nThreads = threadCount(); while (nThreads-- > 0) { wakeup(); } }
Associate the given handle with this group
/** * Associate the given handle with this group */
int associate(OverlappedChannel ch, long handle) throws IOException { keyToChannelLock.writeLock().lock(); // generate a completion key (if not shutdown) int key; try { if (isShutdown()) throw new ShutdownChannelGroupException(); // generate unique key do { key = nextCompletionKey++; } while ((key == 0) || keyToChannel.containsKey(key)); // associate with I/O completion port if (handle != 0L) { createIoCompletionPort(handle, port, key, 0); } // setup mapping keyToChannel.put(key, ch); } finally { keyToChannelLock.writeLock().unlock(); } return key; }
Disassociate channel from the group.
/** * Disassociate channel from the group. */
void disassociate(int key) { boolean checkForShutdown = false; keyToChannelLock.writeLock().lock(); try { keyToChannel.remove(key); // last key to be removed so check if group is shutdown if (keyToChannel.isEmpty()) checkForShutdown = true; } finally { keyToChannelLock.writeLock().unlock(); } // continue shutdown if (checkForShutdown && isShutdown()) { try { shutdownNow(); } catch (IOException ignore) { } } }
Invoked when a channel associated with this port is closed before notifications for all outstanding I/O operations have been received.
/** * Invoked when a channel associated with this port is closed before * notifications for all outstanding I/O operations have been received. */
void makeStale(Long overlapped) { synchronized (staleIoSet) { staleIoSet.add(overlapped); } }
Checks if the given OVERLAPPED is stale and if so, releases it.
/** * Checks if the given OVERLAPPED is stale and if so, releases it. */
private void checkIfStale(long ov) { synchronized (staleIoSet) { boolean removed = staleIoSet.remove(ov); if (removed) { unsafe.freeMemory(ov); } } }
The handler for consuming the result of an asynchronous I/O operation.
/** * The handler for consuming the result of an asynchronous I/O operation. */
static interface ResultHandler {
Invoked if the I/O operation completes successfully.
/** * Invoked if the I/O operation completes successfully. */
public void completed(int bytesTransferred, boolean canInvokeDirect);
Invoked if the I/O operation fails.
/** * Invoked if the I/O operation fails. */
public void failed(int error, IOException ioe); } // Creates IOException for the given I/O error. private static IOException translateErrorToIOException(int error) { String msg = getErrorMessage(error); if (msg == null) msg = "Unknown error: 0x0" + Integer.toHexString(error); return new IOException(msg); }
Long-running task servicing system-wide or per-file completion port
/** * Long-running task servicing system-wide or per-file completion port */
private class EventHandlerTask implements Runnable { public void run() { Invoker.GroupAndInvokeCount myGroupAndInvokeCount = Invoker.getGroupAndInvokeCount(); boolean canInvokeDirect = (myGroupAndInvokeCount != null); CompletionStatus ioResult = new CompletionStatus(); boolean replaceMe = false; try { for (;;) { // reset invoke count if (myGroupAndInvokeCount != null) myGroupAndInvokeCount.resetInvokeCount(); // wait for I/O completion event // An error here is fatal (thread will not be replaced) replaceMe = false; try { getQueuedCompletionStatus(port, ioResult); } catch (IOException x) { // should not happen x.printStackTrace(); return; } // handle wakeup to execute task or shutdown if (ioResult.completionKey() == 0 && ioResult.overlapped() == 0L) { Runnable task = pollTask(); if (task == null) { // shutdown request return; } // run task // (if error/exception then replace thread) replaceMe = true; task.run(); continue; } // map key to channel OverlappedChannel ch = null; keyToChannelLock.readLock().lock(); try { ch = keyToChannel.get(ioResult.completionKey()); if (ch == null) { checkIfStale(ioResult.overlapped()); continue; } } finally { keyToChannelLock.readLock().unlock(); } // lookup I/O request PendingFuture<?,?> result = ch.getByOverlapped(ioResult.overlapped()); if (result == null) { // we get here if the OVERLAPPED structure is associated // with an I/O operation on a channel that was closed // but the I/O operation event wasn't read in a timely // manner. Alternatively, it may be related to a // tryLock operation as the OVERLAPPED structures for // these operations are not in the I/O cache. checkIfStale(ioResult.overlapped()); continue; } // synchronize on result in case I/O completed immediately // and was handled by initiator synchronized (result) { if (result.isDone()) { continue; } // not handled by initiator } // invoke I/O result handler int error = ioResult.error(); ResultHandler rh = (ResultHandler)result.getContext(); replaceMe = true; // (if error/exception then replace thread) if (error == 0) { rh.completed(ioResult.bytesTransferred(), canInvokeDirect); } else { rh.failed(error, translateErrorToIOException(error)); } } } finally { // last thread to exit when shutdown releases resources int remaining = threadExit(this, replaceMe); if (remaining == 0 && isShutdown()) { implClose(); } } } }
Container for data returned by GetQueuedCompletionStatus
/** * Container for data returned by GetQueuedCompletionStatus */
private static class CompletionStatus { private int error; private int bytesTransferred; private int completionKey; private long overlapped; private CompletionStatus() { } int error() { return error; } int bytesTransferred() { return bytesTransferred; } int completionKey() { return completionKey; } long overlapped() { return overlapped; } } // -- native methods -- private static native void initIDs(); private static native long createIoCompletionPort(long handle, long existingPort, int completionKey, int concurrency) throws IOException; private static native void close0(long handle); private static native void getQueuedCompletionStatus(long completionPort, CompletionStatus status) throws IOException; private static native void postQueuedCompletionStatus(long completionPort, int completionKey) throws IOException; private static native String getErrorMessage(int error); static { IOUtil.load(); initIDs(); } }