Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Feb 26, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Background {
long delay() default 0;
int delay() default 0; /* in milliseconds */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let users know that delay is exprimed in ms by puting this information in the javadoc (i mean using /** ... */ above this line)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit rom1v@4161c62.


String serial() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,199 @@
*/
package org.androidannotations.api;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class BackgroundExecutor {

private static Executor executor = Executors.newCachedThreadPool();
private static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
private static Executor executor = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());

/*
* serialRunning is used as a lock in synchronized blocks for both
* serialRunning and serialQueues access
*/

/* Set of queueIds having a currently running task */
private static final Set<String> serialRunning = new HashSet<String>();

/* Tasks queues for each serial */
private static final Map<String, List<Task>> serialQueues = new HashMap<String, List<Task>>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

java.util.List is not a queue, please use java.util.Queue in this purpose.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is debatable.

In theory, you're right. However, the Java API does not provide a queue implementation backed by an array (except for concurrency).
If the queue is intended to contain only very few items, a LinkedList would be less efficient (more instantiations and references).

OK, we don't care, it's fast enough, but that is the reason why I used an ArrayList. I would have accepted to use a Queue (like a LinkedList).

But anyway, it was removed by 5254f56, and the new variable tasks is a list, not a queue.


/**
* Execute a task after (at least) the given delay <strong>and</strong>
* after all tasks added with the same non-null <code>serial</code> (if any)
* have completed execution.
*
* @param runnable
* the task to execute
* @param delay
* the time from now to delay execution, in milliseconds
* @param serial
* the serial queue to use (<code>null</code> or <code>""</code>
* for no serial execution)
* @throws IllegalArgumentException
* if <code>delay</code> is strictly positive and the current
* executor does not support scheduling (if
* {@link #setExecutor(Executor)} has been called with such an
* executor)
*/
public static void execute(Runnable runnable, int delay, String serial) {
/* "" means null (a default annotation String value cannot be null) */
if (serial == null || serial.isEmpty()) {
if (delay > 0) {
/* no serial, but a delay: schedule the task */
if (!(executor instanceof ScheduledExecutorService)) {
throw new IllegalArgumentException("The executor set does not support scheduling");
}
((ScheduledExecutorService) executor).schedule(runnable, delay, TimeUnit.MILLISECONDS);
} else {
/* no serial, no delay: execute now */
executor.execute(runnable);
}
} else {
/* serial is defined, the delay is managed by Task */
Task task = new Task(runnable, delay, serial);

synchronized (serialRunning) {
if (serialRunning.contains(serial)) {
/* a task for this serial is already running, queue this one */
List<Task> queue = serialQueues.get(serial);
if (queue == null) {
/* the queue does not exist yet */
queue = new ArrayList<Task>();
serialQueues.put(serial, queue);
}
/* queue the task for later execution */
queue.add(task);
} else {
/* mark this serial as having a running task */
serialRunning.add(serial);
/* execute the task (a wrapper for runnable) now */
execute(task, delay); /* do not pass serial here */
}
}
}
}

/**
* Execute a task.
*
* Equivalent to {@link #execute(Runnable, int, String) execute(runnable, 0,
* null)}.
*
* @param runnable
* the task to execute
*/
public static void execute(Runnable runnable) {
executor.execute(runnable);
execute(runnable, 0, null);
}

/**
* Execute a task after the given delay.
*
* Equivalent to {@link #execute(Runnable, int, String) execute(runnable,
* delay, null)}.
*
* @param runnable
* the task to execute
* @param delay
* the time from now to delay execution, in milliseconds
* @throws IllegalArgumentException
* if <code>delay</code> is strictly positive and the current
* executor does not support scheduling (if
* {@link #setExecutor(Executor)} has been called with such an
* executor)
*/
public static void execute(Runnable runnable, int delay) {
execute(runnable, delay, null);
}

/**
* Execute a task after all tasks added with the same non-null
* <code>serial</code> (if any) have completed execution.
*
* Equivalent to {@link #execute(Runnable, int, String) execute(runnable, 0,
* serial)}.
*
* @param runnable
* the task to execute
* @param serial
* the serial queue to use (<code>null</code> or <code>""</code>
* for no serial execution)
*/
public static void execute(Runnable runnable, String serial) {
execute(runnable, 0, serial);
}

/**
* Change the executor.
*
* Note that if the given executor is not a {@link ScheduledExecutorService}
* then executing a task after a delay will not be supported anymore.
*
* @param executor
* the new executor
*/
public static void setExecutor(Executor executor) {
BackgroundExecutor.executor = executor;
}

public static void executeDelayed(Runnable runnable, long delay) {
scheduledExecutor.schedule(runnable, delay, TimeUnit.MILLISECONDS);
}
private static class Task implements Runnable {

Runnable runnable;
long targetTime; /* in milliseconds since epoch */
String serial;

Task(Runnable runnable, int delay, String serial) {
this.runnable = runnable;
if (delay > 0) {
targetTime = System.currentTimeMillis() + delay;
}
this.serial = serial;
}

@Override
public void run() {
try {
runnable.run();
} finally {
/* handle next tasks */
postExecute();
}
}

private void postExecute() {
synchronized (serialRunning) {
List<Task> queue = serialQueues.get(serial);
if (queue == null) {
/* no task is queue for this serial, mark it as not running */
serialRunning.remove(serial);
} else {
/* queue is not empty, retrieve the oldest queued task */
Task nextTask = queue.remove(0);

if (queue.isEmpty()) {
/* no more tasks in the queue */
serialQueues.remove(serial);
}

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

/* execute the next task */
execute(nextTask, delay); /* do not pass serial here */
}
}
}

public static void setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
BackgroundExecutor.scheduledExecutor = scheduledExecutor;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,13 @@ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) t
{
// Execute Runnable
Background annotation = element.getAnnotation(Background.class);
long delay = annotation.delay();
int delay = annotation.delay();
String serial = annotation.serial();

JClass backgroundExecutorClass = holder.refClass(BackgroundExecutor.class);
JInvocation executeCall;

if (delay == 0) {
executeCall = backgroundExecutorClass.staticInvoke("execute").arg(_new(anonymousRunnableClass));
} else {
executeCall = backgroundExecutorClass.staticInvoke("executeDelayed").arg(_new(anonymousRunnableClass)).arg(lit(delay));
}
executeCall = backgroundExecutorClass.staticInvoke("execute").arg(_new(anonymousRunnableClass)).arg(lit(delay)).arg(lit(serial));

delegatingMethod.body().add(executeCall);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -51,4 +57,43 @@ public void backgroundDelegatesToExecutor() {
verify(executor).execute(Mockito.<Runnable>any());
}

/**
* Start several requests which add an item to a list in background, with
* "@Background" serial attribute enabled, so the requests must be executed
* sequentially.
*
* Once all tasks have completed execution, check if the items in the list
* are ordered.
*/
@Test
public void serializedBackgroundTasks() {
/* number of items to add to the list */
final int NB_ADD = 10;

/* set an executor with 4 threads */
BackgroundExecutor.setExecutor(Executors.newFixedThreadPool(4));

/* the calls are serialized, but not necessarily on the same thread, so we
* need to synchronize to avoid cache effects */
List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());

/* sem.acquire() will be unlocked exactly after NB_ADD releases */
Semaphore sem = new Semaphore(1 - NB_ADD);

/* execute NB_ADD requests to add an item to the list */
for (int i = 0; i < NB_ADD; i++) {
activity.addSerializedBackgroundMethod(list, i, sem);
}

try {
/* wait for all tasks to be completed */
sem.acquire();

/* check if list items are in the right order */
for (int i = 0; i < NB_ADD; i++) {
Assert.assertEquals("Items must be in order", i, (int) list.get(i));
}
} catch (InterruptedException e) {}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Semaphore;

import org.androidannotations.annotations.Background;
import org.androidannotations.annotations.EActivity;
Expand All @@ -27,6 +29,7 @@
import org.androidannotations.test15.instancestate.MySerializableBean;

import android.app.Activity;
import android.os.SystemClock;

@EActivity
public class ThreadActivity extends Activity {
Expand All @@ -46,6 +49,15 @@ void emptyDelayedBackgroundMethod() {

}

@Background(serial="test")
void addSerializedBackgroundMethod(List<Integer> list, int i, Semaphore sem) {
/* wait a random delay (between 0 and 20 milliseconds) to increase the
* probability of wrong order if buggy */
SystemClock.sleep(new Random().nextInt(20));
list.add(i);
sem.release();
}

@UiThread
void objectUiMethod(Object param) {

Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.