Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jersey @ManagedAsync and copying data between HTTP thread and Worker thread

I am working on a project that works in two flavors with and without multi tenancy.

The project exposes a REST service which I would like to be asynchronous. So my basic service looks like

@Component
@Path("/resouce")
@Consumes(MediaType.APPLICATION_JSON)
public class ResouceEndpoint {
    @POST
    @ManagedAsync
    public void add(final Event event, @Suspended final AsyncResponse asyncResponse) {
        resouce.insert (event);
        asyncResponse.resume( Response.status(Response.Status.NO_CONTENT).build());     
    }
}

That works fine without multi tenancy and I get the benefits of the internal Jersey executor service for free. See @ManagedAsync

When I switch to multi tenancy I add a filter on the request that resolve the tenant id and place it on the thread local (in our case the HTTP thread).

When the processing chain hits the "add()" method above the current thread is the one provided by the Jersey executor service, so it does not include my tenant id. I could think only on the following options to work around this issue.

Extend the ResouceEndpoint to MutliTenantResouceEndpoint and drop the @ManagedAsync Using my own thread executor

public class MutliTenantResouceEndpoint extends ResouceEndpoint {
    @POST
    public void add(final Event event, @Suspended final AsyncResponse asyncResponse) {
        final String tenantId = getTeantIdFromThreadLocal();
        taskExecutor.submit(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                setTeantIdToThreadLocal(tenantId);
                browserEventsAnalyzer.insertEvent(event);
                Response response = Response.status(Response.Status.NO_CONTENT).build();
                asyncResponse.resume(response);
                return null;
            }
        });
    }
}

But this way I need to manage my own thread executor and it feel's like I am missing something here. Any suggestion on a different approach?

like image 421
Haim Raman Avatar asked Jun 30 '15 11:06

Haim Raman


1 Answers

Here are a handful of recommendations, in order.

For context, I've been using Jersey for 2 years, and faced this exact problem 18 months ago.

1. Stop using @ManagedAsync

If you have control over the http server that Jersey is running on, I would recommend you stop using @ManagedAsync.

Instead of setting up Jersey to return it's http handling thread immediately and offload real request work to a managed executor service thread, use something like Grizzly for your http server, and configure it to have a larger worker thread pool. This accomplishes the same thing, but pushes the async responsibility down a layer, below Jersey.

You'll run into many pain points over the course of a year if you use @ManagedAsync for any medium-to-large project. Here are some of them off the top of my head:

  • If any ContainerRequestFilter's hits an external service (e.g. an auth filter hits your security module, which hits the database) you will lose the benefits you thought you were gaining
    • If your DB chokes and that auth filter call takes 5 seconds, Jersey hasn't offloaded work to the async thread yet, so your main thread needed to receive a new conn is blocked
  • If you set up logback's MDC in a filter, and you want that context throughout your request, you'll need to set up the MDC again on the managed async thread
  • Resource methods are cryptic to new comers and ugly to read because:
    • they need an extra parameter
    • they return void, hiding their real response type
    • they can "return" anywhere, without any actual return statements
  • Swagger or other API doc tools cannot automatically document async resource endpoints
  • Guice or other DI frameworks may have trouble dealing with certain scope bindings and/or providers in async resource endpoints

2. Use @Context and ContainerRequest properties

This would involve involved calling requestContext.setProperty("tenant_id", tenantId) in your filter, then calling calling requestContext.getProperty("tenant_id") in your resource with a @Context injected request.

3. Use HK2 AOP instead of Jersey filters

This would involve setting up an HK2 binding of InterceptionService which has a MethodInterceptor that checks for managed async resource methods and manually executes all RequestScoped bound ContainerRequestFilters. Instead of your filters being registered with Jersey, you'd register them with HK2, to be run by the method interceptor.

I can add more detail and code samples to options 2/3 if you'd like, or give additional suggestions, but it would first be helpful to see more of your filter code, and I again suggest option 1 if possible.

like image 60
Alden Avatar answered Sep 28 '22 06:09

Alden