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

ratpack.exec.internal.DefaultExecControl 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.collect.Lists;
import io.netty.channel.EventLoop;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import ratpack.exec.*;
import ratpack.func.Action;
import ratpack.func.NoArgAction;
import ratpack.registry.RegistrySpec;
import ratpack.stream.Streams;
import ratpack.stream.TransformablePublisher;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import static ratpack.func.Action.noop;

public class DefaultExecControl implements ExecControl {

  private static final Logger LOGGER = ExecutionBacking.LOGGER;
  private static final Action LOG_UNCAUGHT = t -> LOGGER.error("Uncaught execution exception", t);
  private static final int MAX_ERRORS_THRESHOLD = 5;

  private final ExecController execController;
  private final ThreadLocal threadBinding = new ThreadLocal<>();

  public DefaultExecControl(ExecController execController) {
    this.execController = execController;
  }

  private ExecutionBacking getBacking() {
    ExecutionBacking executionBacking = threadBinding.get();
    if (executionBacking == null) {
      throw new ExecutionException("Current thread (" + Thread.currentThread().getName() + ") has no bound execution.");
    } else {
      return executionBacking;
    }
  }

  @Override
  public Execution getExecution() {
    return getBacking().getExecution();
  }

  @Override
  public ExecController getController() {
    return execController;
  }

  @Override
  public void addInterceptor(ExecInterceptor execInterceptor, NoArgAction continuation) throws Exception {
    ExecutionBacking backing = getBacking();
    backing.getInterceptors().add(execInterceptor);
    backing.intercept(ExecInterceptor.ExecType.COMPUTE, Collections.singletonList(execInterceptor), continuation);
  }

  @Override
  public ExecStarter exec() {
    return new ExecStarter() {
      private Action onError = LOG_UNCAUGHT;
      private Action onComplete = noop();
      private Action registry;
      private EventLoop eventLoop = execController.getEventLoopGroup().next();

      @Override
      public ExecStarter eventLoop(EventLoop eventLoop) {
        this.eventLoop = eventLoop;
        return this;
      }

      @Override
      public ExecStarter onError(Action onError) {
        List seen = Lists.newLinkedList();
        this.onError = t -> {
          if (seen.size() < MAX_ERRORS_THRESHOLD) {
            seen.add(t);
            onError.execute(t);
          } else {
            seen.forEach(t::addSuppressed);
            LOGGER.error("Error handler " + onError + "reached maximum error threshold (might be caught in an error loop)", t);
          }
        };
        return this;
      }

      @Override
      public ExecStarter onComplete(Action onComplete) {
        this.onComplete = onComplete;
        return this;
      }

      @Override
      public ExecStarter register(Action registry) {
        this.registry = registry;
        return this;
      }

      @Override
      public void start(Action action) {
        Optional startTrace = ExecutionBacking.TRACE ? Optional.of(Thread.currentThread().getStackTrace()) : Optional.empty();

        Action effectiveAction = registry == null ? action : Action.join(registry, action);
        if (eventLoop.inEventLoop() && threadBinding.get() == null) {
          new ExecutionBacking(execController, eventLoop, startTrace, threadBinding, effectiveAction, onError, onComplete);
        } else {
          eventLoop.submit(() -> new ExecutionBacking(execController, eventLoop, startTrace, threadBinding, effectiveAction, onError, onComplete));
        }
      }
    };
  }

  @Override
  public  Promise promise(Action> action) {
    return directPromise(SafeFulfiller.wrapping(this::getBacking, action));
  }

  @Override
  public  Promise blocking(final Callable blockingOperation) {
    final ExecutionBacking backing = getBacking();
    return directPromise(f ->
        backing.streamSubscribe((streamHandle) -> CompletableFuture.supplyAsync(() -> {
            List> holder = Lists.newArrayListWithCapacity(1);
            try {
              backing.intercept(ExecInterceptor.ExecType.BLOCKING, backing.getInterceptors(), () ->
                  holder.add(0, Result.success(blockingOperation.call()))
              );
              return holder.get(0);
            } catch (Exception e) {
              return Result.failure(e);
            }
          }, execController.getBlockingExecutor()
        ).thenAcceptAsync(v -> streamHandle.complete(() -> f.accept(v)), backing.getEventLoop()))
    );
  }

  private  Promise directPromise(Consumer> action) {
    return new DefaultPromise<>(this::getBacking, action);
  }

  public  TransformablePublisher stream(Publisher publisher) {
    return Streams.transformable(subscriber -> {
      final ExecutionBacking executionBacking = getBacking();
      executionBacking.streamSubscribe((handle) ->
          publisher.subscribe(new Subscriber() {
            @Override
            public void onSubscribe(final Subscription subscription) {
              handle.event(() ->
                subscriber.onSubscribe(subscription)
              );
            }

            @Override
            public void onNext(final T element) {
              handle.event(() -> subscriber.onNext(element));
            }

            @Override
            public void onComplete() {
              handle.complete(subscriber::onComplete);
            }

            @Override
            public void onError(final Throwable cause) {
              handle.complete(() -> subscriber.onError(cause));
            }
          })
      );
    });
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy