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

ratpack.exec.internal.ExecutionBacking 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.ImmutableList;
import com.google.common.collect.Iterables;
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 org.slf4j.LoggerFactory;
import ratpack.exec.*;
import ratpack.func.Action;
import ratpack.func.BiAction;
import ratpack.func.Block;
import ratpack.registry.RegistrySpec;
import ratpack.stream.Streams;
import ratpack.stream.TransformablePublisher;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class ExecutionBacking {

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

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

  private final ImmutableList globalInterceptors;
  private final ImmutableList registryInterceptors;
  private List adhocInterceptors;

  // The “stream” must be a concurrent safe collection because stream events can arrive from other threads
  // All other collections do not need to be concurrent safe because they are only accessed on the event loop
  StreamHandle streamHandle;

  private final EventLoop eventLoop;
  private final List closeables = Lists.newArrayList();
  private final BiAction onError;
  private final Action onComplete;

  private volatile boolean done;
  private final Execution execution;

  public ExecutionBacking(
    ExecController controller,
    EventLoop eventLoop,
    ImmutableList globalInterceptors,
    Action registry,
    Action action,
    BiAction onError,
    Action onStart,
    Action onComplete
  ) throws Exception {
    this.eventLoop = eventLoop;
    this.onError = onError;
    this.onComplete = onComplete;
    this.execution = new DefaultExecution(this, eventLoop, controller, closeables);

    registry.execute(execution);
    onStart.execute(execution);

    this.registryInterceptors = ImmutableList.copyOf(execution.getAll(ExecInterceptor.class));
    this.globalInterceptors = globalInterceptors;

    streamHandle = new InitialStreamHandle();
    Deque event = new ArrayDeque<>();
    //noinspection RedundantCast
    event.add((UserCode) () -> action.execute(execution));
    streamHandle.stream.add(event);

    drain();
  }

  private class InitialStreamHandle extends StreamHandle {
    public InitialStreamHandle() {
      super(null);
    }
  }

  public static ExecutionBacking get() throws UnmanagedThreadException {
    return THREAD_BINDING.get();
  }

  public static ExecutionBacking require() throws UnmanagedThreadException {
    ExecutionBacking executionBacking = get();
    if (executionBacking == null) {
      throw new UnmanagedThreadException();
    } else {
      return executionBacking;
    }
  }

  public static  TransformablePublisher stream(Publisher publisher) {
    return Streams.transformable(subscriber -> require().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));
          }
        })
    ));
  }

  public static  Upstream upstream(Upstream upstream) {
    return downstream -> {
      final AtomicBoolean fired = new AtomicBoolean();
      require().streamSubscribe(handle -> {
          try {
            upstream.connect(new Downstream() {
              @Override
              public void error(Throwable throwable) {
                if (!fired.compareAndSet(false, true)) {
                  LOGGER.error("", new OverlappingExecutionException("promise already fulfilled", throwable));
                  return;
                }
                handle.complete(() -> downstream.error(throwable));
              }

              @Override
              public void success(T value) {
                if (!fired.compareAndSet(false, true)) {
                  LOGGER.error("", new OverlappingExecutionException("promise already fulfilled"));
                  return;
                }
                handle.complete(() -> downstream.success(value));
              }

              @Override
              public void complete() {
                if (!fired.compareAndSet(false, true)) {
                  LOGGER.error("", new OverlappingExecutionException("promise already fulfilled"));
                  return;
                }
                handle.complete(downstream::complete);
              }
            });
          } catch (Throwable throwable) {
            if (!fired.compareAndSet(false, true)) {
              LOGGER.error("", new OverlappingExecutionException("promise already fulfilled", throwable));
              return;
            }
            handle.complete(() -> downstream.error(throwable));

          }
        }
      );
    };
  }

  // Marker interface used to detect user code vs infrastructure code, for error handling and interception
  public interface UserCode extends Block {
  }

  public Execution getExecution() {
    return execution;
  }

  public EventLoop getEventLoop() {
    return eventLoop;
  }

  public void addInterceptor(ExecInterceptor interceptor) {
    if (adhocInterceptors == null) {
      adhocInterceptors = Lists.newArrayList();
    }
    adhocInterceptors.add(interceptor);
  }

  public class StreamHandle {
    final StreamHandle parent;
    final Queue> stream = new ConcurrentLinkedQueue<>();

    private StreamHandle(StreamHandle parent) {
      this.parent = parent;
      stream.add(new ArrayDeque<>());
    }

    public void event(UserCode action) {
      streamEvent(action);
    }

    public void complete(UserCode action) {
      //noinspection RedundantCast
      streamEvent((UserCode) () -> {
        streamHandle = parent;
        action.execute();
      });
    }

    public void complete() {
      streamEvent(() -> streamHandle = parent);
    }

    private void streamEvent(Block s) {
      Deque event = new ArrayDeque<>();
      event.add(s);
      stream.add(event);
      drain();
    }
  }

  public void streamSubscribe(Action consumer) {
    if (done) {
      throw new ExecutionException("this execution has completed (you may be trying to use a promise in a cleanup method)");
    }

    if (streamHandle.stream.isEmpty()) {
      streamHandle.stream.add(new ArrayDeque<>());
    }

    streamHandle.stream.element().add(() -> {
      StreamHandle parent = this.streamHandle;
      this.streamHandle = new StreamHandle(parent);
      consumer.execute(this.streamHandle);
    });

    drain();
  }

  public void eventLoopDrain() {
    eventLoop.execute(this::drain);
  }

  private void drain() {
    if (done) {
      return;
    }

    ExecutionBacking threadBoundExecutionBacking = THREAD_BINDING.get();
    if (this.equals(threadBoundExecutionBacking)) {
      return;
    }

    if (!eventLoop.inEventLoop() || threadBoundExecutionBacking != null) {
      if (!done) {
        eventLoop.execute(this::drain);
      }
      return;
    }

    try {
      THREAD_BINDING.set(this);
      while (true) {
        if (streamHandle.stream.isEmpty()) {
          return;
        }

        Block segment = streamHandle.stream.element().poll();
        if (segment == null) {
          streamHandle.stream.remove();
          if (streamHandle.stream.isEmpty()) {
            if (streamHandle.getClass().equals(InitialStreamHandle.class)) {
              done();
              return;
            } else {
              break;
            }
          }
        } else {
          if (segment instanceof UserCode) {
            try {
              intercept(ExecInterceptor.ExecType.COMPUTE, segment);
            } catch (final Throwable e) {
              Deque event = streamHandle.stream.element();
              event.clear();
              event.addFirst(() -> {
                try {
                  onError.execute(execution, e);
                } catch (final Throwable errorHandlerException) {
                  //noinspection RedundantCast
                  streamHandle.stream.element().addFirst((UserCode) () -> {
                    throw errorHandlerException;
                  });
                }
              });
            }
          } else {
            try {
              segment.execute();
            } catch (Exception e) {
              LOGGER.error("Internal Ratpack Error - please raise an issue", e);
            }
          }
        }
      }
    } finally {
      THREAD_BINDING.remove();
    }
  }

  private void intercept(ExecInterceptor.ExecType execType, Block segment) throws Exception {
    Iterator iterator = getAllInterceptors().iterator();
    intercept(execType, iterator, segment);
  }

  public Iterable getAllInterceptors() {
    Iterable interceptors;
    if (adhocInterceptors == null) {
      interceptors = Iterables.concat(globalInterceptors, registryInterceptors);
    } else {
      interceptors = Iterables.concat(globalInterceptors, registryInterceptors, adhocInterceptors);
    }
    return interceptors;
  }

  private void done() {
    done = true;
    try {
      onComplete.execute(getExecution());
    } catch (Throwable e) {
      LOGGER.warn("exception raised during onComplete action", e);
    }

    for (AutoCloseable closeable : closeables) {
      try {
        closeable.close();
      } catch (Throwable e) {
        LOGGER.warn(String.format("exception raised by closeable %s", closeable), e);
      }
    }
  }

  public void intercept(final ExecInterceptor.ExecType execType, final Iterator interceptors, Block action) throws Exception {
    if (interceptors.hasNext()) {
      interceptors.next().intercept(execution, execType, () -> intercept(execType, interceptors, action));
    } else {
      action.execute();
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy