All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.impl.scheduler.CentralJobScheduler Maven / Gradle / Ivy

Go to download

Neo4j kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see http://neo4j.org.

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.scheduler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import org.neo4j.internal.helpers.Exceptions;
import org.neo4j.kernel.impl.scheduler.ThreadPool.ThreadPoolParameters;
import org.neo4j.kernel.lifecycle.LifecycleAdapter;
import org.neo4j.resources.Profiler;
import org.neo4j.scheduler.ActiveGroup;
import org.neo4j.scheduler.CallableExecutor;
import org.neo4j.scheduler.CallableExecutorService;
import org.neo4j.scheduler.CancelListener;
import org.neo4j.scheduler.FailedJobRun;
import org.neo4j.scheduler.Group;
import org.neo4j.scheduler.JobHandle;
import org.neo4j.scheduler.JobMonitoringParams;
import org.neo4j.scheduler.JobScheduler;
import org.neo4j.scheduler.MonitoredJobExecutor;
import org.neo4j.scheduler.MonitoredJobInfo;
import org.neo4j.scheduler.SchedulerThreadFactoryFactory;
import org.neo4j.time.SystemNanoClock;

public class CentralJobScheduler extends LifecycleAdapter implements JobScheduler, AutoCloseable
{
    private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();

    private final TimeBasedTaskScheduler scheduler;
    private final Thread schedulerThread;
    private final TopLevelGroup topLevelGroup;
    private final ThreadPoolManager pools;
    private final ConcurrentHashMap extraParameters;
    private final FailedJobRunsStore failedJobRunsStore;

    private volatile boolean started;

    private static class TopLevelGroup extends ThreadGroup
    {
        TopLevelGroup()
        {
            super( "Neo4j-" + INSTANCE_COUNTER.incrementAndGet() );
        }

        public void setName( String name ) throws Exception
        {
            Field field = ThreadGroup.class.getDeclaredField( "name" );
            field.setAccessible( true );
            field.set( this, name );
        }
    }

    protected CentralJobScheduler( SystemNanoClock clock )
    {
        topLevelGroup = new TopLevelGroup();
        this.failedJobRunsStore = new FailedJobRunsStore( 100 );
        var jobIdCounter = new AtomicLong();
        pools = new ThreadPoolManager( topLevelGroup, clock, failedJobRunsStore, jobIdCounter::incrementAndGet );
        scheduler = new TimeBasedTaskScheduler( clock, pools, failedJobRunsStore, jobIdCounter::incrementAndGet );
        extraParameters = new ConcurrentHashMap<>();

        // The scheduler thread runs at slightly elevated priority for timeliness, and is started in init().
        ThreadFactory threadFactory = new GroupedDaemonThreadFactory( Group.TASK_SCHEDULER, topLevelGroup );
        schedulerThread = threadFactory.newThread( scheduler );
        int priority = Thread.NORM_PRIORITY + 1;
        schedulerThread.setPriority( priority );
    }

    @Override
    public void setTopLevelGroupName( String name )
    {
        try
        {
            topLevelGroup.setName( name );
        }
        catch ( Exception ignore )
        {
        }
    }

    @Override
    public void setParallelism( Group group, int parallelism )
    {
        pools.assumeNotStarted( group );
        extraParameters.computeIfAbsent( group, g -> new ThreadPoolParameters() ).desiredParallelism = parallelism;
    }

    @Override
    public void setThreadFactory( Group group, SchedulerThreadFactoryFactory threadFactory )
    {
        pools.assumeNotStarted( group );
        extraParameters.computeIfAbsent( group, g -> new ThreadPoolParameters() ).providedThreadFactory = threadFactory;
    }

    @Override
    public void init()
    {
        if ( !started )
        {
            schedulerThread.start();
            started = true;
        }
    }

    @Override
    public CallableExecutor executor( Group group )
    {
        return new CallableExecutorService( getThreadPool( group ).getExecutorService() );
    }

    @Override
    public MonitoredJobExecutor monitoredJobExecutor( Group group )
    {
        var threadPool = getThreadPool( group );
        return threadPool::submit;
    }

    @Override
    public ThreadFactory threadFactory( Group group )
    {
        return getThreadPool( group ).getThreadFactory();
    }

    private ThreadPool getThreadPool( Group group )
    {
        return pools.getThreadPool( group, extraParameters.get( group ) );
    }

    @Override
    public  JobHandle schedule( Group group, JobMonitoringParams jobMonitoringParams, Callable job )
    {
        if ( !started )
        {
            throw new RejectedExecutionException( "Scheduler is not started" );
        }
        return tryRegisterCancelListener( job, getThreadPool( group ).submit( jobMonitoringParams, job ) );
    }

    @Override
    public JobHandle schedule( Group group, Runnable job )
    {
        return schedule( group, JobMonitoringParams.NOT_MONITORED, job );
    }

    @Override
    public JobHandle schedule( Group group, JobMonitoringParams jobMonitoringParams, Runnable job )
    {
        if ( !started )
        {
            throw new RejectedExecutionException( "Scheduler is not started" );
        }
        return tryRegisterCancelListener( job, getThreadPool( group ).submit( jobMonitoringParams, job ) );
    }

    @Override
    public JobHandle scheduleRecurring( Group group, Runnable runnable, long period, TimeUnit timeUnit )
    {
        return scheduleRecurring( group, JobMonitoringParams.NOT_MONITORED, runnable, period, timeUnit );
    }

    @Override
    public JobHandle scheduleRecurring( Group group, JobMonitoringParams monitoredJobParams, Runnable runnable, long period, TimeUnit timeUnit )
    {
        return scheduleRecurring( group, monitoredJobParams, runnable, 0, period, timeUnit );
    }

    @Override
    public JobHandle scheduleRecurring( Group group, Runnable runnable, long initialDelay, long period, TimeUnit unit )
    {
        return scheduleRecurring( group, JobMonitoringParams.NOT_MONITORED, runnable, initialDelay, period, unit );
    }

    @Override
    public JobHandle scheduleRecurring( Group group, JobMonitoringParams monitoredJobParams, Runnable runnable, long initialDelay, long period,
            TimeUnit timeUnit )
    {
        return tryRegisterCancelListener( runnable,
                scheduler.submit( group, monitoredJobParams, runnable, timeUnit.toNanos( initialDelay ), timeUnit.toNanos( period ) ) );
    }

    @Override
    public Stream activeGroups()
    {
        List groups = new ArrayList<>();
        pools.forEachStarted( ( group, pool ) ->
        {
            int activeThreadCount = pool.activeThreadCount();
            if ( activeThreadCount > 0 )
            {
                groups.add( new ActiveGroup( group, activeThreadCount ) );
            }
        } );
        return groups.stream();
    }

    @Override
    public void profileGroup( Group group, Profiler profiler )
    {
        if ( !pools.isStarted( group ) )
        {
            return; // Don't bother profiling groups that hasn't been started.
        }
        getThreadPool( group ).activeThreads().forEach( profiler::profile );
    }

    @Override
    public JobHandle schedule( Group group, Runnable runnable, long initialDelay, TimeUnit unit )
    {
        return schedule( group, JobMonitoringParams.NOT_MONITORED, runnable, initialDelay, unit );
    }

    @Override
    public JobHandle schedule( Group group, JobMonitoringParams monitoredJobParams, Runnable runnable, long initialDelay, TimeUnit timeUnit )
    {
        return tryRegisterCancelListener( runnable, scheduler.submit( group, monitoredJobParams, runnable, timeUnit.toNanos( initialDelay ), 0 ) );
    }

    @Override
    public List getMonitoredJobs()
    {
        List monitoredJobInfos = new ArrayList<>( scheduler.getMonitoredJobs() );

        pools.forEachStarted( ( group, pool ) -> monitoredJobInfos.addAll( pool.getMonitoredJobs() ) );

        return monitoredJobInfos;
    }

    @Override
    public List getFailedJobRuns()
    {
        return failedJobRunsStore.getFailedJobRuns();
    }

    @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() );

        if ( exception != null )
        {
            throw new RuntimeException( "Unable to shut down job scheduler properly.", exception );
        }
    }

    @Override
    public void close()
    {
        shutdown();
    }

    private InterruptedException shutDownScheduler()
    {
        scheduler.stop();
        try
        {
            schedulerThread.join();
        }
        catch ( InterruptedException e )
        {
            return e;
        }
        return null;
    }

    private static  JobHandle tryRegisterCancelListener( Object maybeCancelListener, JobHandle handle )
    {
        if ( maybeCancelListener instanceof CancelListener )
        {
            handle.registerCancelListener( (CancelListener) maybeCancelListener );
        }
        return handle;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy