Джерси: закрыть ресурсы ввода-вывода после ответа HTTP

Моя установка. Я создал службу REST (Jersey/Dropwizard), которая выполняет потоковую передачу большого содержимого из базы данных. Во время операции GET служба подключается к базе данных через пул соединений, упаковывает данные в поток и выполняет некоторые преобразования «на лету» для отображения запрошенных данных в различных кодировках (CSV, JSON, ...). Время жизни соединения с базой данных привязано к времени жизни потока, и только когда поток закрывается, соединение с базой данных освобождается.

Потоковое преобразование выполняется классом Encoder, который возвращает StreamingOutput, который затем передается объекту Response. В настоящее время кодировщик обрабатывает закрытие ресурсов, когда поток полностью использован.

Моя проблема. Поскольку StreamingOutput не реализует AutoCloseable, возможны утечки соединения, когда вывод используется лишь частично.

Иногда я наблюдаю, что устаревшие активные соединения накапливаются в пуле соединений, и я подозреваю, что они возникают из-за прерванных HTTP-соединений. Как видно ниже, текущий код обрабатывает исключения, возникающие в блоке try. Что я не могу обработать, так это исключения, возникающие после оператора return, и я не знаю, как прикрепить какие-либо инструкции по закрытию ресурса к объекту Response.

Мой вопрос: как я могу сообщить объекту Response закрыть определенные ресурсы после завершения запроса (регулярно или из-за ошибки)? Или: есть ли лучший способ безопасно закрыть любые связанные ресурсы, когда контекст запроса заканчивается?

 @GET
 //@Produces(...)
 public Response streamData(
        @PathParam("key") String key,
        // ... other params
        ) {

    //decode and validate params
    Stream<Pojo> ps = null;
    try {
        // connect to db and obtain data stream for <key>
        ps = loadData(db, key);
        // apply detailed encoding instrunctions and create a StreamingOutput
        final StreamingOutput stream = Encoder.encodeData(ps, encodingArgs);
        return Response.ok(stream).build();
    } catch (Exception e) {
        closeOnException(ps); // wrapper for ps.close();
        throw e;
    }
 }

person Community    schedule 22.11.2018    source источник
comment
Просто подумав, исходя из моего понимания вашего кода, реальная проблема кажется ссылкой на объект StreamingOutput внутри класса Encode (я не знаю этого класса Encode, возможно, вы его написали). В качестве альтернативного решения вы можете расширить StreamingOutput и создать класс, который также содержит загруженные данные, а Encode — это всего лишь построитель, который не поддерживает ссылки на созданные потоки.   -  person Fabiano Tarlao    schedule 22.11.2018
comment
Таким образом, когда что-то пойдет не так (везде), сборщик мусора может освободить объект, потому что единственная ссылка находится в объекте ответа, который также будет освобожден сборщиком мусора. Возможно ли это в вашем случае? В этом случае я превращу комментарий в ответ.   -  person Fabiano Tarlao    schedule 22.11.2018
comment
@FabianoTarlao: Верно, и это, вероятно, самое надежное решение. Однако такой способ вынуждает меня создавать StreamingOutput вокруг каждого блока логики приложения, который поступает из БД и кодирует.   -  person    schedule 22.11.2018
comment
@FabianoTarlao: вы действительно хотите быстро закрыть ресурсы ввода-вывода и не ждать сборщика мусора. Нет никакой гарантии, что сборщик мусора очистит объекты ответа в ближайшем будущем.   -  person    schedule 22.11.2018
comment
Понятно. Другая идея состоит в том, чтобы вернуть объект, который реализует интерфейс StreamingOutput, возможно, расширяя ваш предыдущий класс, но также обновляя внутреннюю переменную long last_used_time, которую можно периодически проверять классом Encode. Но я не понимаю ваших ограничений, можете ли вы изменить/переписать внутренности классов StreamingOutput и Encode или нет? Я думаю, что это мой последний полезный совет, других идей у ​​меня нет :-) и все эти решения выглядят слишком запутанными ;-)   -  person Fabiano Tarlao    schedule 23.11.2018
comment
... и это именно то, где я застрял ;-)   -  person    schedule 23.11.2018
comment
Добавление переменной last_used_time путем расширения не выглядит очень сложным. Это не красиво и элегантно, но вы сталкиваетесь с конкретными проблемами? примечание: я призываю вас не получать системное время при каждом чтении ;-)   -  person Fabiano Tarlao    schedule 23.11.2018


Ответы (2)


Я получил хороший ответ из списка рассылки dropwizard, который решает мою проблему, и я хочу сослаться на него, если кто-то столкнется с той же проблемой.

https://groups.google.com/forum/#!topic/dropwizard-user/62GoLDBrQuo

Цитата из ответа Шона:

Джерси поддерживает CloseableService, который позволяет регистрировать Closeable объекты, которые будут закрыты, когда запрос завершен:

public Response streamData(..., @Context CloseableService closer) {
  ...
  closer.add(closeable);
  return Response.ok(...).build();
}
person Community    schedule 23.03.2019

Вы можете добавить в свой метод HttpServletResponse:

@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Object streamData(
            @PathParam("key") String key,
            @Context HttpServletResponse response,
            // ... other params
            ) {
  ...
  response.getOutputStream().write(....)
  response.flushBuffer();
  response.getOutputStream().close();
  return null;
}
person xyz    schedule 22.11.2018
comment
Хм, я надеялся на какой-нибудь удобный хук (похожий на Stream.onClose(Autocloseable)), который я мог бы использовать, чтобы избежать шаблонного кода. Вы знаете, как Джерси обрабатывает перенастроенный объект из сигнатуры метода? Его просто выбрасывают? - person ; 22.11.2018
comment
может быть, это будет полезно: / - person xyz; 22.11.2018