flow: Infinite Lambda Chaining in PendingJavaScriptInvocation (Memory Leak)
Description of the bug
Consider the minimal reproducible example below.
Once a button is attached a JS is invoked via Button#initDisableOnClick. Since the button b is not visible, that pending JS invocation will be retained internally. For those invisible components/nodes the pending JS is retained but now with every interaction with the view you can get an infinite lambda chain.
Each time you click on button a, during UidlWriter#createUidl the UIInternals#dumpPendingJavaScriptInvocations method is executed, which at the end is calling UIInternals#registerDetachListenerForPendingInvocation for each retained pending JS invocation. Finally, invocation.then(callback, callback) is being called which then calls
this.successHandler = combineHandlers(this.successHandler, successHandler);
This is called because
- Initially,
sentToBrowseris false because this invocation has not been sent yet since its node was not visible yet. - and the invocation is not
canceled - and the provided successHandler from UIInternals is not null
The provided successHandler parameter is always the same:
SerializableConsumer callback = unused -> listener.onInvocationCompleted(invocation)
Now, during the first call the PendingJavaScriptInvocation#successHandler is null, so combineHandlers will just return the provided not null successHandler and store that in the field.
But, each time you click now on button a in the browser, next combineHandlers will have both not null, the field successHandler as well as the param successHandler. This leads to combining both to a new lambda and storing that in the field successHandler. Next time you click again and again, so you get an endless chaining of lambdas.
Remember that those chained lambda callbacks in the end are always the same ones provided by the calling method UIInternals#registerDetachListenerForPendingInvocation. The chaining would make sense if the callbacks were different ones, but in this case it’s always the same callbacks each effectively doing a onInvocationCompleted() once called.
Assuming that an app has components that are initialized or implemented like in this minimal example, eventually this situation will make the app end up in an OutOfMemoryError, the more invisible components with pending JS there are the faster this will be reached.
Expected behavior
There should be no memory leak in PendingJavaScriptInvocation via potential endless chaining of lambdas.
Minimal reproducible example
@Route("")
public class View extends Div {
public View() {
Button a = new Button("test");
Button b = new Button("invisible");
b.setVisible(false);
add(a, b);
}
}
Each time you click on button a, the PendingJavaScriptInvocation#successHandler gets a new lambda chain longer than the previous one.
Versions
- Vaadin / Flow version: 14.10.1
- Java version: 17.0.6
- OS version: macOS
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 19 (5 by maintainers)
Commits related to this issue
- fix: add detach listener callback once for pending invocations Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvocations. Thi... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvoca... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvoca... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvoca... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvoca... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvoca... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScriptInvoca... — committed to vaadin/flow by mcollovati a year ago
- fix: add detach listener callback once for pending invocations (#17092) (#17103) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScr... — committed to vaadin/flow by vaadin-bot a year ago
- fix: add detach listener callback once for pending invocations (#17092) (#17102) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScr... — committed to vaadin/flow by vaadin-bot a year ago
- fix: add detach listener callback once for pending invocations (#17092) (#17101) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScr... — committed to vaadin/flow by vaadin-bot a year ago
- fix: add detach listener callback once for pending invocations (#17092) (#17100) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScr... — committed to vaadin/flow by vaadin-bot a year ago
- fix: add detach listener callback once for pending invocations (#17092) (#17104) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScr... — committed to vaadin/flow by vaadin-bot a year ago
- fix: add detach listener callback once for pending invocations (#17092) (#17102) Currently, the detach listener callback is added to pending javascript invocation on every call to dumpPendingJavaScr... — committed to vaadin/flow by vaadin-bot a year ago
The fix has been released in Flow 23.3.14, included in Vaadin platform since 23.3.15. For some reason, it is not mentioned in the release notes
@muhammetaltindal thank you for the detailed issue and for the insights. We will start working on this shortly.