Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NoSuchElementException in DetachEvent.getUI when detaching components twice #20685

Open
mperktold opened this issue Dec 12, 2024 · 3 comments
Open

Comments

@mperktold
Copy link

Description of the bug

Many of our views can open non-modal dialogs. When navigating to another view, these dialogs should be closed. Therefore, these views call Dialog.close in their onDetach hooks.

However, if some component inside the dialog also overrides onDetach and calls DetachEvent.getUI, this leads to NoSuchElementException when the whole browser tab is closed. The reason is that the dialog is detached twice in this case: once explicitely by our view, and once implicitely by Vaadin when detaching the whole component tree. Thus, when detached for the second time, the component has actually already been detached, so you cannot get a UI.

Interestingly, onDetach itself is always only called once at some later point, not inside Dialog.close, but the problem does not arise when not calling close.

Expected behavior

Inside an onDetach hook, DetachEvent.getUI should never throw. When all components are detached anyway, an additional Dialog.close should only affect the order in which detach hooks are called.

Minimal reproducible example

public class DetachIssue extends VerticalLayout {

    private Dialog dialog;

    public DetachIssue() {
        add(new Button("open", e -> {
            dialog = new Dialog("lorem ipsum") {
                @Override
                protected void onDetach(DetachEvent detachEvent) {
                    System.out.println("detach logic accessing ui: " + detachEvent.getUI());
                    super.onDetach(detachEvent);
                }
            };
            dialog.setModal(false);
            dialog.open();
        }));
    }

    @Override
    protected void onDetach(DetachEvent detachEvent) {
        if (dialog != null)
            dialog.close();
    }
}
  1. Open the view above.
  2. Click on the button.
  3. Refresh the page.

You see an exception that looks similar to this:

2024-12-12T10:53:09.406+01:00  WARN 37612 --- [p1096116778-107] o.e.jetty.ee10.servlet.ServletChannel    : /

jakarta.servlet.ServletException: Request processing failed: java.util.NoSuchElementException: No value present
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1022) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:547) ~[jakarta.servlet-api-6.0.0.jar:6.0.0]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614) ~[jakarta.servlet-api-6.0.0.jar:6.0.0]
	at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:736) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1614) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195) ~[jetty-ee10-websocket-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.14.jar:6.1.14]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.14.jar:6.1.14]
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.14.jar:6.1.14]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.14.jar:6.1.14]
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.14.jar:6.1.14]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.14.jar:6.1.14]
	at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1586) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1547) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:824) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:436) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:464) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575) ~[jetty-security-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1060) ~[jetty-server-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.server.Server.handle(Server.java:182) ~[jetty-server-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:662) ~[jetty-server-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:414) ~[jetty-server-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322) ~[jetty-io-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99) ~[jetty-io-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53) ~[jetty-io-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:478) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:441) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:293) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:201) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:311) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209) ~[jetty-util-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164) ~[jetty-util-12.0.14.jar:12.0.14]
	at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: java.util.NoSuchElementException: No value present
	at java.base/java.util.Optional.get(Optional.java:143) ~[na:na]
	at com.vaadin.flow.component.internal.AbstractAttachDetachEvent.getUI(AbstractAttachDetachEvent.java:50) ~[flow-server-24.5.5.jar:24.5.5]
	at com.asaon.views.detach.DetachIssue$1.onDetach(DetachIssue.java:25) ~[classes/:na]
	at com.vaadin.flow.component.ComponentUtil.onComponentDetach(ComponentUtil.java:341) ~[flow-server-24.5.5.jar:24.5.5]
	at java.base/java.util.Optional.ifPresent(Optional.java:178) ~[na:na]
	at com.vaadin.flow.internal.nodefeature.ComponentMapping.onDetach(ComponentMapping.java:109) ~[flow-server-24.5.5.jar:24.5.5]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
	at com.vaadin.flow.internal.StateNode.forEachFeature(StateNode.java:377) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.internal.StateNode.fireDetachListeners(StateNode.java:944) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.internal.StateNode.onDetach(StateNode.java:354) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.internal.StateNode.setParent(StateNode.java:289) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.internal.StateTree$RootNode.setParent(StateTree.java:68) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.component.internal.UIInternals.setSession(UIInternals.java:430) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinSession.removeUI(VaadinSession.java:664) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinService.lambda$removeClosedUIs$20ed7015$1(VaadinService.java:1400) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.component.UI.accessSynchronously(UI.java:498) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.component.UI.accessSynchronously(UI.java:459) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinService.removeClosedUIs(VaadinService.java:1398) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinService.cleanupSession(VaadinService.java:1361) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinService.requestEnd(VaadinService.java:1571) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1664) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:398) ~[flow-server-24.5.5.jar:24.5.5]
	at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:106) ~[vaadin-spring-24.5.5.jar:na]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614) ~[jakarta.servlet-api-6.0.0.jar:6.0.0]
	at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:736) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1614) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1547) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.eclipse.jetty.ee10.servlet.Dispatcher.forward(Dispatcher.java:126) ~[jetty-ee10-servlet-12.0.14.jar:12.0.14]
	at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:142) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:178) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:51) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.14.jar:6.1.14]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.14.jar:6.1.14]
	... 43 common frames omitted

The outer one depends on the servlet container, which is Jetty in my case. But the cause shows that the exception happens when removing the UI in requestEnd.

On another note, I wonder if such an error shouldn't be passed to VaadinSession.getErrorHandler. Currently, it simply bubbles up to the servlet container, which apparently simply logs it as a warning. We usually don't enable these loggers, so we didn't know about this exception, even though the issue probably exists for quite some time. I can open a separate issue for this if you like.

The main workaround I've found is to avoid calling dialog.close if the UI is closing:

    protected void onDetach(DetachEvent detachEvent) {
        if (dialog != null && !detachEvent.getUI().isClosing())
            dialog.close();
    }

Versions

  • Vaadin / Flow version: 24.5.5
  • Java version: Eclipse Temurin 21.0.5
  • OS version: Windows 11
@mperktold
Copy link
Author

As an aside, this also shows why #20577 and #20293 make sense.

@knoobie
Copy link
Contributor

knoobie commented Dec 12, 2024

It's probably way simpler to implement BeforeLeaveObserver in your dialog and just call close() in there.

@mperktold
Copy link
Author

I haven't thought about that, that would probably work here. But navigation is just one example where the outer component is detached. But there are other scenarios where the outer component is detached without navigation. For example, it could simply be removed from a parent component for whatever reason. Or, it could itself be embedded in an outer dialog, which you can close manually.
By hooking into onDetach, the outer component doesn't need to know why it is detached, so this is more reusable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 🔖 Low Priority (P3)
Development

No branches or pull requests

3 participants