/* * Copyright 2014 the original author or authors. * * 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 ratpack.exec.internal; import com.google.common.collect.ImmutableList; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.util.concurrent.DefaultThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ratpack.exec.*; import ratpack.func.Action; import ratpack.func.Block; import ratpack.registry.RegistrySpec; import ratpack.util.internal.ChannelImplDetector; import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import static ratpack.func.Action.noop; public class DefaultExecController implements ExecControllerInternal { private static final Action<Throwable> LOG_UNCAUGHT = t -> DefaultExecution.LOGGER.error("Uncaught execution exception", t); private static final Logger LOGGER = LoggerFactory.getLogger(ExecController.class); private final ExecutorService blockingExecutor; private final EventLoopGroup eventLoopGroup; private final int numThreads; private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); private final AtomicBoolean closed = new AtomicBoolean(); private ImmutableList<? extends ExecInterceptor> interceptors = ImmutableList.of(); private ImmutableList<? extends ExecInitializer> initializers = ImmutableList.of(); private Queue<Block> onClose = new ConcurrentLinkedQueue<>(); public DefaultExecController() { this(Runtime.getRuntime().availableProcessors() * 2); } public DefaultExecController(int numThreads) { this.numThreads = numThreads; this.eventLoopGroup = ChannelImplDetector.eventLoopGroup(numThreads, new ExecControllerBindingThreadFactory(true, "ratpack-compute", Thread.MAX_PRIORITY)); this.blockingExecutor = Executors.newCachedThreadPool(new ExecControllerBindingThreadFactory(false, "ratpack-blocking", Thread.NORM_PRIORITY)); } @Override public void setInterceptors(ImmutableList<? extends ExecInterceptor> interceptors) { this.interceptors = interceptors; } @Override public void setInitializers(ImmutableList<? extends ExecInitializer> initializers) { this.initializers = initializers; } @Override public ImmutableList<? extends ExecInterceptor> getInterceptors() { return interceptors; } @Override public ImmutableList<? extends ExecInitializer> getInitializers() { return initializers; } @Override public boolean onClose(Block block) { if (closed.get()) { return false; } else { onClose.add(block); return true; } } public void close() { Block onClose = this.onClose.poll(); while (onClose != null) { try { onClose.execute(); } catch (Exception e) { LOGGER.warn("Exception thrown by exec controller onClose callback will be ignored - ", e); } onClose = this.onClose.poll(); } blockingExecutor.shutdown(); eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS); } @Override public ScheduledExecutorService getExecutor() { return eventLoopGroup; } @Override public ExecutorService getBlockingExecutor() { return blockingExecutor; } @Override public EventLoopGroup getEventLoopGroup() { return eventLoopGroup; } private class ExecControllerBindingThreadFactory extends DefaultThreadFactory { private final boolean compute; ExecControllerBindingThreadFactory(boolean compute, String name, int priority) { super(name, priority); this.compute = compute; } @Override public Thread newThread(final Runnable r) { return super.newThread(() -> { ThreadBinding.bind(compute, DefaultExecController.this); Thread.currentThread().setContextClassLoader(contextClassLoader); r.run(); }); } } @Override public int getNumThreads() { return numThreads; } @Override public ExecStarter fork() { return new ExecStarter() { private Action<? super Throwable> onError = LOG_UNCAUGHT; private Action<? super Execution> onComplete = noop(); private Action<? super Execution> onStart = noop(); private Action<? super RegistrySpec> registry = noop(); private EventLoop eventLoop = getEventLoopGroup().next(); @Override public ExecStarter eventLoop(EventLoop eventLoop) { this.eventLoop = eventLoop; return this; } @Override public ExecStarter onError(Action<? super Throwable> onError) { this.onError = onError; return this; } @Override public ExecStarter onComplete(Action<? super Execution> onComplete) { this.onComplete = onComplete; return this; } @Override public ExecStarter onStart(Action<? super Execution> onStart) { this.onStart = onStart; return this; } @Override public ExecStarter register(Action<? super RegistrySpec> action) { this.registry = action; return this; } @Override public void start(Action<? super Execution> initialExecutionSegment) { if (eventLoop.inEventLoop() && DefaultExecution.get() == null) { try { new DefaultExecution(DefaultExecController.this, eventLoop, registry, initialExecutionSegment, onError, onStart, onComplete); } catch (Throwable e) { throw new InternalError("could not start execution", e); } } else { eventLoop.submit(() -> new DefaultExecution(DefaultExecController.this, eventLoop, registry, initialExecutionSegment, onError, onStart, onComplete) ); } } }; } }