Skip to content

Commit

Permalink
#1 Add AsyncManager.setExecutorService()
Browse files Browse the repository at this point in the history
  • Loading branch information
fluorumlabs committed Sep 28, 2018
1 parent aa9896d commit 27eab6e
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 162 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,17 @@ termination. For polling mode it also supports
dynamic polling intervals: i.e. you can have 5 polls per second in the
first second and then throttle it to send poll requests once per second:
```java
AsyncManager.setPollingIntervals(200, 200, 200, 200, 200, 1000);
AsyncManager.getInstance().setPollingIntervals(200, 200, 200, 200, 200, 1000);
```

It is also possible to set custom exception handler if you
want some custom logging or exception reporting:
```java
AsyncManager.setExceptionHandler(exception -> ...);
AsyncManager.getInstance().setExceptionHandler(exception -> ...);
```

Note: By default all worker threads are started by `ThreadPoolExecutor` which defaults
to pool size of 25 threads. If you want to increase it or change other settings, you can access instance of executor with
`AsyncManager.getExecutor()`.
to pool size of 25 threads. You can change that with `AsyncManager.getInstance().setExecutorService()`.

## Installing with Maven

Expand All @@ -98,7 +97,7 @@ to pool size of 25 threads. If you want to increase it or change other settings,
<dependency>
<groupId>org.vaadin.helper</groupId>
<artifactId>async-manager</artifactId>
<version>1.0.0-alpha1</version>
<version>1.0.0-alpha2</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.vaadin.helper</groupId>
<artifactId>async-manager</artifactId>
<version>1.0.0-alpha1</version>
<version>1.0.0-alpha2</version>
<name>Async Manager</name>
<description>Async Manager for Vaadin Flow</description>

Expand Down
1 change: 0 additions & 1 deletion src/main/java/org/vaadin/flow/helper/AsyncAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*
* @author Artem Godin
* @see AsyncManager#register(Component, AsyncAction)
* @see AsyncManager#register(Component, boolean, AsyncAction)
*/
@FunctionalInterface
public interface AsyncAction {
Expand Down
210 changes: 126 additions & 84 deletions src/main/java/org/vaadin/flow/helper/AsyncManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

/**
Expand All @@ -31,56 +29,125 @@
* of interrupting tasks when the view is detached from UI or if the UI leaves current view.
* <p>
* Initial configuration of AsyncManager can be done using {@link AsyncManager#setExceptionHandler(Consumer)},
* {@link AsyncManager#setPollingIntervals(int...)} and {@link AsyncManager#getExecutor()} static methods.
* {@link AsyncManager#setPollingIntervals(int...)} and {@link AsyncManager#setExecutorService(ExecutorService)} static methods.
*
* @author Artem Godin
* @see AsyncTask
* @see AsyncAction
*/
public class AsyncManager implements Serializable {
public class AsyncManager {
//--- Defaults

/**
* Default pool size (25 threads)
*/
private static final int DEFAULT_POOL_SIZE = 25;
/**
* Default polling intervals (200 ms)
*/
private static final int[] DEFAULT_POLLING_INTERVALS = {200};

//--- The one and only instance of AsyncManager

/**
* Instance of AsyncManager
*/
private static AsyncManager instance;

//-- Private fields

/**
* List of all registered {@link AsyncTask} per component instance
*/
private Map<UI, Set<AsyncTask>> asyncTasks = Collections.synchronizedMap(new WeakHashMap<>());
/**
* Exception handler
*/
private Consumer<Exception> exceptionHandler = AsyncManager::logException;
/**
* Instance of {@link ExecutorService} used for asynchronous tasks
*/
private ExecutorService executorService = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
/**
* Polling intervals
*/
private int[] pollingIntervals = DEFAULT_POLLING_INTERVALS;

private AsyncManager() { // Not directly instantiatable
}

//--- Static methods

/**
* Get instance of AsyncManager
*
* @return Instance of AsyncManager
*/
public static synchronized AsyncManager getInstance() {
if (instance == null) {
instance = new AsyncManager();
}
return instance;
}

/**
* Register and start a new deferred action. Action are started immediately in a separate thread and do not hold
* {@code UI} or {@code VaadinSession} locks.
* <p>
* Shorthand for {@code AsyncManager.getInstance().registerAsync(component, action)}
*
* @param component Component, where the action needs to be performed, typically your view
* @param action Action
* @return {@link AsyncTask}, associated with this action
*/
public static AsyncTask register(Component component, AsyncAction action) {
Objects.requireNonNull(component);

AsyncTask asyncTask = new AsyncTask();
UI ui = component.getUI().orElse(null);
if (ui != null) {
asyncTask.register(ui, component, action);
} else {
component.addAttachListener(attachEvent -> {
attachEvent.unregisterListener();
asyncTask.register(attachEvent.getUI(), component, action);
});
}
return asyncTask;
return getInstance().registerAsync(component, action);
}

/**
* Get a {@link ThreadPoolExecutor} used for asynchronous task execution. This can be used to
* adjust thread pool size.
* Default exception handler that simply logs the exception
*
* @return static instance of {@link ThreadPoolExecutor}
* @param e Exception to handle
*/
public static ThreadPoolExecutor getExecutor() {
return executor;
private static void logException(Throwable e) {
LoggerFactory.getLogger(AsyncManager.class.getName()).warn(e.getMessage(), e);
}

//--- Getters and setters

/**
* Set custom exception handler for exceptions thrown in async tasks if you need custom logging or
* reporting
*
* @param handler Exception handler to set
*/
public static void setExceptionHandler(Consumer<Exception> handler) {
AsyncManager.exceptionHandler = handler;
public void setExceptionHandler(Consumer<Exception> handler) {
exceptionHandler = handler;
}

/**
* Get a {@link ExecutorService} used for asynchronous task execution.
*
* @return static instance of {@link ExecutorService}
*/
ExecutorService getExecutorService() {
return executorService;
}

/**
* Set {@link ExecutorService} to be used for asynchronous task execution.
*/
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}

/**
* Get polling intervals
*
* @return polling intervals in milliseconds
*/
int[] getPollingIntervals() {
return pollingIntervals;
}

/**
Expand All @@ -94,63 +161,46 @@ public static void setExceptionHandler(Consumer<Exception> handler) {
*
* @param milliseconds Polling intervals in milliseconds
*/
public static void setPollingIntervals(int... milliseconds) {
public void setPollingIntervals(int... milliseconds) {
if (milliseconds.length == 0) {
AsyncManager.pollingIntervals = DEFAULT_POLLING_INTERVALS;
pollingIntervals = DEFAULT_POLLING_INTERVALS;
}
AsyncManager.pollingIntervals = milliseconds;
}

private AsyncManager() { // Not directly instantiatable
pollingIntervals = milliseconds;
}

/**
* Default pool size (25 threads)
*/
private static final int DEFAULT_POOL_SIZE = 25;

/**
* Default polling intervals (200 ms)
*/
private static final int[] DEFAULT_POLLING_INTERVALS = {200};

/**
* Instance of {@link ThreadPoolExecutor} used for asynchronous tasks
*/
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(DEFAULT_POOL_SIZE, DEFAULT_POOL_SIZE,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

/**
* List of all registered {@link AsyncTask} per component instance
*/
private static Map<UI, Set<AsyncTask>> asyncTasks = Collections.synchronizedMap(new WeakHashMap<>());

/**
* Exception handler
*/
private static Consumer<Exception> exceptionHandler = AsyncManager::logException;

/**
* Polling intervals
*/
private static int[] pollingIntervals = DEFAULT_POLLING_INTERVALS;

/**
* Default exception handler that simply logs the exception
* Register and start a new deferred action. Action are started immediately in a separate thread and do not hold
* {@code UI} or {@code VaadinSession} locks.
*
* @param e Exception to handle
* @param component Component, where the action needs to be performed, typically your view
* @param action Action
* @return {@link AsyncTask}, associated with this action
*/
private static void logException(Throwable e) {
LoggerFactory.getLogger(AsyncManager.class.getName()).warn(e.getMessage(), e);
public AsyncTask registerAsync(Component component, AsyncAction action) {
Objects.requireNonNull(component);

AsyncTask asyncTask = new AsyncTask(this);
UI ui = component.getUI().orElse(null);
if (ui != null) {
asyncTask.register(ui, component, action);
} else {
component.addAttachListener(attachEvent -> {
attachEvent.unregisterListener();
asyncTask.register(attachEvent.getUI(), component, action);
});
}
return asyncTask;
}

//--- Implementation

/**
* Get list of active asynchronous tasks for specified component
*
* @param ui Owning UI
* @return Set of {@link AsyncTask}
*/
private static Set<AsyncTask> getAsyncTasks(UI ui) {
private Set<AsyncTask> getAsyncTasks(UI ui) {
return asyncTasks.computeIfAbsent(ui, parentComponent -> Collections.synchronizedSet(new HashSet<>()));
}

Expand All @@ -160,8 +210,8 @@ private static Set<AsyncTask> getAsyncTasks(UI ui) {
* @param ui Owning UI
* @param task Task
*/
static void addAsyncTask(UI ui, AsyncTask task) {
AsyncManager.getAsyncTasks(ui).add(task);
void addAsyncTask(UI ui, AsyncTask task) {
getAsyncTasks(ui).add(task);
}

/**
Expand All @@ -170,17 +220,17 @@ static void addAsyncTask(UI ui, AsyncTask task) {
* @param ui Owning UI
* @param task Task
*/
static void removeAsyncTask(UI ui, AsyncTask task) {
AsyncManager.getAsyncTasks(ui).remove(task);
void removeAsyncTask(UI ui, AsyncTask task) {
getAsyncTasks(ui).remove(task);
}

/**
* Adjust polling interval for specified component.
*
* @param ui UI, associated with current task
*/
static void adjustPollingInterval(UI ui) {
int newInterval = AsyncManager.getAsyncTasks(ui).stream()
void adjustPollingInterval(UI ui) {
int newInterval = getAsyncTasks(ui).stream()
.map(AsyncTask::getPollingInterval)
.sorted()
.findFirst().orElse(Integer.MAX_VALUE);
Expand All @@ -200,16 +250,8 @@ static void adjustPollingInterval(UI ui) {
*
* @param e Exception to handle
*/
static void handleException(Exception e) {
AsyncManager.exceptionHandler.accept(e);
void handleException(Exception e) {
exceptionHandler.accept(e);
}

/**
* Get polling intervals
*
* @return polling intervals in milliseconds
*/
static int[] getPollingIntervals() {
return pollingIntervals;
}
}
Loading

0 comments on commit 27eab6e

Please sign in to comment.