Procházet zdrojové kódy

thread management improvements

jrivard@gmail.com před 6 roky
rodič
revize
a0b3e732b3

+ 69 - 4
server/src/main/java/password/pwm/PwmApplication.java

@@ -98,6 +98,10 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -161,6 +165,8 @@ public class PwmApplication
 
     private final PwmServiceManager pwmServiceManager = new PwmServiceManager( this );
 
+    private ScheduledExecutorService applicationExecutorService;
+
     public PwmApplication( final PwmEnvironment pwmEnvironment )
             throws PwmUnrecoverableException
     {
@@ -277,6 +283,8 @@ public class PwmApplication
         LOGGER.debug( "application environment flags: " + JsonUtil.serializeCollection( pwmEnvironment.getFlags() ) );
         LOGGER.debug( "application environment parameters: " + JsonUtil.serializeMap( pwmEnvironment.getParameters() ) );
 
+        applicationExecutorService = JavaHelper.makeSingleThreadExecutorService( this, this.getClass() );
+
         pwmServiceManager.initAllServices();
 
         final boolean skipPostInit = pwmEnvironment.isInternalRuntimeInstance()
@@ -289,10 +297,7 @@ public class PwmApplication
             StatisticsManager.incrementStat( this, Statistic.PWM_STARTUPS );
             LOGGER.debug( "buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE );
 
-            final Thread postInitThread = new Thread( ( ) -> postInitTasks() );
-            postInitThread.setDaemon( true );
-            postInitThread.setName( JavaHelper.makeThreadName( this, PwmApplication.class ) );
-            postInitThread.start();
+            applicationExecutorService.execute( () -> postInitTasks() );
         }
     }
 
@@ -772,6 +777,8 @@ public class PwmApplication
 
     public void shutdown( )
     {
+        applicationExecutorService.shutdown();
+
         LOGGER.warn( "shutting down" );
         {
             // send system audit event
@@ -1008,6 +1015,64 @@ public class PwmApplication
     {
         return inprogressRequests;
     }
+
+    public ScheduledFuture scheduleFutureJob(
+            final Runnable runnable,
+            final Executor executor,
+            final TimeDuration delay
+    )
+    {
+        return applicationExecutorService.schedule(  new WrappedRunner( runnable, executor ), delay.asMillis(), TimeUnit.MILLISECONDS );
+    }
+
+    public void scheduleFixedRateJob(
+            final Runnable runnable,
+            final Executor executor,
+            final TimeDuration initialDelay,
+            final TimeDuration frequency
+    )
+    {
+        if ( initialDelay != null )
+        {
+            applicationExecutorService.schedule( new WrappedRunner( runnable, executor ), initialDelay.asMillis(), TimeUnit.MILLISECONDS );
+        }
+
+        final Runnable jobWithNextScheduler = () ->
+        {
+            new WrappedRunner( runnable, executor ).run();
+            if ( !applicationExecutorService.isShutdown() )
+            {
+                scheduleFixedRateJob( runnable, executor, null, frequency );
+            }
+        };
+
+        applicationExecutorService.schedule(  jobWithNextScheduler, frequency.asMillis(), TimeUnit.MILLISECONDS );
+    }
+
+    private static class WrappedRunner implements Runnable
+    {
+        private final Runnable runnable;
+        private final Executor executor;
+
+        WrappedRunner( final Runnable runnable, final Executor executor )
+        {
+            this.runnable = runnable;
+            this.executor = executor;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                executor.execute( runnable );
+            }
+            catch ( Throwable t )
+            {
+                LOGGER.error( "unexpected error running scheduled job: " + t.getMessage(), t );
+            }
+        }
+    }
 }
 
 

+ 5 - 13
server/src/main/java/password/pwm/health/HealthMonitor.java

@@ -40,10 +40,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
 
 public class HealthMonitor implements PwmService
 {
@@ -67,7 +65,7 @@ public class HealthMonitor implements PwmService
         HEALTH_CHECKERS = records;
     }
 
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
     private HealthMonitorSettings settings;
 
     private volatile Instant lastHealthCheckTime = Instant.ofEpochMilli( 0 );
@@ -152,14 +150,8 @@ public class HealthMonitor implements PwmService
         }
 
 
-        executorService = Executors.newSingleThreadScheduledExecutor(
-                JavaHelper.makePwmThreadFactory(
-                        JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + "-",
-                        true
-                ) );
-
-
-        executorService.scheduleAtFixedRate( new ScheduledUpdater(), 0, settings.getNominalCheckInterval().asMillis(), TimeUnit.MILLISECONDS );
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
+        pwmApplication.scheduleFixedRateJob( new ScheduledUpdater(), executorService, TimeDuration.SECONDS_10, settings.getNominalCheckInterval() );
 
         status = STATUS.OPEN;
     }
@@ -177,7 +169,7 @@ public class HealthMonitor implements PwmService
             final boolean recordsAreStale = TimeDuration.fromCurrent( lastHealthCheckTime ).isLongerThan( settings.getMaximumRecordAge() );
             if ( timeliness == CheckTimeliness.Immediate || ( timeliness == CheckTimeliness.CurrentButNotAncient && recordsAreStale ) )
             {
-                final ScheduledFuture updateTask = executorService.schedule( new ImmediateUpdater(), 0, TimeUnit.NANOSECONDS );
+                final ScheduledFuture updateTask = pwmApplication.scheduleFutureJob( new ImmediateUpdater(), executorService, TimeDuration.ZERO );
                 final Instant beginWaitTime = Instant.now();
                 while ( !updateTask.isDone() && TimeDuration.fromCurrent( beginWaitTime ).isShorterThan( settings.getMaximumForceCheckWait() ) )
                 {

+ 4 - 12
server/src/main/java/password/pwm/svc/cluster/ClusterMachine.java

@@ -38,15 +38,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 
 class ClusterMachine
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ClusterMachine.class );
 
     private final PwmApplication pwmApplication;
-    private final ScheduledExecutorService executorService;
+    private final ExecutorService executorService;
     private final ClusterDataServiceProvider clusterDataServiceProvider;
 
     private ErrorInformation lastError;
@@ -66,16 +65,9 @@ class ClusterMachine
         this.clusterDataServiceProvider = clusterDataServiceProvider;
         this.settings = clusterSettings;
 
-        this.executorService = JavaHelper.makeSingleThreadExecutorService( pwmApplication, ClusterMachine.class );
+        this.executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, ClusterMachine.class );
 
-        final long intervalSeconds = settings.getHeartbeatInterval().as( TimeDuration.Unit.SECONDS );
-
-        this.executorService.scheduleAtFixedRate(
-                new HeartbeatProcess(),
-                intervalSeconds,
-                intervalSeconds,
-                TimeUnit.SECONDS
-        );
+        pwmApplication.scheduleFixedRateJob( new HeartbeatProcess(), executorService, settings.getHeartbeatInterval(), settings.getHeartbeatInterval() );
     }
 
     public void close( )

+ 5 - 10
server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java

@@ -39,9 +39,7 @@ import password.pwm.util.logging.PwmLogger;
 import java.time.Instant;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 
 public class LocalDbAuditVault implements AuditVault
 {
@@ -53,7 +51,7 @@ public class LocalDbAuditVault implements AuditVault
 
     private int maxBulkRemovals = 105;
 
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
     private volatile PwmService.STATUS status = PwmService.STATUS.NEW;
 
 
@@ -76,14 +74,11 @@ public class LocalDbAuditVault implements AuditVault
 
         readOldestRecord();
 
-        executorService = Executors.newSingleThreadScheduledExecutor(
-                JavaHelper.makePwmThreadFactory(
-                        JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + "-",
-                        true
-                ) );
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
 
         status = PwmService.STATUS.OPEN;
-        executorService.scheduleWithFixedDelay( new TrimmerThread(), 0, 10, TimeUnit.MINUTES );
+        final TimeDuration jobFrequency = TimeDuration.of( 10, TimeDuration.Unit.MINUTES );
+        pwmApplication.scheduleFixedRateJob( new TrimmerThread(), executorService, TimeDuration.SECONDS_10, jobFrequency );
     }
 
     public void close( )

+ 7 - 13
server/src/main/java/password/pwm/svc/report/ReportService.java

@@ -64,9 +64,7 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -81,7 +79,7 @@ public class ReportService implements PwmService
     private boolean cancelFlag = false;
     private ReportStatusInfo reportStatus = new ReportStatusInfo( "" );
     private ReportSummaryData summaryData = ReportSummaryData.newSummaryData( null );
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
 
     private UserCacheService userCacheService;
     private ReportSettings settings = new ReportSettings();
@@ -146,12 +144,7 @@ public class ReportService implements PwmService
 
         dnQueue = LocalDBStoredQueue.createLocalDBStoredQueue( pwmApplication, pwmApplication.getLocalDB(), LocalDB.DB.REPORT_QUEUE );
 
-        executorService = Executors.newSingleThreadScheduledExecutor(
-                JavaHelper.makePwmThreadFactory(
-                        JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + "-",
-                        true
-                ) );
-
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
 
         final String startupMsg = "report service started";
         LOGGER.debug( startupMsg );
@@ -420,7 +413,7 @@ public class ReportService implements PwmService
                         if ( executorService != null )
                         {
                             LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "directory unavailable error during background SearchLDAP, will retry; error: " + e.getMessage() );
-                            executorService.schedule( new ReadLDAPTask(), 10, TimeUnit.MINUTES );
+                            pwmApplication.scheduleFutureJob( new ReadLDAPTask(), executorService, TimeDuration.of( 10, TimeDuration.Unit.MINUTES ) );
                         }
                     }
                     else
@@ -497,7 +490,7 @@ public class ReportService implements PwmService
                         if ( executorService != null )
                         {
                             LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "directory unavailable error during background ReadData, will retry; error: " + e.getMessage() );
-                            executorService.schedule( new ProcessWorkQueueTask(), 10, TimeUnit.MINUTES );
+                            pwmApplication.scheduleFutureJob( new ProcessWorkQueueTask(), executorService, TimeDuration.of( 10, TimeDuration.Unit.MINUTES ) );
                         }
                     }
                     else
@@ -739,7 +732,8 @@ public class ReportService implements PwmService
             {
                 final Instant nextZuluZeroTime = JavaHelper.nextZuluZeroTime();
                 final long secondsUntilNextDredge = settings.getJobOffsetSeconds() + TimeDuration.fromCurrent( nextZuluZeroTime ).as( TimeDuration.Unit.SECONDS );
-                executorService.scheduleAtFixedRate( new DailyJobExecuteTask(), secondsUntilNextDredge, TimeDuration.DAY.as( TimeDuration.Unit.SECONDS ), TimeUnit.SECONDS );
+                final TimeDuration initialDelay = TimeDuration.of( secondsUntilNextDredge, TimeDuration.Unit.SECONDS );
+                pwmApplication.scheduleFixedRateJob( new ProcessWorkQueueTask(), executorService, initialDelay, TimeDuration.DAY );
                 LOGGER.debug( "scheduled daily execution, next task will be at " + nextZuluZeroTime.toString() );
             }
             executorService.submit( new RolloverTask() );

+ 5 - 6
server/src/main/java/password/pwm/svc/stats/StatisticsManager.java

@@ -55,8 +55,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
 import java.util.TimerTask;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 
 public class StatisticsManager implements PwmService
 {
@@ -82,7 +81,7 @@ public class StatisticsManager implements PwmService
     private DailyKey currentDailyKey = new DailyKey( new Date() );
     private DailyKey initialDailyKey = new DailyKey( new Date() );
 
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
 
     private final StatisticsBundle statsCurrent = new StatisticsBundle();
     private StatisticsBundle statsDaily = new StatisticsBundle();
@@ -331,10 +330,10 @@ public class StatisticsManager implements PwmService
 
         {
             // setup a timer to roll over at 0 Zula and one to write current stats every 10 seconds
-            executorService = JavaHelper.makeSingleThreadExecutorService( pwmApplication, this.getClass() );
-            executorService.scheduleAtFixedRate( new FlushTask(), 10 * 1000, DB_WRITE_FREQUENCY.asMillis(), TimeUnit.MILLISECONDS );
+            executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
+            pwmApplication.scheduleFixedRateJob( new FlushTask(), executorService, DB_WRITE_FREQUENCY, DB_WRITE_FREQUENCY );
             final TimeDuration delayTillNextZulu = TimeDuration.fromCurrent( JavaHelper.nextZuluZeroTime() );
-            executorService.scheduleAtFixedRate( new NightlyTask(), delayTillNextZulu.asMillis(), TimeUnit.DAYS.toMillis( 1 ), TimeUnit.MILLISECONDS );
+            pwmApplication.scheduleFixedRateJob( new NightlyTask(), executorService, delayTillNextZulu, TimeDuration.DAY );
         }
 
         status = STATUS.OPEN;

+ 4 - 8
server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java

@@ -66,14 +66,13 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 
 public class TelemetryService implements PwmService
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( TelemetryService.class );
 
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
     private PwmApplication pwmApplication;
     private Settings settings;
 
@@ -144,7 +143,7 @@ public class TelemetryService implements PwmService
             LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, "last publish time was " + JavaHelper.toIsoDate( lastPublishTime ) );
         }
 
-        executorService = JavaHelper.makeSingleThreadExecutorService( pwmApplication, TelemetryService.class );
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, TelemetryService.class );
 
         scheduleNextJob();
 
@@ -215,10 +214,7 @@ public class TelemetryService implements PwmService
     private void scheduleNextJob( )
     {
         final TimeDuration durationUntilNextPublish = durationUntilNextPublish();
-        executorService.schedule(
-                new PublishJob(),
-                durationUntilNextPublish.asMillis(),
-                TimeUnit.MILLISECONDS );
+        pwmApplication.scheduleFutureJob( new PublishJob(), executorService, durationUntilNextPublish );
         LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, "next publish time: " + durationUntilNextPublish().asCompactString() );
     }
 

+ 6 - 13
server/src/main/java/password/pwm/svc/token/TokenService.java

@@ -76,9 +76,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.TimerTask;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 
 /**
  * This PWM service is responsible for reading/writing tokens used for forgotten password,
@@ -92,7 +90,7 @@ public class TokenService implements PwmService
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( TokenService.class );
 
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
 
     private PwmApplication pwmApplication;
     private Configuration configuration;
@@ -194,22 +192,17 @@ public class TokenService implements PwmService
             return;
         }
 
-        executorService = Executors.newSingleThreadScheduledExecutor(
-                JavaHelper.makePwmThreadFactory(
-                        JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + "-",
-                        true
-                ) );
+        verifyPwModifyTime = Boolean.parseBoolean( configuration.readAppProperty( AppProperty.TOKEN_VERIFY_PW_MODIFY_TIME ) );
 
-        final TimerTask cleanerTask = new CleanerTask();
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
 
         {
             final int cleanerFrequencySeconds = Integer.parseInt( configuration.readAppProperty( AppProperty.TOKEN_CLEANER_INTERVAL_SECONDS ) );
             final TimeDuration cleanerFrequency = TimeDuration.of( cleanerFrequencySeconds, TimeDuration.Unit.SECONDS );
-            executorService.scheduleAtFixedRate( cleanerTask, 10, cleanerFrequencySeconds, TimeUnit.SECONDS );
+            pwmApplication.scheduleFixedRateJob( new CleanerTask(), executorService, TimeDuration.MINUTE, cleanerFrequency );
             LOGGER.trace( "token cleanup will occur every " + cleanerFrequency.asCompactString() );
         }
 
-        verifyPwModifyTime = Boolean.parseBoolean( configuration.readAppProperty( AppProperty.TOKEN_VERIFY_PW_MODIFY_TIME ) );
 
         status = STATUS.OPEN;
         LOGGER.debug( "open" );
@@ -424,7 +417,7 @@ public class TokenService implements PwmService
         }
     }
 
-    public long size( ) throws PwmUnrecoverableException
+    public long size( )
     {
         if ( status != STATUS.OPEN )
         {

+ 4 - 10
server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -46,8 +46,7 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
 
@@ -58,7 +57,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService
 
     private WordlistConfiguration wordlistConfiguration;
     private WordlistBucket wordklistBucket;
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
 
     private volatile STATUS wlStatus = STATUS.NEW;
 
@@ -94,7 +93,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService
 
         this.wordklistBucket = new WordlistBucket( pwmApplication, wordlistConfiguration, type );
 
-        executorService = JavaHelper.makeSingleThreadExecutorService( pwmApplication, this.getClass() );
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
 
         if ( pwmApplication.getLocalDB() != null )
         {
@@ -108,12 +107,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService
             lastError = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg );
         }
 
-        executorService.scheduleWithFixedDelay(
-                new InspectorJob(),
-                1,
-                wordlistConfiguration.getInspectorFrequency().asMillis(),
-                TimeUnit.MILLISECONDS
-        );
+        pwmApplication.scheduleFixedRateJob( new InspectorJob(), executorService, TimeDuration.SECOND, wordlistConfiguration.getInspectorFrequency() );
     }
 
     boolean containsWord( final String word ) throws PwmUnrecoverableException

+ 8 - 8
server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java

@@ -45,8 +45,8 @@ import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
-import java.util.Timer;
 import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
 
 
 public class SharedHistoryManager implements PwmService
@@ -68,7 +68,7 @@ public class SharedHistoryManager implements PwmService
 
     private volatile PwmService.STATUS status = STATUS.NEW;
 
-    private volatile Timer cleanerTimer = null;
+    private ExecutorService executorService;
 
     private LocalDB localDB;
     private String salt;
@@ -83,9 +83,9 @@ public class SharedHistoryManager implements PwmService
     public void close( )
     {
         status = STATUS.CLOSED;
-        if ( cleanerTimer != null )
+        if ( executorService != null )
         {
-            cleanerTimer.cancel();
+            executorService.shutdown();
         }
         localDB = null;
     }
@@ -251,11 +251,11 @@ public class SharedHistoryManager implements PwmService
         {
             long frequencyMs = maxAgeMs > MAX_CLEANER_FREQUENCY ? MAX_CLEANER_FREQUENCY : maxAgeMs;
             frequencyMs = frequencyMs < MIN_CLEANER_FREQUENCY ? MIN_CLEANER_FREQUENCY : frequencyMs;
+            final TimeDuration frequency = TimeDuration.of( frequencyMs, TimeDuration.Unit.MILLISECONDS );
 
-            LOGGER.debug( "scheduling cleaner task to run once every " + TimeDuration.of( frequencyMs, TimeDuration.Unit.MILLISECONDS ).asCompactString() );
-            final String threadName = JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + " timer";
-            cleanerTimer = new Timer( threadName, true );
-            cleanerTimer.schedule( new CleanerTask(), 1000, frequencyMs );
+            LOGGER.debug( "scheduling cleaner task to run once every " + frequency.asCompactString() );
+            executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
+            pwmApplication.scheduleFixedRateJob( new CleanerTask(), executorService, null, frequency );
         }
     }
 

+ 8 - 11
server/src/main/java/password/pwm/util/db/DatabaseService.java

@@ -58,9 +58,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
+
 
 public class DatabaseService implements PwmService
 {
@@ -84,7 +83,7 @@ public class DatabaseService implements PwmService
     private AtomicLoopIntIncrementer slotIncrementer;
     private final Map<Integer, DatabaseAccessorImpl> accessors = new ConcurrentHashMap<>();
 
-    private ScheduledExecutorService executorService;
+    private ExecutorService executorService;
 
     private final Map<DatabaseAboutProperty, String> debugInfo = new LinkedHashMap<>();
 
@@ -111,14 +110,12 @@ public class DatabaseService implements PwmService
         this.pwmApplication = pwmApplication;
         init();
 
-        executorService = Executors.newSingleThreadScheduledExecutor(
-                JavaHelper.makePwmThreadFactory(
-                        JavaHelper.makeThreadName( pwmApplication, this.getClass() ) + "-",
-                        true
-                ) );
+        executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
 
-        final int watchdogFrequencySeconds = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.DB_CONNECTIONS_WATCHDOG_FREQUENCY_SECONDS ) );
-        executorService.scheduleWithFixedDelay( new ConnectionMonitor(), watchdogFrequencySeconds, watchdogFrequencySeconds, TimeUnit.SECONDS );
+        final TimeDuration watchdogFrequency = TimeDuration.of(
+                Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.DB_CONNECTIONS_WATCHDOG_FREQUENCY_SECONDS ) ),
+                TimeDuration.Unit.SECONDS );
+        pwmApplication.scheduleFixedRateJob( new ConnectionMonitor(), executorService, watchdogFrequency, watchdogFrequency );
     }
 
     private synchronized void init( )

+ 1 - 0
server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java

@@ -127,6 +127,7 @@ public final class WorkQueueProcessor<W extends Serializable>
                     new ArrayBlockingQueue<>( settings.getPreThreads() ),
                     threadFactory
             );
+            executorService.allowCoreThreadTimeOut( true );
         }
     }