Coverage Summary for Class: PoolScheduler (it.unibo.tuprolog.argumentation.parallel.federation)

Class Method, % Branch, % Line, % Instruction, %
PoolScheduler 75% (3/4) 100% (4/4) 100% (31/31)
PoolScheduler$Mailbox 100% (5/5) 80% (8/10) 100% (17/17) 97.2% (140/144)
Total 88.9% (8/9) 80% (8/10) 100% (21/21) 97.7% (171/175)


 package it.unibo.tuprolog.argumentation.parallel.federation
 
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
 import java.util.concurrent.atomic.AtomicBoolean
 
 /**
  * The queues run on a pool of [threads] daemon threads, each queue on one thread at a time: a task that fails is
  * skipped, as an actor resumed on a failure does
  */
 class PoolScheduler(
     threads: Int,
 ) : Scheduler,
     AutoCloseable {
     private val executor: ExecutorService =
         Executors.newFixedThreadPool(threads) { task -> Thread(task, "arg2p-parallel").apply { isDaemon = true } }
 
     override fun queue(): TaskQueue = Mailbox()
 
     /** Stops the threads */
     override fun close() {
         executor.shutdownNow()
     }
 
     private inner class Mailbox : TaskQueue {
         private val tasks = ConcurrentLinkedQueue<() -> Unit>()
         private val scheduled = AtomicBoolean(false)
 
         // The thread running a task of the queue now, if one is: an interruption reaches that task alone
         private val lock = Any()
         private var running: Thread? = null
 
         override fun interrupt() = synchronized(lock) { running?.interrupt() ?: Unit }
 
         override fun post(task: () -> Unit) {
             tasks.add(task)
             schedule()
         }
 
         private fun schedule() {
             if (scheduled.compareAndSet(false, true)) executor.execute(::drain)
         }
 
         private fun drain() {
             try {
                 while (true) {
                     val task = tasks.poll() ?: break
                     synchronized(lock) { running = Thread.currentThread() }
                     handled(task)
                     // an interruption of the task not left to the next ones on the thread, of any queue
                     synchronized(lock) {
                         running = null
                         Thread.interrupted()
                     }
                 }
             } finally {
                 scheduled.set(false)
                 // a task posted after the last poll, before scheduled was reset
                 if (tasks.isNotEmpty()) schedule()
             }
         }
     }
 }