I'm using Jersey for the application's REST API, consider the function below
#POST
public String writeSomething() {
someVeryIntensiveTaskWhichTaking("5 seconds");
Log.info("get request fulfilled") // don't want the logging to happen if user cancelled on UI
return "ok";
}
Assume this POST request is doing some intensive task and might takes up to 5 seconds. Then on the UI, the user decided to cancel the POST request via XMLHttpRequest.abort() at 2 second,
is there any way to track this abortion and prevent some action being done? something like checking IsClientConnected?
Update #1
Thanks to peeskillet's tips, but i still unable to get the callback being triggered upon the XHR's abortion. The below is my code
#POST
public String writeSomething(#Suspended final AsyncResponse asyncResponse) {
asyncResponse.register(new ConnectionCallback() {
public void onDisconnect(AsyncResponse asyncResponse) {
System.out.println("This is canceled, do whatever you want"); // this is not triggered after XHR aborted
}
});
someVeryIntensiveAsynTaskWhichTaking("5 seconds", asyncResponse); // this asyn function will trigger asyncResponse.resume() upon completion
}
Related
I'm using Retrofit 2 to make asynchronous calls. The problem is that after the response is received (onResponse is done with its work), the application still waits for 60 seconds before quitting.
This is the essential bit:
Call<MyResponse> call = client.resource();
call.enqueue(new Callback<MyResponse>() {
#Override
public void onResponse(Call<MyResponse> c, Response<MyResponse> response) {
// This gets called in a few milliseconds
}
#Override
public void onFailure(Call<MydResponse> c, Throwable t) {
}
});
It looks like some ThreadPoolExecutor is waiting to time out (maybe in okhttp). But shouldn't the pool become free after the response is received as there is nothing else to process?
Is this a bug, am I misusing it, or is it normal to wait for 60 seconds when there is in fact nothing else to process?
How do I make the application exit right after the onResponse is done?
I have a work project where I am having multiple threads call a utility function I created that sends a post form to an internal API we have.
I use the callAPI method here which is in a utility class, which is public, final and static :
public static int callAPI(String url, TaskListener listener, String operation, String id, String password, String sUser){
Client client = ClientBuilder.newClient();
Form form = new Form();
form.param("case", id);
form.param("user", sUser);
form.param("password", password);
listener.getLogger().println("*******************************************************");
listener.getLogger().println("Sending API Service Request");
Response response = client.target(url)
.path(operation)
.request(MediaType.APPLICATION_JSON)
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
}
I then have this run method in my Runnable class:
#Override
public void run(){
listener.getLogger().println("Thread Started.\n");
int runId = Utility.callAPI(wUrl, listener, operation, password, sUser);
listener.getLogger().println("call completed");
}
So what actually prints in Jenkins console output:
*******************************************************
Sending API Service Request
Thread Started.
This means I know my run() method is being called and so my thread is starting. I know it can see my runAPI method in the utility.
Now all I can gather is that this line:
Response response = client.target(url)
.path(operation)
.request(MediaType.APPLICATION_JSON)
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
sends a request and from there the thread exits before it finishes off the rest of the run() method.
I've tried:
putting a thread sleep loop
putting the call in a while loop
It has been awhile since I've done any Java programming or threading. This is something very simple that I know I am forgetting.
ALSO I HAVE CHANGED A LOT OF MY CODE AND HAVE ONLY SHOWN THE CRITICAL PARTS BEING THIS IS PART OF A WORK PROJECT AND I DIDNT WANT TO TAKE CHANCES.
Consider the following code to listen for an update with long-polling:
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();
}
The client adds a listener with the normal Jersey client's AsyncInvoker, calls the asynchronous task, and then another task calls the update method.
When I'm testing this, I run into a race condition. Right after I add the listener asynchronously on listenForUpdates(), I make an update on the endpoint with updateTag() synchronously. But the update gets run before the listener is added, and the asynchronous response fails to resume.
A solution to this is to call the suspend() method on the response after adding it to the listeners. But it's not clear how to do that, given that #Suspended provides an already-suspended AsyncResponse object. What should I do so that the async response is suspended only after adding to listener? Will that actually call the suspend method? How can I get this to work with the Jersey async client, or should I use a different long-polling client?
For solutions, I'm open to different libraries, like Atmosphere or Guava. I am not open to adding a Thread.sleep() in my test, since that is an intermittent failure waiting to happen.
I ended up using RxJava, but not before coming up with a just-as-good solution using BlockingQueue instead of List in the Map. It goes something like this:
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();
}
I haven't tested this exact code, but I used some version of it in the past. As long as you call the initListen endpoint synchronously first, you can call the asynchronous listen endpoint and then the synchronous update endpoint and there won't be any significant race condition.
There is a slight hint of a race condition in the update endpoint, but it's minor. The responses blocking queue could become empty on iteration, or it may be updated by multiple sources differently. To alleviate this, I've used the drainTo(Collection) method on a per-request instantiated data structure. This still does not solve the use case where multiple clients may try updating the same tag of listeners, but I do not need this use case.
I have used Square / Retrofit Restful framework to get data from a Restful service, and it works like a charm, code snippet is like below:
FooService restInterface = new RestAdapter.Builder().setEndpoint(FooService.URL).build().create(FooService.class);
restInterface.getAllFoos(new Callback<FooModel>() {
#Override
public void success(FooModel model, Response response) {
//get a list of Foo instances.
}
updateUI();
}
#Override
public void failure(RetrofitError error) {
//log errors.
}
});
I understand this is an async call, however can I have a spinning icon on the top while retrofit is busy working on the background? In case the network is not available.
Also is it possible to set a timeout so when the time is up, a prompt of options to continue waiting or abort the mission?
I noticed there was something close on this site: Is it possible to show progress bar when upload image via Retrofit 2 , but still couldn't figure it out how to do it. Besides, my requirement might be simpler.
Yes, this is perfectly possible.
You can make a call to some sort of startSpinner() function just before or even just after the call to the endpoint. Then, in success() and failure(), you can make a call to some sort of stopSpinner() function.
As for the timeout functionality, you sould set the timeout on the underlying HTTP client rather than on Retrofit. If you do not want to use the default timeout, you can read more about setting a custom one here.
A better approach is to consolidate this piece of logic into a central location. For this you can use an interceptor and just broadcast a local intent on any request your app makes.
Take a look to this Interceptor class written in Kotlin (Same works on Java):
internal class BusyInterceptor constructor(val appContext: Context) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val t1 = System.nanoTime()
// Times are different, so it does handle request and response at the same time
Log.d("REQUEST BUSY INTERCEPTOR------------>","request time: " + t1)
val intent = Intent()
intent.action = "IS_BUSY"
intent.putExtra("isBusy", true)
LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent)
val response: Response = chain.proceed(request)
val t2 = System.nanoTime()
Log.d("RESPONSE BUSY INTERCEPTOR------------>","request time: " + t2)
val intent2 = Intent()
intent2.action = "IS_BUSY"
intent2.putExtra("isBusy", false)
LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent2)
return response
}
}
Then you just add the interceptor to your okhttp client:
// Add Busy Interceptor
val busyInterceptor = BusyInterceptor(appContext)
okHttpClient.addInterceptor(busyInterceptor)
This way, you are not dependent on any specific activity/fragment anymore, you just have to make sure you are using an Application file, and registering for the broadcast.
i.e. MyApplication.java:
#Override
public void onCreate() {
super.onCreate();
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getBooleanExtra("isBusy", false)){
ProgressHUD.showDummyProcessing(getCurrentActivity());
} else {
ProgressHUD.hideDummyProcessing(getCurrentActivity());
}
}
};
IntentFilter filter = new IntentFilter("IS_BUSY");
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, filter);
This gives you the flexibility to decide what you want to do every time a request is made regardless where the user is currently at.
For simplicity I didn't include a queue logic to manage multiple/parallel requests, but its fairly simple to keep track of this at the same file using ArrayList or similar.
Also, some this supports "silent" requests where you don't want to display any feedback to the user (analytics, logging, etc), just pass additional "extras" in the intent :)
My goal is to support long-polling for multiple web service callers, and to keep track of which callers are currently "parked" on a long poll (i.e., connected). By "long polling," I mean that a caller calls a web service and the server (the web service) does not return immediately, but keeps the caller waiting for some preset period of time (an hour in my application), or returns sooner if the server has a message to send to the caller (in which case the server returns the message by calling asyncResponse.resume("MESSAGE")).
I'll break this into two questions.
First question: is this a reasonable way to "park" the callers who are long-polling?
#GET
#Produces(MediaType.TEXT_PLAIN)
#ManagedAsync
#Path("/poll/{id}")
public Response poller(#Suspended final AsyncResponse asyncResponse, #PathParam("id") String callerId) {
// add this asyncResponse to a HashMap that is persisted across web service calls by Jersey.
// other application components that may have a message to send to a caller will look up the
// caller by callerId in this HashMap and call resume() on its asyncResponse.
callerIdAsyncResponseHashMap.put(callerId, asyncResponse);
asyncResponse.setTimeout(3600, TimeUnit.SECONDS);
asyncResponse.setTimeoutHandler(new TimeoutHandler() {
#Override
public void handleTimeout(AsyncResponse asyncResponse) {
asyncResponse.resume(Response.ok("TIMEOUT").build());
}
});
return Response.ok("COMPLETE").build();
}
This works fine. I'm just not sure if it's following best practices. It seems odd to have the "return Response..." line at the end of the method. This line is executed when the caller first connects, but, as I understand it, the "COMPLETE" result is never actually returned to the caller. The caller either gets "TIMEOUT" response or some other response message sent by the server via asyncResponse.resume(), when the server needs to notify the caller of an event.
Second question: my current challenge is to accurately reflect the population of currently-polling callers in the HashMap. When a caller stops polling, I need to remove its entry from the HashMap. A caller can leave for three reasons: 1) the 3600 seconds elapse and so it times out, 2) another application component looks up the caller in the HashMap and calls asyncResponse.resume("MESSAGE"), and 3) the HTTP connection is broken for some reason, such as somebody turning off the computer running the client application.
So, JAX-RS has two callbacks I can register to be notified of connections ending: CompletionCallback (for my end-poll reasons #1 and #2 above), and ConnectionCallback (for my end-poll reason #3 above).
I can add these to my web service method like this:
#GET
#Produces(MediaType.TEXT_PLAIN)
#ManagedAsync
#Path("/poll/{id}")
public Response poller(#Suspended final AsyncResponse asyncResponse, #PathParam("id") String callerId) {
asyncResponse.register(new CompletionCallback() {
#Override
public void onComplete(Throwable throwable) {
//?
}
});
asyncResponse.register(new ConnectionCallback() {
#Override
public void onDisconnect(AsyncResponse disconnected) {
//?
}
});
// add this asyncResponse to a HashMap that is persisted across web service calls by Jersey.
// other application components that may have a message to send to a caller will look up the
// caller by callerId in this HashMap and call resume() on its asyncResponse.
callerIdAsyncResponseHashMap.put(callerId, asyncResponse);
asyncResponse.setTimeout(3600, TimeUnit.SECONDS);
asyncResponse.setTimeoutHandler(new TimeoutHandler() {
#Override
public void handleTimeout(AsyncResponse asyncResponse) {
asyncResponse.resume(Response.ok("TIMEOUT").build());
}
});
return Response.ok("COMPLETE").build();
}
The challenge, as I said, is to use these two callbacks to remove no-longer-polling callers from the HashMap. The ConnectionCallback is actually the easier of the two. Since it receives an asyncResponse instance as a parameter, I can use that to remove the corresponding entry from the HashMap, like this:
asyncResponse.register(new ConnectionCallback() {
#Override
public void onDisconnect(AsyncResponse disconnected) {
Iterator<Map.Entry<String, AsyncResponse>> iterator = callerIdAsyncResponseHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, AsyncResponse> entry = iterator.next();
if (entry.getValue().equals(disconnected)) {
iterator.remove();
break;
}
}
}
});
For the CompletionCallback, though, since the asyncResponse is already done or cancelled at the time the callback is triggered, no asyncResponse parameter is passed in. As a result, it seems the only solution is to run through the HashMap entries checking for done/cancelled ones and removing them, like the following. (Note that I don't need to know whether a caller left because resume() was called or because it timed out, so I don't look at the "throwable" parameter).
asyncResponse.register(new CompletionCallback() {
#Override
public void onComplete(Throwable throwable) {
Iterator<Map.Entry<String, AsyncResponse>> iterator = callerIdAsyncResponseHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, AsyncResponse> entry = iterator.next();
if (entry.getValue().isDone() || entry.getValue().isCancelled()) {
iterator.remove();
}
}
}
});
Any feedback would be appreciated. Does this approach seem reasonable? Is there a better or more Jersey/JAX-RS way to do it?
Your poller() method does not need to return a Response in order to participate in asynchronous processing. It can return void. If you are doing anything complex in the poller however you should consider wrapping the whole method in a try/catch block that resumes your AsyncResponse object with the exception to ensure that any RuntimeExceptions or other unchecked Throwables are not lost. Logging these exceptions in the catch block here also seems like a good idea.
I'm currently researching the question of how to reliably catch an asynchronous request being cancelled by the client and have read at one question that suggests the mechanism isn't working for the questioner[1]. I'll leave it to others to fill out this information for the moment.
[1] AsyncResponse ConnectionCallback does not fire in Jersey