protected CentralJobScheduler() { workStealingExecutors = new ConcurrentHashMap<>( 1 ); topLevelGroup = new TopLevelGroup(); pools = new ThreadPoolManager( topLevelGroup ); ThreadFactory threadFactory = new GroupedDaemonThreadFactory( Group.TASK_SCHEDULER, topLevelGroup ); scheduler = new TimeBasedTaskScheduler( Clocks.nanoClock(), pools ); // The scheduler thread runs at slightly elevated priority for timeliness, and is started in init(). schedulerThread = threadFactory.newThread( scheduler ); int priority = Thread.NORM_PRIORITY + 1; schedulerThread.setPriority( priority ); }
JobHandle submit( Group group, Runnable job ) { ThreadPool threadPool = getThreadPool( group ); return threadPool.submit( job ); }
@Override public void shutdown() { started = false; // First shut down the scheduler, so no new tasks are queued up in the pools. InterruptedException exception = shutDownScheduler(); // Then shut down the thread pools. This involves cancelling jobs which hasn't been cancelled already, // so we avoid having to wait the full maximum wait time on the executor service shut-downs. exception = Exceptions.chain( exception, pools.shutDownAll() ); // Finally, we shut the work-stealing executors down. for ( ExecutorService workStealingExecutor : workStealingExecutors.values() ) { exception = shutdownPool( workStealingExecutor, exception ); } workStealingExecutors.clear(); if ( exception != null ) { throw new RuntimeException( "Unable to shut down job scheduler properly.", exception ); } }
@Override public JobHandle schedule( Group group, Runnable job ) { if ( !started ) { throw new RejectedExecutionException( "Scheduler is not started" ); } return pools.submit( group, job ); }
void submitIfRunnable( ThreadPoolManager pools ) { if ( compareAndSet( RUNNABLE, SUBMITTED ) ) { latestHandle = pools.submit( group, task ); handleRelease.release(); } }
@Override public ThreadFactory threadFactory( Group group ) { return pools.getThreadPool( group ).getThreadFactory(); }
@Before public void setUp() { clock = new FakeClock(); pools = new ThreadPoolManager( new ThreadGroup( "TestPool" ) ); scheduler = new TimeBasedTaskScheduler( clock, pools ); counter = new AtomicInteger(); semaphore = new Semaphore( 0 ); }
@After public void tearDown() { InterruptedException exception = pools.shutDownAll(); if ( exception != null ) { throw new RuntimeException( "Test was interrupted?", exception ); } }
@Override public JobHandle schedule( Group group, Runnable job ) { if ( !started ) { throw new RejectedExecutionException( "Scheduler is not started" ); } return pools.submit( group, job ); }
@Test public void mustNotStartRecurringTasksWherePriorExecutionHasNotYetFinished() { Runnable runnable = () -> { counter.incrementAndGet(); semaphore.acquireUninterruptibly(); }; scheduler.submit( Group.STORAGE_MAINTENANCE, runnable, 100, 100 ); for ( int i = 0; i < 4; i++ ) { scheduler.tick(); clock.forward( 100, TimeUnit.NANOSECONDS ); } semaphore.release( Integer.MAX_VALUE ); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 1 ) ); }
protected CentralJobScheduler() { workStealingExecutors = new ConcurrentHashMap<>( 1 ); topLevelGroup = new TopLevelGroup(); pools = new ThreadPoolManager( topLevelGroup ); ThreadFactory threadFactory = new GroupedDaemonThreadFactory( Group.TASK_SCHEDULER, topLevelGroup ); scheduler = new TimeBasedTaskScheduler( Clocks.nanoClock(), pools ); // The scheduler thread runs at slightly elevated priority for timeliness, and is started in init(). schedulerThread = threadFactory.newThread( scheduler ); int priority = Thread.NORM_PRIORITY + 1; schedulerThread.setPriority( priority ); }
@Override public void shutdown() { started = false; // First shut down the scheduler, so no new tasks are queued up in the pools. InterruptedException exception = shutDownScheduler(); // Then shut down the thread pools. This involves cancelling jobs which hasn't been cancelled already, // so we avoid having to wait the full maximum wait time on the executor service shut-downs. exception = Exceptions.chain( exception, pools.shutDownAll() ); // Finally, we shut the work-stealing executors down. for ( ExecutorService workStealingExecutor : workStealingExecutors.values() ) { exception = shutdownPool( workStealingExecutor, exception ); } workStealingExecutors.clear(); if ( exception != null ) { throw new RuntimeException( "Unable to shut down job scheduler properly.", exception ); } }
void submitIfRunnable( ThreadPoolManager pools ) { if ( compareAndSet( RUNNABLE, SUBMITTED ) ) { latestHandle = pools.submit( group, task ); handleRelease.release(); } }
@Test public void mustNotRescheduleDelayedTasks() throws Exception { JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, counter::incrementAndGet, 100, 0 ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.waitTermination(); assertThat( counter.get(), is( 1 ) ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.waitTermination(); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 1 ) ); }
@Test public void delayedTasksMustNotRunIfCancelledFirst() throws Exception { List<Boolean> cancelListener = new ArrayList<>(); JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, counter::incrementAndGet, 100, 0 ); handle.registerCancelListener( cancelListener::add ); clock.forward( 90, TimeUnit.NANOSECONDS ); scheduler.tick(); handle.cancel( false ); clock.forward( 10, TimeUnit.NANOSECONDS ); scheduler.tick(); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 0 ) ); assertThat( cancelListener, contains( Boolean.FALSE ) ); try { handle.waitTermination(); fail( "waitTermination should have thrown a CancellationException." ); } catch ( CancellationException ignore ) { // Good stuff. } }
@Test public void recurringTasksMustStopWhenCancelled() throws InterruptedException { List<Boolean> cancelListener = new ArrayList<>(); Runnable recurring = () -> { counter.incrementAndGet(); semaphore.release(); }; JobHandle handle = scheduler.submit( Group.STORAGE_MAINTENANCE, recurring, 100, 100 ); handle.registerCancelListener( cancelListener::add ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); assertSemaphoreAcquire(); handle.cancel( true ); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); clock.forward( 100, TimeUnit.NANOSECONDS ); scheduler.tick(); pools.getThreadPool( Group.STORAGE_MAINTENANCE ).shutDown(); assertThat( counter.get(), is( 2 ) ); assertThat( cancelListener, contains( Boolean.TRUE ) ); }
@Override public ThreadFactory threadFactory( Group group ) { return pools.getThreadPool( group ).getThreadFactory(); }
JobHandle submit( Group group, Runnable job ) { ThreadPool threadPool = getThreadPool( group ); return threadPool.submit( job ); }