/** * Copyright 2014 Ricardo Padilha * * 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.dsys.snio.impl.pool; import java.io.IOException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nonnull; import net.dsys.commons.impl.future.MergingCallbackFuture; import net.dsys.commons.impl.future.SettableCallbackFuture; import net.dsys.commons.impl.lang.DaemonThreadFactory; import net.dsys.snio.api.pool.Acceptor; import net.dsys.snio.api.pool.Processor; import net.dsys.snio.api.pool.SelectionType; import net.dsys.snio.api.pool.SelectorExecutor; /** * @author Ricardo Padilha */ final class SelectorExecutorImpl implements SelectorExecutor { private static final int THREAD_COUNT = 3; private final ExecutorService executor; private final SelectorThreadImpl accepter; private final SelectorThreadImpl reader; private final SelectorThreadImpl writer; private volatile boolean accepting; private MergingCallbackFuture<Void> closeFuture; SelectorExecutorImpl(@Nonnull final String name) { this.executor = Executors.newFixedThreadPool(THREAD_COUNT, new DaemonThreadFactory(name)); this.accepter = new SelectorThreadImpl(SelectionType.OP_ACCEPT); this.reader = new SelectorThreadImpl(SelectionType.OP_READ); this.writer = new SelectorThreadImpl(SelectionType.OP_WRITE); this.accepting = false; } /** * Open this executor. */ void open() throws IOException { accepter.open(); reader.open(); writer.open(); executor.execute(reader.getRunnable()); executor.execute(writer.getRunnable()); } /** * @return <code>true</code> if this executor is open. */ boolean isOpen() { return accepter.isOpen() && reader.isOpen() && writer.isOpen(); } /** * {@inheritDoc} */ @Override public void bind(final SelectableChannel channel, final Acceptor acceptor) { if ((channel.validOps() & SelectionKey.OP_ACCEPT) == 0) { throw new IllegalArgumentException("channel does not support SelectionKey.OP_ACCEPT"); } /** XXX: see #getCloseFuture(). */ if (!accepting) { executor.execute(accepter.getRunnable()); accepting = true; } accepter.bind(channel, acceptor); } /** * {@inheritDoc} */ @Override public void connect(final SelectableChannel channel, final Processor processor) { reader.connect(channel, processor); } /** * {@inheritDoc} */ @Override public void register(final SelectableChannel channel, final Processor processor) { reader.register(channel, processor); writer.register(channel, processor); } /** * {@inheritDoc} */ @Override public void cancelBind(final SelectionKey key, final SettableCallbackFuture<Void> future, final Callable<Void> task) { if (key != null) { accepter.cancel(key, future, task); } else { try { task.call(); future.success(null); } catch (final Throwable t) { future.fail(t); } } } /** * {@inheritDoc} */ @Override public void cancelConnect(final SelectionKey readKey, final SettableCallbackFuture<Void> readFuture, final SelectionKey writeKey, final SettableCallbackFuture<Void> writeFuture) { if (readKey != null) { reader.cancel(readKey, readFuture); } else { readFuture.success(null); } if (writeKey != null) { writer.cancel(writeKey, writeFuture); } else { writeFuture.success(null); } } /** * Close this executor. */ void close() { writer.close(); reader.close(); accepter.close(); executor.shutdown(); } MergingCallbackFuture<Void> getCloseFuture() { if (closeFuture == null) { final MergingCallbackFuture.Builder<Void> builder = MergingCallbackFuture.builder(); builder.add(writer.getCloseFuture()); builder.add(reader.getCloseFuture()); /** * XXX: there is a racing condition between this method and bind(). * * It shouldn't be a problem unless someone closes a channel at the * same time they are trying to bind it. * <p> * What happens when someone gets the future and then the channel is * bound? * <p> * How do we include the future from the accepter thread in the * reference that was given out? * <p> * How do we make sure that the reference that was given out is * properly set as done, if the accepter thread is never started? */ if (accepting) { builder.add(accepter.getCloseFuture()); } closeFuture = builder.build(); } return closeFuture; } }