All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ratpack.exec.internal.DefaultExecController Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc-1
Show newest version
/*
 * 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.base.Optional;
import com.google.common.util.concurrent.*;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultThreadFactory;
import ratpack.exec.*;
import ratpack.exec.ExecutionException;
import ratpack.func.Action;
import ratpack.func.Factory;
import ratpack.handling.internal.InterceptedOperation;
import ratpack.registry.internal.SimpleMutableRegistry;
import ratpack.util.ExceptionUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultExecController implements ExecController {

  private static final ThreadLocal THREAD_BINDING = new ThreadLocal<>();

  private final static Logger LOGGER = LoggerFactory.getLogger(DefaultExecController.class);

  private final ThreadLocal executionHolder = new ThreadLocal<>();

  private final ListeningScheduledExecutorService computeExecutor;
  private final ListeningExecutorService blockingExecutor;
  private final EventLoopGroup eventLoopGroup;
  private final ExecControl control;
  private final int numThreads;

  public DefaultExecController(int numThreads) {
    this.numThreads = numThreads;
    this.eventLoopGroup = new NioEventLoopGroup(numThreads, new ExecControllerBindingThreadFactory("ratpack-compute", Thread.MAX_PRIORITY));
    this.computeExecutor = MoreExecutors.listeningDecorator(eventLoopGroup);
    this.blockingExecutor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool(new ExecControllerBindingThreadFactory("ratpack-blocking", Thread.NORM_PRIORITY)));
    this.control = new FactoryBackedExecControl();
  }

  public static Optional getThreadBoundController() {
    return Optional.fromNullable(THREAD_BINDING.get());
  }

  public void close() {
    eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS);
    blockingExecutor.shutdown();
  }

  @Override
  public Execution getExecution() throws ExecutionException {
    Execution execution = executionHolder.get();
    if (execution == null) {
      throw new ExecutionException("No execution is bound to the current thread (are you calling this from a blocking operation or a manual created thread?)");
    } else {
      return execution;
    }
  }

  @Override
  public ListeningScheduledExecutorService getExecutor() {
    return computeExecutor;
  }

  @Override
  public EventLoopGroup getEventLoopGroup() {
    return eventLoopGroup;
  }

  @Override
  public void start(final Action action) {
    if (!isManagedThread() || executionHolder.get() != null) {
      computeExecutor.submit(new Runnable() {
        @Override
        public void run() {
          start(action);
        }
      });
    } else {
      new Execution(action);
    }
  }

  private class FactoryBackedExecControl implements ExecControl {

    private final Factory executionFactory = new Factory() {
      @Override
      public Execution create() {
        return getExecution();
      }
    };

    @Override
    public  Promise blocking(final Callable blockingOperation) {
      return promise(new Action>() {
        @Override
        public void execute(final Fulfiller fulfiller) throws Exception {
          final Execution execution = getExecution();
          ListenableFuture future = blockingExecutor.submit(new BlockingOperation(execution));
          Futures.addCallback(future, new ComputeResume(fulfiller), computeExecutor);
        }

        class BlockingOperation implements Callable {
          private final Execution execution;
          private Exception exception;
          private T result;

          public BlockingOperation(Execution execution) {
            this.execution = execution;
          }

          @Override
          public T call() throws Exception {
            execution.intercept(ExecInterceptor.ExecType.BLOCKING, execution.interceptors, new Action() {
              @Override
              public void execute(Execution execution) throws Exception {
                try {
                  result = blockingOperation.call();
                } catch (Exception e) {
                  exception = e;
                }
              }
            });

            if (exception != null) {
              throw exception;
            } else {
              return result;
            }
          }
        }

        class ComputeResume implements FutureCallback {
          private final Fulfiller fulfiller;

          public ComputeResume(Fulfiller fulfiller) {
            this.fulfiller = fulfiller;
          }

          @Override
          public void onSuccess(final T result) {
            fulfiller.success(result);
          }

          @SuppressWarnings("NullableProblems")
          @Override
          public void onFailure(final Throwable t) {
            fulfiller.error(t);
          }
        }
      });

    }

    @Override
    public  Promise promise(Action> action) {
      return new DefaultPromise<>(executionFactory, action);
    }

    @Override
    public void fork(Action action) {
      start(action);
    }
  }

  public class Execution extends SimpleMutableRegistry implements ratpack.exec.Execution {
    private final List interceptors = new LinkedList<>();
    private final Deque segments = new ConcurrentLinkedDeque<>();
    private final Queue onCompletes = new ConcurrentLinkedQueue<>();

    private Action errorHandler = new Action() {
      @Override
      public void execute(Throwable throwable) throws Exception {
        throw ExceptionUtils.toException(throwable);
      }
    };

    private final AtomicBoolean active = new AtomicBoolean();
    private boolean waiting;
    private boolean done;

    public Execution(Action action) {
      segments.addLast(new UserCodeSegment(action));
      tryDrain();
    }

    @Override
    public  Promise blocking(final Callable operation) {
      return control.blocking(operation);
    }


    @Override
    public  Promise promise(final Action> action) {
      return control.promise(action);
    }

    @Override
    public void fork(Action action) {
      control.fork(action);
    }

    @Override
    public void setErrorHandler(Action errorHandler) {
      this.errorHandler = errorHandler;
    }

    @Override
    public void addInterceptor(ExecInterceptor execInterceptor, Action continuation) throws Exception {
      interceptors.add(execInterceptor);
      intercept(ExecInterceptor.ExecType.COMPUTE, Collections.singletonList(execInterceptor), continuation);
    }

    @Override
    public ExecController getController() {
      return DefaultExecController.this;
    }


    public void join(final Action action) {
      segments.addFirst(new UserCodeSegment(action));
      waiting = false;
      tryDrain();
    }

    public void continueVia(final Runnable runnable) {
      segments.addFirst(new Runnable() {
        @Override
        public void run() {
          waiting = true;
          runnable.run();
        }
      });
    }

    private void tryDrain() {
      assertNotDone();
      if (!done && !waiting && !segments.isEmpty()) {
        if (active.compareAndSet(false, true)) {
          drain();
        }
      }
    }

    private void runOnCompletes() {
      Runnable onComplete = onCompletes.poll();
      while (onComplete != null) {
        try {
          onComplete.run();
        } catch (Exception e) {
          LOGGER.warn("", e);
        } finally {
          onComplete = onCompletes.poll();
        }
      }
    }

    private void drain() {
      if (isManagedThread()) {
        executionHolder.set(this);
        try {
          Runnable segment = segments.poll();
          while (segment != null) {
            segment.run();
            if (waiting) { // the segment initiated an async op
              break;
            } else {
              segment = segments.poll();
              if (segment == null) { // not waiting and no more segments, we are done
                done = true;
                runOnCompletes();
              }
            }
          }
        } finally {
          executionHolder.remove();
          active.set(false);
        }
        if (waiting) {
          tryDrain();
        }
      } else {
        active.set(false);
        eventLoopGroup.submit(new Runnable() {
          @Override
          public void run() {
            tryDrain();
          }
        });
      }
    }

    @Override
    public void onComplete(Runnable runnable) {
      assertNotDone();
      onCompletes.add(runnable);
    }

    private void assertNotDone() {
      if (done) {
        throw new ExecutionException("execution is complete");
      }
    }

    public void intercept(final ExecInterceptor.ExecType execType, final List interceptors, final Action action) throws Exception {
      new InterceptedOperation(execType, interceptors) {
        @Override
        protected void performOperation() throws Exception {
          action.execute(Execution.this);
        }
      }.run();
    }

    /**
     * An execution segment that executes “user code” (i.e. not execution management infrastructure)
     */
    private class UserCodeSegment implements Runnable {
      private final Action action;

      public UserCodeSegment(Action action) {
        this.action = action;
      }

      @Override
      public void run() {
        try {
          try {
            intercept(ExecInterceptor.ExecType.COMPUTE, interceptors, action);
          } catch (ExecutionSegmentTerminationError e) {
            throw e.getCause();
          }
        } catch (final Throwable e) {
          segments.clear();
          segments.addFirst(new Runnable() {
            @Override
            public void run() {
              try {
                errorHandler.execute(e);
              } catch (final Throwable e) {
                segments.addFirst(new UserCodeSegment(new Action() {
                  @Override
                  public void execute(ratpack.exec.Execution execution) throws Exception {
                    throw e;
                  }
                }));
              }
            }
          });
        }
      }
    }
  }

  @Override
  public ExecControl getControl() {
    return control;
  }

  private class ExecControllerBindingThreadFactory extends DefaultThreadFactory {
    public ExecControllerBindingThreadFactory(String name, int priority) {
      super(name, priority);
    }

    @Override
    public Thread newThread(final Runnable r) {
      return super.newThread(new Runnable() {
        @Override
        public void run() {
          THREAD_BINDING.set(DefaultExecController.this);
          r.run();
        }
      });
    }

  }

  @Override
  public boolean isManagedThread() {
    Optional threadBoundController = getThreadBoundController();
    return threadBoundController.isPresent() && threadBoundController.get() == this;
  }

  @Override
  public int getNumThreads() {
    return numThreads;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy