Skip to content

Latest commit

 

History

History
399 lines (305 loc) · 9.02 KB

File metadata and controls

399 lines (305 loc) · 9.02 KB

Execution Model

Jooby is a flexible performant microframework providing both blocking and non-blocking APIs for building web applications in Java and Kotlin.

In this chapter we are going to learn about Jooby execution model, more specifically:

  • Execute code on the event loop

  • Safely execution of blocking code

  • Working with non-blocking types, like: CompletableFuture, Reactive Streams, Kotlin Coroutines, etc.

Mode

Event Loop

The javadoc:ExecutionMode[EVENT_LOOP] mode allows us to run a route handler from the event loop (a.k.a as non-blocking mode).

Java
import static io.jooby.ExecutionMode.EVENT_LOOP;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/", ctx -> "I'm non-blocking!" );
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App::new);
  }
}
Kotlin
import io.jooby.ExecutionMode.EVENT_LOOP
import io.jooby.Jooby.runApp

fun main(args: Array<String>) {
  runApp(args, EVENT_LOOP) {
    get("/") { "I'm non-blocking!" }
  }
}

The javadoc:ExecutionMode[EVENT_LOOP] mode is the more advanced execution mode and requires you carefully design and implement your application due to that BLOCKING IS NOT ALLOWED

What if you need to block?

The javadoc:Router[dispatch, java.lang.Runnable] operator moves execution to a worker executor which allows to do blocking calls:

Java
import static io.jooby.ExecutionMode.EVENT_LOOP;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/", ctx -> {
      return "I'm non-blocking!";
    });

    dispatch(() -> {
      // All the routes defined here are allowed to block:

      get("/db-list", ctx -> {
        /** Safe to block! */
        Object result = ...; // Remote service, db call, etc..
        return result;
      });

    });
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App::new);
  }
}
Kotlin
import io.jooby.ExecutionMode.EVENT_LOOP
import io.jooby.Jooby.runApp

fun main(args: Array<String>) {
  runApp(args, EVENT_LOOP) {

    get("/") {
      "I'm non-blocking!"
    }

    dispatch {
      // All the routes defined here are allowed to block:

      get("/db-list") {
        /** Safe to block! */
        val result = ...; // Remote service, db call, etc..
        result
      }

    }
  }
}

By default, the javadoc:Router[dispatch, java.lang.Runnable] operator moves execution to the server worker executor (executor provided by web server).

You can provide your own worker executor at application level or at dispatch level:

Java
import static io.jooby.ExecutionMode.EVENT_LOOP;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    // Application level executor
    worker(Executors.newCachedThreadPool());

    // Dispatch to application level executor which is cached thread pool
    dispatch(() -> {
      ...
    });

    // Dispatch to a explicit executor
    Executor cpuIntensive = Executors.newSingleThreadExecutor();
    dispatch(cpuIntesive, () -> {
      ...
    });
  }

  public static void main(String[] args) {
    runApp(args, EVENT_LOOP, App:new);
  }
}
Kotlin
import io.jooby.ExecutionMode.EVENT_LOOP
import io.jooby.Jooby.runApp

fun main(args: Array<String>) {
  runApp(args, EVENT_LOOP) {

    // Application level executor
    worker(Executors.newCachedThreadPool())

    // Dispatch to application level executor which is cached thread pool
    dispatch {
      ...
    }

    // Dispatch to a explicit executor
    Executor cpuIntensive = Executors.newSingleThreadExecutor()
    dispatch(cpuIntesive) {
      ...
    }
  }
}

Worker

The javadoc:ExecutionMode[WORKER] mode allows us to do blocking calls from a route handler (a.k.a blocking mode). You just write code without worrying about blocking calls.

Java
import static io.jooby.ExecutionMode.WORKER;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/", ctx -> {
      /** Safe to block! */
      Object result = // Remote service, db call, etc..
      return result;
    });
  }

  public static void main(String[] args) {
    runApp(args, WORKER, App::new);
  }
}
Kotlin
import io.jooby.ExecutionMode.WORKER
import io.jooby.Jooby.runApp

fun main(args: Array<String>) {
  runApp(args, WORKER) {

    get("/") {
      /** Safe to block! */
      val result = ...;// Remote service, db call, etc..
      result
    }
  }
}

Like with javadoc:ExecutionMode[EVENT_LOOP] mode, you can provide your own worker executor:

Java
import static io.jooby.ExecutionMode.WORKER;
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    worker(Executors.newCachedThreadPool());

    get("/", ctx -> {
      /** Safe to block from cached thread pool! */
      Object result = // Remote service, db call, etc..
      return result;
    });
  }

  public static void main(String[] args) {
    runApp(args, WORKER, App::new);
  }
}
Kotlin
import io.jooby.ExecutionMode.WORKER
import io.jooby.Jooby.runApp

fun main(args: Array<String>) {
  runApp(args, WORKER) {

    worker(Executors.newCachedThreadPool())

    get("/") {
      /** Safe to block from cached thread pool! */
      val result = ...;// Remote service, db call, etc..
      result
    }
  }
}
Note

While running in javadoc:ExecutionMode[WORKER] mode, Jooby internally does the dispatch call to the worker executor. This is done per route, not globally.

Default

The javadoc:ExecutionMode[DEFAULT] execution mode is a mix between javadoc:ExecutionMode[WORKER] and javadoc:ExecutionMode[EVENT_LOOP] modes. This (as name implies) is the default execution mode in Jooby.

Jooby detects the route response type and determines which execution mode fits better.

If the response type is considered non-blocking, then it uses the event loop. Otherwise, it uses the worker executor.

A response type is considered non-blocking when route handler produces:

Java
import static io.jooby.Jooby.runApp;

public class App extends Jooby {

  {
    get("/non-blocking", ctx -> {
      return CompletableFuture
          .supplyAsync(() -> "I'm non-blocking!")  // (1)
    });

    get("/blocking", ctx -> {
      return "I'm blocking";                       // (2)
    });
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
Kotlin
import io.jooby.Jooby.runApp

fun main(args: Array<String>) {
  runApp(args) {
    get("/non-blocking") {
      CompletableFuture
          .supplyAsync { "I'm non-blocking!" }  // (1)
    }

    get("/blocking") {
      "I'm blocking"                            // (2)
    }
  }
}
  1. CompletableFuture is a non-blocking type, run in event loop

  2. String is a blocking type, run in worker executor

Tip

You are free to use non-blocking types in all the other execution mode too. Non-blocking response types are not specific to the default mode execution. All the default mode does with them is to dispatch or not to a worker executor.

Worker Executor

This section described some details about the default worker executor provided by web server. The worker executor is used when:

  • Application mode was set to javadoc:ExecutionMode[WORKER]

  • Application mode was set to javadoc:ExecutionMode[EVENT_LOOP] and there is a javadoc:Router[dispatch, java.lang.Runnable] call

Each web server provides a default worker executor:

  • Netty: The javadoc:netty.Netty[text=Netty server] implementation multiply the number of available processors (with a minimum of 2) by 8.

workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8

For example 8 cores gives us 64 worker threads.

  • Undertow: The javadoc:utow.Utow[text=Undertow server] implementation multiply the number of available processors (with a minimum of 2) by 8.

workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8

For example 8 cores gives us 64 worker threads.

  • Jetty: The javadoc:jetty.Jetty[text=Jetty server] implementation uses the default configuration with 200 worker threads.

These are sensible defaults suggested by the server implementation. If you need to increase/decrease worker threads:

Java
{
  configureServer(server -> {
    server.workerThreads(Number);
  });
}
Kotlin
{
  configureServer { server ->
    server.workerThreads(Number)
  }
}