OOM caused by too many PendingJavaScriptInvocations objects

I have to clean them a bit up from the non Vaadin code


public class OperatorMainView extends FlexLayout {

	private static final long serialVersionUID = 1L;
	
	private VerticalLayout _mainLayout;
	private Div _gridLayout;
	
    public OperatorMainView()
    {
    	        _mainLayout = new VerticalLayout();
		_gridLayout = buildGridLayout();
		_mainLayout.add(_gridLayout);
        _mainLayout.setSizeFull();
        setSizeFull();
		add(_mainLayout);
    }

	private Div buildGridLayout() {

		// common part: create layout
		_gridLayout = new Div();

		_gridLayout.getStyle().set("display", "grid");
		_gridLayout.getStyle().set("grid-template-columns", "repeat(1, 1fr)");
		_gridLayout.getStyle().set("grid-template-rows", "repeat(2, auto)");

		_gridLayout.getStyle().set("gap", "10px");

		return _gridLayout;
	}
	
    public Div getGridLayout()
    {
        return _gridLayout;
    }
	
}

Yes, I have background threads that frequently push changes to the UI.

I think the more interesting part is the two classes mentioned above. OperatorMainPage seems to be captured in the error handler lambda, and it keeps OperatorMainView instance bound to the VaadinSession.

My suspicion is that there’s something in those classes that is directly or indirectly calling executeJs (or similar) repeatedly.

Code of OperatorMainPage might contain something. Maybe also the exact code that registers the error handler. I guess the previously posted snippet is just a modified excerpt, because it does not show any captured instance.

Please also inspect the PendingJavaScriptInvocation in the dump to understand what JS expression they are referring to (PendingJavaScriptInvocation → invocation (JavaScriptInvocation ) → expression (String))

Basically, the class listens to some events and adds or removes components from the layout

@Route(value = "OperatorPage")
public class OperatorMainPage
    extends Composite<Div> implements RouterLayout, HasStyle {

	private static final String I18N_TITLE = "OperatorUserInterface.Title";
	private final Log logger = LogFactory.getLog(getClass());
	private static final String CLASS_NAME = "root";

	private UIModel uiModel;
	private DeoEventDispatcher _deoEventDispatcher;
	private final OperatorMainView _view;
	
	
	private DomainObjectManager _domainObjectManager;
	
	public OperatorMainPage() {

		
		VaadinSession.getCurrent().setErrorHandler((ErrorHandler) errorEvent -> {
			logger.error("Uncaught UI exception", errorEvent.getThrowable());
			Notification.show("We are sorry, but an internal error occurred");
		});
		addClassName(CLASS_NAME);
		getContent().setHeight("100%");
		_view = new OperatorMainView();
		getContent().add(_view);
		
		logger.info("Building OperatorMainPage completed");
	}
	
	public void addOrder(Order order) {
		_view.getGridLayout().add(order.getView());
	}
	
	public void removeOrder(Order order) {
		_view.getGridLayout().remove(order.getView());
	}
	
    private class OrderDomainObjectEventListener
    implements DomainObjectManagerEventListener
{
    @Override
    public void handle(DomainObjectManagerChangedEvent event)
    {
        if (event.getDomainObjectType().equals(Order.TYPE))
        {
        	Order workorder = (Order) event.getDomainObject();
        	if (workorder.getId().equals("MyOrder")) {
                if (event.getEventType().equals(EVENT_DOMAINOBJECT_ADDED))
                {            	
                    addOrder(workorder);
                }
                else if (event.getEventType().equals(EVENT_DOMAINOBJECT_REMOVED))
                {
                	removeOrder(workorder);
                }        		
        	}
        }
    }
}

}

OK, so the error handler is capturing OperatorMainPage because it accesses the logger field. You could start with making the logger a static field.

1 Like

But also setting the error handler in the constructor of OperatorMainPage does not seem a good practice. You are replacing the error handle every time you create a view for the same session,
So if you open the same page in two tabs, you will replace the session error handler with the one from the last created UI

Exactly. If you don’t need to subclass OperatorMainPage, I would make it

private static final Log logger = LogFactory.getLog(OperatorMainPage.class); 

to prevent the leak. Or store the logger in a temporary variable to be provided to the lambda expression.

If almost all expressions are return (async function() { this.invalid = $0}).apply($1) something could be repeatedly calling setInvalid(...) on detached components

I am not sure what this means

This is just a guess since without the opportunity to see the real code it’s impossible to know what is happening.
It is likely that pending js invocation (the expression in the screenshot) is enqueue because something is calling the setInvalid method on a component that is currently detached from the UI. The invocation stays pending until the component is attached.

If all pending invocation are referencing the same js expression, it could be that something (a background thread?) is repeatedly calling setInvalid on detached components, thus continously enqueing js invocations that get never cleaned.

The error handler keeping the view alive and bound to the session could be one of the causes.

You need to investigate and understand if setInvalid is effectively called indefinitely and if so who is calling it.
Then you need to check if the component is detached.

A nice debugging session, I’d say :grin:

Do you mean like an explicit call to setInvalid() on a Vaadin component?

There is no explicit JS component in the project

Yes, setInvalid called on a Vaadin component. The method is defined on a mixin interface (HasXyx, I don’t remember the exact name right now). Put a breakpoint there

The interface is HasValidationProperties

hi, I have removed the ErrorHandler but the problem still persists



I’m sorry but just looking at the screenshots it’s hard to try to understand the cause of the leak.

Hi,
I have about 780.000 instances of
com.vaadin.flow.internal.StateTree$BeforeClientResponseEntry and about the same amount of com.vaadin.flow.component.internal.PendingJavaScriptInvocation objects.

Do you think this could be caused by stale Vaadin sessions? Is it possible that the server does not notice when the browser is closed, and continues to send updates to the browser that accumulate?

It’s really hard to say without knowing how the application works. VaadinSession is bound to the HTTP session life cycle so they are usually destroyed when the session expires. UIs are also usually closed if they are inactive (see How to manage the Vaadin application lifecycle).

I think you need to carefully analyze the heap dumps and try to detect who is keeping the pending Javascript invocation alive (closed UIs? detached components referenced by other classes?) and also as said before identify which components are scheduling the js invocation and where/how they are used in the application.

1 Like