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

org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl.kt Maven / Gradle / Ivy

Go to download

Implementation of REPL compiler and preprocessor for Jupyter dialect of Kotlin (IDE-compatible)

There is a newer version: 0.12.0-333
Show newest version
package org.jetbrains.kotlinx.jupyter.execution

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.jetbrains.kotlinx.jupyter.api.KernelLoggerFactory
import org.jetbrains.kotlinx.jupyter.api.getLogger
import org.jetbrains.kotlinx.jupyter.exceptions.isInterruptedException
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread

class JupyterExecutorImpl(
    loggerFactory: KernelLoggerFactory,
) : JupyterExecutor {
    private val logger = loggerFactory.getLogger(this::class)

    private inner class Task(
        private val name: String,
        private val classLoader: ClassLoader,
        private val body: () -> T,
    ) {
        private val resultFuture = CompletableFuture>()

        fun execute() {
            require(!resultFuture.isDone) {
                "Task $name was already executed"
            }

            val myThread = Thread.currentThread()

            myThread.name = name
            myThread.contextClassLoader = classLoader

            var execException: Throwable? = null
            val execRes: T? =
                try {
                    executionInProgress.set(true)
                    body()
                } catch (e: Throwable) {
                    execException = e
                    null
                } finally {
                    myThread.name = IDLE_EXECUTOR_NAME
                    executionInProgress.set(false)
                    if (Thread.interrupted()) {
                        logger.info("Clearing interrupted status")
                    }
                }

            val exception = execException

            val result =
                if (exception == null) {
                    ExecutionResult.Success(execRes!!)
                } else {
                    if (exception.isInterruptedException()) {
                        ExecutionResult.Interrupted
                    } else {
                        ExecutionResult.Failure(exception)
                    }
                }

            resultFuture.complete(result)
        }

        fun join(): ExecutionResult {
            return resultFuture.join()
        }
    }

    private val tasksQueue = ArrayBlockingQueue>(MAX_QUEUED_TASKS)

    private val executionInProgress = AtomicBoolean(false)

    private val executorThread =
        thread(name = IDLE_EXECUTOR_NAME) {
            while (true) {
                tasksQueue.take().execute()
            }
        }

    private val coroutineScope = CoroutineScope(Dispatchers.Default)

    override fun  runExecution(
        name: String,
        classLoader: ClassLoader?,
        body: () -> T,
    ): ExecutionResult {
        val task =
            Task(
                name,
                classLoader ?: Thread.currentThread().contextClassLoader,
                body,
            )
        tasksQueue.put(task)
        return task.join()
    }

    /**
     * We cannot use [Thread.interrupt] here because we have no way
     * to control the code user executes. [Thread.interrupt] will do nothing for
     * the simple calculation (like `while (true) 1`). Consider replacing with
     * something smarter in the future.
     */
    override fun interruptExecution() {
        // We interrupt only current execution and don't clear the queue, it's intended
        logger.info("Stopping execution...")

        if (executionInProgress.get()) {
            val execution = executorThread
            val executionName = execution.name
            logger.info("Stopping $executionName...")

            // We hope that user implemented isInterrupted checks on their side
            execution.interrupt()
            logger.info("$executionName interrupted")
            Thread.sleep(100)

            if (execution.name == executionName) {
                // We're not lucky, execution is still in progress
                // Stopping it
                try {
                    @Suppress("DEPRECATION")
                    execution.stop()
                    logger.info("$executionName stopped")
                } catch (e: UnsupportedOperationException) {
                    logger.warn(
                        "We tried to stop $executionName thread, but it's not supported in the current version of JRE",
                        e,
                    )
                }
            }
        }
    }

    override fun launchJob(runnable: suspend CoroutineScope.() -> Unit) {
        coroutineScope.launch(block = runnable)
    }

    override fun close() {
        tasksQueue.clear()
        interruptExecution()
        coroutineScope.cancel("Jupyter executor was shut down")
    }

    companion object {
        private const val IDLE_EXECUTOR_NAME = ""
        private const val MAX_QUEUED_TASKS = 256
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy