Sometimes I am getting current exception:
[java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ExecutorCompletionService$QueueingFuture#746c196 rejected from java.util.concurrent.ThreadPoolExecutor#5eeac923[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]]
I am calling runAndAwaitResult() function limiting it to 100ms. If results successfully received and ready I am shutting down a ExecuorService if timeout has been exceeded I am taking available results and shutting down service as well. But sometimes I am getting that exception. How should I handle it or what is the best way terminating service to avoid such exception. Note I cannot wait for gentle shutdown I have just 100 ms for both jobs.
try{
generalPersonalisationHandler = new GeneralPersonalisationHandler(resourceStack);
com.company.personalisation.jobs.result.GeneralPersonalisationResult result = timeLimiter.callWithTimeout(new Callable<com.company.personalisation.jobs.result.GeneralPersonalisationResult>() {
#Override
public com.company.personalisation.jobs.result.GeneralPersonalisationResult call() throws Exception {
com.company.personalisation.jobs.result.GeneralPersonalisationResult result = generalPersonalisationHandler.runAndAwaitResults(customerCookie);
generalPersonalisationHandler.shutdown();
return result;
}
}, timeout, TimeUnit.MILLISECONDS, true);
PersonalisationData response = transformAvailableResults(result);
return response;
}
catch (UncheckedTimeoutException e) {
String errorMsg = String.format("TIMEOUT for RunGeneralPersonalisation() execution time has exceeded maximum limit of: [%s] ms", timeout);
ErrorLogger.error(errorMsg, e);
com.company.personalisation.jobs.result.GeneralPersonalisationResult result = generalPersonalisationHandler.returnAvailableResults();
generalPersonalisationHandler.shutdown();
return transformAvailableResults(result);
}
GeneralPersonalisationHandler.java
public class GeneralPersonalisationHandler {
private GeneralPersonalisationResult generalPersonalisationResult;
private final int SUBMITTED_JOBS = 2;
private CompletionService<JobResult> completionService;
private ResourceStack resourceStack;
private ExecutorService executor;
public GeneralPersonalisationHandler(ResourceStack resourceStack){
this.executor = Executors.newFixedThreadPool(SUBMITTED_JOBS);
this.completionService = new ExecutorCompletionService<JobResult>(executor);
this.resourceStack = resourceStack;
}
public GeneralPersonalisationResult runAndAwaitResults(String customerCookie) throws Exception {
Job customerEventsJob = new CustomerEventsJob(customerCookie, resourceStack);
Job customerInfoJob = new CustomerPersonalInfoJob(customerCookie, resourceStack);
completionService.submit(customerInfoJob);
completionService.submit(customerEventsJob);
generalPersonalisationResult = new GeneralPersonalisationResult();
for (int handledJobs = 0; handledJobs < SUBMITTED_JOBS; handledJobs++) {
Future<JobResult> result = completionService.take();
JobResult jobResult = result.get();
if (jobResult instanceof CustomerPersonalInfoJobResult) {
CustomerPersonalInfoJobResult customerPersonalInfoJobResult = (CustomerPersonalInfoJobResult) jobResult;
generalPersonalisationResult.setCustomerPersonalInfoJobResult(customerPersonalInfoJobResult);
}
if (jobResult instanceof CustomerEventsJobResult) {
CustomerEventsJobResult customerEventsJobResult = (CustomerEventsJobResult) jobResult;
generalPersonalisationResult.setCustomerEventsJobResult(customerEventsJobResult);
}
}
}
return generalPersonalisationResult;
}
public GeneralPersonalisationResult returnAvailableResults(){
return this.generalPersonalisationResult;
}
public void shutdown(){
if(!this.executor.isShutdown()) {
this.executor.shutdown();
}
}
}
Thanks for any help!
catch (UncheckedTimeoutException e) {
String errorMsg = String.format("TIMEOUT for RunGeneralPersonalisation() execution time has exceeded maximum limit of: [%s] ms", timeout);
ErrorLogger.error(errorMsg, e);
com.thehutgroup.personalisation.jobs.result.GeneralPersonalisationResult result = generalPersonalisationHandler.returnAvailableResults();
generalPersonalisationHandler.shutdown(); // possible problem !!!
return transformAvailableResults(result);
}
Looks like if UncheckedTimeoutException occurs you shutting down executor service, and after that if you try to add some task, you will have RejectedExecutionException.
Call shutdown only when you sure that any task won't be added.
Related
i'm trying to run a for loop on parrallel using 4 threads and I have to stop all other threads when it gets to an else, basically the code looks like this:
CompletableFuture<ResponseEntity<TramRealTimeResponse>> responseEntityCompletableFuture = null;
ExecutorService pool = Executors.newFixedThreadPool(4);
for (TramRulesDTO rule : tramRulesResponse.getPayload()) {
responseEntityCompletableFuture = CompletableFuture.supplyAsync(() ->
{
logger.info("thread " + Thread.currentThread().getName());
TramRule tramRule = TramRuleFactory.getTramRule(rule.getRuleId());
PartnerRuleDto partnerRuleDto = getPartnerRule(partnerRuleResponse.getPayload(), rule.getRuleId());
if (Objects.nonNull(tramRule)) {
try {
tramRule.doTramLogicAndReturnAction(rule, partnerRuleDto,
paymentDetails, intermediaryDetails, partnerRulesConditions, tramRulesRequest);
} catch (TramRealTimeException ex) {
TramRealTimeResponse response = exceptionHandler.handleTramRealTimeException(ex, partnerRuleDto);
if (response.getStatus().equals(200)) {
return null;
} else {
//Stop all threads
return ResponseEntity.ok(response);
}
}
} else {
return null;
}
return null;
}, pool);
}
ResponseEntity<TramRealTimeResponse> tramRealTimeResponseResponseEntity = responseEntityCompletableFuture.get();
Where is the //Stop all threads comment I need somehow to stop all other threads and retrieve the ResponseEntity object down after the for loop. I've tried with executor service shutDown() and shutDownAll() and it doesn't stop anything
How can I exhaust retries after a number of failed async request calls?
I am using AsyncHttpClient to send requests to our server. In case of request timeouts, connection exceptions, etc., I would like the client to retry N times and throw a custom exception. The calling method should receive this exception or it can be left unhandled.
// calls post
public void call(String data) throws CustomException {
asyncHttpClient.post(data, 10);
}
// posts data to http endpoint
public void post(String data, int retries) throw CustomException {
// if retries are exhausted, throw CustomException to call()
if (retry <= 0) {
throw new CustomException("exc");
}
BoundRequest request = httpClient.preparePost("http_endpoint");
ListenableFuture<Response> responseFuture = httpClient.post(request);
responseFuture.addListener(() -> {
Response response = null;
int status = 0;
try {
response = responseFuture.get();
status = response.getStatusCode();
// HTTP_ACCEPTED = {200, 201, 202}
if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
// ok
} else {
sleep(10);
post(data, retry - 1);
}
} catch (InterruptedException e) {
sleep(10);
post(data, retry - 1);
} catch (ExecutionException e) {
// ConnectionException
// RequestTimeoutException
sleep(10); // 10 seconds
post(data, retry - 1);
} catch (Exception e) {
sleep(10); // 10 seconds
post(data, retry - 1 );
} finally {
responseFuture.done();
}
}, Runnable::run);
}
This approach has a few problems:
uses recursive calls to retry.
the CustomException seems like it is never thrown and after retries == 0, the control goes back to the finally block.
...
} catch (ExecutionException e) {
// ConnectionException
// RequestTimeoutException
sleep(10); // 10 seconds
try {
post(data, retry - 1);
} catch (CustomException e) {
}
}
...
There is a predefined function in the AsyncHttpClient to handle MaxRetries,
The code below shows a simple implementation
AsyncHttpClientConfig cf = new DefaultAsyncHttpClientConfig.Builder().setMaxRequestRetry(5).setKeepAlive(true).build()
final AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(cf);
You can remove the retry logic of yours and let AsyncHttpClient to handle the same.
Alright, so tried to reproduce what you were trying to achieve with your code, but immediately realized that your CustomException only works if it is of type RuntimeException. The reason why is that you want to throw the exception during runtime and in another thread.
The code below shows a simple implementation of the Exception. Keep in mind that not all RuntimeExceptions stop the program. This is explained in this thread. So if you want to terminate the program, you have to manually stop it.
public class CustomException extends RuntimeException {
public CustomException(String msg) {
super(msg);
// print your exception to the console
// optional: exit the program
System.exit(0);
}
}
I changed the remaining of your implementation, so that you don't have to make recursive calls anymore. I removed the callback method and instead call the get() method, which waits for the request to finish. But since I am executing all of this in a separate thread it should be running in the background and not main thread.
public class Main {
private final AsyncHttpClient httpClient;
private final int[] HTTP_ACCEPTED = new int[]{200, 201, 202};
private final static String ENDPOINT = "https://postman-echo.com/post";
public static void main(String[] args) {
String data = "{message: 'Hello World'}";
Main m = new Main();
m.post(data, 10);
}
public Main() {
httpClient = asyncHttpClient();
}
public void post(final String data, final int retry) {
Runnable runnable = () -> {
int retries = retry;
for (int i = 0; i < retry; i++) {
Request request = httpClient.preparePost(ENDPOINT)
.addHeader("Content-Type", "application/json")
.setBody(data)
.build();
ListenableFuture<Response> responseFuture = httpClient.executeRequest(request);
try {
Response response = responseFuture.get();
int status = response.getStatusCode();
if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
System.out.println("Successful! Breaking Loop");
break;
} else {
Thread.sleep(10);
}
} catch (InterruptedException | ExecutionException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
retries--;
}
System.out.println("Remaining retries: " + retries);
if (retries <= 0) {
throw new CustomException("exc");
}
};
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(runnable);
}
}
Alternative
You could use the same Runnable to make asynchronous calls, without having to wait for future.get(). Each listener will conveniently be called in the same thread, which makes it more efficient for your use case.
public void post2(final String data, final int retry) {
Request request = httpClient.preparePost(ENDPOINT)
.addHeader("Content-Type", "application/json")
.setBody(data)
.build();
ListenableFuture<Response> future = httpClient.executeRequest(request);
MyRunnable runnable = new MyRunnable(retry, future, request);
future.addListener(runnable, null);
}
public class MyRunnable implements Runnable {
private int retries;
private ListenableFuture<Response> responseFuture;
private final Request request;
public MyRunnable(int retries, ListenableFuture<Response> future, Request request) {
this.retries = retries;
this.responseFuture = future;
this.request = request;
}
#Override
public void run() {
System.out.println("Remaining retries: " + this.retries);
System.out.println("Thread ID: " + Thread.currentThread().getId());
try {
Response response = responseFuture.get();
int status = response.getStatusCode();
if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
System.out.println("Success!");
//do something here
} else if (this.retries > 0) {
Thread.sleep(10);
this.execute();
} else {
throw new CustomException("Exception!");
}
} catch (InterruptedException | ExecutionException e) {
this.execute();
}
}
private void execute() {
this.retries -= 1;
this.responseFuture = httpClient.executeRequest(this.request);
this.responseFuture.addListener(this, null);
}
}
Lets consider following code:
Client code:
public class MyClient {
private final MyClientSideService myClientSideService;
public MyClient(MyClientSideService myClientSideService) {
this.myClientSideService = myClientSideService;
}
public String requestRow(Integer req) {
return myClientSideService.requestSingleRow(req);
}
}
Client side service:
public class MyClientSideService {
private final MyServerSideService myServerSideService;
public MyClientSideService(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
}
public String requestSingleRow(int req) {
return myServerSideService.requestRowBatch(Arrays.asList(req)).get(0);
}
}
Server side service:
#Slf4j
public class MyServerSideService {
//single threaded bottleneck service
public synchronized List<String> requestRowBatch(List<Integer> batchReq) {
log.info("Req for {} started");
try {
Thread.sleep(100);
return batchReq.stream().map(String::valueOf).collect(Collectors.toList());
} catch (InterruptedException e) {
return null;
} finally {
log.info("Req for {} finished");
}
}
}
And main:
#Slf4j
public class MainClass {
public static void main(String[] args) {
MyClient myClient = new MyClient(new MyClientSideService(new MyServerSideService()));
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int m = 0; m < 100; m++) {
int k = m;
log.info("Response is {}", myClient.requestRow(k));
}
}).start();
}
}
}
According the logs it takes approximately 4 min 22 sec but it too much. Ithink it might be improved dramatically. I would like to implement implicit batching. So MyClientSideService should collect requests and when it becomes 50(it is preconfigured batch size) or some preconfigured timeout expired then to request MyServerSideService and back route result to the clients. Protocol should be synchronous so clients must be blocked until result getting.
I tried to write code using CountDownLatches and CyclicBarriers but my attempts were far from success.
How can I achieve my goal?
P.S.
If to replace requestRowBatch return type List<String> from to Map<Integer, String> to delegate request and response mapping to server following works with limititations. It works only if I send <=25 requests
#Slf4j
public class MyClientSideService {
private final Integer batchSize = 25;
private final Integer maxTimeoutMillis = 5000;
private final MyServerSideService myServerSideService;
private final Queue<Integer> queue = new ArrayBlockingQueue(batchSize);
private final Map<Integer, String> responseMap = new ConcurrentHashMap();
private final AtomicBoolean started = new AtomicBoolean();
private CountDownLatch startBatchRequestLatch = new CountDownLatch(batchSize);
private CountDownLatch awaitBatchResponseLatch = new CountDownLatch(1);
public MyClientSideService(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
}
public String requestSingleRow(int req) {
queue.offer(req);
if (!started.compareAndExchange(false, true)) {
log.info("Start batch collecting");
startBatchCollecting();
}
startBatchRequestLatch.countDown();
try {
log.info("Awaiting batch response latch for {}...", req);
awaitBatchResponseLatch.await();
log.info("Finished awaiting batch response latch for {}...", req);
return responseMap.get(req);
} catch (InterruptedException e) {
e.printStackTrace();
return "EXCEPTION";
}
}
private void startBatchCollecting() {
new Thread(() -> {
try {
log.info("Await startBatchRequestLatch");
startBatchRequestLatch.await(maxTimeoutMillis, TimeUnit.MILLISECONDS);
log.info("await of startBatchRequestLatch finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
responseMap.putAll(requestBatch(queue));
log.info("Releasing batch response latch");
awaitBatchResponseLatch.countDown();
}).start();
}
public Map<Integer, String> requestBatch(Collection<Integer> requestList) {
return myServerSideService.requestRowBatch(requestList);
}
}
Update
According Malt answer I was able to develop following:
#Slf4j
public class MyClientSideServiceCompletableFuture {
private final Integer batchSize = 25;
private final Integer maxTimeoutMillis = 5000;
private final MyServerSideService myServerSideService;
private final Queue<Pair<Integer, CompletableFuture>> queue = new ArrayBlockingQueue(batchSize);
private final AtomicInteger counter = new AtomicInteger(0);
private final Lock lock = new ReentrantLock();
public MyClientSideServiceCompletableFuture(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
}
public String requestSingleRow(int req) {
CompletableFuture<String> future = new CompletableFuture<>();
lock.lock();
try {
queue.offer(Pair.of(req, future));
int counter = this.counter.incrementAndGet();
if (counter != 0 && counter % batchSize == 0) {
log.info("request");
List<Integer> requests = queue.stream().map(p -> p.getKey()).collect(Collectors.toList());
Map<Integer, String> serverResponseMap = requestBatch(requests);
queue.forEach(pair -> {
String response = serverResponseMap.get(pair.getKey());
CompletableFuture<String> value = pair.getValue();
value.complete(response);
});
queue.clear();
}
} finally {
lock.unlock();
}
try {
return future.get();
} catch (Exception e) {
return "Exception";
}
}
public Map<Integer, String> requestBatch(Collection<Integer> requestList) {
return myServerSideService.requestRowBatch(requestList);
}
}
But it doesn't work if size is not multiple of batch size
If to replace requestRowBatch return type from List<String> with Map<Integer, String> to delegate request and response mapping to server I was able to crete following solution:
#Slf4j
public class MyClientSideServiceCompletableFuture {
private final Integer batchSize = 25;
private final Integer timeoutMillis = 5000;
private final MyServerSideService myServerSideService;
private final BlockingQueue<Pair<Integer, CompletableFuture>> queue = new LinkedBlockingQueue<>();
private final Lock lock = new ReentrantLock();
private final Condition requestAddedCondition = lock.newCondition();
public MyClientSideServiceCompletableFuture(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
startQueueDrainer();
}
public String requestSingleRow(int req) {
CompletableFuture<String> future = new CompletableFuture<>();
while (!queue.offer(Pair.of(req, future))) {
log.error("Can't add {} to the queue. Retrying...", req);
}
lock.lock();
try {
requestAddedCondition.signal();
} finally {
lock.unlock();
}
try {
return future.get();
} catch (Exception e) {
return "Exception";
}
}
private void startQueueDrainer() {
new Thread(() -> {
log.info("request");
while (true) {
ArrayList<Pair<Integer, CompletableFuture>> requests = new ArrayList<>();
if (queue.drainTo(requests, batchSize) > 0) {
log.info("drained {} items", requests.size());
Map<Integer, String> serverResponseMap = requestBatch(requests.stream().map(Pair::getKey).collect(Collectors.toList()));
requests.forEach(pair -> {
String response = serverResponseMap.get(pair.getKey());
CompletableFuture<String> value = pair.getValue();
value.complete(response);
});
} else {
lock.lock();
try {
while (queue.size() == 0) {
try {
log.info("Waiting on condition");
requestAddedCondition.await(timeoutMillis, TimeUnit.MILLISECONDS);
log.info("Waking up on condition");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}).start();
}
public Map<Integer, String> requestBatch(Collection<Integer> requestList) {
return myServerSideService.requestRowBatch(requestList);
}
}
It looks like a working solution. But I am not sure if it is optimal.
Your MyClientSideServiceCompletableFuture solution, will send the requests to the server every time you add something to the queue and doesnt wait for requests to be batch sized. You are using BlockingQueue and adding the uneccessary blocking condition and locks. BlockingQueue has blocking-timeout capabilites so no addition condition is neccessary.
You can simplify your solution like this:
It sends requests to server only when the batch is full or the timeout passed and batch is not empty.
private void startQueueDrainer() {
new Thread(() -> {
log.info("request");
ArrayList<Pair<Integer, CompletableFuture>> batch = new ArrayList<>(batchSize);
while (true) {
try {
batch.clear(); //clear batch
long timeTowWait = timeoutMillis;
long startTime = System.currentTimeMillis();
while (timeTowWait > 0 && batch.size() < batchSize) {
Pair<Integer, CompletableFuture> request = queue.poll(timeTowWait , TimeUnit.MILLISECONDS);
if(request != null){
batch.add(request);
}
long timeSpent = (System.currentTimeMillis() - startTime);
timeTowWait = timeTowWait - timeSpent;
}
if (!batch.isEmpty()) {
// we wait at least timeoutMillis or batch is full
log.info("send {} requests to server", batch.size());
Map<Integer, String> serverResponseMap = requestBatch(batch.stream().map(Pair::getKey).collect(Collectors.toList()));
batch.forEach(pair -> {
String response = serverResponseMap.get(pair.getKey());
CompletableFuture<String> value = pair.getValue();
value.complete(response);
});
} else {
log.info("We wait {} but the batch is still empty", System.currentTimeMillis() - startTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Change the method requestSingleRow to not use lock
public String requestSingleRow(int req) {
CompletableFuture<String> future = new CompletableFuture<>();
while (!queue.offer(Pair.of(req, future))) {
log.error("Can't add {} to the queue. Retrying...", req);
}
try {
return future.get();
} catch (Exception e) {
return "Exception";
}
}
You could use CompletableFuture.
Have threads calling MyClientSideService put their request in a Queue (possibly BlockingQueue, and get a new CompletableFuture in return. The calling thread can call CompletableFuture.get() to block until a result is ready, or go on doing other things.
That CompletableFuture will be stored together with the request in MyClientSideService. When you reach 50 requests (and therefore 50 CompletableFuture instances), have the client service send the batch request.
When the request is complete, use the CompletableFuture.complete(value) method of each ComplatableFuture instance in the queue to notify the client thread that the response is ready. This will unblock the client if it has called blocking method like CompletableFuture.get(), or make it return instantly with value if called later.
I am trying to write a simple function that long-polls multiple messages tothe downstream dependency without exhausting it and only exist when all messages succeeded.
I came up with a way to wrap each message polling into a callable and use a ExecutorService to submit a list of callables.
public void poll(final List<Long> messageIdList) {
ExecutorService executorService = Executors.newFixedThreadPool(messageIdList.size());
List<MessageStatusCallable> callables = messageIdList.stream()
.map(messageId -> new MessageStatusCallable(messageId)).collect(Collectors.toList());
boolean allSuccess = false;
try {
allSuccess = executorService.invokeAll(callables).stream().allMatch(success -> {
try {
return success.get().equals(Boolean.TRUE);
} catch (InterruptedException e) {
e.printStackTrace();
return false;
} catch (ExecutionException e) {
e.printStackTrace();
return false;
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class MessageStatusCallable implements Callable<Boolean> {
private Long messageId;
public MessageStatusCallable(Long messageId) {
this.messageId = messageId;
}
/**
* Computes a result, or throws an exception if unable to do so.
*
* #return computed result
* #throws Exception if unable to compute a result
*/
#Override
public Boolean call() throws Exception {
String messageStatus = downstreamService.getMessageStatus(messageId);
while(messageStatus == null || !messageStatus.equals( STATUS_VALUE_SUCCEEDED) {
messageStatus = messageLogToControlServer.getMessageStatus(messageId);
Thread.sleep(TimeUnit.MICROSECONDS.toMillis(100));
}
LOG.info("Message: " + messageId + " Succeded");
return true;
}
}
I wonder if there is a better way to achieve this since Thread.sleep is blocking and ugly.
I'm not sure this is the best solution but it occurred to me you could use a CountDownLatch and ScheduledExecutorService.
public void poll(final List<Long> messageIdList) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(messageIdList.size());
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(POOL_SIZE);
try {
for (Long messageId : messageIdList) {
MessageStatusCallable callable = new MessageStatusCallable(messageId, latch);
executorService.scheduleWithFixedDelay(
() -> {
String messageStatus = downstreamService.getMessageStatus(messageId);
if (STATUS_VALUE_SUCCEEDED.equals(messageStatus)) {
latch.countDown();
throw new CompletionException("Success - killing the task", null);
}
},
0, 100, TimeUnit.MILLISECONDS);
}
latch.await();
} finally {
executorService.shutdown();
}
}
I probably also wouldn't have the Runnable as a lambda other than for brevity in the answer.
I have a simple REST service with a sleep method which doesn't do anything more than sleep for the specified time in milliseconds and then returns with a No Content response. My RESTTest class tries to invoke http://localhost:8080/myapp/rest/sleep/7500 first (to sleep for 7.5 seconds) but only waits for 5 seconds. After the 5 seconds it cancels the received Future (trying to cancel the pending request) and invokes http://localhost:8080/myapp/rest/sleep/5000 (to sleep for 5 seconds) and waits for 5 seconds.
public class RESTTest {
private final Client client = ClientBuilder.newClient();
private final ReentrantLock lock = new ReentrantLock();
private final Condition responseReceived = lock.newCondition();
public static void main(final String... arguments) {
new RESTTest().listen(10000);
}
public void listen(final long time) {
System.out.println("Listen for " + time + " ms.");
Future<Response> _response =
client.
target("http://localhost:8080/myapp/rest/sleep/" + time)).
request().
async().
get(
new InvocationCallback<Response>() {
public void completed(final Response response) {
System.out.println("COMPLETED");
lock.lock();
try {
responseReceived.signalAll();
} finally {
lock.unlock();
}
}
public void failed(final Throwable throwable) {
lock.lock();
try {
responseReceived.signalAll();
} finally {
lock.unlock();
}
}
});
lock.lock();
try {
System.out.println("Waiting for 5000 ms.");
if (!responseReceived.await(5000, TimeUnit.MILLISECONDS)) {
System.out.println("Timed out!");
_response.cancel(true);
listen(5000);
} else {
System.out.println("Response received.");
}
} catch (final InterruptedException exception) {
// Do nothing.
} finally {
lock.unlock();
}
}
}
Now I'd expect to see the "COMPLETED" string printed only once, and the "Response received." string printed only once as well. However, the "COMPLETED" string gets printed twice!
Listen for 7500 ms.
Waiting for 5000 ms.
Timed out!
Listen for 5000 ms.
Waiting for 5000 ms.
COMPLETED
Response received.
COMPLETED
What am I missing here?
Thanks,
I am sure you figured it out but here is an extremely modular solution that you can use with simple Guava ListenableFuture. You don't have to pool the responses like I am doing in the Futures.allAsList of course but you can do something like this at the end and remove your CountDownLatch.
BTW I am pretty sure your problem is a threading issue. You are seeing the COMPLETED because the callback is being invoked after your next call to listen(5000). Keep in mind that async will be threaded so output to the console can get delayed until the next context switch. The server is probably responding just after your 7500 semaphore unlocks.
private Client client;
#Before
public void setup() {
final ClientConfig clientConfig = new ClientConfig();
clientConfig.register(OrtbBidRequestBodyReader.class);
clientConfig.register(OrtbBidRequestBodyWriter.class);
clientConfig.connectorProvider(new CachingConnectorProvider(new HttpUrlConnectorProvider()));
clientConfig.property(ClientProperties.ASYNC_THREADPOOL_SIZE, 3);
client = ClientBuilder.newClient(clientConfig);
}
#Test
public void testAsync() throws InterruptedException, ExecutionException, JsonProcessingException {
final WebTarget target = client
.target("http://localhost:8081/dsp-receiver-0.0.1-SNAPSHOT/ortb/bid/123123?testbid=bid");
final AtomicInteger successcount = new AtomicInteger();
final AtomicInteger noBid = new AtomicInteger();
final AtomicInteger clientError = new AtomicInteger();
final InvocationCallback<Response> callback = new InvocationCallback<Response>() {
#Override
public void completed(final Response response) {
if (response.getStatus() == 200) {
successcount.incrementAndGet();
} else if (response.getStatus() == 204) {
noBid.incrementAndGet();
} else {
clientError.incrementAndGet();
}
}
#Override
public void failed(final Throwable e) {
clientError.incrementAndGet();
logger.info("Client Error", e);
}
};
final Entity<OrtbBidRequest> entity = Entity.entity(testBidRequest, MediaType.APPLICATION_JSON);
final List<ListenableFuture<Response>> allFutures = Lists.newArrayList();
final Stopwatch stopwatch = Stopwatch.createStarted();
for (int i = 0; i < 100000; i++) {
logger.info("Running request {}", i);
final Future<Response> future = target.request().accept(MediaType.APPLICATION_JSON).async().post(entity,
callback);
final ListenableFuture<Response> response = JdkFutureAdapters.listenInPoolThread(future);
allFutures.add(response);
// For each 100 of requests we will wait on them, otherwise we
// may run out of memory. This is really just to test the stamina
// of the dsp
if (i % 200 == 0) {
Futures.allAsList(allFutures).get();
allFutures.clear();
}
}
logger.info("success count {} nobid {} client error {} ", successcount, noBid, clientError);
logger.info("Total time {} ms ", stopwatch.stop());
}