5656import java .net .*;
5757import java .nio .file .Files ;
5858import java .nio .file .Path ;
59- import java .util .*;
59+ import java .util .ArrayList ;
60+ import java .util .Arrays ;
61+ import java .util .HashMap ;
62+ import java .util .List ;
63+ import java .util .Map ;
64+ import java .util .Objects ;
65+ import java .util .Observer ;
66+ import java .util .Optional ;
67+ import java .util .Properties ;
68+ import java .util .ServiceLoader ;
6069import java .util .concurrent .CopyOnWriteArrayList ;
6170import java .util .concurrent .CountDownLatch ;
6271import java .util .concurrent .TimeUnit ;
@@ -105,6 +114,7 @@ public class JettyStart implements LifeCycle.Listener {
105114
106115 @ GuardedBy ("this" ) private int status = STATUS_STOPPED ;
107116 @ GuardedBy ("this" ) private Optional <Thread > shutdownHookThread = Optional .empty ();
117+ @ GuardedBy ("this" ) private Optional <Server > server = Optional .empty ();
108118 @ GuardedBy ("this" ) private int primaryPort = 8080 ;
109119 @ GuardedBy ("this" ) private boolean webAppStartedSuccessfully = false ;
110120 @ GuardedBy ("this" ) private String webAppStartupFailureDetail = null ;
@@ -518,18 +528,18 @@ private URI getURI(final NetworkConnector networkConnector, final ContextHandler
518528
519529 private Optional <Server > startJetty (final List <Object > configuredObjects ) throws Exception {
520530 // For all objects created by XmlConfigurations, start them if they are lifecycles.
521- Optional <Server > server = Optional .empty ();
531+ Optional <Server > serverFound = Optional .empty ();
522532 for (final Object configuredObject : configuredObjects ) {
523533 if (configuredObject instanceof Server _server ) {
524534
525535 //skip this server if we have already started it
526- if (server .map (configuredServer -> configuredServer == _server ).orElse (false )) {
536+ if (serverFound .map (configuredServer -> configuredServer == _server ).orElse (false )) {
527537 continue ;
528538 }
529539
530540 //setup server shutdown
531541 _server .addEventListener (this );
532- BrokerPool .getInstance ().registerShutdownListener (new ShutdownListenerImpl (_server ));
542+ BrokerPool .getInstance ().registerShutdownListener (new ShutdownListenerImpl ());
533543
534544 // register a shutdown hook for the server
535545 final BrokerPoolAndJettyShutdownHook brokerPoolAndJettyShutdownHook =
@@ -550,7 +560,9 @@ private Optional<Server> startJetty(final List<Object> configuredObjects) throws
550560 throw e ;
551561 }
552562
553- server = Optional .of (_server );
563+ _server .setStopTimeout (TimeUnit .SECONDS .toMillis (30 ));
564+ serverFound = Optional .of (_server );
565+ this .server = serverFound ;
554566 }
555567
556568 if (configuredObject instanceof LifeCycle lc && !lc .isRunning ()) {
@@ -559,7 +571,7 @@ private Optional<Server> startJetty(final List<Object> configuredObjects) throws
559571 }
560572 }
561573
562- return server ;
574+ return serverFound ;
563575 }
564576
565577 /**
@@ -854,9 +866,25 @@ public synchronized void shutdown() {
854866
855867 BrokerPool .stopAll (false );
856868
869+ // stop the Jetty server directly (no longer relies on ShutdownListenerImpl timer)
870+ server .ifPresent (srv -> {
871+ try {
872+ srv .stop ();
873+ srv .join ();
874+ } catch (final Exception e ) {
875+ logger .warn ("Error stopping Jetty server: {}" , e .getMessage (), e );
876+ }
877+ });
878+
879+ final long deadline = System .currentTimeMillis () + TimeUnit .SECONDS .toMillis (30 );
857880 while (status != STATUS_STOPPED ) {
881+ final long remaining = deadline - System .currentTimeMillis ();
882+ if (remaining <= 0 ) {
883+ logger .warn ("Timed out waiting for Jetty to reach STATUS_STOPPED (current status={}); forcing shutdown" , status );
884+ break ;
885+ }
858886 try {
859- wait ();
887+ wait (remaining );
860888 } catch (final InterruptedException e ) {
861889 Thread .currentThread ().interrupt ();
862890 }
@@ -865,38 +893,12 @@ public synchronized void shutdown() {
865893
866894 /**
867895 * This class gets called after the database received a shutdown request.
868- *
869- * @author wolf
896+ * Server stop is handled directly in {@link #shutdown()}.
870897 */
871898 private static class ShutdownListenerImpl implements ShutdownListener {
872- private final Server server ;
873-
874- ShutdownListenerImpl (final Server server ) {
875- this .server = server ;
876- }
877-
878899 @ Override
879900 public void shutdown (final String dbname , final int remainingInstances ) {
880- logger .info ("Database shutdown: stopping server in 1sec ..." );
881- if (remainingInstances == 0 ) {
882- // give the webserver a 1s chance to complete open requests
883- final Timer timer = new Timer ("jetty shutdown schedule" , true );
884- timer .schedule (new TimerTask () {
885- @ Override
886- public void run () {
887- try {
888- // stop the server
889- server .stop ();
890- server .join ();
891-
892- // make sure to stop the timer thread!
893- timer .cancel ();
894- } catch (final Exception e ) {
895- logger .error ("An error occurred in the shutdown scheduler: {}" , e .getMessage (), e );
896- }
897- }
898- }, 1000 ); // timer.schedule
899- }
901+ logger .info ("Database shutdown: stopping server ..." );
900902 }
901903 }
902904
@@ -929,9 +931,15 @@ public synchronized boolean isStarted() {
929931 if (status == STATUS_STOPPED ) {
930932 return false ;
931933 }
934+ final long deadline = System .currentTimeMillis () + TimeUnit .SECONDS .toMillis (30 );
932935 while (status != STATUS_STOPPED ) {
936+ final long remaining = deadline - System .currentTimeMillis ();
937+ if (remaining <= 0 ) {
938+ logger .warn ("Timed out waiting for Jetty to reach STATUS_STOPPED (current status={})" , status );
939+ return false ;
940+ }
933941 try {
934- wait ();
942+ wait (remaining );
935943 } catch (final InterruptedException e ) {
936944 Thread .currentThread ().interrupt ();
937945 }
0 commit comments