How to use SimpleTimeLimiter (Guava) with Vaadin

Viewed 502

I'm trying to call some code within the vaadin framework which is longer running that will update the screen using push, however if the process is taking too long I want to be able to cancel it.

With that in mind I'm trying to use Guava's SimpleTimeLimiter class but no matter what I do I can't seem to stop the Vaadin process from stopping. I've tried both to put the SimpleTimeLimiter inside UI.getCurrent().access() method and outside of it but they both just continue to execute the process even if SimpleTimeLimiter throws a TimeoutException. However if I use the same code with a normal thread it seems to work...

public static void limitExecutionTime(Consumer<UI> lambda)
{
    UI currentUI = UI.getCurrent();

    UI.getCurrent().access(() ->
    {
        try
        {
            SimpleTimeLimiter.create(Executors.newSingleThreadExecutor()).callWithTimeout(new Callable<Void>()
            {
                @Override
                public Void call()
                {
                    // This is needed to deal how Vaadin 8 handles UI's
                    UI.setCurrent(currentUI);
                    lambda.accept();
                    return null;
                }
            }, 1, TimeUnit.SECONDS);

        } catch (TimeoutException | InterruptedException | ExecutionException e) {
            NotificationUtils.showError("Execution took beyond the maximum allowed time.");
            currentUI.push();
        }
    });
}

In the above code if the method takes more than 1 second it will throw a TimeoutException and put up the notification window. However it will continue to execute the lambda.

As a result I've tried to do the opposite and put UI.getCurrent().access() in the public Void call() method but this had the exact same result...

1 Answers

You should call UI.access after your background task is ready to update it with some data. You use access method to do changes on the page that the user is viewing.

Background task execution

In your example, you are missing a way to pass task cancellation message to call method. In order to prepare for task cancellation from external event (for example cancel button click) then you need to take this into account in inside the task. The following example shows how you can offer cancel method using Future.cancel.

private void onCancelClick(Button.ClickEvent clickEvent) {
    // This method is called from Vaadin UI thread. We will signal
    // background task thread to stop.
    futureResult.cancel(true);
}

Inside the actual task this can be handled in the following ways

private void simulateLongAndSlowCalculation() {
    while (moreWorkTodo) {
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
        try {
            doSomeBlockingCallThatCanBeInterrupted();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }
}

Starting task and UI.access

When starting task then view should create task and submit it to executor service.

private void onButtonClick(Button.ClickEvent clickEvent) {
    // This runTask creates important link to current UI and the background task.
    // "this" object in these onTask methods is the UI object that we want
    // to update. We need to have someway to pass UI object to background
    // thread. UI.getCurrent() could be a parameter that is passed to the
    // task as well.
    Future<String> futureResult = taskService.runTask(
            this::onTaskDone,
            this::onTaskCancel,
            this::onTaskProgress);
    progressDialog = new ProgressDialog(futureResult);
    progressDialog.show();
}

Now UI.access method is only needed when we want to update UI. In this example, that can happen in the following cases

  1. Task completed successfully
  2. Task progress was updated
  3. Task got cancelled

Note that all of the following methods this refers to the UI object that started the task. So we are updating the correct UI with result and not some other user's UI.

You should not need to call UI.setCurrent in your code.

private void onTaskProgress(double progress) {
    logger.info("onTaskProgress: {}", progress);
    access(() -> progressDialog.setProgress(progress));
}

private void onTaskCancel() {
    logger.info("onTaskCancel");
    access(() -> {
        progressDialog.close();
        setResult("Cancelled");
    });
}

private void onTaskDone(String result) {
    logger.info("onTaskDone");
    access(() -> {
        progressDialog.close();
        setResult(result);
    });
}

Example project

I pushed another project to github that shows how to cancel a background task from cancel button:

https://github.com/m1kah/vaadin-background-task

Edit: Added sections about background tasks and UI.access. Updated example project link to another example.

Related