Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel background tasks #569

Merged
merged 17 commits into from Jun 9, 2013
Merged

Conversation

@rom1v
Copy link
Contributor

@rom1v rom1v commented Apr 22, 2013

Like someone asked on the mailing list, I think it would be useful to be able to cancel background tasks, typically in onStop():

    @Override
    protected void onStop() {
        super.onStop();
        boolean mayInterruptIfRunning = true;
        BackgroundExecutor.cancelAll("longtask", mayInterruptIfRunning);
    }

    @Background(id="longtask")
    public void doSomethingLong() {
        // ...
    }

I implemented it over my changes adding the background execution serialization feature (I changed a bit the implementation).

@Background now supports id (used for cancellation), delay (was already implemented) and serial (for serializing tasks) attributes, and any association of them.

As the background execution feature must be reliable, and the interactions between serializing and cancelling must behave correctly, I wrote a set of functional tests.

rom1v added 11 commits Apr 18, 2013
A ScheduledExecutorService *is* an Executor, so we can use the same to
avoids to create too many threads.

The execute(runnable) method becomes a particular case of
execute(runnable, delay), with delay=0.

As the user can change the executor, delayed executions are not
supported if the given executor does not support scheduling.

This unification paves the way for adding serial execution feature.
Adds an optional serial attribute to @background:

    @background(serial="some_id")

This idea comes from androidannotations#309:
androidannotations#309

The principle of this implementation is to keep a separate queue for
each serial identifier, and to give each task to the (unique) executor
only after the previous task with the same serial identifier (if any)
has completed execution.

It guarantees that all tasks with the same serial identifier will be
called in the order, one at a time (but not necessarily on the same
thread).

It is still compatible with the delay attribute. For example:

    @background(serial="some_id", delay=2000)

executes the task in background after at least 2 seconds *and* after the
previous task requested with serial="some_id" (if any) have completed
execution.
Test if actions are sequential as expected.
Avoid try/catch InterruptedException of Thread.sleep().
The previous implementation used a set for serial running and a map of
lists of tasks for managing the serial queues.

In practice, there will be very few parallel tasks, using maps and
creating/destroying lists is complex and unefficient.

A better approach is to use only a list of tasks (only the ones we need
to keep, having a non-null serial) and run through it sequentially for
retrieving tasks.

Moreover, it is more general, and paves the way for adding a task
cancellation feature.
Extract the surround-with-try-catch part of the anonymous Runnable
generation, in order to reuse it for generating other anonymous classes.
Instead of create a new Runnable instance which will be wrapped by a
Task instance, directly create a Task (which is a Runnable).
Adds an optional id attribute to @background:

    @background(id="some_id")

The user is able to cancel all background tasks having a specified id:

    BackgroundExecutor.cancelAll("some_id");
Tests the serialization and cancellation of background tasks.
If the executor is replaced by another not supporting cancellation (not
extending ExecutorService), it failed the first time a task with a
non-null id was requested.

Now, it just warns during cancellation.
@melnikovdv
Copy link

@melnikovdv melnikovdv commented Apr 26, 2013

Very cool, thx

@rom1v
Copy link
Contributor Author

@rom1v rom1v commented Apr 26, 2013

@melnikovdv I didn't see the issue #478 (because it was closed). I reference it here.

@rom1v
Copy link
Contributor Author

@rom1v rom1v commented May 19, 2013

this comment answers to a deleted comment by gebing

@gebing Which javac version do you use?

There is such a bug in Oracle JDK version which is fixed in 6u32.

@gebing
Copy link
Contributor

@gebing gebing commented May 19, 2013

sorry, it is my fault to compile with duplicate jars

@DayS
Copy link
Contributor

@DayS DayS commented May 21, 2013

I was looking at your PR and I saw all the comments in the code and commit descriptions. I was just... wow. Thanks for that 👍

However, we have to do lots of tests on this feature

@rom1v
Copy link
Contributor Author

@rom1v rom1v commented May 22, 2013

@DayS

I made this stupid code to test your feature:

But I only had one log... It seems that the computed delay is always near of Long.MAX_VALUE because it wasn't initiated

This bug was fixed in commit 5254f56. If you test cancel branch, it should work.

I could fix it by changing targetTime from long to Long and replacing line 203 by this:

/* compute the remaining delay */
int delay;
if (targetTime == null) {
   delay = 0;
} else {
   delay = Math.max(0, (int) (nextTask.targetTime - System.currentTimeMillis()));
}

I solved it a similar way, I just used 0 as special value instead of using a Long:

  • 0 is in the past (epoch) ;
  • even if date device is configured to be just before epoch (very unlikely), the probability to be exactly at time 0 is very low ;
  • it avoids to instantiate a Long and dereference it.

Another solution could be to use Long.MIN_VALUE as special value.

I accept your solution too, let me know what do you prefer.

@pyricau
Copy link
Contributor

@pyricau pyricau commented May 22, 2013

Thank you for contributing this and for going through all these reviews. I believe this is a very sensitive part of the code, and it's hard to get threading right, which is why everybody gets very sensitive about this.

This code looks good to me. What I'm missing now is more code using this. Ideally we'd need to put that into apps, see what are the use cases, and if that fits with what we usually want to do.

I think it makes sense to bring both pull requests into the code, but we should also start testing that in apps.

DayS added a commit that referenced this issue Jun 9, 2013
@DayS DayS merged commit 77cbcea into androidannotations:develop Jun 9, 2013
@DayS
Copy link
Contributor

@DayS DayS commented Jun 9, 2013

Thanks for this feature
I made all the tests I could think of and everything seems to work. Great job 👍

@rom1v
Copy link
Contributor Author

@rom1v rom1v commented Jun 9, 2013

Great! Thank you for your time and for merging ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

6 participants