From 00e2640c3789b499614767ff97ec6b9101cdb3cd Mon Sep 17 00:00:00 2001 From: Francesco Mari Date: Wed, 11 Mar 2026 12:34:28 +0100 Subject: [PATCH] Fix NPE and incomplete cleanup during WhiteboardManager shutdown During concurrent bundle shutdown, WhiteboardManager.deactivate() can encounter a null PerContextHandlerRegistry because the handler was already deactivated by another thread. This causes a NullPointerException that propagates up through JettyService.stopJetty(), preventing the Jetty server from stopping and restarting cleanly. This commit applies five defensive fixes: - Null-guard handler.getRegistry() in deactivate() before calling contextDestroyed() - Wrap each ServiceTracker.close() in stop() with try-catch so one failing tracker does not prevent cleanup of the remaining trackers and maps - Wrap controller.unregister() in JettyService.stopJetty() with try-catch so exceptions do not prevent server.stop() from running (both jetty and jetty12 variants) - Guard against empty handler lists in getMatchingContexts() and getContextHandler() to prevent IndexOutOfBoundsException - Remove empty lists from contextMap after the retry loop in removeContextHelper() exhausts all fallback candidates --- .../whiteboard/WhiteboardManager.java | 26 +++++++++++++++++-- .../http/jetty/internal/JettyService.java | 9 ++++++- .../http/jetty/internal/JettyService.java | 9 ++++++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java index 3ba52d81ab..80f902e739 100644 --- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java +++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java @@ -199,7 +199,14 @@ public void stop() this.serviceRuntime.unregister(); for(final ServiceTracker t : this.trackers) { - t.close(); + try + { + t.close(); + } + catch (final Exception e) + { + SystemLogger.LOGGER.error("Exception while closing service tracker", e); + } } this.trackers.clear(); this.preprocessorHandlers = Collections.emptyList(); @@ -328,7 +335,10 @@ private void deactivate(final WhiteboardContextHandler handler) } } // context listeners last - handler.getRegistry().getEventListenerRegistry().contextDestroyed(); + if ( handler.getRegistry() != null ) + { + handler.getRegistry().getEventListenerRegistry().contextDestroyed(); + } for(final WhiteboardServiceInfo info : listeners) { this.unregisterWhiteboardService(handler, info); @@ -480,6 +490,10 @@ else if ( activateNext ) this.failureStateHandler.addFailure(newHead.getContextInfo(), DTOConstants.FAILURE_REASON_SERVICE_NOT_GETTABLE); } } + if ( handlerList.isEmpty() ) + { + this.contextMap.remove(info.getName()); + } } } } @@ -494,6 +508,10 @@ else if ( activateNext ) private List getMatchingContexts(final WhiteboardServiceInfo info) { final List result = new ArrayList<>(); for(final List handlerList : this.contextMap.values()) { + if ( handlerList.isEmpty() ) + { + continue; + } final WhiteboardContextHandler h = handlerList.get(0); // check if the context matches @@ -881,6 +899,10 @@ private WhiteboardContextHandler getContextHandler(final String name) { for(final List handlerList : this.contextMap.values()) { + if ( handlerList.isEmpty() ) + { + continue; + } final WhiteboardContextHandler h = handlerList.get(0); if ( h.getContextInfo().getName().equals(name) ) { diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java index b62f218fa2..6b8a700d44 100644 --- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java +++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java @@ -215,7 +215,14 @@ private void stopJetty() if (this.server != null) { this.controller.getEventDispatcher().setActive(false); - this.controller.unregister(); + try + { + this.controller.unregister(); + } + catch (final Exception e) + { + SystemLogger.LOGGER.error("Exception during controller unregister", e); + } if (this.fileRequestLog != null) { diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java index 24b45d8868..f33a213171 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java @@ -225,7 +225,14 @@ private void stopJetty() if (this.server != null) { this.controller.getEventDispatcher().setActive(false); - this.controller.unregister(); + try + { + this.controller.unregister(); + } + catch (final Exception e) + { + SystemLogger.LOGGER.error("Exception during controller unregister", e); + } if (this.fileRequestLog != null) {