NullPointerException in ServerSentEmitter.send()
Hello. In a small number of cases, calling sse.send() throws such an exception. I can't reproduce this in tests, but in production this situation happens 1-2 times an hour. Seems like this is a bug of Undertow and not Jooby, but can you please take a look at it)
[2021-11-15 19:47:03,609]-[worker task-1] ERROR my.project.sse - POST /abc 500 Server Error
java.lang.NullPointerException: Cannot invoke "io.undertow.io.IoCallback.onComplete(io.undertow.server.HttpServerExchange, io.undertow.io.Sender)" because "callback" is null
at io.undertow.io.AsyncSenderImpl.invokeOnComplete(AsyncSenderImpl.java:435)
at io.undertow.io.AsyncSenderImpl.send(AsyncSenderImpl.java:192)
at io.jooby.internal.utow.UtowSeverSentEmitter.send(UtowSeverSentEmitter.java:50)
at my.project.sse$1$6.invoke(MyServer.kt:532)
at my.project.sse$1$6.invoke(MyServer.kt:457)
at io.jooby.Kooby$route$1.apply(Kooby.kt:245)
at io.jooby.Route$Before.lambda$then$e67b40fd$1(Route.java:134)
at io.jooby.internal.handler.SendDirect.apply(SendDirect.java:22)
at io.jooby.internal.handler.WorkerHandler.lambda$apply$0(WorkerHandler.java:23)
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1449)
at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
at java.base/java.lang.Thread.run(Thread.java:832)
Unfortuanetely, same error on 2.12.0 with Undertow - 2.2.14
Here are some additions after updating the server to Jooby 2.16.1. I created and use the following function to send SSE messages:
inline fun ServerSentEmitter.trySend(data: ServerSentMessage) {
if (!this.isOpen) return
try {
this.send(data)
} catch (e: Exception) {
println("SSE send error 1: ${this.context.header("token").value("")} isOpen: ${this.isOpen} ${e.message} ${e::class.java}")
try {
this.send(data)
println("SSE send retry success: ${this.context.header("token").value("")}")
} catch (e1: Exception) {
println("SSE send error 2: ${this.context.header("token").value("")} isOpen: ${this.isOpen} ${e.message} ${e::class.java}")
}
}
}
I get the following statistics for 64h server uptime: SSE send error 1 - 3599 times SSE send retry success - 154 times SSE send error 2 - 3445 times
Example of error:
SSE send error 1: *** isOpen: true UT000043: Data is already being sent. You must wait for the completion callback to be be invoked before calling send() again class java.lang.IllegalStateException
SSE send error 1: *** isOpen: true Cannot invoke "io.undertow.io.IoCallback.onComplete(io.undertow.server.HttpServerExchange, io.undertow.io.Sender)" because "callback" is null class java.lang.NullPointerException
+
SSE send error 1: *** isOpen: true null class java.lang.NullPointerException //I think message is null here because of JVM optimization for frequent stacktrace.
= 154 times. 100% success retry for this type of exception, 0% - for IllegalStateException.
The total number of events sent is approximately 203000, with errors occurring 1.8% of the time. I have noticed that the number of errors increases significantly when you need to send a large number of events one after another (or near parallel). The number of simultaneously connected clients is 400-600. Is there anything I can do to help figure out the cause of the errors?