Описание проблемы
Servlet-3.0 API позволяет отделить контекст запроса/ответа и ответить на него позже.
Однако, если я попытаюсь написать большой объем данных, что-то вроде:
AsyncContext ac = getWaitingContext() ;
ServletOutputStream out = ac.getResponse().getOutputStream();
out.print(some_big_data);
out.flush()
Он может блокировать - и он блокирует тривиальные тестовые примеры - как для Tomcat 7, так и для Jetty 8. Учебники рекомендуют создать пул потоков, который обрабатывать такую настройку - ведьма обычно является контрположительной для традиционной архитектуры 10K.
Однако, если у меня есть 10 000 открытых подключений и пул потоков, пусть говорят 10 потоков, достаточно даже для 1% клиентов, имеющих низкоскоростные соединения или просто заблокированные соединение для блокировки пула потоков и полностью блокировать реакцию кометы или значительно замедлить его.
Ожидаемая практика заключается в получении уведомления о готовности к записи или уведомлении о завершении ввода-вывода и чем продолжать нажимать данные.
Как это можно сделать с помощью API Servlet-3.0, то есть как я могу получить:
- Уведомление о асинхронном завершении операции ввода-вывода.
 - Получите неблокирующий ввод-вывод с уведомлением о готовности.
 
Если это не поддерживается API-интерфейсом Servlet-3.0, существуют ли какие-либо API-интерфейсы для веб-сервера (например, Jetty Continuation или Tomcat CometEvent), которые позволяют обрабатывать такие события по-настоящему асинхронно, не создавая асинхронный ввод-вывод с использованием пула потоков.
Кто-нибудь знает?
И если это невозможно, вы можете подтвердить его ссылкой на документацию?
Демонстрация проблемы в примерном коде
Я добавил код ниже, который эмулирует поток событий.
Примечания:
-  он использует 
ServletOutputStream, который бросаетIOExceptionдля обнаружения отключенных клиентов -  отправляет сообщения 
keep-alive, чтобы убедиться, что клиенты все еще там - Я создал пул потоков для "эмулирования" асинхронных операций.
 
В таком примере я явно определил пул потоков размером 1, чтобы показать проблему:
- Запустить приложение
 -  Запуск от двух терминалов 
curl http://localhost:8080/path/to/app(дважды) -  Теперь отправьте данные с помощью 
curd -d m=message http://localhost:8080/path/to/app - Оба клиента получили данные
 -  Теперь приостановите действие одного из клиентов (Ctrl + Z) и отправьте сообщение еще раз 
curd -d m=message http://localhost:8080/path/to/app - Обратите внимание, что другой не приостановленный клиент либо ничего не получил, либо после того, как сообщение было передано, перестали получать запросы keep-alive, потому что другой поток заблокирован.
 
Я хочу решить такую проблему без использования пула потоков, потому что с 1000-5000 открытыми соединения. Я могу очень быстро исчерпать поток.
Пример кода ниже.
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
import javax.servlet.AsyncContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletOutputStream;
@WebServlet(urlPatterns = "", asyncSupported = true)
public class HugeStreamWithThreads extends HttpServlet {
    private long id = 0;
    private String message = "";
    private final ThreadPoolExecutor pool = 
        new ThreadPoolExecutor(1, 1, 50000L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
        // it is explicitly small for demonstration purpose
    private final Thread timer = new Thread(new Runnable() {
        public void run()
        {
            try {
                while(true) {
                    Thread.sleep(1000);
                    sendKeepAlive();
                }
            }
            catch(InterruptedException e) {
                // exit
            }
        }
    });
    class RunJob implements Runnable {
        volatile long lastUpdate = System.nanoTime();
        long id = 0;
        AsyncContext ac;
        RunJob(AsyncContext ac) 
        {
            this.ac = ac;
        }
        public void keepAlive()
        {
            if(System.nanoTime() - lastUpdate > 1000000000L)
                pool.submit(this);
        }
        String formatMessage(String msg)
        {
            StringBuilder sb = new StringBuilder();
            sb.append("id");
            sb.append(id);
            for(int i=0;i<100000;i++) {
                sb.append("data:");
                sb.append(msg);
                sb.append("\n");
            }
            sb.append("\n");
            return sb.toString();
        }
        public void run()
        {
            String message = null;
            synchronized(HugeStreamWithThreads.this) {
                if(this.id != HugeStreamWithThreads.this.id) {
                    this.id = HugeStreamWithThreads.this.id;
                    message = HugeStreamWithThreads.this.message;
                }
            }
            if(message == null)
                message = ":keep-alive\n\n";
            else
                message = formatMessage(message);
            if(!sendMessage(message))
                return;
            boolean once_again = false;
            synchronized(HugeStreamWithThreads.this) {
                if(this.id != HugeStreamWithThreads.this.id)
                    once_again = true;
            }
            if(once_again)
                pool.submit(this);
        }
        boolean sendMessage(String message) 
        {
            try {
                ServletOutputStream out = ac.getResponse().getOutputStream();
                out.print(message);
                out.flush();
                lastUpdate = System.nanoTime();
                return true;
            }
            catch(IOException e) {
                ac.complete();
                removeContext(this);
                return false;
            }
        }
    };
    private HashSet<RunJob> asyncContexts = new HashSet<RunJob>();
    @Override
    public void init(ServletConfig config) throws ServletException
    {
        super.init(config);
        timer.start();
    }
    @Override
    public void destroy()
    {
        for(;;){
            try {
                timer.interrupt();
                timer.join();
                break;
            }
            catch(InterruptedException e) {
                continue;
            }
        }
        pool.shutdown();
        super.destroy();
    }
    protected synchronized void removeContext(RunJob ac)
    {
        asyncContexts.remove(ac);
    }
    // GET method is used to establish a stream connection
    @Override
    protected synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Content-Type header
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("utf-8");
        // Access-Control-Allow-Origin header
        response.setHeader("Access-Control-Allow-Origin", "*");
        final AsyncContext ac = request.startAsync();
        ac.setTimeout(0);
        RunJob job = new RunJob(ac);
        asyncContexts.add(job);
        if(id!=0) {
            pool.submit(job);
        }
    }
    private synchronized void sendKeepAlive()
    {
        for(RunJob job : asyncContexts) {
            job.keepAlive();
        }
    }
    // POST method is used to communicate with the server
    @Override
    protected synchronized void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException 
    {
        request.setCharacterEncoding("utf-8");
        id++;
        message = request.getParameter("m");        
        for(RunJob job : asyncContexts) {
            pool.submit(job);
        }
    }
}
В приведенном выше примере используются потоки для предотвращения блокировки... Однако, если количество блокирующих клиентов больше, чем размер пула потоков, он блокирует.
Как это можно реализовать без блокировки?