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

com.googlecode.concurrent.ExecutorServiceManager.scala Maven / Gradle / Ivy

package com.googlecode.concurrent

import java.util.concurrent.BlockingQueue
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ExecutorCompletionService
import java.util.concurrent.Future
import java.util.concurrent.ScheduledFuture
import org.scala_tools.time.Imports._

/**
 * manages executor instantiation, provides factory methods
 * for various executors
 *
 * @author kostantinos.kougios
 *
 *         15 Nov 2011
 */
object ExecutorServiceManager
{

	def wrap(executor: ExecutorService) = new Executor with Shutdown
	{
		protected val executorService = executor
	}

	def newSingleThreadExecutor = new Executor with Shutdown
	{
		protected val executorService = Executors.newSingleThreadExecutor
	}

	def newCachedThreadPool(
		corePoolSize: Int,
		maximumPoolSize: Int,
		keepAliveTimeInSeconds: Int = 60,
		workQueue: BlockingQueue[Runnable] = new SynchronousQueue) =
		new Executor with Shutdown
		{
			override protected val executorService = new ThreadPoolExecutor(
				corePoolSize,
				maximumPoolSize,
				keepAliveTimeInSeconds,
				TimeUnit.SECONDS,
				workQueue)
		}

	def newCachedThreadPoolCompletionService[V](
		corePoolSize: Int,
		maximumPoolSize: Int,
		keepAliveTimeInSeconds: Int = 60,
		workQueue: BlockingQueue[Runnable] = new SynchronousQueue) =
		new CompletionExecutor[V](
			new ThreadPoolExecutor(
				corePoolSize,
				maximumPoolSize,
				keepAliveTimeInSeconds,
				TimeUnit.SECONDS,
				workQueue)
		)

	def newScheduledThreadPool(corePoolSize: Int, errorLogger: Throwable => Unit) =
		new Executor with Shutdown with Scheduling
		{
			override protected val executorService = new ScheduledThreadPoolExecutor(corePoolSize)
			override val onError = errorLogger
		}

	def newScheduledThreadPool(corePoolSize: Int) =
		new Executor with Shutdown with Scheduling
		{
			override protected val executorService = new ScheduledThreadPoolExecutor(corePoolSize)
			override val onError = (t: Throwable) => t.printStackTrace
		}

	def newFixedThreadPool(nThreads: Int) =
		new Executor with Shutdown
		{
			override protected val executorService = Executors.newFixedThreadPool(nThreads)
		}

	def newFixedThreadPoolCompletionService[V](nThreads: Int) =
		new CompletionExecutor[V](Executors.newFixedThreadPool(nThreads))

	/**
	 * creates an executor of nThread, submits f() x times and returns V x times
	 * as returned by f(). It then shutsdown the executor.
	 *
	 * f: Int => V , where Int is the i-th execution, i is between [1..times]
	 * inclusive.
	 *
	 * If any of the invocation of f() fails, the executor will be shut down
	 * and no further threads will be submitted to it. The exception will propagate
	 * to the caller.
	 */
	def lifecycle[V](nThreads: Int, times: Int)(f: Int => V): Seq[V] = {
		val pool = newFixedThreadPool(nThreads)
		try {
			val seq = for (i <- 1 to times) yield pool.submit(f(i))
			seq.map(_.get)
		} finally {
			pool.shutdown
		}
	}

	/**
	 * creates an executor of nThread, submits f() x params.size and returns V x params.size
	 * as returned by f(). It then shutsdown the executor.
	 *
	 * f: T => V , each thread getting a different parameter from the traversable
	 *
	 * If any of the invocation of f() fails, the executor will be shut down
	 * and no further threads will be submitted to it. The exception will propagate
	 * to the caller.
	 */
	def lifecycle[T, V](nThreads: Int, params: Traversable[T])(f: T => V): Traversable[V] = {
		val pool = newFixedThreadPool(nThreads)
		try {
			val results = params.map(param => pool.submit(f(param)))
			results.map(_.get)
		} finally {
			pool.shutdown
		}
	}
}

/**
 * wrapper for the ExecutorService
 *
 * new Executor with Shutdown
 */
abstract class Executor
{
	// ideally the underlying executor should not be accessible
	protected val executorService: ExecutorService

	/**
	 * submits a task for execution and returns a Future.
	 *
	 * example:
	 * 
	 * val future=executor.submit {
	 * // will run on a separate thread soon
	 * 25
	 * }
	 * ...
	 * val result = future.get // result=25
	 * 
	 */
	def submit[R](f: => R) = executorService.submit(new Callable[R]
	{
		def call = f
	})

	def submit[V](task: Callable[V]) = executorService.submit(task)

	def submit(task: Runnable) = executorService.submit(task)
}

/*
 * new Executor with Scheduling with Shutdown
 */
trait Scheduling
{
	protected val executorService: ScheduledExecutorService
	val onError: Throwable => Unit

	/**
	 * schedules a task to run in the future
	 *
	 * example:
	 *
	 * 
	 * val future=schedule(100,TimeUnit.MILLISECONDS) {
	 * // to do in 100 millis from now
	 * }
	 * ...
	 * val result=future.get
	 * 
	 */
	def schedule[R](delay: Long, unit: TimeUnit)(f: => R): ScheduledFuture[R] = executorService.schedule(new Callable[R]
	{
		def call = f
	}, delay, unit)

	/**
	 * schedule a task to run in the future.
	 *
	 * example:
	 * 
	 * usage: val future=schedule(DateTime.now + 2.days) {
	 * // to do in 2 days from now
	 * }
	 * 
	 */
	def schedule[R](runAt: DateTime): (=> R) => ScheduledFuture[R] = {
		val dt = runAt.millis - System.currentTimeMillis
		if (dt < 0) throw new IllegalArgumentException("next run time is in the past : %s".format(runAt))
		schedule(dt, TimeUnit.MILLISECONDS) _
	}

	/**
	 * runs a task periodically. The task initially runs on firstTime. The result R is then
	 * used to call process(R) and if that returns a new DateTime, the task will be executed
	 * again on that time. If process(R) returns None, the task won't be executed again.
	 *
	 * This method returns straight away, any processing occurs on separate threads using
	 * the executor.
	 *
	 * If the task throws an exception, the onError function will be called to log
	 * the error (by default it prints the stacktrace to the console)
	 *
	 * If the process throws an exception, the scheduling of the task will stop.
	 *
	 * @param firstRun		DateTime of the first run, i.e. DateTime.now + 2.seconds
	 * @param process		a function to process the result and specify the next
	 *                       time the task should run. The value is calculated after f is
	 *                       executed and if None the task will not be executed anymore.
	 * @param f				the task
	 */
	def runPeriodically[R](firstRun: DateTime, process: Option[R] => Option[DateTime])(f: => R): Unit =
		schedule(firstRun) {
			val r = try {
				Some(f)
			} catch {
				case e: Throwable =>
					onError(e)
					None
			}
			process(r) match {
				case Some(nextRun) => runPeriodically(nextRun, process)(f)
				case None =>
			}
		}

	/**
	 * periodically runs f, starting on firstRun and repeating according to
	 * the calculated "process" value.
	 *
	 * example:
	 * 
	 *
	 * import org.scala_tools.time.Imports._
	 *
	 * val executorService = ExecutorServiceManager.newScheduledThreadPool(5)
	 *
	 * val start = System.currentTimeMillis
	 * executorService.runPeriodically(DateTime.now + 50.millis, Some(DateTime.now + 1.second)) {
	 * // should print dt 6 times, once per second
	 * println("dt:%d".format(System.currentTimeMillis - start))
	 * }
	 *
	 * Thread.sleep(5500)
	 * executorService.shutdownAndAwaitTermination(DateTime.now + 100.millis)
	 *
	 * 
	 *
	 * If the task throws an exception, the onError function will be called to log
	 * the error (by default it prints the stacktrace to the console)
	 *
	 * If the process throws an exception, the scheduling of the task will stop.
	 *
	 * @param firstRun		DateTime of the first run, i.e. DateTime.now + 2.seconds
	 * @param whenToReRun	a by-value parameter specifying the next time the task should
	 *                       run. The value is calculated after f is executed and if None
	 *                       the task will not be executed anymore.
	 * @param f				the task
	 */
	def runPeriodically[R](firstRun: DateTime, whenToReRun: => Option[DateTime])(f: => R): Unit =
		schedule(firstRun) {
			try {
				f
			} catch {
				case e: Throwable => onError(e)
			}
			whenToReRun match {
				case Some(nextRun) => runPeriodically(nextRun, whenToReRun)(f)
				case None =>
			}
		}
}

/**
 * provides shutdown services to Executor
 */
trait Shutdown
{
	protected val executorService: ExecutorService

	def shutdown = executorService.shutdown

	def shutdownNow = executorService.shutdownNow

	def awaitTermination(timeout: Long, unit: TimeUnit): Unit = executorService.awaitTermination(timeout, unit)

	import org.scala_tools.time.Imports._

	def awaitTermination(timeoutWhen: DateTime): Unit = awaitTermination(timeoutWhen.millis - System.currentTimeMillis, TimeUnit.MILLISECONDS)

	def shutdownAndAwaitTermination(waitTimeInSeconds: Int) {
		shutdown
		awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)
	}

	def shutdownAndAwaitTermination(timeoutWhen: DateTime) {
		shutdown
		awaitTermination(timeoutWhen)
	}
}

/*
 * @see CompletionService
 */
class CompletionExecutor[V](protected val executorService: ExecutorService) extends Shutdown
{
	private val completionService = new ExecutorCompletionService[V](executorService)

	def submit(f: => V): Future[V] = completionService.submit(new Callable[V]
	{
		def call = f
	})

	def submit(task: Callable[V]) = completionService.submit(task)

	def submit(task: Runnable, result: V) = completionService.submit(task, result)

	/**
	 * Retrieves and removes the Future representing the next
	 * completed task, waiting if none are yet present.
	 *
	 * @return the Future representing the next completed task
	 * @throws InterruptedException if interrupted while waiting
	 */
	def take: Future[V] = completionService.take

	/**
	 * Retrieves and removes the Future representing the next
	 * completed task or None if none are present.
	 *
	 * @return the Future representing the next completed task, or
	 *         None if none are present
	 */
	def poll: Option[Future[V]] = {
		val t = completionService.poll
		if (t == null) None else Some(t)
	}

	/**
	 * Retrieves and removes the Future representing the next
	 * completed task, waiting if necessary up to the specified wait
	 * time if none are yet present.
	 *
	 * @param timeout how long to wait before giving up, in units of
	 *                unit
	 * @param unit a TimeUnit determining how to interpret the
	 *             timeout parameter
	 * @return the Future representing the next completed task or
	 *         None if the specified waiting time elapses
	 *         before one is present
	 * @throws InterruptedException if interrupted while waiting
	 */
	def poll(timeout: Long, unit: TimeUnit): Option[Future[V]] = {
		val t = completionService.poll(timeout, unit)
		if (t == null) None else Some(t)
	}

	/**
	 * polls, waiting max until the provided DateTime.
	 */
	def poll(till: DateTime): Option[Future[V]] = pollWaitInMillis(till.millis - System.currentTimeMillis)

	def pollWaitInMillis(timeoutMs: Long): Option[Future[V]] = poll(timeoutMs, TimeUnit.MILLISECONDS)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy