Skip to content
This repository has been archived by the owner on Feb 26, 2023. It is now read-only.

BackgroundExecutor may cause deadlock for serial tasks #1689

Closed
guitcastro opened this issue Feb 4, 2016 · 0 comments · Fixed by #1803
Closed

BackgroundExecutor may cause deadlock for serial tasks #1689

guitcastro opened this issue Feb 4, 2016 · 0 comments · Fixed by #1803
Labels

Comments

@guitcastro
Copy link
Contributor

First of all, this issue only happens when running robolectric test because I override the Executor, but it can happen in production if tasks are executed too fast.

I have the following executor, that simple execute tasks in serial for test purpose:

public class UIThreadExecutor implements Executor{

    @Override
    public void execute(@NonNull Runnable command) {
        try {
            command.run();
        }catch (Exception ignored){
        }
    }
}

And I have the follow test:


    @Test
    public void testSerial(){

        BackgroundExecutor.setExecutor(new UIThreadExecutor());

        for (int i = 0; i < 10; i++){
            activity.testSerial();
        }
        await().atMost(10, TimeUnit.SECONDS).until(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return  activity.count == 10;
            }
        });
    }

The activity testSerial implementation is:

    public static int count = 0;
    @Background(serial = "test")
    public void testSerial(){
        count++;
        System.out.println("count = " + count);
    }

If you run this test, it will cause dead lock. Digging in the code I was able find out why.

Look at this code:

public static synchronized void execute(Task task) {
        Future<?> future = null;
        if (task.serial == null || !hasSerialRunning(task.serial)) {
            task.executionAsked = true;
            future = directExecute(task, task.remainingDelay);
        }
        if (task.id != null || task.serial != null) {
            /* keep task */
            task.future = future;
            TASKS.add(task);
        }
    }

As you see the task is add after the directExecute is called. Let's take a look at the Task clean post executed code:

    private void postExecute() {
            if (id == null && serial == null) {
                /* nothing to do */
                return;
            }
            CURRENT_SERIAL.set(null);
            synchronized (BackgroundExecutor.class) {
                /* execution complete */
                TASKS.remove(this);

                if (serial != null) {
                    Task next = take(serial);
                    if (next != null) {
                        if (next.remainingDelay != 0) {
                            /* the delay may not have elapsed yet */
                            next.remainingDelay = Math.max(0L, targetTimeMillis - System.currentTimeMillis());
                        }
                        /* a task having the same serial was queued, execute it */
                        BackgroundExecutor.execute(next);
                    }
                }
            }
        }

As you see in this code we remove the task after the execution, but my execution always finish before the Task is added to the stack. Causing a dead lock, because the task is added to to list and never removed.

One possible solution would be simple check if the task is already finished (managed) before add it to the list: if ( (task.id != null || task.serial != null) && !task.managed.get())

guitcastro pushed a commit to guitcastro/androidannotations that referenced this issue Feb 4, 2016
rom1v added a commit to rom1v/androidannotations that referenced this issue Jun 15, 2016
rom1v added a commit to rom1v/androidannotations that referenced this issue Jun 15, 2016
BackgroundExecutor assumed that its executor was asynchronous, so that
in execute(Task), a task could not be removed before it has been added
to the TASKS list.

However, this assumption was wrong with a synchronous executor:
directExecute(…) would execute the task synchronously, leading to call
Task#postExecute(), that removes the task from TASKS. Immediately after
the execution, directExecute(…) would add the task to TASKS, which let
the BackgroundExecutor in an incorrect state.

Therefore, add the task to the TASKS list before the execution, so that
it will also work with synchronous executors.

Fixes androidannotations#1689.
<androidannotations#1689>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants