DataCommunicator gets stuck after browser refresh, eventually causing OutOfMemoryError
See original GitHub issueDescription of the bug
After upgrading to Vaadin 23.1.6, we encountered situations where the DataCommunicator of a Grid gets stuck after a browser refresh. It seems that the DataCommunicator gets stuck in some kind of flush-loop, which ultimately leads to an OutOfMemoryError. I suppose the problem was introduced in flow-data 23.1.4, this change might be relevant: https://github.com/vaadin/flow/commit/c55bdcc6dd9dac75be42291b5752d1d86d5a9997 This bug does not appear after downgrading to Vaadin 23.1.4
Note: In our application, we often use views that have a Grid on the left side and a TabSheet on the right side which can show data about the object that is selected in the left Grid. Most of the time, the data on the right side will be shown with a Grid. This type of view is affected by the problem. In general, it seems that the problem can appear in case that a component is shown, which was already used and removed before the browser refresh.
I created this issue in the flow repository, since the problem is at its core caused by the DataCommunicator.
Expected behavior
After a browser refresh, it should be possible to show a component that contains a Grid and was shown before the browser refresh without causing this bevahiour (this was possible before 23.1.6). The DataCommunicator should not get stuck in an infinite loop.
Minimal reproducible example
Below is an example that can reproduce the bug.
Steps to reproduce the bug: Go to /test Select “1” in left table Click “Show Table 1” Click “Show Table 2” Press F5 Click “Show Table 1”
import java.util.ArrayList;
import java.util.Arrays;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.AfterNavigationEvent;
import com.vaadin.flow.router.AfterNavigationObserver;
import com.vaadin.flow.router.PreserveOnRefresh;
import com.vaadin.flow.router.Route;
@PreserveOnRefresh
@Route("test")
public class TestView extends Div implements AfterNavigationObserver{
private HorizontalLayout hLayout;
private Div wrapperRight;
private Grid<String> tableLeft;
private Grid<String> tableRightOne;
private Grid<String> tableRightTwo;
private Button buttonShowTableOne;
private Button buttonShowTableTwo;
private Div buttonSlot;
private Div tableSlot;
public TestView() {
setSizeFull();
build();
}
private void build() {
String value1 = "1";
String value2 = "2";
hLayout = new HorizontalLayout();
hLayout.setSizeFull();
Div wrapperLeft = new Div();
wrapperLeft.setHeightFull();
wrapperLeft.setWidth("50%");
hLayout.add(wrapperLeft);
tableLeft = new Grid<>();
tableLeft.addColumn(e -> e).setFlexGrow(1);
wrapperLeft.add(tableLeft);
tableLeft.setItems(Arrays.asList(value1, value2));
wrapperRight = new Div();
wrapperRight.setHeightFull();
wrapperRight.setWidth("50%");
hLayout.add(wrapperRight);
buttonSlot = new Div();
buttonShowTableOne = new Button("Show Table 1");
buttonSlot.add(buttonShowTableOne);
buttonShowTableTwo = new Button("Show Table 2");
buttonSlot.add(buttonShowTableTwo);
wrapperRight.add(buttonSlot);
buttonShowTableOne.addClickListener(e -> {
tableSlot.removeAll();
tableSlot.add(tableRightOne);
});
buttonShowTableTwo.addClickListener(e -> {
tableSlot.removeAll();
tableSlot.add(tableRightTwo);
});
tableSlot = new Div();
wrapperRight.add(tableSlot);
tableRightOne = new Grid<>();
tableRightOne.addColumn( e -> e).setHeader("c1").setFlexGrow(1);
tableRightOne.addColumn(e -> new Checkbox()).setHeader("c2").setFlexGrow(1);
tableRightOne.addColumn( e -> e).setHeader("c3").setFlexGrow(1).setVisible(false);
tableRightOne.setItems(new ArrayList<>(Arrays.asList("1", "2", "3")));
tableRightTwo = new Grid<>();
tableRightTwo.addColumn( e -> e).setHeader("c1").setFlexGrow(1);
tableRightTwo.addColumn(e -> new Checkbox()).setHeader("c2").setFlexGrow(1);
tableRightTwo.addColumn( e -> e).setHeader("c3").setFlexGrow(1).setVisible(false);
tableRightTwo.setItems(new ArrayList<>(Arrays.asList("4", "5", "6")));
tableLeft.addSelectionListener(e -> {
refreshRightSide();
});
}
private void refreshRightSide() {
displayNothingSelected();
if(tableLeft.getSelectedItems().isEmpty()) {
} else {
if("1".equals(tableLeft.getSelectedItems().iterator().next())) {
tableRightOne.setItems(new ArrayList<>(Arrays.asList("1", "2", "3")));
} else {
tableRightTwo.setItems(new ArrayList<>(Arrays.asList("4", "5", "6")));
}
}
}
private void displayNothingSelected() {
tableSlot.removeAll();
tableSlot.add(new Div());
}
protected void wrapRightSide(Component component) {
if (!component.isAttached()) {
Div wrapper = new Div();
wrapper.setSizeFull();
wrapper.add(component);
wrapperRight.removeAll();
wrapperRight.add(wrapper);
}
}
@Override
public void afterNavigation(AfterNavigationEvent event) {
removeAll();
add(hLayout);
refreshRightSide();
}
}
Versions
- Vaadin / Flow version: 23.1.6
- Java version: 17
- OS version: Windows
Issue Analytics
- State:
- Created a year ago
- Comments:9 (4 by maintainers)
Top GitHub Comments
The example I provided was simply intended to reproduce the problem we encountered in 23.1.6 in several places.
A more realsitic scenario in our application looks like this:
We have a view with a grid on the left side and a tabsheet on the right side. The grid on the left side contains objects of different types. The data/components which are shown in the tabs on the right side depend on the object that is selected in the left grid. Example: When I select an object of type A in the left grid, I have to show grid X in the first tab of the tabsheet. Grid X contains data about the object that is selected in the left grid. When I select an object of type B in the left grid, I have to show grid Y in the first tab of the tabsheet. Grid Y contains data about the object that is selected in the left grid. This means that everytime I change the selection in the left grid I possibly have the switch the grid that is displayed in the first tab and also have to refresh its content. In the second tab we use another grid to show different data about the object that is selected in the left grid. For the second tab however we can use the same grid regardless of the object type that is selected in the left grid.
This can lead to the same process that you described earlier: Select object of type A in the left grid ->Show grid X in first tab of tabsheet Select object of type B in the left grid ->Show grid Y in first tab of tabsheet (which requires to remove grid X) Press F5 Select object of type A in the left grid ->Show grid X in first tab of tabsheet
Some additional notes: This problem would also arise if we use the same grid in the first tab, because switching the selected tab in the tabsheet also requires removing/adding grids. We reuse the grids for performance and simplicity reasons - we do not want to recreate grids whenever the selection in the left grid changes. We also want to refresh the data on the right side when the user hits F5. The user assumes that the grid in the tabsheet displays the current data after pressing F5.
In any case I’d argue that this is a problem of the DataCommunicator, regardless of whether the provided example makes sense or not.
This ticket/PR has been released with Vaadin 23.1.11.