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

org.gridkit.util.concurrent.SensibleTaskService Maven / Gradle / Ivy

/**
 * Copyright 2012 Alexey Ragozin
 *
 * 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 org.gridkit.util.concurrent;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 

* Practice have shown that standard java thread pools are totally * useless, when you are dealing with complex multi-agent system. *

*

* They doesn't solve problem of thread sharing between activities * either producing crapload of threads or leave you deadlock prone * due to lack of threads. *

*

* Ironically thread per action approach end up being much more practical. *

*

* Here is may attempt for create dead lock resistant, but economic * thread scheduler. *

* * @author Alexey Ragozin ([email protected]) */ public class SensibleTaskService implements TaskService.Component { static boolean TRACE = false; private static class Shared { static TaskService INSTANCE = new SensibleTaskService(new DefaultConfig("SharedTaskService") { @Override public ThreadFactory getThreadFactory() { return new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); return thread; } }; } }); } public static TaskService getShareInstance() { return Shared.INSTANCE; } private static long TIME_ANCHOR = System.nanoTime(); private static long EON = TimeUnit.DAYS.toNanos(1); private static long now() { return System.nanoTime() - TIME_ANCHOR; } public interface Config { public String getName(); public float getSoftCoreCap(); public float getHardCoreCap(); public long getDelayFactor(); public int getStandbyCap(); public long getStatsUpdatePeriod(); public ThreadFactory getThreadFactory(); } public static class DefaultConfig implements Config { private String name; public DefaultConfig(String name) { this.name = name; } public String getName() { return name; } @Override public float getSoftCoreCap() { return 1; } @Override public float getHardCoreCap() { return (float)Runtime.getRuntime().availableProcessors(); } @Override public long getDelayFactor() { return TimeUnit.MILLISECONDS.toNanos(500); } @Override public int getStandbyCap() { // thread operations are expensive // while idle threads are cheap return 2 * Runtime.getRuntime().availableProcessors(); } @Override public long getStatsUpdatePeriod() { return TimeUnit.MILLISECONDS.toNanos(500); } @Override public ThreadFactory getThreadFactory() { return Executors.defaultThreadFactory(); } } // configuartion private final String name; private final ThreadFactory factory; private float softCoreCap; private float hardCoreCap; private long delayFactor; private int standByCap; private long statsUpdatePeriod; // dynamic state private volatile float utilizationFactor; private AtomicInteger idleThreads = new AtomicInteger(); private int threadIdCounter = 1; private final PriorityQueue queue = new PriorityQueue(); private final Map threads = new ConcurrentHashMap(8, 4); private final ReentrantLock queueLock = new ReentrantLock(); private final ReentrantLock controlLock = new ReentrantLock(false); /** This signal is used to wake up balancer thread */ private final Semaphore controlSignal = new Semaphore(0); private final Semaphore scheduleSignal = new Semaphore(1); private volatile long nextControlerTick = now(); private Controler controler; private volatile boolean terminated; public SensibleTaskService(String name) { this(new DefaultConfig(name)); } public SensibleTaskService(Config config) { name = config.getName(); softCoreCap = config.getSoftCoreCap(); hardCoreCap = config.getHardCoreCap(); delayFactor = config.getDelayFactor(); standByCap = config.getStandbyCap(); statsUpdatePeriod = config.getStatsUpdatePeriod(); factory = config.getThreadFactory(); startControler(); } private void startControler() { controler = new Controler(); Thread thread = factory.newThread(controler); controler.thread = thread; thread.start(); } @Override public void schedule(Task task) { schedule(task, 0, TimeUnit.NANOSECONDS); } @Override public void schedule(Task task, long delay, TimeUnit tu) { long scheduledTime = now() + tu.toNanos(delay); TaskUnit unit = new TaskUnit(scheduledTime, task); enqueue(unit); checkThreadDemand(false); } public void shutdown() { if (terminated) { // TODO wait for termination return; } terminated = true; scheduleSignal.release(Integer.MAX_VALUE >> 1); queueLock.lock(); try { while(!queue.isEmpty()) { TaskUnit tu = queue.poll(); if (tu != null) { tu.abort(); } } } finally { queueLock.unlock(); } controlSignal.release(); try { controler.thread.join(); } catch (InterruptedException e) { // TODO logging // ignore } controlLock.lock(); try { for(Worker worker: threads.values()) { TaskUnit tu = worker.currentTask; if (tu != null) { tu.abort(); } // TODO there is a small chance that // some Task will miss abort call } } finally { controlLock.unlock(); } } private void checkThreadDemand(boolean isControler) { TaskUnit tu = peekTask(); if (tu == null) { return; } long nextScheduled = tu.scheduled; if (nextScheduled > now()) { if (nextControlerTick > nextScheduled) { kickControlThread(); return; } } else { // we have task ready for execution if (idleThreads.get() > 0) { // somebody is idle, let him care scheduleSignal.release(); return; } else { if (!isControler) { if (!controlLock.tryLock()) { kickControlThread(); return; } } else { controlLock.lock(); } try { if (isUnderutilized() || (isBelowHardLimit() && isOverdue(tu.scheduled))) { spawnWorker(); } } finally { controlLock.unlock(); } } } } private boolean isUnderutilized() { float uf = utilizationFactor; if (softCoreCap > uf) { if (TRACE) { System.out.println("Effective usage: " + uf + " (cap " + softCoreCap + ")"); } return true; } else { return false; } } private boolean isBelowHardLimit() { float uf = utilizationFactor; if (hardCoreCap > uf) { if (TRACE) { System.out.println("Effective usage: " + uf + " (hard cap " + softCoreCap + ")"); } return true; } else { return false; } } private boolean isOverdue(long scheduleTime) { return scheduleTime + delayFactor < now(); } private void spawnWorker() { controlLock.lock(); try { Worker worker = new Worker(); Thread thread = factory.newThread(worker); worker.thread = thread; threads.put(thread.getId(), worker); worker.workerNo = threadIdCounter; utilizationFactor += 1; idleThreads.incrementAndGet(); if (TRACE) { System.out.println("Worker " + threadIdCounter + " spawned, usage: " + utilizationFactor); } ++threadIdCounter; thread.start(); } finally{ controlLock.unlock(); } } private void enqueue(TaskUnit unit) { if (terminated){ unit.abort(); } enqueueTask(unit); if (terminated){ unit.abort(); queueLock.lock(); try { queue.remove(unit); } finally { queueLock.unlock(); } } } private void enqueueTask(TaskUnit unit) { queueLock.lock(); try { queue.add(unit); } finally { queueLock.unlock(); } } private TaskUnit pollTask() { queueLock.lock(); try { TaskUnit tu = queue.peek(); if (tu == null || tu.scheduled > now()) { return null; } else { return queue.poll(); } } finally { queueLock.unlock(); } } private TaskUnit peekTask() { queueLock.lock(); try { return queue.peek(); } finally { queueLock.unlock(); } } /** * Signals control thread to wake up as soon as possible */ private void kickControlThread() { controlSignal.release(); } private static class TaskUnit implements Runnable, Comparable { final Task task; long scheduled; Thread thread; boolean started; boolean finished; boolean canceled; public TaskUnit(long scheduled, Task task) { this.task = task; this.scheduled = scheduled; } public int compareTo(TaskUnit o) { long c = scheduled - o.scheduled; return c > 0 ? 1 : c < 0 ? -1 : 0; } @Override public void run() { if (setStarted()) { try { task.run(); } catch(Throwable e) { shipException(e); } finally { setFinished(); } } } synchronized void abort() { if (finished) { return; } else { if (!started) { canceled = true; try { task.canceled(); } catch(Throwable e) { shipException(e); } } else { if (!canceled) { try { canceled = true; task.interrupt(thread); } catch(Throwable e) { shipException(e); } } } } } private void shipException(Throwable e) { // TODO Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); } private synchronized boolean setStarted() { started = true; thread = Thread.currentThread(); return !canceled; } private synchronized void setFinished() { finished = true; } } private class Controler implements Runnable { private Thread thread; @SuppressWarnings("unused") private String originalThreadName; private long nextStatsUpdate; @Override public void run() { originalThreadName = thread.getName(); thread.setName(name + ":coordinator"); nextStatsUpdate = now(); controlSignal.release(); while(!terminated) { boolean shouldUpdateStatistics = nextStatsUpdate <= now(); if (shouldUpdateStatistics) { updateUtilization(); } TaskUnit tu = peekTask(); long nextScheduled = tu == null ? now() + EON : tu.scheduled; boolean hasPendingTasks = nextScheduled <= now(); nextControlerTick = Math.min(nextScheduled, nextStatsUpdate); if (hasPendingTasks) { if (idleThreads.get() > 0) { scheduleSignal.release(); } else { checkThreadDemand(true); } } long sleepTime = nextControlerTick - now(); if (sleepTime > 0) { sleep(sleepTime); } } } private void updateUtilization() { controlLock.lock(); try { float utilization = 0; for(Worker worker: threads.values()) { float wu = worker.collectUsage(); utilization += wu; } if (TRACE) { System.out.println("New usage: " + utilization + " (idle: " + idleThreads.get() + ")"); } utilizationFactor = utilization; nextStatsUpdate = now() + statsUpdatePeriod; } finally { controlLock.unlock(); } } private void sleep(long sleepTime) { try { controlSignal.tryAcquire(sleepTime, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { // ignore } controlSignal.drainPermits(); } } private class Worker implements Runnable { private int workerNo; private Thread thread; @SuppressWarnings("unused") private String originalThreadName; private int taskNo = 0; @SuppressWarnings("unused") private long started; private volatile TaskUnit currentTask; // stats private int lastTask; private long lastCheck; private int encounters; private boolean trackCpu; private long lastCpuTime; /** * When control thread decides to forcibly reduce * thread population. It set blackMark for few threads * which should be discontinued. */ private volatile boolean blackMark; @Override public void run() { originalThreadName = thread.getName(); setThreadName(null); while(!terminated) { try { TaskUnit tu = pollTask(); if (tu != null) { idleThreads.decrementAndGet(); checkThreadDemand(false); execute(tu); // TODO if (blackMark) { break; } idleThreads.incrementAndGet(); continue; } if (isOverQuota()) { break; } else { // waiting until somebody will trigger execution scheduleSignal.acquire(); } } catch (InterruptedException e) { if (terminated) { return; } } } dispose(); } public float collectUsage() { if (currentTask == null) { // idle threads are counted against cap return 1f; } else { int taskNo = this.taskNo; if (lastTask != taskNo) { lastTask = taskNo; lastCheck = now(); encounters = 1; trackCpu = false; return 1f; } else { float factor = 1; long now = now(); encounters++; if (trackCpu && now > lastCheck) { long newCpu = getCpuTime(thread); long cpuDelta = newCpu - lastCpuTime; factor = (1f * cpuDelta) / (now - lastCheck); lastCpuTime = newCpu; } else if (encounters > 2) { trackCpu = true; lastCpuTime = getCpuTime(thread); } lastCheck = now; return factor; } } } private boolean isOverQuota() { if (idleThreads.get() > standByCap) { int r = idleThreads.decrementAndGet(); if (r >= standByCap) { return true; } else { idleThreads.incrementAndGet(); } } return false; } private void dispose() { // IMPORTANT idleThreads is already decreased in isOverQuota if (TRACE) { System.out.println("Disposing thread " + workerNo); } controlLock.lock(); try { threads.remove(thread.getId()); } finally { controlLock.unlock(); } } private void execute(TaskUnit unit) { this.taskNo++; this.currentTask = unit; this.started = now(); setThreadName(unit); if (terminated) { unit.abort(); } else { unit.run(); } this.currentTask = null; setThreadName(null); } private void setThreadName(TaskUnit unit) { String tname = name + ":worker-" + workerNo; if (unit == null) { tname = tname + " - idle"; } else { try { tname = tname + " - " + clip(unit.task.toString(), 32); } catch(Throwable e) { tname = tname + " - ???"; } } thread.setName(tname); } private String clip(String string, int limit) { return string.length() > limit ? string.substring(0, limit) : string; } } private static ThreadMXBean THREADS_MBEAN = ManagementFactory.getThreadMXBean(); private static long getCpuTime(Thread thread) { return THREADS_MBEAN.getThreadCpuTime(thread.getId()); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy