获取JAX-RS AsyncResponse,但稍后暂停

时间:2016-07-21 17:20:09

标签: java multithreading asynchronous jax-rs

考虑以下代码来监听长轮询的更新:

Map<String, List<AsyncResponse>> tagMap = new ConcurrentGoodStuff();

// This endpoint listens for notifications of the tag
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@GET
@Path("listen/{tag}")
public void listenForUpdates(
        @PathParam("tag") final String tag,
        @Suspended final AsyncResponse response) {
    tagMap.get(tag).add(response);
}

// This endpoint is for push-style notifications
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@PUT
@Path("update/{tag}/{value}")
public Response updateTag(
        @PathParam("tag") final String tag,
        @PathParam("value") final String value) {
    for(AsyncResponse response : tagMap.get(tag)) {
        // Resumes all previously suspended responses
        response.resume(value);
    }
    return Response.ok("cool whatever").build();
}

客户端使用普通的Jersey客户端AsyncInvoker添加侦听器,调用异步任务,然后另一个任务调用更新方法。

当我测试时,我遇到了竞争状态。在listenForUpdates()上异步添加侦听器后,我同步地在updateTag()端点上进行更新。但更新在添加侦听器之前运行,异步响应无法恢复。

对此的解决方案是在将<{em>}添加到侦听器之后调用上的suspend()方法。但鉴于@Suspended提供了一个已经暂停的AsyncResponse对象,目前尚不清楚如何做到这一点。我应该怎么做才能在添加到侦听器之后暂停异步响应?这实际上会调用suspend方法吗?如何使用Jersey async客户端,或者我应该使用不同的长轮询客户端?

对于解决方案,我对不同的图书馆开放,比如Atmosphere或Guava。我不愿意在我的测试中添加Thread.sleep(),因为这是一个等待发生的间歇性故障。

1 个答案:

答案 0 :(得分:0)

我最终使用了RxJava,但在List中使用BlockingQueue而不是Map提出一个同样出色的解决方案之前。它是这样的:

ConcurrentMap<String, BlockingQueue<AsyncResponse>> tagMap = new ConcurrentGoodStuff();

// This endpoint initiates a listener array for the tag.
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@GET
@Path("initListen/{tag}")
public void listenForUpdates(
        @PathParam("tag") final String tag) {
    tagMap.putIfAbsent(tag, new LinkedBlockingQueue<>());
}

// This endpoint listens for notifications of the tag
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@GET
@Path("listen/{tag}")
public void listenForUpdates(
        @PathParam("tag") final String tag,
        @Suspended final AsyncResponse response) {
    BlockingQueue<AsyncResponse> responses = tagMap.get(tag);

    if (responses != null) {
        responses.add(response);
    }
}

// This endpoint is for push-style notifications
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@PUT
@Path("update/{tag}/{value}")
public Response updateTag(
        @PathParam("tag") final String tag,
        @PathParam("value") final String value) {
    BlockingQueue<AsyncResponse> responses = tagMap.get(tag);

    if (responses == null) {
        return Response.noContent().build();
    }
    if (responses.isEmpty()) {
        // Block-wait for an async listener
        try {
            AsyncResponse response = tagMap.poll(15, TimeUnit.SECONDS);

            if (response == null) {
                return Response.noContent().build();
            }

            response.resume(value);
        } catch (InterruptedException e) {
            return Response.noContent().build();
        }
    } else {
        for (AsyncResponse response : responses) {
            // Resumes all previously suspended responses
            response.resume(value);
        }
    }
    return Response.ok("cool whatever").build();
}

我还没有测试过这个确切的代码,但我过去使用过它的某些版本。只要您首先同步调用initListen端点,就可以调用异步listen端点,然后调用同步update端点,并且不会出现任何重要的竞争条件。< / p>

update端点有一点竞争条件,但它是次要的。 responses阻塞队列在迭代时可能变为空,或者可能由多个源以不同方式更新。为了缓解这种情况,我在每个请求实例化的数据结构上使用了drainTo(Collection)方法。这仍然无法解决多个客户端可能尝试更新同一个监听器标签的用例,但我不需要这个用例。