Skip to content

Fix thread-safety of RouteImpl #739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 8, 2018
Merged

Fix thread-safety of RouteImpl #739

merged 6 commits into from
Jan 8, 2018

Conversation

visualage
Copy link
Contributor

When multiple concurrent requests is processed by the same route at the same time, due to the fact that the RouteImpl instance is shared across multiple requests, it creates a thread-unsafe situation. The main cause is that actualHandlerIndex and actualFailureHandlerIndex are states of a request, not the state of the route instance itself.
Moved those two indexes into RoutingContext instead since that is the place where the state belonging to a request lives.

When multiple concurrent requests is processed by the same route at the same time, due to the fact that the RouteImpl instance is shared across multiple requests, it creates a thread-unsafe situation. The main cause is that actualHandlerIndex and actualFailureHandlerIndex are states of a request, not the state of the route instance itself.
Moved those two indexes into RoutingContext instead since that is the place where the state belonging to a request lives.
@vietj
Copy link
Contributor

vietj commented Nov 2, 2017

have you observed this issue ?

a route should be single threaded and at most one request at a time should be processed

@visualage
Copy link
Contributor Author

visualage commented Nov 2, 2017 via email

@vietj
Copy link
Contributor

vietj commented Nov 3, 2017

I don't know if the solution is valid or not (I haven't checked) however I'm concerned by the fact that the index seems to be internal and should not be exposed on the RoutingContext interface (i.e the implementation should likely cast to RoutingContextImpl to obtain these)

@visualage
Copy link
Contributor Author

Agree that ideally we should not expose these as part of the public RoutingContext. I did not spend too much time on reading the code to see if it is OK to cast it to RoutingContextImpl or RoutingContextImplBase.
At least this is a problem we need to find a proper fix. Agree?

@visualage
Copy link
Contributor Author

I removed the methods from RoutingContext and moved them into RoutingContextImplBase. Sounds reasonable?

@slinkydeveloper
Copy link
Member

This is related #729

@slinkydeveloper
Copy link
Member

Hi @visualage , I'm the guy that have created this problems 😄 I was working on a fix similar to yours and for me sounds good. I'm writing some tests to check your solution. I update you ASAP

@vietj
Copy link
Contributor

vietj commented Nov 4, 2017

we need tests indeed for this patch

@slinkydeveloper
Copy link
Member

Ok so I've created a pr inside @visualage 's fork. It has tests for:

  • The multiple connections problem
  • The time delayed and multiple connections problem
  • And I've also mixed ok requests with failing ones

I've also switched indexes to AtomicInteger to don't occur in other bugs

Added some tests for multiple handlers
@visualage
Copy link
Contributor Author

@slinkydeveloper, your PR is merged into my fork.

@azagniotov
Copy link

@slinkydeveloper / @vietj is the current fix going to be a part of 3.5.1 release?

@azagniotov
Copy link

Thank you for giving this attention, @slinkydeveloper

@slinkydeveloper
Copy link
Member

I've added another test, for me this pr is ok 😄

@sahlone
Copy link

sahlone commented Jul 28, 2018

@slinkydeveloper Looks like its still not fixed. I am using 3.5.3 with this code asn its still failing with the same error

val server = vertx
                    .createHttpServer()
                server.requestStream().toFlowable().parallel().runOn(rxScheduler).map {
                    router.accept(it.delegate)
                }.sequential().subscribe()

Trace:

j.l.IllegalStateException: Request has already been read
	at i.v.c.h.i.HttpServerRequestImpl.checkEnded(HttpServerRequestImpl.java:438)
	at i.v.c.h.i.HttpServerRequestImpl.handler(HttpServerRequestImpl.java:203)
	at i.v.e.w.i.HttpServerRequestWrapper.handler(HttpServerRequestWrapper.java:39)
	at i.v.e.w.h.i.BodyHandlerImpl.handle(BodyHandlerImpl.java:73)
	at i.v.e.w.h.i.BodyHandlerImpl.handle(BodyHandlerImpl.java:42)
	at i.v.e.w.i.RouteImpl.handleContext(RouteImpl.java:219)
	at i.v.e.w.i.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:120)
	... 14 common frames omitted
Wrapped by: c.a.c.w.ErrorResponse: Internal Server Error
	at c.a.c.w.ResponseHandlingKt.withFailureHandling(ResponseHandling.kt:123)
	at c.a.c.w.ResponseHandlingKt.access$withFailureHandling(ResponseHandling.kt:1)
	at c.a.c.w.DefaultRouterErrorHandler.handle(ResponseHandling.kt:114)
	at c.a.c.w.DefaultRouterErrorHandler.handle(ResponseHandling.kt:112)
	at i.v.e.w.i.RouteImpl.handleFailure(RouteImpl.java:223)
	at i.v.e.w.i.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:117)
	at i.v.e.w.i.RoutingContextImpl.next(RoutingContextImpl.java:133)
	at i.v.e.w.i.RoutingContextImpl.doFail(RoutingContextImpl.java:465)
	at i.v.e.w.i.RoutingContextImpl.fail(RoutingContextImpl.java:166)
	at i.v.e.w.i.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:128)
	at i.v.e.w.i.RoutingContextImpl.next(RoutingContextImpl.java:133)
	at i.v.e.w.i.RouterImpl.accept(RouterImpl.java:79)
	at c.a.s.MasterDataAPIVerticle$2$2.apply(MasterDataAPIVerticle.kt:58)
	at c.a.s.MasterDataAPIVerticle$2$2.apply(MasterDataAPIVerticle.kt:26)
	at i.r.i.o.p.ParallelMap$ParallelMapSubscriber.onNext(ParallelMap.java:113)
	at i.r.i.o.p.ParallelRunOn$RunOnSubscriber.run(ParallelRunOn.java:273)
	at i.v.r.ContextScheduler$ContextWorker$TimedAction.run(ContextScheduler.java:184)
	at i.v.c.i.ContextImpl.lambda$wrapTask$2(ContextImpl.java:339)
	at i.n.u.c.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
	at i.n.u.c.SingleThreadEventExecutor.r..."}

@pmlopes
Copy link
Member

pmlopes commented Jul 29, 2018

Your exception tells that your body handler is too late to process the request body. Body handling must start running on the same call that received the request. Any async call in between will render this exception. Check the manual for this, either call the handler sooner or pause and resume the request on demand.

@sahlone
Copy link

sahlone commented Jul 29, 2018

@pmlopes I think you are right about being late for processing the request. I tried pause and resume methodology but didn't work.
Body handling must start running on the same call that received the request did you mean the same thread ?

The intention is to process the request as a stream in parallel and if i go with vertx standards and use the single thread to read the body then I am not able to scale the server and I dont want to go the way of having multiple verticles.
Can you help me in any way to fix this so I can achive the parallelism ?

I think its still the multi threading issue with router implementation , I say so because if I replace the router with my simple implementation then the above code works fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

6 participants