Browse Source

Suppress unavoidable UIDetachedException (#11146)

* Suppress unavoidable UIDetachedException

Fixes #11144
tags/8.7.0.alpha1
Leif Åstrand 5 years ago
parent
commit
199bff6db1

+ 37
- 0
server/src/main/java/com/vaadin/server/ErrorHandlingRunnable.java View File

@@ -16,6 +16,7 @@
package com.vaadin.server;

import java.io.Serializable;
import java.util.Objects;

/**
* Defines the interface to handle exceptions thrown during the execution of a
@@ -28,6 +29,8 @@ public interface ErrorHandlingRunnable extends Runnable, Serializable {

/**
* Handles exceptions thrown during the execution of a FutureAccess.
* Exceptions thrown by this method are handled by the default error
* handler.
*
* @since 7.1.8
* @param exception
@@ -35,4 +38,38 @@ public interface ErrorHandlingRunnable extends Runnable, Serializable {
*/
public void handleError(Exception exception);

/**
* Process the given exception in the context of the given runnable. If the
* runnable extends {@link ErrorHandlingRunnable}, then the exception is
* passed to {@link #handleError(Exception)} and null is returned. If
* {@link #handleError(Exception)} throws an exception, that exception is
* returned. If the runnable does not extend {@link ErrorHandlingRunnable},
* then the original exception is returned.
*
* @since
* @param runnable
* the runnable for which the exception should be processed, not
* <code>null</code>
* @param exception
* the exception to process, not <code>null</code>
* @return the resulting exception, or <code>null</code> if the exception is
* fully processed
*/
public static Exception processException(Runnable runnable,
Exception exception) {
Objects.requireNonNull(runnable, "The runnable cannot be null.");
if (runnable instanceof ErrorHandlingRunnable) {
ErrorHandlingRunnable errorHandlingRunnable = (ErrorHandlingRunnable) runnable;

try {
errorHandlingRunnable.handleError(exception);
return null;
} catch (Exception exceptionFromHandler) {
return exceptionFromHandler;
}
}

return exception;
}

}

+ 7
- 1
server/src/main/java/com/vaadin/server/VaadinService.java View File

@@ -40,6 +40,7 @@ import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@@ -2047,8 +2048,13 @@ public abstract class VaadinService implements Serializable {

try {
pendingAccess.get();

} catch (Exception exception) {
if (exception instanceof ExecutionException) {
Throwable cause = exception.getCause();
if (cause instanceof Exception) {
exception = (Exception) cause;
}
}
pendingAccess.handleError(exception);
}
}

+ 3
- 4
server/src/main/java/com/vaadin/server/VaadinSession.java View File

@@ -123,11 +123,10 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
public void handleError(Exception exception) {
try {
if (runnable instanceof ErrorHandlingRunnable) {
ErrorHandlingRunnable errorHandlingRunnable = (ErrorHandlingRunnable) runnable;
exception = ErrorHandlingRunnable.processException(runnable,
exception);

errorHandlingRunnable.handleError(exception);
} else {
if (exception != null) {
ErrorEvent errorEvent = new ErrorEvent(exception);

ErrorHandler errorHandler = ErrorEvent

+ 36
- 8
server/src/main/java/com/vaadin/ui/UI.java View File

@@ -230,8 +230,8 @@ public abstract class UI extends AbstractSingleComponentContainer
}
json.append("]}");
getRpcProxy(DebugWindowClientRpc.class)
.reportLayoutProblems(json.toString());
}
.reportLayoutProblems(json.toString());
}

@Override
public void showServerDesign(Connector connector) {
@@ -1567,17 +1567,45 @@ public abstract class UI extends AbstractSingleComponentContainer
@Override
public void handleError(Exception exception) {
try {
if (runnable instanceof ErrorHandlingRunnable) {
ErrorHandlingRunnable errorHandlingRunnable = (ErrorHandlingRunnable) runnable;

errorHandlingRunnable.handleError(exception);
} else {
exception = ErrorHandlingRunnable.processException(runnable,
exception);

if (exception instanceof UIDetachedException) {
assert session != null;
/*
* UI was detached after access was run, but before
* accessSynchronously. Furthermore, there wasn't an
* ErrorHandlingRunnable that handled the exception.
*/
getLogger().log(Level.WARNING,
"access() task ignored because UI got detached after the task was enqueued."
+ " To suppress this message, change the task to implement {} and make it handle {}."
+ " Affected task: {}",
new Object[] {
ErrorHandlingRunnable.class.getName(),
UIDetachedException.class.getName(),
runnable });
} else if (exception != null) {
/*
* If no ErrorHandlingRunnable, or if it threw an
* exception of its own.
*/
ConnectorErrorEvent errorEvent = new ConnectorErrorEvent(
UI.this, exception);

ErrorHandler errorHandler = com.vaadin.server.ErrorEvent
.findErrorHandler(UI.this);

if (errorHandler == null && getSession() == null) {
/*
* Special case where findErrorHandler(UI) cannot
* find the session handler because the UI has
* recently been detached.
*/
errorHandler = com.vaadin.server.ErrorEvent
.findErrorHandler(session);
}

if (errorHandler == null) {
errorHandler = new DefaultErrorHandler();
}
@@ -1699,7 +1727,7 @@ public abstract class UI extends AbstractSingleComponentContainer
// If pushMode is disabled then there should never be a pushConnection;
// if enabled there should always be
assert (pushConnection == null)
^ getPushConfiguration().getPushMode().isEnabled();
^ getPushConfiguration().getPushMode().isEnabled();

if (pushConnection == this.pushConnection) {
return;

+ 5
- 0
uitest/src/main/java/com/vaadin/tests/components/AbstractTestUI.java View File

@@ -177,6 +177,11 @@ public abstract class AbstractTestUI extends UI {
getLayout().addComponent(c);
}

public void addComponent(Component c, String id) {
c.setId(id);
addComponent(c);
}

public void addComponents(Component... c) {
getLayout().addComponents(c);
}

+ 78
- 0
uitest/src/main/java/com/vaadin/tests/components/ui/DetachedAccessErrorHandling.java View File

@@ -0,0 +1,78 @@
package com.vaadin.tests.components.ui;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import com.vaadin.annotations.Widgetset;
import com.vaadin.server.Constants;
import com.vaadin.server.ErrorHandler;
import com.vaadin.server.ErrorHandlingRunnable;
import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.UIDetachedException;

@Widgetset(Constants.DEFAULT_WIDGETSET)
public class DetachedAccessErrorHandling extends AbstractTestUI {

private static final Runnable NOP = () -> {
};

private static final class ListErrorHandler implements ErrorHandler {
private List<com.vaadin.server.ErrorEvent> errors = new CopyOnWriteArrayList<>();

@Override
public void error(com.vaadin.server.ErrorEvent event) {
errors.add(event);
}
}

@Override
protected void setup(VaadinRequest request) {
ListErrorHandler errorHandler = ensureErrorHandlerSet();

addComponent(new Button("Show errors", event -> {
errorHandler.errors.forEach(error -> {
Label errorLabel = new Label(error.getThrowable().getMessage());
errorLabel.setStyleName("errorLabel");
addComponent(errorLabel);
});
}), "show");

addComponent(new Button("Add simple detach listener", event -> {
addDetachListener(detachEvent -> access(NOP));
}), "simple");

addComponent(new Button("Add error handling detach listener", event -> {
addDetachListener(detachEvent -> {
access(new ErrorHandlingRunnable() {
@Override
public void run() {
}

@Override
public void handleError(Exception exception) {
if (exception instanceof UIDetachedException) {
UIDetachedException ignore = (UIDetachedException) exception;
} else {
throw new RuntimeException(exception);
}
}
});
});
}), "handling");
}

private ListErrorHandler ensureErrorHandlerSet() {
ErrorHandler currentErrorHandler = getSession().getErrorHandler();

if (!(currentErrorHandler instanceof ListErrorHandler)) {
currentErrorHandler = new ListErrorHandler();
getSession().setErrorHandler(currentErrorHandler);
}

return (ListErrorHandler) currentErrorHandler;
}

}

+ 22
- 0
uitest/src/main/java/com/vaadin/tests/components/ui/UIAccessExceptionHandling.java View File

@@ -6,6 +6,7 @@ import java.util.concurrent.Future;

import com.vaadin.server.DefaultErrorHandler;
import com.vaadin.server.ErrorHandler;
import com.vaadin.server.ErrorHandlingRunnable;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.tests.components.AbstractTestUIWithLog;
@@ -70,6 +71,27 @@ public class UIAccessExceptionHandling extends AbstractTestUIWithLog
CurrentInstance.restoreInstances(instances);
}));

addComponent(
new Button("Throw through ErrorHandlingRunnable", event -> {
access(new ErrorHandlingRunnable() {
@Override
public void run() {
log.clear();
throw new NullPointerException();
}

@Override
public void handleError(Exception exception) {
// "Handle" other exceptions, but leave NPE for
// default handler
if (exception instanceof NullPointerException) {
NullPointerException npe = (NullPointerException) exception;
throw npe;
}
}
});
}));

addComponent(new Button("Clear", event -> log.clear()));
}


+ 31
- 0
uitest/src/test/java/com/vaadin/tests/components/ui/DetachedAccessErrorHandlingTest.java View File

@@ -0,0 +1,31 @@
package com.vaadin.tests.components.ui;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.By;

import com.vaadin.testbench.elements.ButtonElement;
import com.vaadin.tests.tb3.SingleBrowserTest;

public class DetachedAccessErrorHandlingTest extends SingleBrowserTest {
@Test
public void testDetachedErrorHandling_pageOpen_noErrors() {
openTestURL();

$(ButtonElement.class).id("simple").click();
assertNoErrors();

// The thing to really test here is that nothing is logged to stderr,
// but that's not practical to detect
$(ButtonElement.class).id("handling").click();
assertNoErrors();
}

private void assertNoErrors() {
// Reload page to trigger detach event
openTestURL();

$(ButtonElement.class).id("show").click();
Assert.assertEquals(0, findElements(By.className("errorLabel")).size());
}
}

+ 9
- 5
uitest/src/test/java/com/vaadin/tests/components/ui/UIAccessExceptionHandlingTest.java View File

@@ -5,9 +5,9 @@ import static org.junit.Assert.assertEquals;
import org.junit.Test;

import com.vaadin.testbench.elements.ButtonElement;
import com.vaadin.tests.tb3.MultiBrowserTest;
import com.vaadin.tests.tb3.SingleBrowserTest;

public class UIAccessExceptionHandlingTest extends MultiBrowserTest {
public class UIAccessExceptionHandlingTest extends SingleBrowserTest {

@Test
public void testExceptionHandlingOnUIAccess() throws Exception {
@@ -15,17 +15,21 @@ public class UIAccessExceptionHandlingTest extends MultiBrowserTest {
$(ButtonElement.class).first().click();
assertLogTexts(
"1. Exception caught on get: java.util.concurrent.ExecutionException",
"0. Exception caught on execution with ConnectorErrorEvent : java.util.concurrent.ExecutionException");
"0. Exception caught on execution with ConnectorErrorEvent : java.lang.RuntimeException");

$(ButtonElement.class).get(1).click();
assertLogTexts(
"1. Exception caught on get: java.util.concurrent.ExecutionException",
"0. Exception caught on execution with ErrorEvent : java.util.concurrent.ExecutionException");
"0. Exception caught on execution with ErrorEvent : java.lang.RuntimeException");

$(ButtonElement.class).get(2).click();
assertLogTexts(
"1. Exception caught on get: java.util.concurrent.ExecutionException",
"0. Exception caught on execution with ConnectorErrorEvent : java.util.concurrent.ExecutionException");
"0. Exception caught on execution with ConnectorErrorEvent : java.lang.RuntimeException");

$(ButtonElement.class).get(3).click();
assertLogText(0,
"0. Exception caught on execution with ConnectorErrorEvent : java.lang.NullPointerException");
}

private void assertLogTexts(String first, String second) {

Loading…
Cancel
Save