Skip to content

Commit

Permalink
Wrap remove with UI.accessSynchronous and remove AsyncTask.sync
Browse files Browse the repository at this point in the history
  • Loading branch information
fluorumlabs committed Sep 27, 2018
1 parent 48e015f commit ca77ab0
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 78 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ui.access(() -> {
});
```
While it will make load time much faster, there is a problem there: since the UI is
blocked by computation thread, user will have to wait for finish before his actions
blocked by computation thread, user will have to wait for it to finish before his actions
will be processed. That is, he will see the same loading indicator if he, for example,
click some button that has server side click listener.

Expand Down Expand Up @@ -54,7 +54,7 @@ addBeforeLeaveListener(event -> ui.setPollingInterval(-1));
```
See how easy our 4 line snippet turns to 10 line monster? And what if we have
several of those worker threads? Moreover, even if we have push enabled, something
should terminate threads when it's result is not needed anymore (i.e. user has left the view).
should terminate threads when their results are not needed anymore (i.e. user has left the view).

But wander no more, there is an easy solution: **Async Manager**. It is really easy to use:
```java
Expand All @@ -68,7 +68,7 @@ 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.setPollingIntervals(200, 200, 200, 200, 200, 1000);
```

It is also possible to set custom exception handler if you
Expand All @@ -77,8 +77,8 @@ want some custom logging or exception reporting:
AsyncManager.setExceptionHandler(exception -> ...);
```

By default all worker threads are started by `ThreadPoolExecutor` which defaults
to pool size of 25 threads. You can access instance of executor with
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()`.

## Development instructions
Expand Down
34 changes: 9 additions & 25 deletions src/main/java/org/vaadin/flow/helper/AsyncManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.vaadin.external.org.slf4j.LoggerFactory;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.server.Command;

import java.io.Serializable;
import java.util.*;
Expand Down Expand Up @@ -49,31 +48,16 @@ public class AsyncManager implements Serializable {
* @return {@link AsyncTask}, associated with this action
*/
public static AsyncTask register(Component component, AsyncAction action) {
return register(component, false, action);
}

/**
* 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. Polling mode can be forced for this action even if push is enabled,
* which allows to use {@link AsyncTask#sync(Command)} for doing UI operations requiring access to {@code VaadinResponse}
* (for example, to add a cookie)
*
* @param component Component, where the action needs to be performed, typically your view
* @param forcePolling If <tt>true</tt>, polling will be used even if push is enabled
* @param action Action
* @return {@link AsyncTask}, associated with this action
*/
public static AsyncTask register(Component component, boolean forcePolling, AsyncAction action) {
Objects.requireNonNull(component);

AsyncTask asyncTask = new AsyncTask();
Optional<UI> uiOptional = component.getUI();
if (uiOptional.isPresent()) {
asyncTask.register(uiOptional.get(), component, forcePolling, action);
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, forcePolling, action);
asyncTask.register(attachEvent.getUI(), component, action);
});
}
return asyncTask;
Expand Down Expand Up @@ -173,8 +157,8 @@ private static Set<AsyncTask> getAsyncTasks(UI ui) {
/**
* Add {@link AsyncTask} to the {@link #asyncTasks} for current component
*
* @param ui Owning UI
* @param task Task
* @param ui Owning UI
* @param task Task
*/
static void addAsyncTask(UI ui, AsyncTask task) {
AsyncManager.getAsyncTasks(ui).add(task);
Expand All @@ -183,8 +167,8 @@ static void addAsyncTask(UI ui, AsyncTask task) {
/**
* Remove {@link AsyncTask} from the {@link #asyncTasks} for current component
*
* @param ui Owning UI
* @param task Task
* @param ui Owning UI
* @param task Task
*/
static void removeAsyncTask(UI ui, AsyncTask task) {
AsyncManager.getAsyncTasks(ui).remove(task);
Expand All @@ -193,7 +177,7 @@ static void removeAsyncTask(UI ui, AsyncTask task) {
/**
* Adjust polling interval for specified component.
*
* @param ui UI, associated with current task
* @param ui UI, associated with current task
*/
static void adjustPollingInterval(UI ui) {
int newInterval = AsyncManager.getAsyncTasks(ui).stream()
Expand Down
64 changes: 16 additions & 48 deletions src/main/java/org/vaadin/flow/helper/AsyncTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

/**
* Asynchronous task created by {@link AsyncManager#register(Component, AsyncAction)} or
* {@link AsyncManager#register(Component, boolean, AsyncAction)}.
* {@link AsyncManager#register(Component, AsyncAction)}.
*
* @author Artem Godin
* @see AsyncManager
Expand All @@ -22,24 +22,6 @@ public class AsyncTask {
AsyncTask() {
}

/**
* Perform command in {@code VaadinRequest} context. That requires the AsyncTask to be registered
* in a polling mode. The command will be executed in PollEvent listener meaning that
* {@link UI#accessSynchronously(Command)} is not needed.
*
* @param command Command to run
*/
public void sync(Command command) {
if (parentUI == null) return;
if (missedPolls == PUSH_ACTIVE) {
throw new IllegalStateException("Sync is called but task is not in polling mode");
}
if (syncCommand != null) {
throw new IllegalStateException("Sync can be used only once");
}
syncCommand = command;
}

/**
* Perform command in UI context. It uses {@link UI#accessSynchronously(Command)} internally.
*
Expand All @@ -49,9 +31,9 @@ public void push(Command command) {
if (parentUI == null) return;
if (missedPolls == PUSH_ACTIVE && parentUI.getPushConfiguration().getPushMode() == PushMode.MANUAL) {
parentUI.accessSynchronously(() -> {
command.execute();
command.execute();
parentUI.push();
});
});
} else {
// Automatic -- changes will be pushed automatically
// Disabled -- we're using polling and this is called
Expand Down Expand Up @@ -107,11 +89,6 @@ private void execute() {
*/
private Registration beforeLeaveListenerRegistration;

/**
* Command that needs to be executed in VaadinRequest context
*/
private Command syncCommand;

/**
* Number of poll events happened while action is executing, or {@link #PUSH_ACTIVE} if
* push is used for current task
Expand All @@ -121,13 +98,12 @@ private void execute() {
/**
* Register action
*
* @param ui UI owning current view
* @param forcePolling <tt>true</tt> if polling must be used
* @param action Action
* @param ui UI owning current view
* @param action Action
*/
void register(UI ui, Component component, boolean forcePolling, AsyncAction action) {
void register(UI ui, Component component, AsyncAction action) {
this.parentUI = ui;
if (!forcePolling && ui.getPushConfiguration().getPushMode().isEnabled()) {
if (ui.getPushConfiguration().getPushMode().isEnabled()) {
registerPush(component, action);
} else {
registerPoll(component, action);
Expand Down Expand Up @@ -191,9 +167,7 @@ private FutureTask<AsyncTask> createFutureTask(AsyncAction action) {
// Dump
AsyncManager.handleException(e);
} finally {
if (syncCommand == null && !Thread.currentThread().isInterrupted()) {
remove();
}
remove();
}
}, this);
}
Expand All @@ -213,17 +187,19 @@ private synchronized void remove() {
AsyncManager.removeAsyncTask(parentUI, this);
// Polling interval needs to be adjusted if task is finished
try {
parentUI.accessSynchronously(() -> AsyncManager.adjustPollingInterval(parentUI));
parentUI.accessSynchronously(() -> {
AsyncManager.adjustPollingInterval(parentUI);

if (componentDetachListenerRegistration != null) componentDetachListenerRegistration.remove();
if (uiDetachListenerRegistration != null) uiDetachListenerRegistration.remove();
if (pollingListenerRegistration != null) pollingListenerRegistration.remove();
if (beforeLeaveListenerRegistration != null) beforeLeaveListenerRegistration.remove();
});
} catch (UIDetachedException ignore) {
// ignore detached ui -- there will be no polling events for them anyway
}

parentUI = null;

if (componentDetachListenerRegistration != null) componentDetachListenerRegistration.remove();
if (uiDetachListenerRegistration != null) uiDetachListenerRegistration.remove();
if (pollingListenerRegistration != null) pollingListenerRegistration.remove();
if (beforeLeaveListenerRegistration != null) beforeLeaveListenerRegistration.remove();
}
}

Expand Down Expand Up @@ -276,13 +252,5 @@ private void onPollEvent(PollEvent event) {
missedPolls++;
AsyncManager.adjustPollingInterval(parentUI);
}
if (syncCommand != null) {
try {
syncCommand.execute();
} catch (RuntimeException e) {
AsyncManager.handleException(e);
}
remove();
}
}
}

0 comments on commit ca77ab0

Please sign in to comment.