As there is no "request end" call after invoking UI.access() from a background thread, the connector map was not earlier properly cleaned afterwards. If you toggled visibility of a component from the background thread, the tracker state became inconsistent. If this becomes a performance problem, it could probably be optimized to that cleanup is done in request end and only at the end of access if not inside a request. Fixes #9654tags/8.1.0
final long duration = (System.nanoTime() - (Long) request | final long duration = (System.nanoTime() - (Long) request | ||||
.getAttribute(REQUEST_START_TIME_ATTRIBUTE)) / 1000000; | .getAttribute(REQUEST_START_TIME_ATTRIBUTE)) / 1000000; | ||||
session.setLastRequestDuration(duration); | session.setLastRequestDuration(duration); | ||||
// Check that connector tracker is in a consistent state here to | |||||
// avoid doing it multiple times for a single request | |||||
for (UI ui : session.getUIs()) { | |||||
try { | |||||
ui.getConnectorTracker().ensureCleanedAndConsistent(); | |||||
} catch (AssertionError | Exception e) { | |||||
getLogger().log(Level.SEVERE, | |||||
"Error cleaning ConnectionTracker", e); | |||||
} | |||||
} | |||||
} finally { | } finally { | ||||
session.unlock(); | session.unlock(); | ||||
} | } |
} | } | ||||
try { | try { | ||||
ui.getConnectorTracker().cleanConnectorMap(); | ui.getConnectorTracker().cleanConnectorMap(); | ||||
} catch (Exception e) { | |||||
} catch (AssertionError | Exception e) { | |||||
getLogger().log(Level.SEVERE, | getLogger().log(Level.SEVERE, | ||||
"Exception while cleaning connector map for ui " | "Exception while cleaning connector map for ui " | ||||
+ ui.getUIId(), | + ui.getUIId(), |
} | } | ||||
cleanStreamVariables(); | cleanStreamVariables(); | ||||
} | |||||
/** | |||||
* Performs expensive checks to ensure that the connector tracker is cleaned | |||||
* properly and in a consistent state. | |||||
* <p> | |||||
* This should only be called by the framework. | |||||
* | |||||
* @since 8.1 | |||||
*/ | |||||
public void ensureCleanedAndConsistent() { | |||||
// Do this expensive check only with assertions enabled | // Do this expensive check only with assertions enabled | ||||
assert isHierarchyComplete() : "The connector hierarchy is corrupted. " | assert isHierarchyComplete() : "The connector hierarchy is corrupted. " | ||||
+ "Check for missing calls to super.setParent(), super.attach() and super.detach() " | + "Check for missing calls to super.setParent(), super.attach() and super.detach() " | ||||
} else if (!uninitializedConnectors.contains(connector) | } else if (!uninitializedConnectors.contains(connector) | ||||
&& !LegacyCommunicationManager | && !LegacyCommunicationManager | ||||
.isConnectorVisibleToClient(connector)) { | .isConnectorVisibleToClient(connector)) { | ||||
// Connector was visible to the client but is no longer (e.g. | |||||
// setVisible(false) has been called or SelectiveRenderer tells | |||||
// it's no longer shown) -> make sure that the full state is | |||||
// sent again when/if made visible | |||||
uninitializedConnectors.add(connector); | uninitializedConnectors.add(connector); | ||||
diffStates.remove(connector); | diffStates.remove(connector); | ||||
assert isRemovalSentToClient(connector) : "Connector " | assert isRemovalSentToClient(connector) : "Connector " |
import java.util.logging.Logger; | import java.util.logging.Logger; | ||||
import com.vaadin.server.VaadinRequest; | import com.vaadin.server.VaadinRequest; | ||||
import com.vaadin.server.VaadinService; | |||||
import com.vaadin.server.VaadinSession; | |||||
import com.vaadin.tests.components.AbstractReindeerTestUIWithLog; | import com.vaadin.tests.components.AbstractReindeerTestUIWithLog; | ||||
import com.vaadin.ui.Button; | import com.vaadin.ui.Button; | ||||
import com.vaadin.ui.Button.ClickEvent; | import com.vaadin.ui.Button.ClickEvent; | ||||
@Override | @Override | ||||
protected void setup(VaadinRequest request) { | protected void setup(VaadinRequest request) { | ||||
// Catch log messages so we can see if there is an error | // Catch log messages so we can see if there is an error | ||||
Logger vaadinServiceLogger = Logger | |||||
.getLogger(VaadinService.class.getName()); | |||||
vaadinServiceLogger.addHandler(new Handler() { | |||||
Logger vaadinSessionLogger = Logger | |||||
.getLogger(VaadinSession.class.getName()); | |||||
vaadinSessionLogger.addHandler(new Handler() { | |||||
@Override | @Override | ||||
public void publish(LogRecord record) { | public void publish(LogRecord record) { | ||||
if (record.getThrown() instanceof AssertionError) { | if (record.getThrown() instanceof AssertionError) { | ||||
pendingErrors.add(record); | pendingErrors.add(record); | ||||
vaadinServiceLogger.removeHandler(this); | |||||
vaadinSessionLogger.removeHandler(this); | |||||
} | } | ||||
} | } | ||||
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
* use this file except in compliance with the License. You may obtain a copy of | |||||
* the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
* License for the specific language governing permissions and limitations under | |||||
* the License. | |||||
*/ | |||||
package com.vaadin.tests.push; | |||||
import com.vaadin.annotations.Push; | |||||
import com.vaadin.annotations.Widgetset; | |||||
import com.vaadin.server.VaadinRequest; | |||||
import com.vaadin.ui.Button; | |||||
import com.vaadin.ui.Label; | |||||
import com.vaadin.ui.UI; | |||||
import com.vaadin.ui.VerticalLayout; | |||||
@Push | |||||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||||
public class PushToggleComponentVisibility extends UI { | |||||
@Override | |||||
protected void init(VaadinRequest request) { | |||||
VerticalLayout mainLayout = new VerticalLayout(); | |||||
setContent(mainLayout); | |||||
Label label = new Label("Please wait"); | |||||
label.setId("label"); | |||||
label.setVisible(false); | |||||
mainLayout.addComponent(label); | |||||
Button button = new Button("Hide me 3 secondes"); | |||||
button.setId("hide"); | |||||
button.addClickListener(event1 -> { | |||||
button.setVisible(false); | |||||
label.setVisible(true); | |||||
new Thread(() -> { | |||||
try { | |||||
Thread.sleep(3000); | |||||
} catch (InterruptedException e) { | |||||
e.printStackTrace(); | |||||
} | |||||
button.getUI().access(() -> { | |||||
button.setVisible(true); | |||||
label.setVisible(false); | |||||
button.getUI().push(); | |||||
}); | |||||
}).start(); | |||||
}); | |||||
mainLayout.addComponent(button); | |||||
} | |||||
} |
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
* use this file except in compliance with the License. You may obtain a copy of | |||||
* the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
* License for the specific language governing permissions and limitations under | |||||
* the License. | |||||
*/ | |||||
package com.vaadin.tests.push; | |||||
import org.junit.Assert; | |||||
import org.junit.Test; | |||||
import org.openqa.selenium.By; | |||||
import com.vaadin.testbench.elements.ButtonElement; | |||||
import com.vaadin.testbench.elements.LabelElement; | |||||
import com.vaadin.tests.tb3.SingleBrowserTest; | |||||
public class PushToggleComponentVisibilityTest extends SingleBrowserTest { | |||||
private static final String HIDE = "hide"; | |||||
@Test | |||||
public void ensureComponentVisible() { | |||||
openTestURL(); | |||||
$(ButtonElement.class).id(HIDE).click(); | |||||
Assert.assertEquals("Please wait", | |||||
$(LabelElement.class).first().getText()); | |||||
waitForElementPresent(By.id(HIDE)); | |||||
$(ButtonElement.class).id(HIDE).click(); | |||||
Assert.assertEquals("Please wait", | |||||
$(LabelElement.class).first().getText()); | |||||
} | |||||
} |