I have a job that runs every hour, on 40core server, each job can have between 1 to 100 thousand tasks (need large queue), each task execute HTTP request when it finish, each task is critical which means it must run & complete.
Tasks can run asynchronously.
How do I config the number of threads in pool ? how do I config the queue size ?
in this test I'm trying to get my tasks rejected and flood my thread pool but instead i'm getting SocketTimeoutException
public static void main(String[] args) throws IOReactorException {
String url = "http://internal.server:8001/get";
int connectionTimeout = 3000;
int soTimeout = 3000;
int maxHttpConnections = 30;
IOReactorConfig customIOReactorConfig = IOReactorConfig.custom()
.setIoThreadCount(Runtime.getRuntime().availableProcessors())
.setConnectTimeout(connectionTimeout)
.setSoTimeout(soTimeout)
.build();
ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(customIOReactorConfig);
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
connManager.setDefaultMaxPerRoute(maxHttpConnections);
connManager.setMaxTotal(maxHttpConnections);
CloseableHttpAsyncClient customHttpAsyncClient = HttpAsyncClients.custom()
.setConnectionManager(connManager)
.build();
HttpComponentsAsyncClientHttpRequestFactory asyncRequestFactory = new HttpComponentsAsyncClientHttpRequestFactory(customHttpAsyncClient);
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(asyncRequestFactory);
System.out.println("start");
for (int i = 0; i < 30_000; i++) {
asyncRestTemplate.execute(url, HttpMethod.GET, request -> logger.info("doWithRequest..."), response -> {
logger.info("extractData...");
return response.getStatusText();
}).addCallback(new ListenableFutureCallback<String>() {
#Override
public void onFailure(Throwable ex) {
logger.error("onFailure [{}] [{}]", ex.getMessage(), ex.getStackTrace()[0].toString());
}
#Override
public void onSuccess(String result) {
logger.info("onSuccess");
}
});
}
System.out.println("end loop");
}
you can do something like:
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
poolTaskExecutor.setQueueCapacity(100);
CloseableHttpAsyncClient httpclient = HttpAsyncClients
.custom()
.setThreadFactory(poolTaskExecutor).build();
Related
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.
Im pretty new to apache spark. I would like to get some guidance on if this is bad practice for a Apache spark job
The goal is to make requests out to a external rest api and join in the response while processing data. This needs to be able to handle thousands of requests. I am trying to make async http request and return the http responses as a RDD.
Here is an example of what I am trying to do
public final class AsyncSparkJob implements Serializable {
// Java-friendly version of SparkContext
// Used to return JavaRDDs and works with Java Collections.
private static JavaSparkContext sc;
// AsyncSparkJob - constructor
public AsyncSparkJob(JavaSparkContext sc) {
// initialize the spark context
this.sc = sc;
}
// run - execute the spark transformations and actions
public void run(String filePath ) {
System.out.println("Starting spark job");
JavaRDD<String> inputFile = this.sc.textFile(filePath);
// Send a partition of http requests to each executor
Long results = inputFile.mapPartitions(new FlatMapFunction<Iterator<String>, HttpResponse>(){
// call - FlatMapFunction call implementation
public Iterator<HttpResponse> call(Iterator<String> stringIterator) throws Exception {
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(300000)
.setConnectTimeout(300000).build();
CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
.setDefaultRequestConfig(requestConfig).setMaxConnTotal(500).setMaxConnPerRoute(500)
.build();
httpClient.start();
List<HttpResponse> httpResponseList = new LinkedList<HttpResponse>();
try {
List<Future<HttpResponse>> futureResponseList = new LinkedList<Future<HttpResponse>>();
// As long as we have values in the Iterator keep looping
while (stringIterator.hasNext()) {
String uri = stringIterator.next();
HttpGet request = new HttpGet(uri);
Future<HttpResponse> futureResponse = httpClient.execute(request, new FutureCallback<HttpResponse>() {
public void completed(HttpResponse httpResponse) {
System.out.println("Completed request");
}
public void failed(Exception e) {
System.out.println("failed" + e);
}
public void cancelled() {
System.out.println("cancelled");
}
});
futureResponseList.add(futureResponse);
}
// Now that we have submitted all of the responses we can start
// looking threw and trying to read the response.
for (Future<HttpResponse> futureResponse : futureResponseList) {
/* This will cause a block. However We have already submitted
all of our requests. So if we block once we should expect to see less
often blocks when reading from the "future" responses;
*/
httpResponseList.add(futureResponse.get());
}
} catch ( Exception e ) {
System.out.println("Caught " + e);
}finally {
httpClient.close();
}
return httpResponseList.iterator();
}
}).count();
System.out.println("Final result count : " + results);
}
public static void main( String[] args ) {
// Init the spark context
JavaSparkContext sc = new JavaSparkContext(new SparkConf().setAppName("AsyncSparkJob"));
// Create the spark job
AsyncSparkJob asj = new AsyncSparkJob(sc);
asj.run(args[0]);
System.out.println("Done");
}
}
Is this a valid use cases ?
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.
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());
}