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

com.epam.deltix.util.concurrent.QuickExecutorWithQueue Maven / Gradle / Ivy

/*
 * Copyright 2021 EPAM Systems, Inc
 *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership. Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.epam.deltix.util.concurrent;

import com.epam.deltix.gflog.api.Log;
import com.epam.deltix.gflog.api.LogFactory;
import com.epam.deltix.gflog.api.LogLevel;
import com.epam.deltix.util.collections.QuickList;
import com.epam.deltix.util.lang.Util;
import java.util.*;
import java.util.concurrent.locks.LockSupport;
import net.jcip.annotations.GuardedBy;

/**
 *  Similar to standard Java executors, but does not allocate memory on task
 *  reschedule.
 */
public class QuickExecutorWithQueue {
    public static final boolean         DEBUG_TASKS = false;
    public static final Log LOGGER = LogFactory.getLog("deltix.executor");
    private static final int            JOIN_TIMEOUT_MS = 1000;

    private enum TaskState {
        IDLE,

        /**
         *  Not enough workers, sitting in a queue.
         */
        QUEUED,

        /**
         *  Running in a Worker
         */
        RUNNING,

        /**
         *  Scheduled while running; will be re-run when finished.
         */
        REARMED
    }

    //
    // Strict lock ordering policy: QuickTask THEN QuickExecutorWithQueue instance.
    //
    public static abstract class QuickTask extends QuickList.Entry  {
        protected final QuickExecutorWithQueue       executor;
        //
        //  The state is guarded by this
        //
        TaskState                           state = TaskState.IDLE;
        Worker                              worker = null;

        protected QuickTask (QuickExecutorWithQueue executor) {
            if (executor == null)
                throw new IllegalArgumentException ("null executor");
            
            this.executor = executor;
        }

        protected QuickTask () {
            this (getGlobalInstance ());
        }

        protected boolean                   killSupported () {
            return (false);
        }

        protected boolean                   queueSupported () {
            return (false);
        }

        /**
         *  This method must stop immediately on interrupt and throw
         *  InterruptedException, to cooperate with shutdown.
         */
        public abstract void                run ()
            throws InterruptedException;

        private synchronized boolean        finished () {
            if (state == TaskState.REARMED) {
                if (DEBUG_TASKS)
                    System.out.println (this + " is re-armed");

                state = TaskState.RUNNING;  // go again
                return (false);
            }
            else {
                if (DEBUG_TASKS)
                    System.out.println (this + " is finished");

                state = TaskState.IDLE;
                worker = null;
                return (true);
            }
        }

        public final synchronized void      unschedule () {
            if (state == TaskState.REARMED) {
                if (DEBUG_TASKS)
                    System.out.println (this + " is disarmed");

                state = TaskState.RUNNING;
            }
        }

        public final synchronized void      kill () {
            if (!killSupported ())
                throw new UnsupportedOperationException (this + " does not support kill ()");
            
            switch (state) {
                case REARMED:
                    state = TaskState.RUNNING;
                    // Fall through to RUNNING
                case RUNNING:
                    if (DEBUG_TASKS)
                        System.out.println (this + " is being killed");

                    worker.interrupt ();
                    break;
            }
        }

        public final void                   submit () {
            if (DEBUG_TASKS)
                System.out.println (this + " is being submitted");

            synchronized (this) {
                switch (state) {
                    case REARMED:
                        return;

                    case RUNNING:
                        state = TaskState.REARMED;
                        return;

                    case IDLE:
                        executor.submit (this);
                        break;

                    default:
                        throw new RuntimeException (state.name ());
                }
            }
        }

        @Override
        public synchronized String          toString () {
            if (worker == null)
                return super.toString ();
            else
                return (super.toString () + " running in " + worker);
        }
    }

    private enum WorkerState {
        RUNNING_TASK,

        FREE,

        TERMINATED
    }

    private class Worker extends Thread {
        QuickTask               task;
        volatile WorkerState    state;

        Worker (int idx) {
            super ("Worker #" + idx + " for " + QuickExecutorWithQueue.this);
        }

        @Override
        public void             run () {
            try {
                while (state != WorkerState.TERMINATED) {
                    if (task == null && !getTask (this))
                        continue;

                    try {
                        task.run ();
                    } catch (UncheckedInterruptedException | InterruptedException x) {
                        if (state != WorkerState.TERMINATED)
                            LOGGER.log(LogLevel.DEBUG).append(task).append(" interrupted.").append(x).commit();
                    } catch (Throwable x) {
                        LOGGER.log(LogLevel.ERROR).append(task).append(" failed.").append(x).commit();
                    } finally {
                        if (task.finished ())
                            task = null;
                    }
                }
            } catch (Throwable x) {
                x.printStackTrace ();
            }
        }
    }

    private static QuickExecutorWithQueue            globalInstance = null;
    private static int                      usages = 0;

    public static synchronized QuickExecutorWithQueue getGlobalInstance () {
        if (globalInstance == null)
            globalInstance = new QuickExecutorWithQueue ("Global Executor");

        return (globalInstance);
    }
    
    private final String                    name;

    private volatile int                    maxNumWorkers = 40; // temporarily so low

    private final QuickList      queue = new QuickList  ();

    @GuardedBy ("this")
    private final ArrayList         freePool = new ArrayList  ();
    
    @GuardedBy ("this")
    private final Set               workers = new HashSet  ();
    
    @GuardedBy ("this")
    private int                             workerId = 1;

    private volatile boolean                shutdownInProgress = false;

    private QuickExecutorWithQueue (String name) {
        this.name = name;
    }

    @Override
    public String           toString () {
        return ("QuickExecutorWithQueue \"" + name + "\"");
    }

    private boolean                             getTask (Worker w) {
        synchronized (this) {
            if (workers.size () > maxNumWorkers) {
                if (DEBUG_TASKS)
                    System.out.println (w + " is being disposed (too many running).");

                if (w.state == WorkerState.FREE)
                    freePool.remove (w);

                workers.remove (w);

                w.state = WorkerState.TERMINATED;              
                return (false);
            }

            QuickTask       task = queue.getFirst ();

            if (task != null) {
                assert task.state == TaskState.QUEUED :
                    "Unexpected state " + task.state;

                if (DEBUG_TASKS)
                    System.out.println (task + " is de-queued and dispatched to " + w);

                task.worker = w;
                task.state = TaskState.RUNNING;

                w.task = task;
                return (true);
            }

            //  Park it.
            w.state = WorkerState.FREE;
            freePool.add (w);
        }

        if (DEBUG_TASKS)
            System.out.println (w + " is parking.");

        do {
            LockSupport.park ();                                  
        } while (w.state == WorkerState.FREE);

        return (w.task != null);     // force re-check of all conditions on wakeup.
    }
    
    private synchronized void                   submit (QuickTask task) {
        assert task.executor == this :
            task + " is being submitted to the wrong executor";

        assert task.worker == null;
        assert Thread.holdsLock (task);
        
        if (shutdownInProgress)
            throw new IllegalStateException ("Shutdown in progress");

        Worker      w;
        int         numFreeWorkers = freePool.size ();

        if (numFreeWorkers > 0) {
            w = freePool.remove (numFreeWorkers - 1);

            assert w.state == WorkerState.FREE;

            task.state = TaskState.RUNNING;
            task.worker = w;

            if (DEBUG_TASKS)
                System.out.println (task + " is dispatched to FREE " + w);

            w.task = task;
            w.state = WorkerState.RUNNING_TASK;

            LockSupport.unpark (w);
        } 
        else if (workers.size () < maxNumWorkers || !task.queueSupported ()) {
            w = new Worker (workerId++);

            workers.add (w);

            if (DEBUG_TASKS)
                System.out.println (task + " is dispatched to NEW " + w + " Current # Workers: " + workers.size ());

            task.state = TaskState.RUNNING;
            task.worker = w;

            w.task = task;
            w.state = WorkerState.RUNNING_TASK;
            w.start ();
        }
        else {
            if (DEBUG_TASKS)
                System.out.println (task + " is BEING QUEUED");

            queue.linkLast (task);

            task.state = TaskState.QUEUED;
        }                        
    }

    public synchronized static QuickExecutorWithQueue    reuse () {
        usages++;
        return getGlobalInstance();
    }

    public synchronized static void             shutdown () {
        usages--;

        if (usages <= 0) {
            globalInstance.shutdown (true);
            globalInstance = null;
        }
    }

    private void                                shutdown (
        boolean                                     waitForCompleteShutdown
    )
    {
        if (shutdownInProgress)
            throw new IllegalStateException ("Shutdown in progress");
        
        LOGGER.info (this + " shutting down");
        
        Worker []               workerSnapshot;
        
        shutdownInProgress = true;

        synchronized (this) {
            workerSnapshot = workers.toArray (new Worker [workers.size ()]);
                       
            for (Worker w : workerSnapshot) {
                if (w.state == WorkerState.RUNNING_TASK)
                    w.interrupt ();
                else
                    LockSupport.unpark (w);
                
                w.state = WorkerState.TERMINATED;
            }

            workers.clear ();
            freePool.clear ();

            workerId = 1;
        }

        if (waitForCompleteShutdown) {
            for (Worker w : workerSnapshot) {
                try {
                    //
                    //  Keep interrupting until w is dead.
                    //  This works around ignored interrupts in
                    //      misbehaving tasks.
                    for (;;) {
                        w.join (JOIN_TIMEOUT_MS);

                        if (!w.isAlive ())
                            break;

                        LOGGER.warn("%s failed to terminate in %s ms, interrupting again ...").with(w).with(JOIN_TIMEOUT_MS);

                        w.interrupt ();
                    }
                } catch (InterruptedException x) {
                    LOGGER.warn().append("While shutting down ").append(this).append(x).commit();
                }
            }
        }

        shutdownInProgress = false;

        if (DEBUG_TASKS)
            System.out.println (this + ": shutdown completed.");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy