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

ratpack.exec.Blocking Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc-1
Show newest version
/*
 * Copyright 2015 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;

import io.netty.channel.EventLoop;
import ratpack.exec.internal.DefaultExecution;
import ratpack.exec.internal.DefaultPromise;
import ratpack.exec.internal.ThreadBinding;
import ratpack.func.Block;
import ratpack.func.Factory;

import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

/**
 * Facilitates working with code that blocks (e.g. synchronous IO)
 */
public abstract class Blocking {

  private Blocking() {
  }

  /**
   * Performs a blocking operation on a separate thread, returning a promise for its value.
   * 

* This method should be used to perform blocking IO, or to perform any operation that synchronously waits for something to happen. * The given factory function will be executed on a thread from a special pool for such operations (i.e. not a thread from the main compute event loop). *

* The operation should do as little computation as possible. * It should just perform the blocking operation and immediately return the result. * Performing computation during the operation will degrade performance. * * @param factory the operation that blocks * @param the type of value created by the operation * @return a promise for the return value of the given blocking operation */ public static Promise get(Factory factory) { return new DefaultPromise<>(downstream -> { DefaultExecution execution = DefaultExecution.require(); EventLoop eventLoop = execution.getEventLoop(); execution.delimit(continuation -> eventLoop.execute(() -> CompletableFuture.supplyAsync( new Supplier>() { Result result; @Override public Result get() { try { DefaultExecution.THREAD_BINDING.set(execution); intercept(execution, execution.getAllInterceptors().iterator(), () -> { try { result = Result.success(factory.create()); } catch (Throwable e) { result = Result.error(e); } }); return result; } catch (Throwable e) { DefaultExecution.interceptorError(e); return result; } finally { DefaultExecution.THREAD_BINDING.remove(); } } }, execution.getController().getBlockingExecutor() ).thenAcceptAsync(v -> continuation.resume(() -> downstream.accept(v)), eventLoop) ) ); }); } /** * Blocks execution waiting for this promise to complete and returns the promised value. *

* This method allows the use of asynchronous API, by synchronous API. * This may occur when integrating with other libraries that are not asynchronous. * The following example simulates using a library that takes a callback that is expected to produce a value synchronously, * but where the production of the value is actually asynchronous. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import ratpack.exec.ExecResult;
   * import ratpack.exec.Blocking;
   * import ratpack.exec.Promise;
   * import ratpack.func.Factory;
   *
   * import static org.junit.Assert.assertEquals;
   *
   * public class Example {
   *   static  T produceSync(Factory factory) throws Exception {
   *     return factory.create();
   *   }
   *
   *   public static void main(String... args) throws Exception {
   *     ExecResult result = ExecHarness.yieldSingle(e ->
   *       Blocking.get(() ->
   *         produceSync(() ->
   *           Blocking.on(Promise.value("foo")) // block and wait for the promised value
   *         )
   *       )
   *     );
   *
   *     assertEquals("foo", result.getValue());
   *   }
   * }
   * }
* *

* Important: this method can only be used inside a {@link Blocking} function. * That is, it can only be used from a Ratpack managed blocking thread. * If it is called on a non Ratpack managed blocking thread it will immediately throw an {@link ExecutionException}. *

* When this method is called, the promise will be subscribed to on a compute thread while the blocking thread waits. * When the promised value has been produced, and the compute thread segment has completed, the value will be returned * allowing execution to continue on the blocking thread. * The following example visualises this flow by capturing the sequence of events via an {@link ExecInterceptor}. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import ratpack.exec.Blocking;
   * import ratpack.exec.Promise;
   * import ratpack.exec.ExecResult;
   * import ratpack.exec.ExecInterceptor;
   *
   * import java.util.List;
   * import java.util.ArrayList;
   * import java.util.Arrays;
   *
   * import static org.junit.Assert.assertEquals;
   *
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *     List events = new ArrayList<>();
   *
   *     ExecHarness.yieldSingle(
   *       r -> r.add(ExecInterceptor.class, (execution, execType, continuation) -> {
   *         events.add(execType + "-start");
   *         try {
   *           continuation.execute();
   *         } finally {
   *           events.add(execType + "-stop");
   *         }
   *       }),
   *       e -> Blocking.get(() -> Blocking.on(Promise.value("foo")))
   *     );
   *
   *     List actualEvents = Arrays.asList(
   *       "COMPUTE-start",
   *       "COMPUTE-stop",
   *         "BLOCKING-start",
   *           "COMPUTE-start",
   *           "COMPUTE-stop",
   *         "BLOCKING-stop",
   *       "COMPUTE-start",
   *       "COMPUTE-stop"
   *     );
   *
   *     assertEquals(actualEvents, events);
   *   }
   * }
   * }
* * @param promise the promise to block on * @param the type of value returned by the promise * @return the promised value * @throws ExecutionException if not called on a Ratpack managed blocking thread * @throws Exception any thrown while producing the value */ public static T on(Promise promise) throws Exception { ThreadBinding.requireBlockingThread("Blocking.on() can only be used while blocking (i.e. use Blocking.get() first)"); DefaultExecution backing = DefaultExecution.require(); CountDownLatch latch = new CountDownLatch(1); AtomicReference> resultReference = new AtomicReference<>(); backing.delimit(continuation -> promise.connect( new Downstream() { @Override public void success(T value) { unlatch(Result.success(value)); } @Override public void error(Throwable throwable) { unlatch(Result.error(throwable)); } @Override public void complete() { unlatch(Result.success(null)); } private void unlatch(Result result) { continuation.resume(() -> { resultReference.set(result); latch.countDown(); }); } } ) ); backing.eventLoopDrain(); latch.await(); return resultReference.get().getValueOrThrow(); } public static Operation op(Block block) { return Blocking.get(() -> { block.execute(); return null; }).operation(); } public static void exec(Block block) { op(block).then(); } private static void intercept(Execution execution, final Iterator interceptors, Runnable runnable) throws Exception { if (interceptors.hasNext()) { interceptors.next().intercept(execution, ExecInterceptor.ExecType.BLOCKING, () -> intercept(execution, interceptors, runnable)); } else { runnable.run(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy