vertx-web RoutingContext.next() throws unhandled exception if RoutingContext failed.

473 views
Skip to first unread message

akino512

unread,
Jun 18, 2022, 2:14:12 PM6/18/22
to vert.x
I just tried to add a timeout feature to my vert.x web server.
But I found out that RoutingContext.next() throws an unhandled exception if RoutingContext is failed.
I had added failureHandler() to my route,and it catch exception as well when fail() called.But after that, it threw an exception which is the SAME as the one caught by failureHandler when .next() called.

Maybe call .next() is not allowed in failed RoutingContext, but I think it should be a exception to notice developer that this RoutingContext  already failed ,rather than an exception which is  the same as caught by failureHandler .

could some one tell me whether this is a bug or not?

```
package com.example.starter

import io.vertx.core.AbstractVerticle
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext

class MainVerticle : AbstractVerticle() {
fun handlerStart(ctx: RoutingContext) {
println("handlerStart")
ctx.fail(Exception("some error"))//everything alright if no this line
vertx.executeBlocking<Void> {
Thread.sleep(5000)
ctx.next() //throw Exception("some error") if ctx.fail(e) called
}
}

fun handlerEnd(ctx: RoutingContext) {
println("handlerEnd")
if (ctx.response().ended()) return
ctx.end("ok")
}

fun handlerFailure(ctx: RoutingContext) {
println("handlerFailure")
ctx.end(ctx.failure().message)
}

override fun start() {
val router = Router.router(vertx)
router.post().handler(this::handlerStart)
router.post().handler(this::handlerEnd)
router.post().failureHandler(this::handlerFailure)
vertx.createHttpServer()
.requestHandler(router)
.listen(9999)
}
}
```

Thomas SEGISMONT

unread,
Jun 20, 2022, 4:02:07 AM6/20/22
to ve...@googlegroups.com
Just in case you missed it, Vert.x Web provides a TimeoutHandler out of the box:

router.route("/foo/").handler(TimeoutHandler.create(5000));


--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
To view this discussion on the web, visit https://groups.google.com/d/msgid/vertx/97229d96-e841-42fe-8d3a-c7ff5bdcc16dn%40googlegroups.com.

akino512

unread,
Jun 21, 2022, 4:11:39 PM6/21/22
to vert.x
Thank you for your reply,but it seems that it didnt works well as expect.Could you please have a look about this.

Suppose that a vertx server was configured with a timeout(5s) setting ,and a long time(10s) was spent to compute result when request arrives.
Then it should be performed as below.
(1)In 5s,timeout exception will be thrown,and caught by failureHandler().The client get a timeout response.
(2)In 10s,the server get the result but the result would be ignored, because response has already sent.
This is the code:

```
package com.example.starter

import io.vertx.core.AbstractVerticle
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.handler.TimeoutHandler


class MainVerticle : AbstractVerticle() {
  fun handlerStart(ctx: RoutingContext) {
    println("handlerStart")
    vertx.setTimer(5000){
      ctx.fail(Exception("my_timeout_exception"))
    }
    vertx.executeBlocking<Void> {
      Thread.sleep(10000)
      ctx.next()

    }
  }

  fun handlerEnd(ctx: RoutingContext) {
    println("handlerEnd")
    if (ctx.response().ended()) return
    ctx.end("ok")
  }

  fun handlerFailure(ctx: RoutingContext) {
    println("handlerFailure")
    ctx.failure()?.message.also {
      println(it)
    }
    ctx.end("failed")
  }

  fun handlerError(ctx: RoutingContext) {
    println("handlerError")
    ctx.failure()?.message.also {
      println(it)
    }
  }

  override fun start() {
    vertx.exceptionHandler {
      println(it)
    }
    val router = Router.router(vertx)
    router.post().failureHandler(this::handlerFailure)
//    router.post().handler(TimeoutHandler.create(5000))
    router.post().handler(this::handlerStart)
    router.post().handler(this::handlerEnd)
    router.errorHandler(500,this::handlerError)

    vertx.createHttpServer()
      .requestHandler(router)
      .listen(9999)
  }
}
```

Now I run the program on my machine,(1) was worked as expect,but in 10s ,another timeout exception (with same infomation) was thrown,and couldn't caught by failureHandler().
This exception was caused by .next(),and could be caught by errorHandler() which set for the whole router.In other words,customized exception "my_timeout_exception" was thrown twice.
It makes me so confused,not only because calling .next() throws exception,but also the exception is same as the previous.
Maybe vertx was designed that calling .next() is not allowed in failed RoutingContext,But I think it should be a exception with message like "calling next() is not allowed ,RoutingContext already failed"
The logs is  
```
INFO: Succeeded in deploying verticle
handlerStart
handlerFailure
my_timeout_exception// in 5s
handlerError
my_timeout_exception // in 10s
```

I also tried to use TimeoutHandler.create(5000) to handle timeout ,but worse,logs shows that an exception was unhandled.
There is no chance to know what the exception is, because even vertx global exceptionHandler could not catching it.
```
INFO: Succeeded in deploying verticle
handlerStart
handlerFailure
null   // in 5s.TimeoutHandler would not fail the ctx,so failure message is null.
Jun 22, 2022 3:19:57 AM io.vertx.ext.web.RoutingContext
SEVERE: Unhandled exception in router // in 10s
```

vertx version:4.3.1

Thomas SEGISMONT

unread,
Jun 28, 2022, 5:43:42 AM6/28/22
to ve...@googlegroups.com
ctx.next invocation after 10 seconds in handlerStart does not throw an exception.

The router state is evaluated in io.vertx.ext.web.impl.RoutingContextImplBase#iterateNext and, because the routing context is failed, no failure handler is invoked.
Then io.vertx.ext.web.impl.RoutingContextImplBase#checkHandleNoMatch and io.vertx.ext.web.impl.RoutingContextImplBase#unhandledFailure are invoked.

This is why eventully the errorHandler is invoked.

What you do in handlerError is this:

public void handlerError(RoutingContext ctx) {
if (ctx.response().ended()) {
System.out.println("already ended");
return;
}
System.out.println("handlerError");
Throwable failure = ctx.failure();
if (failure != null) {
failure.printStackTrace();
}
}

This Java not Kotlin but you get the idea

akino512

unread,
Jun 30, 2022, 12:38:07 PM6/30/22
to vert.x
Thanks for the code,it works well to avoid this happen,but I just want to figure out why this happen.Now I think I knew why.
I added another failureHandler 'handlerFailure2' and after next() was called, it runs into handlerFailure2 and no extra exception was thrown.
So if ctx failed, call next() will find next failureHandler instead of a normal handler,and if there is no more failureHandler, it throws error.
But I still think it's helpful if the error message could be more clear ,maybe likes 'no more failureHandler for next()'.
Reply all
Reply to author
Forward
0 new messages