/* * JBoss, Home of Professional Open Source * * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * 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 org.xnio.nativeimpl; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayDeque; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import org.xnio.Cancellable; import org.xnio.ChannelListener; import org.xnio.ChannelListeners; import org.xnio.ChannelPipe; import org.xnio.ClosedWorkerException; import org.xnio.FailedIoFuture; import org.xnio.FinishedIoFuture; import org.xnio.FutureResult; import org.xnio.IoFuture; import org.xnio.LocalSocketAddress; import org.xnio.OptionMap; import org.xnio.Options; import org.xnio.StreamConnection; import org.xnio.XnioExecutor; import org.xnio.XnioIoFactory; import org.xnio.XnioIoThread; import org.xnio.channels.BoundChannel; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.WriteReadyHandler; import static org.xnio.nativeimpl.Log.log; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ abstract class NativeWorkerThread extends XnioIoThread implements XnioExecutor { private static final long LONGEST_DELAY = 9223372036853L; private volatile int state; private static final int SHUTDOWN = (1 << 31); private static final AtomicIntegerFieldUpdater<NativeWorkerThread> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(NativeWorkerThread.class, "state"); private final Object lock = new Object(); private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>(); private final ArrayDeque<Runnable> localQueue = new ArrayDeque<>(); NativeWorkerThread(final NativeXnioWorker worker, final int threadNumber, final String name, final ThreadGroup group, final long stackSize) { super(worker, threadNumber, group, name, stackSize); } static NativeWorkerThread getCurrent() { final Thread thread = currentThread(); return thread instanceof NativeWorkerThread ? (NativeWorkerThread) thread : null; } public NativeXnioWorker getWorker() { return (NativeXnioWorker) super.getWorker(); } protected IoFuture<StreamConnection> acceptTcpStreamConnection(final InetSocketAddress destination, final ChannelListener<? super StreamConnection> openListener, final ChannelListener<? super BoundChannel> bindListener, final OptionMap optionMap) { return acceptGeneralStreamConnection(destination, openListener, bindListener, optionMap); } protected IoFuture<StreamConnection> acceptLocalStreamConnection(final LocalSocketAddress destination, final ChannelListener<? super StreamConnection> openListener, final ChannelListener<? super BoundChannel> bindListener, final OptionMap optionMap) { return acceptGeneralStreamConnection(destination, openListener, bindListener, optionMap); } protected IoFuture<StreamConnection> acceptGeneralStreamConnection(final SocketAddress destination, final ChannelListener<? super StreamConnection> openListener, final ChannelListener<? super BoundChannel> bindListener, final OptionMap optionMap) { assert destination instanceof InetSocketAddress || destination instanceof LocalSocketAddress; try { getWorker().checkShutdown(); } catch (ClosedWorkerException e) { return new FailedIoFuture<StreamConnection>(e); } final FutureResult<StreamConnection> futureResult = new FutureResult<StreamConnection>(this); try { boolean ok = false; final int fd = streamSocket(destination); if (optionMap.contains(Options.KEEP_ALIVE)) { Native.testAndThrow(Native.setOptKeepAlive(fd, optionMap.get(Options.KEEP_ALIVE, false), null)); } if (optionMap.contains(Options.RECEIVE_BUFFER)) { Native.testAndThrow(Native.setOptSendBuffer(fd, optionMap.get(Options.RECEIVE_BUFFER, -1), null)); } if (destination instanceof InetSocketAddress) { if (optionMap.contains(Options.TCP_OOB_INLINE)) { Native.testAndThrow(Native.setOptOobInline(fd, optionMap.get(Options.TCP_OOB_INLINE, false), null)); } if (optionMap.contains(Options.TCP_NODELAY)) { Native.testAndThrow(Native.setOptTcpNoDelay(fd, optionMap.get(Options.TCP_NODELAY, false), null)); } } try { Native.listen(fd, 1, null); final NativeDescriptor listener = new NativeDescriptor(this, fd) { protected void handleReadReady() { final int nfd = Native.accept(fd, null); if (nfd == -Native.EAGAIN) { return; } if (nfd < 0) { if (futureResult.setException(Native.exceptionFor(nfd))) { unregister(); Native.close(fd, null); } } else { final NativeStreamConnection connection = destination instanceof LocalSocketAddress ? new UnixConnection(NativeWorkerThread.this, nfd, null) : new TcpConnection(NativeWorkerThread.this, nfd, null); final NativeStreamConduit conduit = connection.getConduit(); try { if (optionMap.contains(Options.SEND_BUFFER)) { Native.testAndThrow(Native.setOptSendBuffer(fd, optionMap.get(Options.SEND_BUFFER, -1), null)); } register(conduit); } catch (IOException e) { if (futureResult.setException(e)) { unregister(); Native.close(fd, null); } return; } if (futureResult.setResult(connection)) { unregister(); Native.close(fd, null); ChannelListeners.invokeChannelListener(connection, openListener); } } } protected void handleWriteReady() { } }; register(listener); try { doResume(listener, true, false, true); ok = true; } finally { if (! ok) { unregister(listener); } } } finally { if (! ok) { Native.close(fd, null); } } } catch (IOException e) { return new FailedIoFuture<>(e); } return futureResult.getIoFuture(); } protected IoFuture<StreamConnection> openTcpStreamConnection(final InetSocketAddress bindAddress, final InetSocketAddress destinationAddress, final ChannelListener<? super StreamConnection> openListener, final ChannelListener<? super BoundChannel> bindListener, final OptionMap optionMap) { return openGeneralStreamConnection(bindAddress, destinationAddress, openListener, bindListener, optionMap); } protected IoFuture<StreamConnection> openLocalStreamConnection(final LocalSocketAddress bindAddress, final LocalSocketAddress destinationAddress, final ChannelListener<? super StreamConnection> openListener, final ChannelListener<? super BoundChannel> bindListener, final OptionMap optionMap) { return openGeneralStreamConnection(bindAddress, destinationAddress, openListener, bindListener, optionMap); } protected IoFuture<StreamConnection> openGeneralStreamConnection(final SocketAddress bindAddress, final SocketAddress destinationAddress, final ChannelListener<? super StreamConnection> openListener, final ChannelListener<? super BoundChannel> bindListener, final OptionMap optionMap) { assert bindAddress instanceof InetSocketAddress && destinationAddress instanceof InetSocketAddress || bindAddress instanceof LocalSocketAddress && destinationAddress instanceof LocalSocketAddress || bindAddress == null && (destinationAddress instanceof LocalSocketAddress || destinationAddress instanceof InetSocketAddress); try { getWorker().checkShutdown(); } catch (ClosedWorkerException e) { return new FailedIoFuture<StreamConnection>(e); } boolean ok = false; try { final int fd = streamSocket(destinationAddress); if (optionMap.contains(Options.KEEP_ALIVE)) { Native.testAndThrow(Native.setOptKeepAlive(fd, optionMap.get(Options.KEEP_ALIVE, false), null)); } if (optionMap.contains(Options.TCP_OOB_INLINE)) { Native.testAndThrow(Native.setOptOobInline(fd, optionMap.get(Options.TCP_OOB_INLINE, false), null)); } if (optionMap.contains(Options.TCP_NODELAY)) { Native.testAndThrow(Native.setOptTcpNoDelay(fd, optionMap.get(Options.TCP_NODELAY, false), null)); } if (optionMap.contains(Options.SEND_BUFFER)) { Native.testAndThrow(Native.setOptSendBuffer(fd, optionMap.get(Options.SEND_BUFFER, -1), null)); } if (optionMap.contains(Options.RECEIVE_BUFFER)) { Native.testAndThrow(Native.setOptSendBuffer(fd, optionMap.get(Options.RECEIVE_BUFFER, -1), null)); } try { final NativeStreamConnection connection = destinationAddress instanceof LocalSocketAddress ? new UnixConnection(this, fd, null) : new TcpConnection(this, fd, null); final NativeStreamConduit conduit = connection.getConduit(); register(conduit); if (Native.testAndThrowConnect(Native.connect(fd, Native.encodeSocketAddress(destinationAddress), null)) == 0) { // would block final FutureResult<StreamConnection> futureResult = new FutureResult<StreamConnection>(this); final WriteReadyHandler oldHandler = conduit.getWriteReadyHandler(); conduit.setWriteReadyHandler(new WriteReadyHandler() { public void writeReady() { int res = Native.finishConnect(fd, null); if (res == -Native.EAGAIN) { log.tracef("Connect incomplete"); // try again return; } if (res == 0) { log.tracef("Connect complete"); // connect finished conduit.suspendWrites(); conduit.setWriteReadyHandler(oldHandler); if (futureResult.setResult(connection)) { ChannelListeners.invokeChannelListener(connection, openListener); } return; } futureResult.setException(Native.exceptionFor(res)); unregister(connection.conduit); Native.close(fd, null); } public void forceTermination() { } public void terminated() { } }); conduit.resumeWrites(); ok = true; futureResult.addCancelHandler(new Cancellable() { public Cancellable cancel() { if (futureResult.setCancelled()) { unregister(conduit); } return this; } }); return futureResult.getIoFuture(); } else { // connected return new FinishedIoFuture<StreamConnection>(connection); } } finally { if (! ok) { Native.close(fd, null); } } } catch (IOException e) { return new FailedIoFuture<StreamConnection>(e); } } private int streamSocket(final SocketAddress bindAddress) throws IOException { if (bindAddress instanceof LocalSocketAddress) { return Native.testAndThrow(Native.socketLocalStream(null)); } else if (bindAddress instanceof InetSocketAddress) { final InetAddress address = ((InetSocketAddress) bindAddress).getAddress(); if (address instanceof Inet4Address) { return Native.testAndThrow(Native.socketTcp(null)); } else if (address instanceof Inet6Address) { return Native.testAndThrow(Native.socketTcp6(null)); } } throw new IOException("Invalid socket type"); } NativeWorkerThread getNextThread() { final NativeWorkerThread[] all = getWorker().getAll(); final int number = getNumber(); if (number == all.length - 1) { return all[0]; } else { return all[number + 1]; } } public ChannelPipe<StreamConnection, StreamConnection> createFullDuplexPipeConnection() throws IOException { return super.createFullDuplexPipeConnection(); } public ChannelPipe<StreamConnection, StreamConnection> createFullDuplexPipeConnection(final XnioIoFactory peer) throws IOException { return super.createFullDuplexPipeConnection(peer); } public ChannelPipe<StreamSourceChannel, StreamSinkChannel> createHalfDuplexPipe(final XnioIoFactory peer) throws IOException { return super.createHalfDuplexPipe(peer); } abstract void close(); abstract void doWakeup(); abstract void doSelection(long delayTimeMillis); public final void interrupt() { doWakeup(); super.interrupt(); } public final void run() { try { log.tracef("Starting worker thread %s", this); Runnable task; int oldState; for (;;) { // run tasks first do { task = queue.poll(); if (task == null) task = localQueue.poll(); safeRun(task); } while (task != null); // all tasks have been run oldState = state; if ((oldState & SHUTDOWN) != 0) { close(); return; } // perform select doSelection(LONGEST_DELAY); } } finally { getWorker().closeResource(); log.tracef("Shutting down channel thread \"%s\"", this); } } private static void safeRun(final Runnable command) { if (command != null) try { log.tracef("Running task %s", command); command.run(); } catch (Throwable t) { log.taskFailed(command, t); } } public void execute(final Runnable command) { if ((state & SHUTDOWN) != 0) { throw log.threadExiting(); } if (this != currentThread()) { queue.add(command); doWakeup(); } else { localQueue.add(command); } } void executeLocal(final Runnable runnable) { assert this == currentThread(); localQueue.add(runnable); } final void shutdown() { int oldState; do { oldState = state; if ((oldState & SHUTDOWN) != 0) { // idempotent return; } } while (! stateUpdater.compareAndSet(this, oldState, oldState | SHUTDOWN)); doWakeup(); } abstract void register(NativeDescriptor channel) throws IOException; abstract void doResume(NativeDescriptor channel, boolean read, boolean write, boolean edge); abstract void unregister(final NativeDescriptor channel); public String toString() { return String.format("Thread %s (number %d)", getName(), Integer.valueOf(getNumber())); } }