Похоже, не существует обобщенной концепции обработчика исключений для всех обработчиков исходящих обработчиков, который будет отлавливать ошибки независимо от того, где.Это означает, что если вы не зарегистрировали прослушиватель для обнаружения определенной ошибки, ошибка времени выполнения, вероятно, приведет к ее «проглатыванию», в результате чего вы почесываете голову, почему ничего не возвращается.
Тем не менее, возможно, этоне имеет смысла иметь обработчик / прослушиватель, который всегда будет выполняться при наличии ошибки (поскольку это должно быть очень общим), но он делает ошибки регистрации немного сложнее, чем нужно.
После записи куча обучающих тестов (которые я предлагаю проверить!) В итоге я получил эти идеи, которые в основном являются именами моих тестов JUnit (после некоторых манипуляций с регулярными выражениями):
- слушатель может записать в канал после завершения родительской записи
- слушатель записи может удалить слушателей из конвейера и записать ошибочную запись
- все слушатели вызываютсяв случае успеха, если то же обещание передано
- , обработчик ошибок рядом с хвостом не может отловить ошибку от обработчика ближе* head
- netty не вызывает следующие обработчики записи при исключении времени выполнения
- netty вызывает прослушиватель записи один раз при обычной записи
- netty вызывает прослушиватель записи один раз в ошибочномwrite
- netty вызывает следующие обработчики write с написанным сообщением
- обещания могут быть использованы для прослушивания следующих обработчиков успеха или неудачи
- обещания могут использоваться для прослушивания не немедленныхрезультат обработчиков, если обещание передано
- обещания не могут быть использованы для прослушивания результата, не являющегося непосредственным обработчиком, если новое обещание передано
- обещания не могут использоваться для прослушивания результата не немедленного обработчика, еслиобещание не передается
- , только слушатель, добавленный к окончательной записи, вызывается при ошибке, если обещание не передается
- , только слушатель, добавленный к окончательной записи, вызывается при успехе, еслиобещание не передается
- запись слушателей вызывается из хвоста
ThisНа примере, приведенном в вопросе, понимание означает, что если ошибка возникает около хвоста и authHandler
не передает обещание, то обработчик ошибок рядом с головой никогда не будет вызываться, так какпредоставляется новое обещание, так как ctx.write(msg)
по существу ctx.channel.write(msg, newPromise())
.
В нашей ситуации мы в итоге решили ситуацию, внедрив одинаковую совместную обработку ошибок между всеми обработчиками бизнес-логики.
Обработчик выглядел следующим образом
@ChannelHandler.Sharable
class OutboundErrorHandler extends ChannelOutboundHandlerAdapter {
private final static Logger logger = LoggerFactory.getLogger(OutboundErrorHandler.class);
private Throwable handledCause = null;
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ctx.write(msg, promise).addListener(writeResult -> handleWriteResult(ctx, writeResult));
}
private void handleWriteResult(ChannelHandlerContext ctx, Future<?> writeResult) {
if (!writeResult.isSuccess()) {
final Throwable cause = writeResult.cause();
if (cause instanceof ClosedChannelException) {
// no reason to close an already closed channel - just ignore
return;
}
// Since this handler is shared and added multiple times
// we need to avoid spamming the logs N number of times for the same error
if (handledCause == cause) return;
handledCause = cause;
logger.error("Uncaught exception on write!", cause);
// By checking on channel writability and closing the channel after writing the error message,
// only the first listener will signal the error to the client
final Channel channel = ctx.channel();
if (channel.isWritable()) {
ctx.writeAndFlush(serverErrorJSON(cause.getMessage()), channel.newPromise());
ctx.close();
}
}
}
}
Тогда в нашей настройке конвейера мы имеем это
// Prepend the error handler to every entry in the pipeline.
// The intention behind this is to have a catch-all
// outbound error handler and thereby avoiding the need to attach a
// listener to every ctx.write(...).
final OutboundErrorHandler outboundErrorHandler = new OutboundErrorHandler();
for (Map.Entry<String, ChannelHandler> entry : pipeline) {
pipeline.addBefore(entry.getKey(), entry.getKey() + "#OutboundErrorHandler", outboundErrorHandler);
}