Today I found a wierd problem. What I want is to check server availability (Particularly SSL checking) once application started and display proper message if server is down. This process should work in background and user is able to navigate the app if server has problem (app can works offline).
What I did is simple. In main activity I have
#Override
protected void onStart()
{
super.onStart();
// Check Internet connection
// Check Location sensor
// Check server accessibility
BackendCheck backendCheck = new BackendCheck(this);
if (!backendCheck.execute())
{
displayErrorDialog();
return;
}
}
This is BackendCheck class:
public class BackendCheck implements Callable<Boolean>
{
private static final String TAG = BackendCheck.class.getSimpleName();
// Thread sleep time
private static final int THREAD_SLEEP = 5000;
// Number of attempts to call an API in order to get response
private static final int MAX_ATTEMPT = 3;
// Current attempt
private int counter = 0;
// The url that should be used in order to get server response
private String mTestUrl;
// App mContext
private Context mContext;
// Server status
private boolean mServerStatus = false;
public BackendCheck(Context context)
{
this(context, "");
}
public BackendCheck(Context context, String url)
{
this.mTestUrl = url;
this.mContext = context;
}
public boolean execute()
{
// Check #mTestUrl and use Feature API if this variable is empty
if (TextUtils.isEmpty(mTestUrl))
{
mTestUrl = PassengerConstants.URL_BASE + mContext.getResources()
.getString(R.string.uri_feature_payments);
}
// Get ExecutorService from Executors utility class, thread pool size is 10
ExecutorService executor = Executors.newFixedThreadPool(10);
do
{
// Increment counter
counter++;
// Submit Callable tasks to be executed by thread pool
Future<Boolean> future = executor.submit(this);
try
{
// Break Do-While loop if server responded to request (there is no error)
if (!future.get())
{
mServerStatus = true;
break;
}
}
catch (InterruptedException e)
{
Logger.error(TAG, e.getMessage());
}
catch (ExecutionException e)
{
Logger.error(TAG, e.getMessage());
}
} while (counter < MAX_ATTEMPT);
// Shut down the executor service now
executor.shutdown();
// Return server status
return mServerStatus;
}
#Override
public Boolean call() throws Exception
{
// Sleep thread for a few seconds
Thread.sleep(THREAD_SLEEP);
try
{
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(mTestUrl);
Logger.debug(TAG, "Attempt (" + counter + "), try to check => " + mTestUrl);
HttpResponse httpResponse = client.execute(get);
int connectionStatusCode = httpResponse.getStatusLine().getStatusCode();
Logger.debug(TAG,
"Connection code: " + connectionStatusCode + " for Attempt (" + counter
+ ") of request: " + mTestUrl);
if (isServerError(connectionStatusCode))
{
return true;
}
}
catch (IllegalArgumentException e)
{
Logger.error(TAG, e.getMessage());
}
catch (Exception e)
{
Logger.error(TAG, e.getMessage());
}
return false;
}
/**
* Server status checker.
*
* #param statusCode status code of HTTP request
* #return True if connection code is 5xx, False otherwise.
*/
private static boolean isServerError(int statusCode)
{
return (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR);
}
}
What happens is, When I launch the application splash screen displays. Then after a few seconds mainActivity runs (first code) then - since my server is down (for testing purposes) - I have black screen for 15 seconds (since I set MAX_ATTEMPT to 3 and have 5 seconds thread sleep) and after that I'm able to see UI of mainActivity and my error message.
I expect Callable<> should works in background and I see mainActivity after splashScreen without problem (black screen).
What you think? What problem might be? Thanks.
It would appear that you are executing the BackendCheck callable in the main thread.
Classes that extend Callable are usually executed via an ExecutorService which is a separate thread itself, thus it executes in the background. You may want to take a look at the Runnable interface or Thread if you'd like to run a separate thread to execute in the background that does not return a value. Calling the start method will cause the class to execute in a separate thread as indicated by the documentation:
When an object implementing interface Runnable is used to create a thread, starting the thread causes the object's run method to be called in that separately executing thread.
If you need to return some data at the end of execution I highly recommend an ExecutorService but you could probably also get away with using a FutureTask though I have less experience with that class. Hopefully that helps.
Okay, I just fixed my problem. 'njzk2' is right. The problem is future.get() which is running on or blocking main thread. I fixed the issue by doing a few changes.
First, I call my execute() method from a new thread. Therefore the whole of processing will be done in another thread.
I added new start() method in order to run it.
Add a listener in BackendCheck class and implemented it in my activity.
Since I want to display a dialog if server is down and I'm in another thread then runOnUiThread(runnable) uses to show the dialog in main thread.
This is my complete code for your reference.
In my activity:
#Override
protected void onStart()
{
super.onStart();
// Check Location sensor
// Check server accessibility
BackendCheck backendCheck = new BackendCheck(this);
backendCheck.setServerListener(new BackendCheck.BackendCheckListener()
{
#Override
public void onServerIsDown()
{
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
displayErrorDialog();
}
});
}
});
backendCheck.start();
}
And my BackendCheck class:
public class BackendCheck implements Callable<Boolean>
{
public interface BackendCheckListener
{
public void onServerIsDown();
}
private static final String TAG = BackendCheck.class.getSimpleName();
// Thread sleep time
private static final int THREAD_SLEEP = 5000;
// Number of attempts to call an API in order to get response
private static final int MAX_ATTEMPT = 3;
// Current attempt
private int counter = 0;
// The url that should be used in order to get server response
private String mTestUrl;
// App mContext
private Context mContext;
// Server status
private boolean mIsServerWorking = false;
// Server listener
private BackendCheckListener mListener;
public BackendCheck(Context context)
{
this(context, "");
}
public BackendCheck(Context context, String url)
{
this.mTestUrl = url;
this.mContext = context;
}
public void setServerListener (BackendCheckListener listener)
{
this.mListener = listener;
}
public void start()
{
Thread thread = new Thread()
{
#Override
public void run() {
boolean isServerWorking = execute();
if(!isServerWorking)
{
mListener.onServerIsDown();
}
}
};
thread.start();
}
private boolean execute()
{
// Check #mTestUrl and use Feature API if this variable is empty
if (TextUtils.isEmpty(mTestUrl))
{
mTestUrl = PassengerConstants.URL_BASE + mContext.getResources()
.getString(R.string.uri_feature_payments);
}
// Get ExecutorService from Executors utility class
ExecutorService executor = Executors.newFixedThreadPool(1);
do
{
// Increment counter
counter++;
// Submit Callable tasks to be executed by thread pool
Future<Boolean> future = executor.submit(this);
try
{
// Skip sleeping in first attempt
if(counter > 1)
{
// Sleep thread for a few seconds
Thread.sleep(THREAD_SLEEP);
}
// Break Do-While loop if server responded to request (there is no error)
if (!future.get())
{
mIsServerWorking = true;
break;
}
}
catch (InterruptedException e)
{
Logger.error(TAG, e.getMessage());
}
catch (ExecutionException e)
{
Logger.error(TAG, e.getMessage());
}
} while (counter < MAX_ATTEMPT);
// Try to shut down the executor service now
try
{
executor.shutdown();
executor.awaitTermination(THREAD_SLEEP, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e)
{
Logger.error(TAG, e.getMessage());
}
// Return server status
return mIsServerWorking;
}
#Override
public Boolean call() throws Exception
{
try
{
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(mTestUrl);
Logger.debug(TAG, "Attempt (" + counter + "), try to check => " + mTestUrl);
HttpResponse httpResponse = client.execute(get);
int connectionStatusCode = httpResponse.getStatusLine().getStatusCode();
Logger.debug(TAG,
"Connection code: " + connectionStatusCode + " for Attempt (" + counter
+ ") of request: " + mTestUrl);
if (isServerError(connectionStatusCode))
{
return true;
}
}
catch (IllegalArgumentException e)
{
Logger.error(TAG, e.getMessage());
}
catch (Exception e)
{
Logger.error(TAG, e.getMessage());
}
return false;
}
/**
* Server status checker.
*
* #param statusCode status code of HTTP request
* #return True if connection code is 5xx, False otherwise.
*/
private static boolean isServerError(int statusCode)
{
return (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR);
}
}
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);
}
}
I am trying to execute 2 jobs parallel from main thread but if a callback method take long time to give response rest of requests are pause and wait to complete first.
Here is my code:
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
private void executeService(String uuid) {
System.out.println("query executed done: " + uuid);
}
private String getAsynchTest(final String uuid) throws Exception {
testAsynchF = executorService.submit(
new Callable<String>() {
public String call() throws Exception {
executeService(uuid);
return getFutuerResult(uuid, Thread.currentThread()); // long processing
}
});
return testAsynchF.get();
}
public String getFutuerResult(String uuid, Thread t) {
String dummy = "your result for request: "+uuid;
if (uuid.equalsIgnoreCase("112")) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return dummy;
}
public static void main(String[] args) {
SynchronousTimeoutTester tester = new SynchronousTimeoutTester();
try {
String one = "112"
System.out.println("Result sync call:*** " + tester.getAsynchTest(one));
String two = "115";
System.out.println("Result sync call:**** " + tester.getAsynchTest(two));
} catch (Exception e) {
System.out.println("catched as Exception: " + e);
}
}
Why is this stopping to execute request 115 if request 112 thread is pause?
Since you pass Thread.currentThread() to getFutureResult and that method calls join() on its thread argument (in case uuid is "112"), the method will wait for its own thread to end, which it can't since it's waiting.
I have 7 threads running in an ExecutorPool that process data and occasionally need data from a listener instance running on another thread. The listener sends a request over a socket to a server and a while later, when the result is returned, the listener will return the data to the worker thread that called it. I want to block the worker thread until the requested data is returned, but I don't want to block the listener from making other requests from the other worker threads. How do I do that?
If one thread hands off work to another thread, and then subsequently simply waits for the result, you don't need another thread to do the work. You may need a class that does the work, but which is called on the same thread. And if the same instance is used by multiple threads some synchronization may be needed. But the bottom line is this :
You don't need the listener thread. Replace it with a component that handles a request, and call it synchronously.
Edit
Given your own answer, your problem is a bit clearer. As #JimN suggests you probably want to hand out a Future to the worker thread, and make it a CompletableFuture the Listener keeps in a Map keyed by request id until the response returns.
Sample code :
public class WorkUnitProcessor implements Runnable {
// ...
#Override
public void run() {
while(true) {
WorkUnit work = master.getNextWorkUnit();
if(work == null) return;
doWork(work);
}
}
public void doWork(WorkUnit work) {
//Do some work...
try {
DataRequest dataRequest = createRequest(work);
Future<Response> future = server.getData(dataRequest);
Response response = future.get(); // this call blocks until the Response is available.
//finish doing work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
// handle e.getCause()
}
}
// ...
}
public class Server implements DataSourceDrivenCallback {
private final DataSource dataSource;
private Map<Integer, CompletableFuture<Response>> openRequests = new ConcurrentHashMap<>();
public Server(DataSource dataSource) {
this.dataSource = dataSource;
}
#Override
public void incomingDataCallback(int requestId, ChunkOfData requestedData) {
CompletableFuture<Response> responseHolder = openRequests.remove(requestId); // get the responseHolder
if (responseHolder != null) {
responseHolder.complete(toResponse(requestedData)); // make the response available.
}
}
public Future<Response> getData(DataRequest datarequest) {
int requestId = dataSource.submitRequest(serializeAndTranslateRequest(datarequest));
CompletableFuture<Response> future = new CompletableFuture<>();
openRequests.put(requestId, future);
return future;
}
// ...
}
I think this might work. What I was looking for is described here:
https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
It's the ability to make a thread sleep until it is notified by the thread that it is waiting on. Seems easy to use.
public class DataProcessor {
private List<WorkUnit> work;
private Server server;
public DataProcessor(List<WorkUnit> work, int numprocessors) {
this.work = work;
setupProcessors(numprocessors);
Server server = new Server();
}
private void setupProcessors(int numprocessors) {
for(int i = 0; i < numprocessors; i++) {
WorkUnitProcessor worker = new WorkUnitProcessor(this, server);
worker.start();
}
}
public synchronized WorkUnit getNextWorkUnit() {
if(work.isEmpty()) return null;
return work.remove(0);
}
}
public class WorkUnitProcessor(Server server) {
private DataProcessor master;
private Server server;
public WorkUnitProcessor(DataProcessor master) {
this.master = master;
}
#Override
public void run() {
while(true) {
WorkUnit work = master.getNextWorkUnit();
if(work == null) return;
doWork(work);
}
}
public void doWork(WorkUnit work) {
//Do some work...
server.getData(datarequest, this);
while(!datarequest.filled) {
try {
wait();
} catch (InterruptedException e) {}
}
//finish doing work
}
}
public class Server implements DataSourceDrivenCallback {
private DataSource ds;
private Map<Integer, OpenRequest> openrequests;
public Server() {
//setup socket and establish communication with server through DataSource object
DataSource ds = new DataSource(<ID>, <Socket>);
}
public synchronized void getData(DataRequest datarequest, WorkUnitProcessor workerthread) {
int requestid = ds.submitRequest(serializeAndTranslateRequest(datarequest));
openrequests.add(new OpenRequest(workerthread, datarequest));
}
#Override
public void incomingDataCallback(int requestid, ChunkOfData requesteddata) {
OpenRequest request = openrequests.get(requestid);
request.datarequest.storeData(requesteddata);
request.workerthread.notify();
}
}
public class OpenRequest {
private WorkUnitProcessor workerthread;
private DataRequest datarequest;
//other details about request
}
I am developing an application that delivers notifications to android and iOS devices. I am using basic scaling and have implemented logic (modifying this example) so an appropriate number of workers are active at a given time without using a resident instance.
public class NotificationWorkerServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final Logger log = Logger
.getLogger(NotificationWorkerServlet.class.getName());
private static final int MAX_WORKER_COUNT = 5;
private static final int MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED = 2500;
private static final int TEN_MINUTES = (10 * 60 * 1000);
// Area of concern
private static SyncCounter counter;
/**
* Used to keep number of running workers in sync
*/
private class SyncCounter {
private int c = 0;
public SyncCounter(){
log.info("Sync counter instantiated");
}
public synchronized void increment() {
c++;
log.info("Increment sync counter, workers:" + c);
}
public synchronized void decrement() {
c--;
log.info("Decrement sync counter, workers:" + c);
}
public synchronized int value() {
return c;
}
}
/**
* Call made from module when notification was added to task queue
*/
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doPost(req, resp);
// Instantiate counter with first call
if(counter == null){
counter = new SyncCounter();
}
log.info("Starting to build workers");
for (int workerNo = counter.value(); workerNo < MAX_WORKER_COUNT; workerNo++) {
log.info("Starting thread for worker: " + workerNo);
// Get the current queue to check it's statistics
Queue notificationQueue = QueueFactory
.getQueue("notification-delivery");
if (notificationQueue.fetchStatistics().getNumTasks() > 30 * workerNo) {
counter.increment();
Thread thread = ThreadManager
.createBackgroundThread(new Runnable() {
#Override
public void run() {
try {
doPolling();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
} else {
break; // Current number of threads is sufficient.
}
}
resp.setStatus(HttpServletResponse.SC_OK);
}
/**
* poll the task queue and lease the tasks
*
* Wait for up to 10 minutes for tasks to be added to queue before killing
* tasks
*
*/
private void doPolling() {
log.info("Doing pulling");
try {
int loopsWithoutProcessedTasks = 0;
Queue notificationQueue = QueueFactory
.getQueue("notification-delivery");
NotificationWorker worker = new NotificationWorker(
notificationQueue);
while (!LifecycleManager.getInstance().isShuttingDown()) {
boolean tasksProcessed = worker.processBatchOfTasks();
ApiProxy.flushLogs();
if (!tasksProcessed) {
log.info("waiting for tasks");
// Wait before trying to lease tasks again.
try {
loopsWithoutProcessedTasks++;
// If worker hasn't had any tasks for 30 min, kill it.
if (loopsWithoutProcessedTasks >= (TEN_MINUTES / MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED)) {
break;
} else {
// Else, wait and try again (to avoid tearing down
// useful Notification Senders)
Thread.sleep(MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED);
}
} catch (InterruptedException e) {
log.info("Notification worker thread interrupted");
break;
}
} else {
log.info("processed batch of tasks");
loopsWithoutProcessedTasks = 0;
}
}
} catch (Exception e) {
log.warning("Exception caught and handled in notification worker: "
+ e.getLocalizedMessage());
} finally {
counter.decrement();
}
log.info("Instance is shutting down");
}
}
In a controlled testing scenario, it works just fine. However, I know static, mutable values are bad news in servlets where multiple users could potentially be connecting at the same time.
Has anyone done something similar and had issues with pushing multiple notifications to the same device, lost tasks or had idle tasks burning a hole in the bank?
I have a long operation running in the background, like uploading stuff, converting images, audio, video, etc.
I would like to stop/cancel them if the user requested to stop the operation altogether.
How can accomplish this? Is there a design pattern for this?
Note: Some of the running code can be canceled and some can't. How do I find a compromise around that?
EDIT: I should have said that I want the operation to stop immediately.
To summarize and extend on what Jon has said:
You should let the thread know that it should exit the loop (volatile flag).
You may interrupt() the thread if you want it to exit out of a blocking state.
You should handle the InterruptedException inside the run method.
You should exit gracefully when you're interrupted (i.e. finish up whatever you're doing and clean up).
Some code:
private volatile bool _running;// volatile guarantees that the flag will not be cached
public void kill(){_running = false;}
public void run()
{
while(_running)
{
try
{
DoWork(); // you may need to synchronize here
}
catch(InterruptedException e)
{
// Handle e
}
}
}
(I'm assuming you're already performing the background work in a separate thread.)
Basically, you keep a shared boolean flag which the UI thread can set and the background thread periodically reads. When the flag says "stop", you stop :)
Note that the flag should be volatile or you should use a lock in order to make sure that the background thread definitely "sees" a change written from the UI thread.
It's relatively crude and feels a bit "manual" but it means you don't risk instability through aborting half way through an operation, unlike approaches such as Thread.stop().
My 2 cents. A task that is cancelable. The cancelImpl() and runImpl() need to be implemented.
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class CancelableTask extends Observable<CancelableTask>
implements Runnable {
private volatile boolean isStarted = false;
private volatile boolean isCanceled = false;
private volatile boolean isSuccess = false;
private volatile Exception e;
private volatile AtomicBoolean doneLock = new AtomicBoolean(false);
protected final AtomicInteger progress = new AtomicInteger(0);
public CancelableTask() {
}
#Override
public final void run() {
try {
runUnsafe();
} catch (Exception e) {
Config.getLog().i("CancelableTask threw an exception", e);
}
}
public final void runUnsafe() throws Exception {
// Config.getLog().d("Running cancelable task: " + toString());
notifyObservers(this);
isStarted = true;
try {
if (!isCanceled) {
runImpl();
}
} catch (Exception e) {
// Note: Cancel may throw exception
if (doneLock.compareAndSet(false, true)) {
this.e = e;
notifyObservers(this);
clearObservers();
// Someone else should do something with the exception
// Config.getLog().i("Failed cancelable task: " + toString(), e);
throw e;
}
// Cancel got to the lock first but may NOT have yet changed the cancel flag.
// Must throw cancellation exception.
}
if (doneLock.compareAndSet(false, true)) {
isSuccess = true;
progress.set(100);
notifyObservers(this);
clearObservers();
// Config.getLog().d("Finished cancelable task: " + toString());
return;
}
// The task was canceled but the isCanceled may not have been set yet.
synchronized (doneLock) { // Waiting for the cancel to finish it's logic
}
// assert isCanceled; // Commented out because android crashes the app in assertion
// No need to notify here because cancel was already notified in
// cancel method.
// notifyObservers(this);
// Config.getLog().d("Already canceled task: " + toString());
throw new CancellationException("Canceled while running!");
}
protected abstract void runImpl() throws Exception;
protected void cancelImpl() {}
public final void cancel() {
synchronized (doneLock) {
if (doneLock.compareAndSet(false, true)) {
// Config.getLog().i("Canceling cancelable task: " + toString());
isCanceled = true;
cancelImpl();
notifyObservers(this);
clearObservers();
}
}
}
public final boolean isCanceled() {
return isCanceled;
}
public final boolean isSuccessful() {
return isSuccess;
}
public final boolean isDone() {
return doneLock.get();
}
public final boolean isStarted() {
return isStarted;
}
public final Exception getError() {
return e;
}
public int getProgress() {
return progress.get();
}
/**
* Observers will be cleared after the task is done but only after all of them are notified.
*/
#Override
public void addObserver(Observer<CancelableTask> observer) {
super.addObserver(observer);
}
// protected void incrementProgress(int value) {
// progress += value;
// }
}
There is the CancelableCollection as well:
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException;
public class CancelableCollection extends CancelableTask {
private LinkedHashMap<CancelableTask, Integer> cancelables = new LinkedHashMap<CancelableTask, Integer>();
private volatile boolean normalizing;
private volatile State state = new State(null, 0, 0);
private boolean isOneFailsAll = true;
// public boolean isOneFailsAll() {
// return isOneFailsAll;
// }
public void setOneFailsAll(boolean isOneFailsAll) {
this.isOneFailsAll = isOneFailsAll;
}
public int getTotalWeight() {
Collection<Integer> values = cancelables.values();
int total = 0;
for (int weight : values) {
total += weight;
}
return total;
}
/**
* Adds and runs the cancelable
*
* #param cancelable
* #return
* #throws Exception
* if failed while running
* #throws CancellationException
* if already canceled
*/
public void add(CancelableTask cancelable, int relativeTime) {
if (cancelable == null) {
return;
}
cancelables.put(cancelable, relativeTime);
if (isCanceled()) {
throw new CancellationException("Canceled while running!");
}
if (isDone()) {
throw new RuntimeException(
"Cannot add tasks if the Cancelable collection is done running");
}
if (normalizing) {
throw new RuntimeException(
"Cannot add tasks if already started normalizing");
}
}
#Override
protected void runImpl() throws Exception {
normalizeProgress();
for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
int currentRelativeTime = entry.getValue();
CancelableTask currentTask = entry.getKey();
// Advance the state to the next one with the progress from the
// previous one.
state = new State(currentTask, currentRelativeTime, state.getProgress());
try {
currentTask.runUnsafe();
} catch (Exception e) {
if (isOneFailsAll) {
throw e;
}
Config.getLog().i("Task failed but continueing with other tasks", e);
}
}
state = new State(null, 0, 100);
}
private void normalizeProgress() {
normalizing = true;
int overall = 0;
for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
overall += entry.getValue();
}
double factor = overall == 0 ? 1 : (double)100 / overall;
for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
entry.setValue((int) (entry.getValue() * factor));
}
}
#Override
protected void cancelImpl() {
for (CancelableTask cancelable : cancelables.keySet()) {
cancelable.cancel();
}
}
#Override
public int getProgress() {
int progress = this.progress.get();
int stateProgress = state.getProgress();
this.progress.compareAndSet(progress, stateProgress); // Setting this value just for easier debugging. I has no meaning in CancelableCollection
return super.getProgress();
}
private static class State {
private CancelableTask currentTask;
private int currentRelativeTime;
private int progress;
public State(CancelableTask currentTask, int currentRelativeTime,
int progress) {
super();
this.currentTask = currentTask;
this.currentRelativeTime = currentRelativeTime;
this.progress = progress;
}
public int getProgress() {
return progress
+ (currentTask == null ? 0 : (int)(currentTask.getProgress()
* (double)currentRelativeTime / 100));
}
}
}
stop the thread or asynctask youre using or call this.finish