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

.kovenant.kovenant-core.3.2.2.source-code.dispatcher-jvm.kt Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/*
 * Copyright (c) 2015 Mark Platvoet
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * THE SOFTWARE.
 */

package nl.komponents.kovenant

import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger


fun MutableDispatcherContext.jvmDispatcher(body: JvmDispatcherBuilder.() -> Unit) {
    dispatcher = buildJvmDispatcher(body)
}

fun buildJvmDispatcher(body: JvmDispatcherBuilder.() -> Unit): Dispatcher = concreteBuildDispatcher(body)

internal fun concreteBuildDispatcher(body: JvmDispatcherBuilder.() -> Unit): Dispatcher {
    val builder = ConcreteDispatcherBuilder()
    builder.body()
    return builder.build()
}

interface JvmDispatcherBuilder : DispatcherBuilder {
    var threadFactory: (target: Runnable, dispatcherName: String, id: Int) -> Thread
}

private class ConcreteDispatcherBuilder : JvmDispatcherBuilder {
    override var name = "kovenant-dispatcher"
    private var localNumberOfThreads = threadAdvice
    override var exceptionHandler: (Exception) -> Unit = { e -> e.printStackTrace(System.err) }
    override var errorHandler: (Throwable) -> Unit = { t -> t.printStackTrace(System.err) }

    override var workQueue: WorkQueue<() -> Unit> = NonBlockingWorkQueue()
    private val pollStrategyBuilder = ConcretePollStrategyBuilder()

    override var threadFactory: (target: Runnable, dispatcherName: String, id: Int) -> Thread
            = fun(target: Runnable, dispatcherName: String, id: Int): Thread {
        val thread = Thread(target, "$dispatcherName-$id")
        thread.isDaemon = false
        return thread
    }


    override var concurrentTasks: Int
        get() = localNumberOfThreads
        set(value) {
            if (value < 1) {
                throw ConfigurationException("concurrentTasks must be at least 1, but was $value")
            }
            localNumberOfThreads = value
        }


    override fun pollStrategy(body: PollStrategyBuilder.() -> Unit) {
        pollStrategyBuilder.clear()
        pollStrategyBuilder.body()
    }

    fun build(): Dispatcher {
        val localWorkQueue = workQueue

        return NonBlockingDispatcher(name = name,
                numberOfThreads = localNumberOfThreads,
                exceptionHandler = exceptionHandler,
                errorHandler = errorHandler,
                workQueue = localWorkQueue,
                pollStrategy = pollStrategyBuilder.build(localWorkQueue),
                threadFactory = threadFactory)
    }
}


class ConcretePollStrategyBuilder() : PollStrategyBuilder {
    private val factories = ArrayList Unit>>()

    fun clear() = factories.clear()

    override fun yielding(numberOfPolls: Int) {
        factories.add(YieldingPollStrategyFactory(attempts = numberOfPolls))
    }

    override fun sleeping(numberOfPolls: Int, sleepTimeInMs: Long) {
        factories.add(SleepingPollStrategyFactory(attempts = numberOfPolls, sleepTimeMs = sleepTimeInMs))
    }

    override fun busy(numberOfPolls: Int) {
        factories.add(BusyPollStrategyFactory(attempts = numberOfPolls))
    }

    override fun blocking() {
        factories.add(BlockingPollStrategyFactory())
    }

    override fun blockingSleep(numberOfPolls: Int, sleepTimeInMs: Long) {
        factories.add(BlockingSleepPollStrategyFactory(attempts = numberOfPolls, sleepTimeMs = sleepTimeInMs))
    }

    private fun buildDefaultStrategy(pollable: Pollable<() -> Unit>): PollStrategy<() -> Unit> {
        val defaultFactories = listOf(YieldingPollStrategyFactory<() -> Unit>(), SleepingPollStrategyFactory<() -> Unit>())
        return ChainPollStrategyFactory(defaultFactories).build(pollable)
    }

    fun build(pollable: Pollable<() -> Unit>): PollStrategy<() -> Unit> = if (factories.isEmpty()) {
        buildDefaultStrategy(pollable)
    } else {
        ChainPollStrategyFactory(factories).build(pollable)
    }
}


private class NonBlockingDispatcher(val name: String,
                                    val numberOfThreads: Int,
                                    private val exceptionHandler: (Exception) -> Unit,
                                    private val errorHandler: (Throwable) -> Unit,
                                    private val workQueue: WorkQueue<() -> Unit>,
                                    private val pollStrategy: PollStrategy<() -> Unit>,
                                    private val threadFactory: (target: Runnable, dispatcherName: String, id: Int) -> Thread) : ProcessAwareDispatcher {

    init {
        if (numberOfThreads < 1) {
            throw IllegalArgumentException("numberOfThreads must be at least 1 but was $numberOfThreads")
        }
    }

    private val running = AtomicBoolean(true)
    private val threadId = AtomicInteger(0)
    private val contextCount = AtomicInteger(0)

    private val threadContexts = ConcurrentLinkedQueue()


    override fun offer(task: () -> Unit): Boolean {
        if (running.get()) {
            workQueue.offer(task)
            val threadSize = contextCount.get()
            if (threadSize < numberOfThreads) {
                val threadNumber = contextCount.incrementAndGet()
                if (threadNumber <= numberOfThreads && workQueue.size() > 0) {
                    val newThreadContext = newThreadContext()
                    threadContexts.offer(newThreadContext)
                    if (!running.get()) {
                        //it can be the case that during initialization of the context the dispatcher has been shutdown
                        //and this newly created created is missed. So request shutdown again.
                        newThreadContext.interrupt()
                    }

                } else {
                    contextCount.decrementAndGet()
                }
            }
            return true
        }
        return false
    }

    override fun stop(force: Boolean, timeOutMs: Long, block: Boolean): List<() -> Unit> {
        if (running.compareAndSet(true, false)) {

            //Notify all thread to simply die as soon as possible
            threadContexts.forEach { it.kamikaze() }

            if (block) {
                val localTimeOutMs = if (force) 1 else timeOutMs
                val now = System.currentTimeMillis()
                val napTimeMs = Math.min(timeOutMs, 10L)

                fun allThreadsShutdown() = contextCount.get() <= 0
                fun keepWaiting() = localTimeOutMs < 1 || (System.currentTimeMillis() - now) >= localTimeOutMs

                var interrupted = false
                while (!allThreadsShutdown() && keepWaiting()) {
                    try {
                        Thread.sleep(napTimeMs)
                    } catch (e: InterruptedException) {
                        //ignoring for now since it would break the shutdown contract
                        //remember and interrupt later
                        interrupted = true
                    }
                }
                if (interrupted) {
                    //calling thread was interrupted during shutdown, set the interrupted flag again
                    Thread.currentThread().interrupt()
                }

                threadContexts.forEach { it.interrupt() }

                return depleteQueue()
            }
        }
        return ArrayList() //depleteQueue() also returns an ArrayList, returning this for consistency
    }

    override val terminated: Boolean get() = stopped && contextCount.get() < 0

    override val stopped: Boolean get() = !running.get()

    private fun depleteQueue(): List<() -> Unit> {
        val remains = ArrayList<() -> Unit>()
        do {
            val function = workQueue.poll() ?: return remains
            remains.add(function)
        } while (true)
        throw IllegalStateException("unreachable")
    }

    override fun tryCancel(task: () -> Unit): Boolean {
        if (workQueue.remove(task)) return true

        //try any of the running threadContexts
        threadContexts.forEach {
            if (it.cancel(task)) return true
        }
        return false
    }

    // implemented for completeness. not the best implementation out there, iteration is quite wasteful
    // on resources. Not used in primary processes though. Consider using a custom list implementation,
    // there is more then one already implemented
    override fun ownsCurrentProcess(): Boolean = threadContexts.any { it.isCurrentThread() }

    private fun newThreadContext(): ThreadContext {
        return ThreadContext(threadId.incrementAndGet())
    }


    internal fun deRegisterRequest(context: ThreadContext, force: Boolean = false): Boolean {

        val succeeded = threadContexts.remove(context)
        if (!force && succeeded && threadContexts.isEmpty() && workQueue.isNotEmpty() && running.get()) {
            //that, hopefully rare, state where all threadContexts thought they had nothing to do
            //but the queue isn't empty. Reinstate anyone that notes this.
            threadContexts.add(context)
            return false
        }
        if (succeeded) {
            contextCount.decrementAndGet()
        }
        return true
    }


    private inner class ThreadContext(val id: Int) : Runnable {

        private val pending = 0
        private val running = 1
        private val polling = 2
        private val mutating = 3

        private val state = AtomicInteger(pending)
        private val thread: Thread
        private @Volatile var alive = true
        private @Volatile var keepAlive: Boolean = true
        private @Volatile var pollResult: (() -> Unit)? = null


        init {
            thread = threadFactory(this, name, id)
            if (!thread.isAlive) {
                thread.start()
            }
        }


        private fun changeState(expect: Int, update: Int) {
            while (state.compareAndSet(expect, update));
        }

        private fun tryChangeState(expect: Int, update: Int): Boolean = state.compareAndSet(expect, update)

        override fun run() {
            while (alive) {
                changeState(pending, polling)
                try {
                    pollResult = if (keepAlive) pollStrategy.get() else workQueue.poll(block = false)
                    if (!keepAlive || pollResult == null) {
                        // not interested in keeping alive and no work left. Die.
                        if (deRegisterRequest(this)) {
                            //de register succeeded, shutdown this context.
                            interrupt()
                        }
                    }

                } catch(e: InterruptedException) {
                    // we need to catch it for graceful shutdown and can ignore it
                    // because this can only mean polling has failed and therefor pollResult == null
                } finally {
                    changeState(polling, pending)
                    if (!keepAlive && alive) {
                        // if at this point keepAlive is false but the context itself is alive
                        // the thread might be in an interrupted state, we need to clear that because
                        // we are probably in graceful shutdown mode and we don't want to interfere with any
                        // tasks that need to be executed
                        Thread.interrupted()

                        if (!alive) {
                            // oh you concurrency, you tricky bastard. We might just cleared that interrupted flag
                            // but it can be the case that after checking all the flags this context was interrupted
                            // for a full shutdown and we just cleared that. So interrupt again.
                            thread.interrupt()
                        }
                    }
                }

                val fn = pollResult
                if (fn != null) {
                    try {
                        changeState(pending, running)
                        try {
                            fn()
                        } finally {
                            // Need to switch back to pending as soon as possible
                            // otherwise funny things might happen when we use cancel.
                            // cancel may only interrupt during running state.
                            changeState(running, pending)
                        }
                    } catch(e: InterruptedException) {
                        // we only want to report unexpected interrupted exception. The expected
                        // cases are cancellation of a task or a total shutdown of the dispatcher.
                        // `pollResult == null` means the task has been cancelled.
                        if (pollResult != null && alive) {
                            exceptionHandler(e)
                        }

                    } catch(e: Exception) {
                        exceptionHandler(e)
                    } catch(t: Throwable) {
                        // I think the StackOverFlowError is the only one we can reasonably recover from since the
                        // complete stack has been unwound at this point.
                        if (t !is StackOverflowError) {
                            // This can be anything. Most likely out of memory errors, so everything can go haywire
                            // from here. Let's *try* to gracefully dismiss this thread by un registering from the pool and
                            // die.
                            deRegisterRequest(this, force = true)
                        }

                        // Try to report it
                        errorHandler(t)


                    } finally {
                        // No matter what, we are going to try to clear the interrupted flag if any.
                        // this is because this thread can be in an interrupted state because of cancellation,
                        // the fact that we might be dead or simply by user code which has bluntly called interrupt().
                        // whatever reason we are going to clear it, because it interferes with polling for and running
                        // the next task.
                        Thread.interrupted()

                        // the only state that may keep the thread in interrupted state is when alive == false.
                        // but since we are the last statement the loop is going to end anyway because it checks
                        // whether we're still alive.

                        // KOV-67
                        //
                        // null out the poll result. When blocking PollStrategies are it might not get updated with
                        // a new value
                        pollResult = null
                    }
                }
            }
        }


        /*
        Become 'kamikaze' or just really suicidal. Try to bypass the pollStrategy for quick and clean death.
         */
        fun kamikaze() {
            keepAlive = false
            if (tryChangeState(polling, mutating)) {
                //this thread is in the polling state and we are going to interrupt it.
                //because our polling strategies might be blocked
                thread.interrupt()
                changeState(mutating, polling)
            }

        }

        fun cancel(task: () -> Unit): Boolean {
            while (task === pollResult) {
                //Can't catch this at state pending or polling, loop while we are running

                //if we're in running state try interrupting it
                //so we need to claim the time for doing this by going from a running to
                //mutating state
                if (tryChangeState(running, mutating)) {

                    //Though we successfully changed from running to mutating it might
                    //just be a complete different task already. check again
                    val cancelable = task === pollResult
                    if (cancelable) {
                        //interrupt this thread so any running process can catch that
                        thread.interrupt()

                        //set pollResult to null to signal we cancelled this task.
                        //That way we can clear the interrupted flag
                        pollResult = null
                    }

                    //always change back to running
                    changeState(mutating, running)

                    //we had a successful cacncel request
                    if (cancelable) return true
                }
            }
            return false
        }

        fun interrupt() {
            alive = false
            thread.interrupt()
            deRegisterRequest(this, force = true)
        }

        fun isCurrentThread(): Boolean = Thread.currentThread() == thread
    }


}


interface PollStrategy {
    fun get(): V?
}

private interface PollStrategyFactory {
    fun build(pollable: Pollable): PollStrategy
}

private class ChainPollStrategyFactory(private val factories: List>) : PollStrategyFactory {
    override fun build(pollable: Pollable): PollStrategy {
        val strategies = factories.map { it.build(pollable) }
        return ChainPollStrategy(strategies)
    }
}


private class ChainPollStrategy(private val strategies: List>) : PollStrategy {
    override fun get(): V? {
        strategies.forEach { strategy ->
            val result = strategy.get()
            if (result != null) return result
        }
        return null
    }
}

private class YieldingPollStrategyFactory(private val attempts: Int = 1000) : PollStrategyFactory {
    override fun build(pollable: Pollable): PollStrategy {
        return YieldingPollStrategy(pollable, attempts)
    }

}

private class YieldingPollStrategy(private val pollable: Pollable,
                                            private val attempts: Int) : PollStrategy {
    override fun get(): V? {
        for (i in 0..attempts) {
            val value = pollable.poll(block = false)
            if (value != null) return value
            Thread.`yield`()
            if (Thread.currentThread().isInterrupted) break
        }
        return null
    }
}


private class BusyPollStrategyFactory(private val attempts: Int = 1000) : PollStrategyFactory {
    override fun build(pollable: Pollable): PollStrategy {
        return BusyPollStrategy(pollable, attempts)
    }

}

private class BusyPollStrategy(private val pollable: Pollable,
                                        private val attempts: Int) : PollStrategy {
    override fun get(): V? {
        for (i in 0..attempts) {
            val value = pollable.poll(block = false)
            if (value != null) return value
            if (Thread.currentThread().isInterrupted) break
        }
        return null
    }
}


private class SleepingPollStrategyFactory(private val attempts: Int = 100,
                                                   private val sleepTimeMs: Long = 10) : PollStrategyFactory {
    override fun build(pollable: Pollable): PollStrategy {
        return SleepingPollStrategy(pollable, attempts, sleepTimeMs)
    }

}

private class SleepingPollStrategy(private val pollable: Pollable,
                                            private val attempts: Int,
                                            private val sleepTimeMs: Long) : PollStrategy {
    override fun get(): V? {
        for (i in 0..attempts) {
            val value = pollable.poll(block = false)
            if (value != null) return value
            Thread.sleep(sleepTimeMs)
        }
        return null
    }
}

private class BlockingSleepPollStrategyFactory(private val attempts: Int = 100,
                                                        private val sleepTimeMs: Long = 10) : PollStrategyFactory {
    override fun build(pollable: Pollable): PollStrategy {
        return BlockingSleepPollStrategy(pollable, attempts, sleepTimeMs)
    }

}

private class BlockingSleepPollStrategy(private val pollable: Pollable,
                                                 private val attempts: Int = 100,
                                                 private val sleepTimeMs: Long = 10) : PollStrategy {
    override fun get(): V? {
        for (i in 0..attempts) {
            val value = pollable.poll(block = true, timeoutMs = sleepTimeMs)
            if (value != null) return value
        }
        return null
    }
}

private class BlockingPollStrategyFactory() : PollStrategyFactory {
    override fun build(pollable: Pollable): PollStrategy {
        return BlockingPollStrategy(pollable)
    }
}

private class BlockingPollStrategy(private val pollable: Pollable) : PollStrategy {
    override fun get(): V? = pollable.poll(block = true)
}

//on multicores, leave one thread out so that
//the dispatcher thread can run on it's own core
private val threadAdvice: Int
    get() = Math.max(availableProcessors - 1, 1)

private val availableProcessors: Int
    get() = Runtime.getRuntime().availableProcessors()






© 2015 - 2024 Weber Informatics LLC | Privacy Policy