Поймите, как сделать Non-blocking IO?

Я использую Undertow для создания простого приложения.

public class App {
    public static void main(String[] args) {
        Undertow server = Undertow.builder().addListener(8080, "localhost")
                .setHandler(new HttpHandler() {

                    public void handleRequest(HttpServerExchange exchange) throws Exception {
                        Thread.sleep(5000);
                        exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                        exchange.getResponseSender().send("Hello World");
                    }

                }).build();
        server.start();
    }
}

Я открываю вкладку браузера на localhost:8080, и я открываю вторую  вкладку также на localhost:8080

В этот раз первая вкладка будет ждать 5 секунд, а вторая будет ждать 10 секунд

Почему это так?

Ответ 1

HttpHandler выполняется в потоке ввода-вывода. Как указано в документации:

IO-потоки выполняют задачи без блокировки, а никогда не должны выполнять операции блокировки, потому что они несут ответственность за несколько соединений, поэтому, когда операция блокирует другие соединения, они будут зависать. Один IO-поток на ядре процессора является разумным по умолчанию.

В документах жизненного цикла запроса обсуждается, как отправить запрос в рабочий поток:

import io.undertow.Undertow;
import io.undertow.server.*;
import io.undertow.util.Headers;

public class Under {
  public static void main(String[] args) {
    Undertow server = Undertow.builder()
        .addListener(8080, "localhost")
        .setHandler(new HttpHandler() {
          public void handleRequest(HttpServerExchange exchange)
              throws Exception {
            if (exchange.isInIoThread()) {
              exchange.dispatch(this);
              return;
            }
            exchange.getResponseHeaders()
                    .put(Headers.CONTENT_TYPE, "text/plain");
            exchange.getResponseSender()
                    .send("Hello World");
          }
        })
        .build();
    server.start();
  }
}

Я заметил, что вы не обязательно получите один рабочий поток для каждого запроса - когда я установил точку останова на заголовке, я получил один поток на каждого клиента. Есть пробелы как в Undertow, так и в базовых документах XNIO, поэтому я не уверен, что это за намерение.

Ответ 2

Undertow использует NIO, что означает, что один поток обрабатывает все запросы. Если вы хотите выполнять блокирующие операции в обработчике запросов, вам нужно отправить эту операцию в рабочий поток.

В вашем примере вы помещаете поток в режим сна, что означает, что любая обработка запросов укладывается в спящий режим, так как этот поток обрабатывает все запросы.

Однако, даже если вы отправили операцию в рабочий поток и поставили ее на спящий режим, вы все равно увидите проблему блокировки, которую вы упомянули. Это связано с тем, что вы открываете один и тот же URL-адрес на нескольких вкладках в одном браузере. У браузеров есть внутренняя блокировка. Если вы откроете тот же URL-адрес на разных вкладках, второй URL-адрес запустит запрос после завершения первого. Попробуйте любой URL, который вы хотите увидеть сами. Вы можете легко смутить это поведение браузера.

Ответ 3

Проще всего сделать, чтобы обернуть обработчик в BlockingHandler.

import io.undertow.Undertow;
import io.undertow.server.*;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.util.Headers;

public class Under {
    public static void main(String[] args) {
        Undertow server = Undertow.builder()
                .addHttpListener(8080, "localhost")
                .setHandler(new BlockingHandler(new HttpHandler() {
                    public void handleRequest(HttpServerExchange exchange)
                            throws Exception {
                        exchange.getResponseHeaders()
                                .put(Headers.CONTENT_TYPE, "text/plain");
                        exchange.getResponseSender()
                                .send("Hello World");
                    }
                })).build();
        server.start();
    }
}