diff --git a/buildSrc/src/main/kotlin/Ci.kt b/buildSrc/src/main/kotlin/Ci.kt index eb53a4302..abe07f268 100644 --- a/buildSrc/src/main/kotlin/Ci.kt +++ b/buildSrc/src/main/kotlin/Ci.kt @@ -27,7 +27,7 @@ object Ci { // this is the version used for building snapshots // .GITHUB_RUN_NUMBER-snapshot will be appended - private const val snapshotBase = "0.7.4" + private const val snapshotBase = "0.8.0" private val githubRunNumber = System.getenv("GITHUB_RUN_NUMBER") diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 3e4689987..00127eba3 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -32,7 +32,7 @@ object Libs { } object Coroutines { - private const val version = "1.5.1" + private const val version = "1.5.2" const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" const val jdk8 = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$version" } diff --git a/buildSrc/src/main/kotlin/Plugins.kt b/buildSrc/src/main/kotlin/Plugins.kt index 2b7e542e7..ade109d0f 100644 --- a/buildSrc/src/main/kotlin/Plugins.kt +++ b/buildSrc/src/main/kotlin/Plugins.kt @@ -23,7 +23,7 @@ * Licensor: infinitic.io */ -const val kotlinVersion = "1.5.30" +const val kotlinVersion = "1.5.31" object Plugins { object Kotlin { @@ -38,12 +38,7 @@ object Plugins { object Ktlint { const val id = "org.jlleitschuh.gradle.ktlint" - const val version = "10.1.0" - } - - object Shadow { - const val id = "com.github.johnrengelman.shadow" - const val version = "7.0.0" + const val version = "10.2.0" } object TestLogger { diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/Deferred.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/Deferred.kt index c6bc34155..8bf2435ee 100644 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/Deferred.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/Deferred.kt @@ -25,10 +25,18 @@ package io.infinitic.client -import java.util.UUID +import java.util.concurrent.CompletableFuture -interface Deferred { - val id: UUID - fun await(): T - fun join(): Deferred +interface Deferred { + val id: String + + fun await(): R + + fun cancelAsync(): CompletableFuture + + fun cancel(): Unit = cancelAsync().join() + + fun retryAsync(): CompletableFuture + + fun retry(): Unit = retryAsync().join() } diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/InfiniticClient.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/InfiniticClient.kt index aa576f02a..8785fde5c 100644 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/InfiniticClient.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/InfiniticClient.kt @@ -25,89 +25,92 @@ package io.infinitic.client -import io.infinitic.client.deferred.DeferredTask -import io.infinitic.client.deferred.DeferredWorkflow -import io.infinitic.client.proxies.ClientDispatcher -import io.infinitic.common.clients.data.ClientName +import io.infinitic.client.dispatcher.ClientDispatcher +import io.infinitic.client.dispatcher.ClientDispatcherImpl import io.infinitic.common.clients.messages.ClientMessage -import io.infinitic.common.proxies.SendChannelProxyHandler -import io.infinitic.common.proxies.TaskProxyHandler -import io.infinitic.common.proxies.WorkflowProxyHandler +import io.infinitic.common.data.ClientName +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.proxies.GetTaskProxyHandler +import io.infinitic.common.proxies.GetWorkflowProxyHandler +import io.infinitic.common.proxies.NewTaskProxyHandler +import io.infinitic.common.proxies.NewWorkflowProxyHandler +import io.infinitic.common.proxies.ProxyHandler import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskMeta -import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.data.TaskOptions import io.infinitic.common.tasks.data.TaskTag import io.infinitic.common.tasks.engine.SendToTaskEngine -import io.infinitic.common.tasks.engine.messages.CancelTask -import io.infinitic.common.tasks.engine.messages.RetryTask import io.infinitic.common.tasks.tags.SendToTaskTagEngine -import io.infinitic.common.tasks.tags.messages.CancelTaskPerTag -import io.infinitic.common.tasks.tags.messages.RetryTaskPerTag -import io.infinitic.common.workflows.data.workflows.WorkflowCancellationReason +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowMeta -import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.data.workflows.WorkflowOptions import io.infinitic.common.workflows.data.workflows.WorkflowTag import io.infinitic.common.workflows.engine.SendToWorkflowEngine -import io.infinitic.common.workflows.engine.messages.CancelWorkflow -import io.infinitic.common.workflows.engine.messages.RetryWorkflowTask import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine -import io.infinitic.common.workflows.tags.messages.CancelWorkflowPerTag -import io.infinitic.common.workflows.tags.messages.RetryWorkflowTaskPerTag -import io.infinitic.exceptions.clients.CanNotApplyOnChannelException -import io.infinitic.exceptions.clients.CanNotApplyOnNewTaskStubException -import io.infinitic.exceptions.clients.CanNotApplyOnNewWorkflowStubException -import io.infinitic.exceptions.clients.CanNotAwaitStubPerTag -import io.infinitic.exceptions.clients.CanNotReuseWorkflowStubException -import io.infinitic.exceptions.clients.NotAStubException -import io.infinitic.exceptions.thisShouldNotHappen +import io.infinitic.exceptions.clients.InvalidStubException +import io.infinitic.workflows.Consumer0 +import io.infinitic.workflows.Consumer1 +import io.infinitic.workflows.Consumer2 +import io.infinitic.workflows.Consumer3 +import io.infinitic.workflows.Consumer4 +import io.infinitic.workflows.Consumer5 +import io.infinitic.workflows.Consumer6 +import io.infinitic.workflows.Consumer7 +import io.infinitic.workflows.Consumer8 +import io.infinitic.workflows.Consumer9 import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.cancel -import kotlinx.coroutines.future.future import kotlinx.coroutines.job -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mu.KotlinLogging import java.io.Closeable import java.lang.reflect.Proxy -import java.util.UUID import java.util.concurrent.CompletableFuture import java.util.concurrent.Executors @Suppress("MemberVisibilityCanBePrivate", "unused") abstract class InfiniticClient : Closeable { - abstract val clientName: ClientName + /** + * Client's name + * This name must be unique + */ + val name by lazy { clientName.toString() } + protected abstract val clientName: ClientName protected abstract val sendToTaskTagEngine: SendToTaskTagEngine protected abstract val sendToTaskEngine: SendToTaskEngine protected abstract val sendToWorkflowTagEngine: SendToWorkflowTagEngine protected abstract val sendToWorkflowEngine: SendToWorkflowEngine - protected val logger = KotlinLogging.logger {} private val sendThreadPool = Executors.newCachedThreadPool() - - open val sendingScope = CoroutineScope(sendThreadPool.asCoroutineDispatcher() + Job()) + protected val sendingScope = CoroutineScope(sendThreadPool.asCoroutineDispatcher() + Job()) private val runningThreadPool = Executors.newCachedThreadPool() + protected val runningScope = CoroutineScope(runningThreadPool.asCoroutineDispatcher() + Job()) - open val runningScope = CoroutineScope(runningThreadPool.asCoroutineDispatcher() + Job()) - - private val dispatcher by lazy { - ClientDispatcher( + protected val dispatcher: ClientDispatcher by lazy { + ClientDispatcherImpl( sendingScope, clientName, - sendToTaskTagEngine, sendToTaskEngine, - sendToWorkflowTagEngine, - sendToWorkflowEngine + sendToWorkflowEngine, + sendToTaskTagEngine, + sendToWorkflowTagEngine ) } + /** + * Get last Deferred created by the call of a stub + */ + val lastDeferred get() = dispatcher.getLastDeferred() + + /** + * Close all resources used + */ override fun close() { // first make sure that all messages are sent join() @@ -120,471 +123,737 @@ abstract class InfiniticClient : Closeable { runningThreadPool.shutdown() } + /** + * Wait for all messages to be sent + */ fun join() = runBlocking { sendingScope.coroutineContext.job.children.forEach { it.join() } } - suspend fun handle(message: ClientMessage) { + internal suspend fun handle(message: ClientMessage) { logger.debug { "receiving $message" } dispatcher.handle(message) } /** - * Create stub for a new task + * Create a stub for a new task */ @JvmOverloads fun newTask( klass: Class, tags: Set = setOf(), - options: TaskOptions? = null, + options: TaskOptions = TaskOptions(), meta: Map = mapOf() - ): T = TaskProxyHandler( + ): T = NewTaskProxyHandler( klass = klass, taskTags = tags.map { TaskTag(it) }.toSet(), - taskOptions = options ?: TaskOptions(), + taskOptions = options, taskMeta = TaskMeta(meta) ) { dispatcher }.stub() /** - * Create stub for an existing task targeted per id + * Create a stub for a new workflow */ - fun getTask( + @JvmOverloads + fun newWorkflow( klass: Class, - id: UUID - ): T = TaskProxyHandler( + tags: Set = setOf(), + options: WorkflowOptions = WorkflowOptions(), + meta: Map = mapOf(), + ): T = NewWorkflowProxyHandler( klass = klass, - perTaskId = TaskId(id), - perTag = null + workflowTags = tags.map { WorkflowTag(it) }.toSet(), + workflowOptions = options, + workflowMeta = WorkflowMeta(meta) ) { dispatcher }.stub() /** - * Create stub for an existing task targeted per tag + * Create a stub for an existing task targeted by id */ - fun getTask( + fun getTaskById( klass: Class, - tag: String - ): T = TaskProxyHandler( + id: String + ): T = GetTaskProxyHandler( klass = klass, - perTaskId = null, - perTag = TaskTag(tag) + TaskId(id), + null ) { dispatcher }.stub() /** - * Synchronous call to get task'ids per tag and name + * Create a stub for existing task targeted by tag */ - fun getTaskIds( + fun getTaskByTag( klass: Class, tag: String - ): Set = dispatcher.getTaskIdsPerTag( - TaskName(klass.name), + ): T = GetTaskProxyHandler( + klass = klass, + null, TaskTag(tag) - ) + ) { dispatcher }.stub() /** - * Create stub for a new workflow + * Create a stub for an existing workflow targeted by id */ - @JvmOverloads - fun newWorkflow( + fun getWorkflowById( klass: Class, - tags: Set = setOf(), - options: WorkflowOptions? = null, - meta: Map = mapOf() - ): T = WorkflowProxyHandler( + id: String + ): T = GetWorkflowProxyHandler( klass = klass, - workflowTags = tags.map { WorkflowTag(it) }.toSet(), - workflowOptions = options ?: WorkflowOptions(), - workflowMeta = WorkflowMeta(meta) + WorkflowId(id), + null ) { dispatcher }.stub() /** - * Create stub for an existing workflow per id + * Create a stub for existing workflow targeted by tag */ - fun getWorkflow( + fun getWorkflowByTag( klass: Class, - id: UUID - ): T = WorkflowProxyHandler( + tag: String + ): T = GetWorkflowProxyHandler( klass = klass, - perTag = null, - perWorkflowId = WorkflowId(id) + null, + WorkflowTag(tag) ) { dispatcher }.stub() /** - * Create stub for an existing workflow per tag + * Dispatch without parameter a task or workflow returning an object */ - fun getWorkflow( - klass: Class, - tag: String - ): T = WorkflowProxyHandler( - klass = klass, - perTag = WorkflowTag(tag), - perWorkflowId = null - ) { dispatcher }.stub() + fun dispatchAsync( + method: () -> R + ): CompletableFuture> = startAsync { method.invoke() } /** - * Synchronous call to get WorkflowIds per tag and name + * Dispatch with 1 parameter a task or workflow returning an object */ - fun getWorkflowIds( - klass: Class, - tag: String - ): Set = dispatcher.getWorkflowIdsPerTag( - WorkflowName(klass.name), - WorkflowTag(tag) - ) - - /** - * Asynchronously process a task or a workflow - */ - fun async(proxy: T, method: T.() -> S): Deferred { - if (proxy !is Proxy) throw NotAStubException(proxy::class.java.name, "async") - - return when (val handler = Proxy.getInvocationHandler(proxy)) { - is TaskProxyHandler<*> -> { - handler.reset() - handler.isSync = false - proxy.method() - dispatcher.dispatch(handler) - } - is WorkflowProxyHandler<*> -> { - if (! handler.isNew()) throw CanNotReuseWorkflowStubException("${handler.workflowName}") - handler.isSync = false - proxy.method() - dispatcher.dispatch(handler) - } - is SendChannelProxyHandler<*> -> throw CanNotApplyOnChannelException("cancel") - else -> thisShouldNotHappen("unknown handle type ${handler::class}") - } - } + fun dispatchAsync( + method: (p1: P1) -> R, + p1: P1 + ): CompletableFuture> = startAsync { method.invoke(p1) } /** - * Asynchronously process a task (helper) + * Dispatch with 2 parameters a task or workflow returning an object */ - @JvmOverloads - fun asyncTask( - klass: Class, - tags: Set = setOf(), - options: TaskOptions? = null, - meta: Map = mapOf(), - method: T.() -> S - ) = async(newTask(klass, tags, options, meta), method) + fun dispatchAsync( + method: (p1: P1, p2: P2) -> R, + p1: P1, + p2: P2 + ): CompletableFuture> = startAsync { method.invoke(p1, p2) } /** - * Asynchronously process a workflow (helper) + * Dispatch with 3 parameters a task or workflow returning an object */ - @JvmOverloads - fun asyncWorkflow( - klass: Class, - tags: Set = setOf(), - options: WorkflowOptions? = null, - meta: Map = mapOf(), - method: T.() -> S - ) = async(newWorkflow(klass, tags, options, meta), method) + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3) -> R, + p1: P1, + p2: P2, + p3: P3 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3) } /** - * Cancel a task or a workflow from a stub + * Dispatch with 4 parameters a task or workflow returning an object */ - fun cancel(proxy: T): CompletableFuture { - if (proxy !is Proxy) throw NotAStubException(proxy::class.java.name, "cancel") + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3, p4: P4) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3, p4) } - return when (val handler = Proxy.getInvocationHandler(proxy)) { - is TaskProxyHandler<*> -> cancelTaskHandler(handler) - is WorkflowProxyHandler<*> -> cancelWorkflowHandler(handler) - is SendChannelProxyHandler<*> -> throw CanNotApplyOnChannelException("cancel") - else -> thisShouldNotHappen("Unknown handle type ${handler::class}") - } - } + /** + * Dispatch with 5 parameters a task or workflow returning an object + */ + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3, p4, p5) } /** - * Cancel a task by id + * Dispatch with 6 parameters a task or workflow returning an object */ - fun cancelTask( - klass: Class, - id: UUID - ) = cancel(getTask(klass, id)) + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3, p4, p5, p6) } /** - * Cancel a task by tag + * Dispatch with 7 parameters a task or workflow returning an object */ - fun cancelTask( - klass: Class, - tag: String - ) = cancel(getTask(klass, tag)) + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3, p4, p5, p6, p7) } /** - * Cancel a workflow by id + * Dispatch with 8 parameters a task or workflow returning an object */ - fun cancelWorkflow( - klass: Class, - id: UUID - ) = cancel(getWorkflow(klass, id)) + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3, p4, p5, p6, p7, p8) } /** - * Cancel a workflow by tag + * Dispatch with 9 parameters a task or workflow returning an object */ - fun cancelWorkflow( - klass: Class, - tag: String - ) = cancel(getWorkflow(klass, tag)) + fun dispatchAsync( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9 + ): CompletableFuture> = startAsync { method.invoke(p1, p2, p3, p4, p5, p6, p7, p8, p9) } /** - * Await a task or a workflowTask from a stub + * Dispatch without parameter a task or workflow returning an object */ - fun await(proxy: T): Any { - if (proxy !is Proxy) throw NotAStubException(proxy::class.java.name, "retry") + fun dispatch( + method: () -> R + ): Deferred = dispatchAsync(method).join() - return when (val handler = Proxy.getInvocationHandler(proxy)) { - is TaskProxyHandler<*> -> awaitTaskHandler(handler) - is WorkflowProxyHandler<*> -> awaitWorkflowHandler(handler) - is SendChannelProxyHandler<*> -> throw CanNotApplyOnChannelException("await") - else -> thisShouldNotHappen("Unknown handle type ${handler::class}") - } - } + /** + * Dispatch with 1 parameter a task or workflow returning an object + */ + fun dispatch( + method: (p1: P1) -> R, + p1: P1 + ): Deferred = dispatchAsync(method, p1).join() /** - * Await a task by id + * Dispatch with 2 parameters a task or workflow returning an object */ - fun awaitTask( - klass: Class, - id: UUID - ): Any = await(getTask(klass, id)) + fun dispatch( + method: (p1: P1, p2: P2) -> R, + p1: P1, + p2: P2 + ): Deferred = dispatchAsync(method, p1, p2).join() /** - * Await a workflow by id + * Dispatch with 3 parameters a task or workflow returning an object */ - fun awaitWorkflow( - klass: Class, - id: UUID - ): Any = await(getWorkflow(klass, id)) + fun dispatch( + method: (p1: P1, p2: P2, p3: P3) -> R, + p1: P1, + p2: P2, + p3: P3 + ): Deferred = dispatchAsync(method, p1, p2, p3).join() /** - * Complete a task or a workflow from a stub + * Dispatch with 4 parameters a task or workflow returning an object */ - fun complete(proxy: T, value: Any?) { - if (proxy !is Proxy) throw NotAStubException(proxy::class.java.name, "complete") + fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4 + ): Deferred = dispatchAsync(method, p1, p2, p3, p4).join() - when (val handler = Proxy.getInvocationHandler(proxy)) { - is TaskProxyHandler<*> -> TODO("Not yet implemented") - is WorkflowProxyHandler<*> -> TODO("Not yet implemented") - is SendChannelProxyHandler<*> -> throw CanNotApplyOnChannelException("complete") - else -> throw RuntimeException("Unknown handle type ${handler::class}") - } - } + /** + * Dispatch with 5 parameters a task or workflow returning an object + */ + fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5 + ): Deferred = dispatchAsync(method, p1, p2, p3, p4, p5).join() /** - * Complete a task by id + * Dispatch with 6 parameters a task or workflow returning an object */ - fun completeTask( - klass: Class, - id: UUID, - value: Any? - ) = complete(getTask(klass, id), value) + fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6 + ): Deferred = dispatchAsync(method, p1, p2, p3, p4, p5, p6).join() /** - * Complete a task by tag + * Dispatch with 7 parameters a task or workflow returning an object */ - fun completeTask( - klass: Class, - tag: String, - value: Any? - ) = complete(getTask(klass, tag), value) + fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7 + ): Deferred = dispatchAsync(method, p1, p2, p3, p4, p5, p6, p7).join() /** - * Complete a workflow by id + * Dispatch with 8 parameters a task or workflow returning an object */ - fun completeWorkflow( - klass: Class, - id: UUID, - value: Any? - ) = complete(getWorkflow(klass, id), value) + fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8 + ): Deferred = dispatchAsync(method, p1, p2, p3, p4, p5, p6, p7, p8).join() /** - * Complete a workflow by tag + * Dispatch with 9 parameters a task or workflow returning an object */ - fun completeWorkflow( - klass: Class, - tag: String, - value: Any? - ) = complete(getWorkflow(klass, tag), value) + fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9 + ): Deferred = dispatchAsync(method, p1, p2, p3, p4, p5, p6, p7, p8, p9).join() /** - * Retry a task or a workflowTask from a stub + * Dispatch without parameter a task or workflow returning void */ - fun retry(proxy: T): CompletableFuture { - if (proxy !is Proxy) throw NotAStubException(proxy::class.java.name, "retry") + fun dispatchVoidAsync( + method: Consumer0 + ): CompletableFuture> = startVoidAsync { method.apply() } - return when (val handler = Proxy.getInvocationHandler(proxy)) { - is TaskProxyHandler<*> -> retryTaskHandler(handler) - is WorkflowProxyHandler<*> -> retryWorkflowHandler(handler) - is SendChannelProxyHandler<*> -> throw CanNotApplyOnChannelException("retry") - else -> throw RuntimeException("Unknown handle type ${handler::class}") - } - } + /** + * Dispatch with 1 parameter a task or workflow returning void + */ + fun dispatchVoidAsync( + method: Consumer1, + p1: P1 + ): CompletableFuture> = startVoidAsync { method.apply(p1) } /** - * Retry a task by id + * Dispatch with 2 parameters a task or workflow returning void */ - fun retryTask( - klass: Class, - id: UUID - ) = retry(getTask(klass, id)) + fun dispatchVoidAsync( + method: Consumer2, + p1: P1, + p2: P2 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2) } /** - * Retry a task by tag + * Dispatch with 3 parameters a task or workflow returning void */ - fun retryTask( - klass: Class, - tag: String - ) = retry(getTask(klass, tag)) + fun dispatchVoidAsync( + method: Consumer3, + p1: P1, + p2: P2, + p3: P3 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3) } /** - * Retry a workflow by id + * Dispatch with 4 parameters a task or workflow returning void */ - fun retryWorkflow( - klass: Class, - id: UUID - ) = retry(getWorkflow(klass, id)) + fun dispatchVoidAsync( + method: Consumer4, + p1: P1, + p2: P2, + p3: P3, + p4: P4 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3, p4) } /** - * Retry a workflow by tag + * Dispatch with 5 parameters a task or workflow returning void */ - fun retryWorkflow( - klass: Class, - tag: String - ) = retry(getWorkflow(klass, tag)) - - private fun cancelTaskHandler(handler: TaskProxyHandler): CompletableFuture { - if (handler.isNew()) throw CanNotApplyOnNewTaskStubException("${handler.taskName}", "cancel") - - return sendingScope.future { - when { - handler.perTaskId != null -> { - val msg = CancelTask( - taskId = handler.perTaskId!!, - taskName = handler.taskName - ) - launch { sendToTaskEngine(msg) } - } - handler.perTag != null -> { - val msg = CancelTaskPerTag( - taskTag = handler.perTag!!, - taskName = handler.taskName - ) - launch { sendToTaskTagEngine(msg) } - } - else -> thisShouldNotHappen() - } - - Unit + fun dispatchVoidAsync( + method: Consumer5, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3, p4, p5) } + + /** + * Dispatch with 6 parameters a task or workflow returning void + */ + fun dispatchVoidAsync( + method: Consumer6, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3, p4, p5, p6) } + + /** + * Dispatch with 7 parameters a task or workflow returning void + */ + fun dispatchVoidAsync( + method: Consumer7, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3, p4, p5, p6, p7) } + + /** + * Dispatch with 8 parameters a task or workflow returning void + */ + fun dispatchVoidAsync( + method: Consumer8, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3, p4, p5, p6, p7, p8) } + + /** + * Dispatch with 9 parameters a task or workflow returning void + */ + fun dispatchVoidAsync( + method: Consumer9, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9 + ): CompletableFuture> = startVoidAsync { method.apply(p1, p2, p3, p4, p5, p6, p7, p8, p9) } + + /** + * Dispatch without parameter a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer0 + ): Deferred = dispatchVoidAsync(method).join() + + /** + * Dispatch with 1 parameter a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer1, + p1: P1 + ): Deferred = dispatchVoidAsync(method, p1).join() + + /** + * Dispatch with 2 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer2, + p1: P1, + p2: P2 + ): Deferred = dispatchVoidAsync(method, p1, p2).join() + + /** + * Dispatch with 3 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer3, + p1: P1, + p2: P2, + p3: P3 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3).join() + + /** + * Dispatch with 4 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer4, + p1: P1, + p2: P2, + p3: P3, + p4: P4 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3, p4).join() + + /** + * Dispatch with 5 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer5, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3, p4, p5).join() + + /** + * Dispatch with 6 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer6, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3, p4, p5, p6).join() + + /** + * Dispatch with 7 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer7, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3, p4, p5, p6, p7).join() + + /** + * Dispatch with 8 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer8, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3, p4, p5, p6, p7, p8).join() + + /** + * Dispatch with 9 parameters a task or workflow returning void + */ + fun dispatchVoid( + method: Consumer9, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9 + ): Deferred = dispatchVoidAsync(method, p1, p2, p3, p4, p5, p6, p7, p8, p9).join() + + /** + * Await a task or a workflow targeted by its id + */ + @Suppress("UNCHECKED_CAST") + fun await( + stub: T + ): Any? = when (val handler = getProxyHandler(stub)) { + is GetTaskProxyHandler -> when { + handler.taskId != null -> + dispatcher.awaitTask( + handler.returnType, + handler.taskName, + handler.methodName, + handler.taskId!!, + false + ) + handler.taskTag != null -> + TODO("Not yet implemented") + else -> + thisShouldNotHappen() + } + is GetWorkflowProxyHandler -> when { + handler.workflowId != null -> + dispatcher.awaitWorkflow( + handler.returnType, + handler.workflowName, + handler.methodName, + handler.workflowId!!, + null, + false + ) + handler.workflowTag != null -> + TODO("Not yet implemented") + else -> + thisShouldNotHappen() } + else -> throw InvalidStubException("$stub") } - private fun retryTaskHandler(handler: TaskProxyHandler): CompletableFuture { - if (handler.isNew()) throw CanNotApplyOnNewTaskStubException("${handler.taskName}", "retry") - - return sendingScope.future { - when { - handler.perTaskId != null -> { - val msg = RetryTask( - taskId = handler.perTaskId!!, - taskName = handler.taskName - ) - launch { sendToTaskEngine(msg) } - } - handler.perTag != null -> { - val msg = RetryTaskPerTag( - taskTag = handler.perTag!!, - taskName = handler.taskName - ) - launch { sendToTaskTagEngine(msg) } - } - else -> thisShouldNotHappen() - } - - Unit + /** + * Await a method from a running workflow targeted by its id and the methodRunId + */ + @Suppress("UNCHECKED_CAST") + fun await( + stub: T, + methodRunId: String + ): Any? = when (val handler = getProxyHandler(stub)) { + is GetWorkflowProxyHandler -> when { + handler.workflowId != null -> + dispatcher.awaitWorkflow( + handler.returnType, + handler.workflowName, + handler.methodName, + handler.workflowId!!, + MethodRunId(methodRunId), + false + ) + handler.workflowTag != null -> + throw InvalidStubException("$stub") + else -> + thisShouldNotHappen() } + else -> throw InvalidStubException("$stub") } - private fun awaitTaskHandler(handler: TaskProxyHandler): Any { - if (handler.isNew()) throw CanNotApplyOnNewTaskStubException("${handler.taskName}", "await") - - return when { - handler.perTaskId != null -> DeferredTask( - taskName = handler.taskName, - taskId = handler.perTaskId!!, - isSync = false, - dispatcher = dispatcher - ).await() - handler.perTag != null -> throw CanNotAwaitStubPerTag("${handler.taskName}") - else -> thisShouldNotHappen() - } + /** + * Cancel a task or a workflow + */ + @Suppress("UNCHECKED_CAST") + fun cancelAsync( + stub: T + ): CompletableFuture = when (val handler = getProxyHandler(stub)) { + is GetTaskProxyHandler -> + dispatcher.cancelTaskAsync(handler.taskName, handler.taskId, handler.taskTag) + is GetWorkflowProxyHandler -> + dispatcher.cancelWorkflowAsync(handler.workflowName, handler.workflowId, null, handler.workflowTag) + else -> + throw InvalidStubException("$stub") } - private fun cancelWorkflowHandler(handler: WorkflowProxyHandler): CompletableFuture { - if (handler.isNew()) throw CanNotApplyOnNewWorkflowStubException("${handler.workflowName}", "cancel") - - return sendingScope.future { - when { - handler.perWorkflowId != null -> { - val msg = CancelWorkflow( - workflowId = handler.perWorkflowId!!, - workflowName = handler.workflowName, - reason = WorkflowCancellationReason.CANCELED_BY_CLIENT - ) - launch { sendToWorkflowEngine(msg) } - } - handler.perTag != null -> { - val msg = CancelWorkflowPerTag( - workflowTag = handler.perTag!!, - workflowName = handler.workflowName, - reason = WorkflowCancellationReason.CANCELED_BY_CLIENT - ) - launch { sendToWorkflowTagEngine(msg) } - } - else -> thisShouldNotHappen() - } - - Unit - } + /** + * Cancel a task or a workflow + */ + @Suppress("UNCHECKED_CAST") + fun cancel( + stub: T + ): Unit = cancelAsync(stub).join() + + /** + * Complete a task or a workflow + */ + @Suppress("UNCHECKED_CAST") + fun completeAsync( + stub: T, + value: Any? + ): CompletableFuture = when (val handler = getProxyHandler(stub)) { + is GetTaskProxyHandler -> + dispatcher.completeTaskAsync(handler.taskName, handler.taskId, handler.taskTag, value) + is GetWorkflowProxyHandler -> + dispatcher.completeWorkflowAsync(handler.workflowName, handler.workflowId, handler.workflowTag, value) + else -> + throw InvalidStubException("$stub") } - private fun retryWorkflowHandler(handler: WorkflowProxyHandler): CompletableFuture { - if (handler.isNew()) throw CanNotApplyOnNewWorkflowStubException("${handler.workflowName}", "retry") - - return sendingScope.future { - when { - handler.perWorkflowId != null -> { - val msg = RetryWorkflowTask( - workflowId = handler.perWorkflowId!!, - workflowName = handler.workflowName - ) - launch { sendToWorkflowEngine(msg) } - } - handler.perTag != null -> { - val msg = RetryWorkflowTaskPerTag( - workflowTag = handler.perTag!!, - workflowName = handler.workflowName - ) - launch { sendToWorkflowTagEngine(msg) } - } - else -> thisShouldNotHappen() - } - - Unit - } + /** + * Complete a task or a workflow + */ + @Suppress("UNCHECKED_CAST") + fun complete( + stub: T, + value: Any? + ): Unit = completeAsync(stub, value).join() + + /** + * Retry a task or a workflow + */ + @Suppress("UNCHECKED_CAST") + fun retryAsync( + stub: T, + ): CompletableFuture = when (val handler = getProxyHandler(stub)) { + is GetTaskProxyHandler -> + dispatcher.retryTaskAsync(handler.taskName, handler.taskId, handler.taskTag) + is GetWorkflowProxyHandler -> + dispatcher.retryWorkflowAsync(handler.workflowName, handler.workflowId, handler.workflowTag) + else -> + throw InvalidStubException("$stub") + } + + /** + * Retry a task or a workflow + */ + @Suppress("UNCHECKED_CAST") + fun retry( + stub: T, + ): Unit = retryAsync(stub).join() + + /** + * get ids of a stub, associated to a specific tag + */ + @Suppress("UNCHECKED_CAST") + fun getIds( + stub: T + ): Set = when (val handler = getProxyHandler(stub)) { + is GetTaskProxyHandler -> + dispatcher.getTaskIdsByTag(handler.taskName, handler.taskId, handler.taskTag) + is GetWorkflowProxyHandler -> + dispatcher.getWorkflowIdsByTag(handler.workflowName, handler.workflowId, handler.workflowTag) + else -> + throw InvalidStubException("$stub") + } + + private fun startAsync( + invoke: () -> R + ): CompletableFuture> { + val handler = ProxyHandler.async(invoke) ?: throw InvalidStubException() + + return dispatcher.dispatchAsync(handler) + } + + private fun startVoidAsync( + invoke: () -> Unit + ): CompletableFuture> { + val handler = ProxyHandler.async(invoke) ?: throw InvalidStubException() + + return dispatcher.dispatchAsync(handler) } - private fun awaitWorkflowHandler(handler: WorkflowProxyHandler): Any { - if (handler.isNew()) throw CanNotApplyOnNewWorkflowStubException("${handler.workflowName}", "await") - - return when { - handler.perWorkflowId != null -> DeferredWorkflow( - workflowName = handler.workflowName, - workflowId = handler.perWorkflowId!!, - isSync = false, - dispatcher = dispatcher - ).await() - handler.perTag != null -> throw CanNotAwaitStubPerTag("${handler.workflowName}") - else -> thisShouldNotHappen() + private fun getProxyHandler(stub: Any): ProxyHandler<*> { + val exception by lazy { InvalidStubException(stub::class.java.name) } + + val handler = try { + Proxy.getInvocationHandler(stub) + } catch (e: IllegalArgumentException) { + throw exception } + + if (handler !is ProxyHandler<*>) throw exception + + return handler } } diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/clientExtensions.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/clientExtensions.kt deleted file mode 100644 index ec74b46ad..000000000 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/clientExtensions.kt +++ /dev/null @@ -1,214 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -@file:Suppress("unused") - -package io.infinitic.client - -import io.infinitic.common.tasks.data.TaskOptions -import io.infinitic.common.workflows.data.workflows.WorkflowOptions -import java.util.UUID - -/** - * Create stub for a new task - */ -inline fun InfiniticClient.newTask( - tags: Set = setOf(), - options: TaskOptions? = null, - meta: Map = mapOf() -): T = newTask(T::class.java, tags, options, meta) - -/** - * Create stub for a new workflow - */ -inline fun InfiniticClient.newWorkflow( - tags: Set = setOf(), - options: WorkflowOptions? = null, - meta: Map = mapOf() -): T = newWorkflow(T::class.java, tags, options, meta) - -/** - * Create stub for an existing task targeted per id - */ -inline fun InfiniticClient.getTask( - id: UUID -): T = getTask(T::class.java, id) - -/** - * Create stub for an existing task targeted per tag - */ -inline fun InfiniticClient.getTask( - tag: String -): T = getTask(T::class.java, tag) - -/** - * Create stub for an existing workflow per id - */ -inline fun InfiniticClient.getWorkflow( - id: UUID -): T = getWorkflow(T::class.java, id) - -/** - * Create stub for an existing workflow per tag - */ -inline fun InfiniticClient.getWorkflow( - tag: String -): T = getWorkflow(T::class.java, tag) - -/** - * Dispatch a task (helper) - */ -inline fun InfiniticClient.asyncTask( - tags: Set = setOf(), - options: TaskOptions? = null, - meta: Map = mapOf(), - noinline method: T.() -> S -) = asyncTask(T::class.java, tags, options, meta, method) - -/** - * Dispatch a workflow (helper) - */ -inline fun InfiniticClient.asyncWorkflow( - tags: Set = setOf(), - options: WorkflowOptions? = null, - meta: Map = mapOf(), - noinline method: T.() -> S -) = asyncWorkflow(T::class.java, tags, options, meta, method) - -/** - * Cancel task per id - */ -inline fun InfiniticClient.cancelTask( - id: UUID -) = cancelTask(T::class.java, id) - -/** - * Cancel task per tag - */ -inline fun InfiniticClient.cancelTask( - tag: String -) = cancelTask(T::class.java, tag) - -/** - * Cancel workflow per id - */ -inline fun InfiniticClient.cancelWorkflow( - id: UUID -) = cancelWorkflow(T::class.java, id) - -/** - * Cancel workflow per tag - */ -inline fun InfiniticClient.cancelWorkflow( - tag: String -) = cancelWorkflow(T::class.java, tag) - -/** - * Complete task per id - */ -inline fun InfiniticClient.completeTask( - id: UUID, - value: Any -) = completeTask(T::class.java, id, value) - -/** - * Complete task per tag - */ -inline fun InfiniticClient.completeTask( - tag: String, - value: Any -) = completeTask(T::class.java, tag, value) - -/** - * Complete workflow per id - */ -inline fun InfiniticClient.completeWorkflow( - id: UUID, - value: Any -) = completeWorkflow(T::class.java, id, value) - -/** - * Complete workflow per tag - */ -inline fun InfiniticClient.completeWorkflow( - tag: String, - value: Any -) = completeWorkflow(T::class.java, tag, value) - -/** - * Retry task per id - */ -inline fun InfiniticClient.retryTask( - id: UUID -) = retryTask(T::class.java, id) - -/** - * Retry task per tag - */ -inline fun InfiniticClient.retryTask( - tag: String -) = retryTask(T::class.java, tag) - -/** - * Retry workflow per id - */ -inline fun InfiniticClient.retryWorkflow( - id: UUID -) = retryWorkflow(T::class.java, id) - -/** - * Retry workflow per tag - */ -inline fun InfiniticClient.retryWorkflow( - tag: String -) = retryWorkflow(T::class.java, tag) - -/** - * Await task per id - */ -inline fun InfiniticClient.awaitTask( - id: UUID -) = awaitTask(T::class.java, id) - -/** - * Await workflow per id - */ -inline fun InfiniticClient.awaitWorkflow( - id: UUID -) = awaitWorkflow(T::class.java, id) - -/** - * Get ids of running tasks per tag and name - */ -inline fun InfiniticClient.getTaskIds( - tag: String -) = getTaskIds(T::class.java, tag) - -/** - * Get ids of running workflows per tag and name - */ -inline fun InfiniticClient.getWorkflowIds( - tag: String -) = getWorkflowIds(T::class.java, tag) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/Dispatcher.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredChannel.kt similarity index 64% rename from infinitic-common/src/main/kotlin/io/infinitic/common/proxies/Dispatcher.kt rename to infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredChannel.kt index 25fb321c3..3f777c0b5 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/Dispatcher.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredChannel.kt @@ -23,21 +23,27 @@ * Licensor: infinitic.io */ -package io.infinitic.common.proxies +package io.infinitic.client.deferred -import io.infinitic.exceptions.clients.SuspendMethodNotSupportedException -import java.lang.reflect.Method +import io.infinitic.client.Deferred +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.workflows.SendChannel import java.util.concurrent.CompletableFuture -import kotlin.reflect.jvm.kotlinFunction -interface Dispatcher { - fun dispatchAndWait(handler: TaskProxyHandler<*>): S +internal class DeferredChannel> ( + private val channel: R +) : Deferred { - fun dispatchAndWait(handler: WorkflowProxyHandler<*>): S - - fun dispatchAndWait(handler: SendChannelProxyHandler<*>): CompletableFuture + override fun cancelAsync(): CompletableFuture { + thisShouldNotHappen() + } - fun checkMethodIsNotSuspend(method: Method) { - if (method.kotlinFunction?.isSuspend == true) throw SuspendMethodNotSupportedException(method.declaringClass.name, method.name) + override fun retryAsync(): CompletableFuture { + thisShouldNotHappen() } + + override fun await(): R = channel + + override val id: String + get() { thisShouldNotHappen() } } diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredMethod.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredMethod.kt new file mode 100644 index 000000000..bf7fb2223 --- /dev/null +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredMethod.kt @@ -0,0 +1,74 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.client.deferred + +import io.infinitic.client.Deferred +import io.infinitic.client.dispatcher.ClientDispatcher +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.workflows.data.methodRuns.MethodRunId +import io.infinitic.common.workflows.data.workflows.WorkflowId +import io.infinitic.common.workflows.data.workflows.WorkflowName +import io.infinitic.common.workflows.data.workflows.WorkflowTag + +class DeferredMethod ( + internal val returnClass: Class, + internal val workflowName: WorkflowName, + internal val methodName: MethodName, + internal val workflowId: WorkflowId?, + internal val methodRunId: MethodRunId?, + internal val workflowTag: WorkflowTag?, + private val dispatcher: ClientDispatcher, +) : Deferred { + + override fun cancelAsync() = when { + workflowId != null -> + dispatcher.cancelWorkflowAsync(workflowName, workflowId, methodRunId, null) + workflowTag != null -> + dispatcher.cancelWorkflowAsync(workflowName, null, null, workflowTag) + else -> + thisShouldNotHappen() + } + + // this method retries workflowTask (unique for a workflow instance) + override fun retryAsync() = dispatcher.retryWorkflowAsync(workflowName, workflowId, workflowTag) + + @Suppress("UNCHECKED_CAST") + override fun await(): R = when { + workflowId != null -> + dispatcher.awaitWorkflow(returnClass, workflowName, methodName, workflowId, methodRunId!!, true) + workflowTag != null -> + throw TODO() + else -> + thisShouldNotHappen() + } + + override val id: String = when { + workflowId != null -> methodRunId!!.toString() + workflowTag != null -> throw Exception() + else -> thisShouldNotHappen() + } +} diff --git a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/MethodRunIndex.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredSend.kt similarity index 54% rename from infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/MethodRunIndex.kt rename to infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredSend.kt index effe45c36..6273f2408 100644 --- a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/MethodRunIndex.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredSend.kt @@ -23,27 +23,31 @@ * Licensor: infinitic.io */ -package io.infinitic.workflows.workflowTask +package io.infinitic.client.deferred -import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition -import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition.Companion.POSITION_SEPARATOR +import io.infinitic.client.Deferred +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.workflows.data.channels.ChannelSignalId +import java.util.concurrent.CompletableFuture -internal data class MethodRunIndex( - val parent: MethodRunIndex? = null, - val index: Int = -1, -) { - val methodPosition: MethodRunPosition = when (parent) { - null -> MethodRunPosition("$index") - else -> MethodRunPosition("${parent.methodPosition}$POSITION_SEPARATOR$index") - } - - override fun toString() = "$methodPosition" +internal class DeferredSend ( + internal val channelSignalId: ChannelSignalId +) : Deferred { - fun next() = MethodRunIndex(parent, index + 1) + override fun cancelAsync(): CompletableFuture { + thisShouldNotHappen() + } - fun up() = parent + override fun retryAsync(): CompletableFuture { + thisShouldNotHappen() + } - fun down() = MethodRunIndex(this) + // Send return type is always CompletableFuture + // also we do not apply the join method + // in order to send asynchronously the message + // despite the synchronous syntax: workflow.channel + @Suppress("UNCHECKED_CAST") + override fun await(): R = Unit as R - fun leadsTo(target: MethodRunPosition) = "${target}$POSITION_SEPARATOR".startsWith("$methodPosition$POSITION_SEPARATOR") + override val id: String = channelSignalId.toString() } diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredTask.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredTask.kt index c2c920ef4..a05ca1a90 100644 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredTask.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredTask.kt @@ -26,31 +26,24 @@ package io.infinitic.client.deferred import io.infinitic.client.Deferred -import io.infinitic.client.proxies.ClientDispatcher +import io.infinitic.client.dispatcher.ClientDispatcher +import io.infinitic.common.data.methods.MethodName import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskName -import io.infinitic.exceptions.thisShouldNotHappen -import java.util.UUID -import java.util.concurrent.CompletableFuture -internal class DeferredTask ( +class DeferredTask ( + internal val returnClass: Class, internal val taskName: TaskName, + internal val methodName: MethodName, internal val taskId: TaskId, - internal val isSync: Boolean, private val dispatcher: ClientDispatcher, - private val future: CompletableFuture? = null -) : Deferred { - override fun await(): T = dispatcher.await(this) +) : Deferred { - override fun join(): Deferred { - when (future) { - null -> thisShouldNotHappen() - else -> future.join() - } + override fun cancelAsync() = dispatcher.cancelTaskAsync(taskName, taskId, null) - return this - } + override fun retryAsync() = dispatcher.retryTaskAsync(taskName, taskId, null) - override val id: UUID - get() = taskId.id + override fun await(): R = dispatcher.awaitTask(returnClass, taskName, methodName, taskId, true) + + override val id: String by lazy { taskId.toString() } } diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredWorkflow.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredWorkflow.kt index 902b9bc25..08e7d2918 100644 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredWorkflow.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/deferred/DeferredWorkflow.kt @@ -26,31 +26,28 @@ package io.infinitic.client.deferred import io.infinitic.client.Deferred -import io.infinitic.client.proxies.ClientDispatcher +import io.infinitic.client.dispatcher.ClientDispatcher +import io.infinitic.common.data.methods.MethodName import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.exceptions.thisShouldNotHappen -import java.util.UUID -import java.util.concurrent.CompletableFuture -internal class DeferredWorkflow ( +class DeferredWorkflow ( + internal val returnClass: Class, internal val workflowName: WorkflowName, + internal val methodName: MethodName, internal val workflowId: WorkflowId, - internal val isSync: Boolean, private val dispatcher: ClientDispatcher, - private val future: CompletableFuture? = null -) : Deferred { - override fun await(): T = dispatcher.await(this) +) : Deferred { - override fun join(): Deferred { - when (future) { - null -> thisShouldNotHappen() - else -> future.join() - } + override fun cancelAsync() = + dispatcher.cancelWorkflowAsync(workflowName, workflowId, null, null) - return this - } + override fun retryAsync() = + dispatcher.retryWorkflowAsync(workflowName, workflowId, null) - override val id: UUID - get() = workflowId.id + @Suppress("UNCHECKED_CAST") + override fun await(): R = + dispatcher.awaitWorkflow(returnClass, workflowName, methodName, workflowId, null, true) + + override val id: String = workflowId.toString() } diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/dispatcher/ClientDispatcher.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/dispatcher/ClientDispatcher.kt new file mode 100644 index 000000000..91fc8737c --- /dev/null +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/dispatcher/ClientDispatcher.kt @@ -0,0 +1,119 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.client.dispatcher + +import io.infinitic.client.Deferred +import io.infinitic.common.clients.messages.ClientMessage +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.proxies.ProxyDispatcher +import io.infinitic.common.proxies.ProxyHandler +import io.infinitic.common.tasks.data.TaskId +import io.infinitic.common.tasks.data.TaskName +import io.infinitic.common.tasks.data.TaskTag +import io.infinitic.common.workflows.data.methodRuns.MethodRunId +import io.infinitic.common.workflows.data.workflows.WorkflowId +import io.infinitic.common.workflows.data.workflows.WorkflowName +import io.infinitic.common.workflows.data.workflows.WorkflowTag +import java.util.concurrent.CompletableFuture + +interface ClientDispatcher : ProxyDispatcher { + + suspend fun handle(message: ClientMessage) + + fun getLastDeferred(): Deferred<*>? + + fun dispatchAsync( + handler: ProxyHandler<*> + ): CompletableFuture> + + fun awaitTask( + returnClass: Class, + taskName: TaskName, + methodName: MethodName, + taskId: TaskId, + clientWaiting: Boolean + ): T + + fun awaitWorkflow( + returnClass: Class, + workflowName: WorkflowName, + methodName: MethodName, + workflowId: WorkflowId, + methodRunId: MethodRunId?, + clientWaiting: Boolean + ): T + + fun completeTaskAsync( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag?, + value: Any? + ): CompletableFuture + + fun completeWorkflowAsync( + workflowName: WorkflowName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag?, + value: Any? + ): CompletableFuture + + fun cancelTaskAsync( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag? + ): CompletableFuture + + fun cancelWorkflowAsync( + workflowName: WorkflowName, + workflowId: WorkflowId?, + methodRunId: MethodRunId?, + workflowTag: WorkflowTag? + ): CompletableFuture + + fun retryTaskAsync( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag? + ): CompletableFuture + + fun retryWorkflowAsync( + workflowName: WorkflowName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag? + ): CompletableFuture + + fun getTaskIdsByTag( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag? + ): Set + + fun getWorkflowIdsByTag( + workflowName: WorkflowName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag? + ): Set +} diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/dispatcher/ClientDispatcherImpl.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/dispatcher/ClientDispatcherImpl.kt new file mode 100644 index 000000000..859f8ce6c --- /dev/null +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/dispatcher/ClientDispatcherImpl.kt @@ -0,0 +1,815 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.client.dispatcher + +import io.infinitic.client.Deferred +import io.infinitic.client.deferred.DeferredChannel +import io.infinitic.client.deferred.DeferredMethod +import io.infinitic.client.deferred.DeferredSend +import io.infinitic.client.deferred.DeferredTask +import io.infinitic.client.deferred.DeferredWorkflow +import io.infinitic.common.clients.messages.ClientMessage +import io.infinitic.common.clients.messages.MethodCanceled +import io.infinitic.common.clients.messages.MethodCompleted +import io.infinitic.common.clients.messages.MethodFailed +import io.infinitic.common.clients.messages.MethodRunUnknown +import io.infinitic.common.clients.messages.TaskCanceled +import io.infinitic.common.clients.messages.TaskCompleted +import io.infinitic.common.clients.messages.TaskFailed +import io.infinitic.common.clients.messages.TaskIdsByTag +import io.infinitic.common.clients.messages.TaskUnknown +import io.infinitic.common.clients.messages.WorkflowIdsByTag +import io.infinitic.common.clients.messages.interfaces.MethodMessage +import io.infinitic.common.clients.messages.interfaces.TaskMessage +import io.infinitic.common.data.ClientName +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.errors.FailedWorkflowError +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.proxies.ChannelProxyHandler +import io.infinitic.common.proxies.GetTaskProxyHandler +import io.infinitic.common.proxies.GetWorkflowProxyHandler +import io.infinitic.common.proxies.NewTaskProxyHandler +import io.infinitic.common.proxies.NewWorkflowProxyHandler +import io.infinitic.common.proxies.ProxyHandler +import io.infinitic.common.tasks.data.TaskId +import io.infinitic.common.tasks.data.TaskName +import io.infinitic.common.tasks.data.TaskTag +import io.infinitic.common.tasks.engine.SendToTaskEngine +import io.infinitic.common.tasks.engine.messages.CancelTask +import io.infinitic.common.tasks.engine.messages.DispatchTask +import io.infinitic.common.tasks.engine.messages.RetryTask +import io.infinitic.common.tasks.engine.messages.WaitTask +import io.infinitic.common.tasks.tags.SendToTaskTagEngine +import io.infinitic.common.tasks.tags.messages.AddTagToTask +import io.infinitic.common.tasks.tags.messages.CancelTaskByTag +import io.infinitic.common.tasks.tags.messages.GetTaskIdsByTag +import io.infinitic.common.tasks.tags.messages.RetryTaskByTag +import io.infinitic.common.workflows.data.channels.ChannelSignalId +import io.infinitic.common.workflows.data.methodRuns.MethodRunId +import io.infinitic.common.workflows.data.workflows.WorkflowCancellationReason +import io.infinitic.common.workflows.data.workflows.WorkflowId +import io.infinitic.common.workflows.data.workflows.WorkflowName +import io.infinitic.common.workflows.data.workflows.WorkflowTag +import io.infinitic.common.workflows.engine.SendToWorkflowEngine +import io.infinitic.common.workflows.engine.messages.CancelWorkflow +import io.infinitic.common.workflows.engine.messages.DispatchMethod +import io.infinitic.common.workflows.engine.messages.DispatchWorkflow +import io.infinitic.common.workflows.engine.messages.RetryWorkflowTask +import io.infinitic.common.workflows.engine.messages.SendSignal +import io.infinitic.common.workflows.engine.messages.WaitWorkflow +import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine +import io.infinitic.common.workflows.tags.messages.AddTagToWorkflow +import io.infinitic.common.workflows.tags.messages.CancelWorkflowByTag +import io.infinitic.common.workflows.tags.messages.DispatchMethodByTag +import io.infinitic.common.workflows.tags.messages.GetWorkflowIdsByTag +import io.infinitic.common.workflows.tags.messages.RetryWorkflowTaskByTag +import io.infinitic.common.workflows.tags.messages.SendSignalByTag +import io.infinitic.exceptions.CanceledTaskException +import io.infinitic.exceptions.CanceledWorkflowException +import io.infinitic.exceptions.FailedTaskException +import io.infinitic.exceptions.FailedWorkflowException +import io.infinitic.exceptions.UnknownTaskException +import io.infinitic.exceptions.UnknownWorkflowException +import io.infinitic.exceptions.WorkerException +import io.infinitic.exceptions.clients.InvalidChannelUsageException +import io.infinitic.exceptions.clients.InvalidRunningTaskException +import io.infinitic.workflows.SendChannel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.future.future +import kotlinx.coroutines.launch +import mu.KotlinLogging +import java.util.concurrent.CompletableFuture + +internal class ClientDispatcherImpl( + val scope: CoroutineScope, + val clientName: ClientName, + val sendToTaskEngine: SendToTaskEngine, + val sendToWorkflowEngine: SendToWorkflowEngine, + val sendToTaskTagEngine: SendToTaskTagEngine, + val sendToWorkflowTagEngine: SendToWorkflowTagEngine +) : ClientDispatcher { + val logger = KotlinLogging.logger {} + + private val responseFlow = MutableSharedFlow(replay = 0) + + companion object { + @JvmStatic + private val localLastDeferred: ThreadLocal?> = ThreadLocal() + } + + override suspend fun handle(message: ClientMessage) { + responseFlow.emit(message) + } + + override fun getLastDeferred(): Deferred<*>? = localLastDeferred.get() + + // asynchronous call: dispatch(stub::method)(*args) + override fun dispatchAsync( + handler: ProxyHandler<*> + ): CompletableFuture> = when (handler) { + is NewTaskProxyHandler -> dispatchTaskAsync(handler) + is NewWorkflowProxyHandler -> dispatchWorkflowAsync(handler) + is GetTaskProxyHandler -> throw InvalidRunningTaskException("${handler.stub()}") + is GetWorkflowProxyHandler -> dispatchMethodAsync(handler) + is ChannelProxyHandler -> dispatchSignalAsync(handler) + } + + override fun dispatchAndWait(handler: ProxyHandler<*>): R = when (handler) { + is NewTaskProxyHandler -> dispatchTaskAndWait(handler) + is NewWorkflowProxyHandler -> dispatchWorkflowAndWait(handler) + is GetTaskProxyHandler -> throw InvalidRunningTaskException("${handler.stub()}") + is GetWorkflowProxyHandler -> dispatchMethodAndWait(handler) + is ChannelProxyHandler -> dispatchSignalAndWait(handler) + } + + override fun awaitTask( + returnClass: Class, + taskName: TaskName, + methodName: MethodName, + taskId: TaskId, + clientWaiting: Boolean + ): T { + // if task was initially not sync, then send WaitTask message + if (clientWaiting) { + val waitTask = WaitTask( + taskName = taskName, + taskId = taskId, + emitterName = clientName + ) + scope.launch { sendToTaskEngine(waitTask) } + } + + // wait for result + val taskResult = scope.future { + responseFlow.first { + logger.debug { "ResponseFlow: $it" } + it is TaskMessage && it.taskId == taskId + } + }.join() + + @Suppress("UNCHECKED_CAST") + return when (taskResult) { + is TaskCompleted -> taskResult.taskReturnValue.value() as T + is TaskCanceled -> throw CanceledTaskException( + taskName = taskName.toString(), + taskId = taskId.toString(), + methodName = methodName.toString() + ) + is TaskFailed -> throw FailedTaskException( + taskName = taskName.toString(), + taskId = taskId.toString(), + methodName = methodName.toString(), + workerException = WorkerException.from(taskResult.error) + ) + is TaskUnknown -> throw UnknownTaskException( + taskName = taskName.toString(), + taskId = taskId.toString() + ) + else -> thisShouldNotHappen("Unexpected ${taskResult::class}") + } + } + + override fun awaitWorkflow( + returnClass: Class, + workflowName: WorkflowName, + methodName: MethodName, + workflowId: WorkflowId, + methodRunId: MethodRunId?, + clientWaiting: Boolean + ): T { + val runId = methodRunId ?: MethodRunId.from(workflowId) + + // if task was not initially sync, then send WaitTask message + if (clientWaiting) { + val waitWorkflow = WaitWorkflow( + workflowName = workflowName, + workflowId = workflowId, + methodRunId = runId, + emitterName = clientName + ) + scope.launch { sendToWorkflowEngine(waitWorkflow) } + } + + // wait for result + val workflowResult = scope.future { + responseFlow.first { + logger.debug { "ResponseFlow: $it" } + it is MethodMessage && + it.workflowId == workflowId && + it.methodRunId == runId + } + }.join() + + @Suppress("UNCHECKED_CAST") + return when (workflowResult) { + is MethodCompleted -> { + workflowResult.methodReturnValue.value() as T + } + is MethodCanceled -> { + throw CanceledWorkflowException( + workflowName = workflowName.toString(), + workflowId = workflowId.toString(), + methodRunId = methodRunId?.toString() + ) + } + is MethodFailed -> { + throw FailedWorkflowException.from( + FailedWorkflowError( + workflowName = workflowName, + methodName = methodName, + workflowId = workflowId, + methodRunId = methodRunId, + deferredError = workflowResult.cause + ) + ) + } + is MethodRunUnknown -> { + throw UnknownWorkflowException( + workflowName = workflowName.toString(), + workflowId = workflowId.toString(), + methodRunId = methodRunId?.toString() + ) + } + else -> { + thisShouldNotHappen("Unexpected ${workflowResult::class}") + } + } + } + + override fun completeTaskAsync( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag?, + value: Any? + ): CompletableFuture = TODO("Not yet implemented") + + override fun completeWorkflowAsync( + workflowName: WorkflowName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag?, + value: Any? + ): CompletableFuture = TODO("Not yet implemented") + + override fun cancelTaskAsync( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag? + ): CompletableFuture = scope.future { + when { + taskId != null -> { + val msg = CancelTask( + taskName = taskName, + taskId = taskId, + emitterName = clientName + ) + sendToTaskEngine(msg) + } + taskTag != null -> { + val msg = CancelTaskByTag( + taskName = taskName, + taskTag = taskTag, + emitterName = clientName + ) + sendToTaskTagEngine(msg) + } + else -> thisShouldNotHappen() + } + } + + override fun cancelWorkflowAsync( + workflowName: WorkflowName, + workflowId: WorkflowId?, + methodRunId: MethodRunId?, + workflowTag: WorkflowTag? + ): CompletableFuture = scope.future { + when { + workflowId != null -> { + val msg = CancelWorkflow( + workflowName = workflowName, + workflowId = workflowId, + methodRunId = methodRunId, + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterName = clientName + ) + sendToWorkflowEngine(msg) + } + workflowTag != null -> { + val msg = CancelWorkflowByTag( + workflowName = workflowName, + workflowTag = workflowTag, + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterWorkflowId = null, + emitterName = clientName + ) + sendToWorkflowTagEngine(msg) + } + else -> thisShouldNotHappen() + } + } + + override fun retryTaskAsync( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag? + ): CompletableFuture = scope.future { + when { + taskId != null -> { + val msg = RetryTask( + taskName = taskName, + taskId = taskId, + emitterName = clientName + ) + sendToTaskEngine(msg) + } + taskTag != null -> { + val msg = RetryTaskByTag( + taskName = taskName, + taskTag = taskTag, + emitterName = clientName + ) + sendToTaskTagEngine(msg) + } + else -> thisShouldNotHappen() + } + } + + override fun retryWorkflowAsync( + workflowName: WorkflowName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag? + ): CompletableFuture = scope.future { + when { + workflowId != null -> { + val msg = RetryWorkflowTask( + workflowName = workflowName, + workflowId = workflowId, + emitterName = clientName + ) + sendToWorkflowEngine(msg) + } + workflowTag != null -> { + val msg = RetryWorkflowTaskByTag( + workflowName = workflowName, + workflowTag = workflowTag, + emitterWorkflowId = null, + emitterName = clientName + ) + sendToWorkflowTagEngine(msg) + } + else -> thisShouldNotHappen() + } + } + + // synchronously get task ids per tag + override fun getTaskIdsByTag( + taskName: TaskName, + taskId: TaskId?, + taskTag: TaskTag? + ): Set = when { + taskId != null -> + setOf(taskId.toString()) + taskTag != null -> { + val taskIdsByTag = scope.future { + val msg = GetTaskIdsByTag( + taskName = taskName, + taskTag = taskTag, + emitterName = clientName + ) + launch { sendToTaskTagEngine(msg) } + + responseFlow.first { + it is TaskIdsByTag && it.taskName == taskName && it.taskTag == taskTag + } as TaskIdsByTag + }.join() + + taskIdsByTag.taskIds.map { it.toString() }.toSet() + } + else -> + thisShouldNotHappen() + } + + override fun getWorkflowIdsByTag( + workflowName: WorkflowName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag? + ): Set = when { + workflowId != null -> + setOf(workflowId.toString()) + workflowTag != null -> { + val workflowIdsByTag = scope.future { + val msg = GetWorkflowIdsByTag( + workflowName = workflowName, + workflowTag = workflowTag, + emitterName = clientName + ) + launch { sendToWorkflowTagEngine(msg) } + + responseFlow.first { + logger.debug { "ResponseFlow: $it" } + it is WorkflowIdsByTag && it.workflowName == workflowName && it.workflowTag == workflowTag + } as WorkflowIdsByTag + }.join() + + workflowIdsByTag.workflowIds.map { it.toString() }.toSet() + } + else -> + thisShouldNotHappen() + } + + // asynchronous call: dispatch(stub::method)(*args) + @Suppress("UNCHECKED_CAST") + private fun dispatchTaskAsync( + handler: NewTaskProxyHandler<*> + ): CompletableFuture> { + val deferredTask: DeferredTask = + deferredTask( + handler.method.returnType as Class, + handler.taskName, + handler.methodName + ) + + return scope.future { dispatchTask(deferredTask, false, handler); deferredTask } + } + + @Suppress("UNCHECKED_CAST") + private fun dispatchTaskAndWait( + handler: NewTaskProxyHandler<*> + ): R { + val deferredTask: DeferredTask = + deferredTask( + handler.method.returnType as Class, + handler.taskName, + handler.methodName + ) + + scope.launch { dispatchTask(deferredTask, true, handler) } + + with(deferredTask) { + return awaitTask(returnClass, taskName, handler.methodName, taskId, false) + } + } + + private fun deferredTask( + returnClass: Class, + taskName: TaskName, + methodName: MethodName + ): DeferredTask { + val deferredTask = DeferredTask(returnClass, taskName, methodName, TaskId(), this) + + // store in ThreadLocal to be used in ::getLastDeferred + localLastDeferred.set(deferredTask) + + return deferredTask + } + + private fun dispatchTask( + deferred: DeferredTask, + clientWaiting: Boolean, + handler: NewTaskProxyHandler<*> + ) { + scope.future { + coroutineScope { + // add provided tags for this id + handler.taskTags.map { + val addTagToTask = AddTagToTask( + taskName = deferred.taskName, + taskTag = it, + taskId = deferred.taskId, + emitterName = clientName + ) + launch { sendToTaskTagEngine(addTagToTask) } + } + + // dispatch this task + val dispatchTask = DispatchTask( + taskName = deferred.taskName, + taskId = deferred.taskId, + taskOptions = handler.taskOptions, + clientWaiting = clientWaiting, + methodName = handler.methodName, + methodParameterTypes = handler.methodParameterTypes, + methodParameters = handler.methodParameters, + workflowId = null, + workflowName = null, + methodRunId = null, + taskTags = handler.taskTags, + taskMeta = handler.taskMeta, + emitterName = clientName + ) + launch { sendToTaskEngine(dispatchTask) } + } + }.join() + } + + // asynchronous call: dispatch(stub::method)(*args) + @Suppress("UNCHECKED_CAST") + private fun dispatchWorkflowAsync( + handler: NewWorkflowProxyHandler<*>, + ): CompletableFuture> = when (handler.isChannelGetter()) { + true -> + throw InvalidChannelUsageException() + false -> { + val deferredWorkflow = deferredWorkflow( + handler.method.returnType as Class, + handler.workflowName, + handler.methodName + ) + + scope.future { dispatchWorkflow(deferredWorkflow, false, handler); deferredWorkflow } + } + } + + // asynchronous call: dispatch(stub::method)(*args) + @Suppress("UNCHECKED_CAST") + private fun dispatchWorkflowAndWait( + handler: NewWorkflowProxyHandler<*>, + ): R = when (handler.isChannelGetter()) { + true -> + throw InvalidChannelUsageException() + false -> { + val deferredWorkflow = deferredWorkflow( + handler.method.returnType as Class, + handler.workflowName, + handler.methodName + ) + + scope.launch { dispatchWorkflow(deferredWorkflow, true, handler) } + + with(deferredWorkflow) { + awaitWorkflow(returnClass, workflowName, methodName, workflowId, null, false) + } + } + } + + private fun deferredWorkflow( + returnClass: Class, + workflowName: WorkflowName, + methodName: MethodName, + ): DeferredWorkflow { + val workflowId = WorkflowId() + + val deferredWorkflow = DeferredWorkflow(returnClass, workflowName, methodName, workflowId, this) + + // store in ThreadLocal to be used in ::getDeferred + localLastDeferred.set(deferredWorkflow) + + return deferredWorkflow + } + + private fun dispatchWorkflow( + deferred: DeferredWorkflow, + clientWaiting: Boolean, + handler: NewWorkflowProxyHandler<*> + ) { + scope.future { + coroutineScope { + // add provided tags + handler.workflowTags.map { + val addTagToWorkflow = AddTagToWorkflow( + workflowName = deferred.workflowName, + workflowTag = it, + workflowId = deferred.workflowId, + emitterName = clientName + ) + launch { sendToWorkflowTagEngine(addTagToWorkflow) } + } + // dispatch workflow + val dispatchWorkflow = DispatchWorkflow( + workflowName = deferred.workflowName, + workflowId = deferred.workflowId, + methodName = handler.methodName, + methodParameters = handler.methodParameters, + methodParameterTypes = handler.methodParameterTypes, + workflowOptions = handler.workflowOptions, + workflowTags = handler.workflowTags, + workflowMeta = handler.workflowMeta, + parentWorkflowName = null, + parentWorkflowId = null, + parentMethodRunId = null, + clientWaiting = clientWaiting, + emitterName = clientName + ) + launch { sendToWorkflowEngine(dispatchWorkflow) } + } + }.join() + } + + // asynchronous call: dispatch(stub::method)(*args) + @Suppress("UNCHECKED_CAST") + private fun dispatchMethodAsync( + handler: GetWorkflowProxyHandler<*> + ): CompletableFuture> = when (handler.isChannelGetter()) { + true -> { + // special case of getting a channel from a workflow + @Suppress("UNCHECKED_CAST") + val channel = ChannelProxyHandler>(handler).stub() + @Suppress("UNCHECKED_CAST") + CompletableFuture.completedFuture(DeferredChannel(channel) as Deferred) + } + false -> { + val deferredMethod = deferredMethod( + handler.method.returnType as Class, + handler.workflowName, + handler.methodName, + handler.workflowId, + handler.workflowTag + ) + + scope.future { dispatchMethod(deferredMethod, false, handler); deferredMethod } + } + } + + // asynchronous call: dispatch(stub::method)(*args) + @Suppress("UNCHECKED_CAST") + private fun dispatchMethodAndWait( + handler: GetWorkflowProxyHandler<*> + ): R = when (handler.isChannelGetter()) { + true -> { + // special case of getting a channel from a workflow + @Suppress("UNCHECKED_CAST") + ChannelProxyHandler>(handler).stub() as R + } + false -> { + val deferredMethod = deferredMethod( + handler.method.returnType as Class, + handler.workflowName, + handler.methodName, + handler.workflowId, + handler.workflowTag + ) + + scope.launch { dispatchMethod(deferredMethod, true, handler) } + + with(deferredMethod) { + when { + workflowId != null -> + awaitWorkflow(returnClass, workflowName, methodName, workflowId, methodRunId, false) + workflowTag != null -> + throw TODO() + else -> + thisShouldNotHappen() + } + } + } + } + + private fun deferredMethod( + returnClass: Class, + workflowName: WorkflowName, + methodName: MethodName, + workflowId: WorkflowId?, + workflowTag: WorkflowTag? + ): DeferredMethod { + val methodRunId = MethodRunId() + val deferredMethod = when { + workflowId != null -> + DeferredMethod(returnClass, workflowName, methodName, workflowId, methodRunId, null, this) + workflowTag != null -> + DeferredMethod(returnClass, workflowName, methodName, null, methodRunId, workflowTag, this) + else -> + thisShouldNotHappen() + } + + // store in ThreadLocal to be used in ::getDeferred + localLastDeferred.set(deferredMethod) + + return deferredMethod + } + + private fun dispatchMethod( + deferred: DeferredMethod, + clientWaiting: Boolean, + handler: GetWorkflowProxyHandler<*> + ) = when { + deferred.workflowId != null && deferred.methodRunId != null -> { + val dispatchMethod = DispatchMethod( + workflowName = deferred.workflowName, + workflowId = deferred.workflowId, + methodRunId = deferred.methodRunId, + methodName = handler.methodName, + methodParameters = handler.methodParameters, + methodParameterTypes = handler.methodParameterTypes, + parentWorkflowId = null, + parentWorkflowName = null, + parentMethodRunId = null, + clientWaiting = clientWaiting, + emitterName = clientName + ) + sendToWorkflowEngine(dispatchMethod) + } + deferred.workflowTag != null -> { + val dispatchMethodByTag = DispatchMethodByTag( + workflowName = deferred.workflowName, + workflowTag = deferred.workflowTag, + parentWorkflowId = null, + parentWorkflowName = null, + parentMethodRunId = null, + methodRunId = MethodRunId(), + methodName = handler.methodName, + methodParameterTypes = handler.methodParameterTypes, + methodParameters = handler.methodParameters, + clientWaiting = clientWaiting, + emitterName = clientName + ) + sendToWorkflowTagEngine(dispatchMethodByTag) + } + else -> + thisShouldNotHappen() + } + + // asynchronous call: dispatch(stub.channel::send, signal) + private fun dispatchSignalAsync( + handler: ChannelProxyHandler<*> + ): CompletableFuture> { + + val deferredSend = deferredSend() + + return scope.future { dispatchSignal(deferredSend, handler); deferredSend } + } + + // synchronous call: stub.channel.send(signal) + private fun dispatchSignalAndWait( + handler: ChannelProxyHandler<*> + ): S { + + val deferredSend = deferredSend() + + // synchronous call + dispatchSignal(deferredSend, handler) + + return deferredSend.await() + } + + // asynchronous call: dispatch(stub.channel::send, signal) + private fun dispatchSignal( + deferredSend: DeferredSend<*>, + handler: ChannelProxyHandler<*> + ) { + if (handler.methodName.toString() != SendChannel<*>::send.name) thisShouldNotHappen() + + when { + handler.workflowId != null -> { + val sendSignal = SendSignal( + workflowName = handler.workflowName, + workflowId = handler.workflowId!!, + channelName = handler.channelName, + channelSignalId = deferredSend.channelSignalId, + channelSignal = handler.channelSignal, + channelSignalTypes = handler.channelSignalTypes, + emitterName = clientName + ) + sendToWorkflowEngine(sendSignal) + } + handler.workflowTag != null -> { + val sendSignalByTag = SendSignalByTag( + workflowName = handler.workflowName, + workflowTag = handler.workflowTag!!, + channelName = handler.channelName, + channelSignalId = deferredSend.channelSignalId, + channelSignal = handler.channelSignal, + channelSignalTypes = handler.channelSignalTypes, + emitterWorkflowId = null, + emitterName = clientName + ) + sendToWorkflowTagEngine(sendSignalByTag) + } + else -> + thisShouldNotHappen() + } + } + + private fun deferredSend(): DeferredSend { + val deferredSend = DeferredSend(ChannelSignalId()) + + // store in ThreadLocal to be used in ::getLastDeferred + localLastDeferred.set(deferredSend) + + return deferredSend + } +} diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/proxies/ClientDispatcher.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/proxies/ClientDispatcher.kt deleted file mode 100644 index 2f13b0527..000000000 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/proxies/ClientDispatcher.kt +++ /dev/null @@ -1,410 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.client.proxies - -import io.infinitic.client.deferred.DeferredTask -import io.infinitic.client.deferred.DeferredWorkflow -import io.infinitic.common.clients.data.ClientName -import io.infinitic.common.clients.messages.ClientMessage -import io.infinitic.common.clients.messages.TaskCanceled -import io.infinitic.common.clients.messages.TaskCompleted -import io.infinitic.common.clients.messages.TaskFailed -import io.infinitic.common.clients.messages.TaskIdsPerTag -import io.infinitic.common.clients.messages.UnknownTask -import io.infinitic.common.clients.messages.UnknownWorkflow -import io.infinitic.common.clients.messages.WorkflowAlreadyCompleted -import io.infinitic.common.clients.messages.WorkflowCanceled -import io.infinitic.common.clients.messages.WorkflowCompleted -import io.infinitic.common.clients.messages.WorkflowFailed -import io.infinitic.common.clients.messages.WorkflowIdsPerTag -import io.infinitic.common.clients.messages.interfaces.TaskMessage -import io.infinitic.common.clients.messages.interfaces.WorkflowMessage -import io.infinitic.common.data.methods.MethodName -import io.infinitic.common.data.methods.MethodParameterTypes -import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.proxies.Dispatcher -import io.infinitic.common.proxies.SendChannelProxyHandler -import io.infinitic.common.proxies.TaskProxyHandler -import io.infinitic.common.proxies.WorkflowProxyHandler -import io.infinitic.common.tasks.data.TaskId -import io.infinitic.common.tasks.data.TaskName -import io.infinitic.common.tasks.data.TaskTag -import io.infinitic.common.tasks.engine.SendToTaskEngine -import io.infinitic.common.tasks.engine.messages.DispatchTask -import io.infinitic.common.tasks.engine.messages.WaitTask -import io.infinitic.common.tasks.tags.SendToTaskTagEngine -import io.infinitic.common.tasks.tags.messages.AddTaskTag -import io.infinitic.common.tasks.tags.messages.GetTaskIds -import io.infinitic.common.workflows.data.channels.ChannelEvent -import io.infinitic.common.workflows.data.channels.ChannelEventId -import io.infinitic.common.workflows.data.channels.ChannelEventType -import io.infinitic.common.workflows.data.channels.ChannelName -import io.infinitic.common.workflows.data.workflows.WorkflowId -import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.common.workflows.data.workflows.WorkflowTag -import io.infinitic.common.workflows.engine.SendToWorkflowEngine -import io.infinitic.common.workflows.engine.messages.DispatchWorkflow -import io.infinitic.common.workflows.engine.messages.SendToChannel -import io.infinitic.common.workflows.engine.messages.WaitWorkflow -import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine -import io.infinitic.common.workflows.tags.messages.AddWorkflowTag -import io.infinitic.common.workflows.tags.messages.GetWorkflowIds -import io.infinitic.common.workflows.tags.messages.SendToChannelPerTag -import io.infinitic.exceptions.clients.AlreadyCompletedWorkflowException -import io.infinitic.exceptions.clients.CanceledDeferredException -import io.infinitic.exceptions.clients.FailedDeferredException -import io.infinitic.exceptions.clients.UnknownMethodInSendChannelException -import io.infinitic.exceptions.clients.UnknownTaskException -import io.infinitic.exceptions.clients.UnknownWorkflowException -import io.infinitic.exceptions.thisShouldNotHappen -import io.infinitic.workflows.SendChannel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.future.future -import kotlinx.coroutines.launch -import mu.KotlinLogging -import java.util.UUID -import java.util.concurrent.CompletableFuture -import kotlin.reflect.full.isSubclassOf - -internal class ClientDispatcher( - val scope: CoroutineScope, - val clientName: ClientName, - val sendToTaskTagEngine: SendToTaskTagEngine, - val sendToTaskEngine: SendToTaskEngine, - val sendToWorkflowTagEngine: SendToWorkflowTagEngine, - val sendToWorkflowEngine: SendToWorkflowEngine -) : Dispatcher { - private val logger = KotlinLogging.logger {} - - private val responseFlow = MutableSharedFlow(replay = 0) - - suspend fun handle(message: ClientMessage) { - responseFlow.emit(message) - } - - // synchronously get task ids per tag - internal fun getTaskIdsPerTag(taskName: TaskName, taskTag: TaskTag): Set { - val taskIdsPerTag = scope.future() { - val msg = GetTaskIds( - taskTag = taskTag, - taskName = taskName, - clientName = clientName - ) - launch { sendToTaskTagEngine(msg) } - - responseFlow.first { - it is TaskIdsPerTag && it.taskName == taskName && it.taskTag == taskTag - } as TaskIdsPerTag - }.join() - - return taskIdsPerTag.taskIds.map { it.id }.toSet() - } - - // synchronously get workflow ids per tag - internal fun getWorkflowIdsPerTag(workflowName: WorkflowName, workflowTag: WorkflowTag): Set { - val workflowIdsPerTag = scope.future() { - val msg = GetWorkflowIds( - clientName = clientName, - workflowTag = workflowTag, - workflowName = workflowName - ) - launch { sendToWorkflowTagEngine(msg) } - - responseFlow.first { - logger.debug { "ResponseFlow: $it" } - it is WorkflowIdsPerTag && it.workflowName == workflowName && it.workflowTag == workflowTag - } as WorkflowIdsPerTag - }.join() - - return workflowIdsPerTag.workflowIds.map { it.id }.toSet() - } - - // synchronous call: task.method() - override fun dispatchAndWait(handler: TaskProxyHandler<*>): T = dispatch(handler).await() - - // asynchronous call: async(newTask) { method() } - internal fun dispatch(handler: TaskProxyHandler<*>): DeferredTask { - checkMethodIsNotSuspend(handler.method) - - val taskId = TaskId() - - // store values - val taskName = handler.taskName - val isSync = handler.isSync - val methodName = handler.methodName - val method = handler.method - val methodArgs = handler.methodArgs - val taskTags = handler.taskTags!! - val taskOptions = handler.taskOptions!! - val taskMeta = handler.taskMeta!! - - // reset for reuse - handler.reset() - - // handler now target an existing task - handler.perTaskId = taskId - - // send messages asynchronously - val future = scope.future { - - // add provided tags for this id - handler.taskTags!!.map { - val addTaskTag = AddTaskTag( - taskTag = it, - taskName = taskName, - taskId = taskId - ) - launch { sendToTaskTagEngine(addTaskTag) } - } - - // dispatch this task - val dispatchTask = DispatchTask( - taskId = taskId, - clientName = clientName, - clientWaiting = isSync, - taskName = taskName, - methodName = MethodName(methodName), - methodParameterTypes = MethodParameterTypes.from(method), - methodParameters = MethodParameters.from(method, methodArgs), - workflowId = null, - workflowName = null, - methodRunId = null, - taskTags = taskTags, - taskOptions = taskOptions, - taskMeta = taskMeta - ) - launch { sendToTaskEngine(dispatchTask) } - - Unit - } - - return DeferredTask(taskName, taskId, isSync, this, future) - } - - internal fun await(deferredTask: DeferredTask): T { - val taskResult = scope.future { - // if task was initially not sync, then send WaitTask message - if (! deferredTask.isSync) { - val waitTask = WaitTask( - taskId = deferredTask.taskId, - taskName = deferredTask.taskName, - clientName = clientName - ) - launch { sendToTaskEngine(waitTask) } - } - // wait for result - responseFlow.first { - logger.debug { "ResponseFlow: $it" } - it is TaskMessage && it.taskId == deferredTask.taskId - } - }.join() - - @Suppress("UNCHECKED_CAST") - return when (taskResult) { - is TaskCompleted -> taskResult.taskReturnValue.get() as T - is TaskCanceled -> throw CanceledDeferredException( - "${deferredTask.taskId}", - "${deferredTask.taskName}", - ) - is TaskFailed -> throw FailedDeferredException( - "${deferredTask.taskId}", - "${deferredTask.taskName}", - taskResult.error - ) - is UnknownTask -> throw UnknownTaskException( - "${deferredTask.taskId}", - "${deferredTask.taskName}" - ) - else -> throw RuntimeException("Unexpected ${taskResult::class}") - } - } - - // synchronous call on a new workflow: newWorkflow.method() - @Suppress("UNCHECKED_CAST") - override fun dispatchAndWait(handler: WorkflowProxyHandler<*>): S { - // special case of getting a channel - val method = handler.method - - if (method.returnType.kotlin.isSubclassOf(SendChannel::class)) { - return SendChannelProxyHandler( - method.returnType, - handler.workflowName, - ChannelName(method.name), - handler.perWorkflowId, - handler.perTag - ) { this }.stub() as S - } - - // dispatch and wait - return dispatch(handler).await() - } - - // asynchronous workflow: async(newWorkflow) { method() } - internal fun dispatch(handler: WorkflowProxyHandler<*>): DeferredWorkflow { - checkMethodIsNotSuspend(handler.method) - - val workflowId = WorkflowId() - - // store values to use after handler reset - val workflowName = handler.workflowName - val isSync = handler.isSync - - // send messages asynchronously - val future = scope.future() { - - // add provided tags - handler.workflowTags!!.map { - val addWorkflowTag = AddWorkflowTag( - workflowTag = it, - workflowName = workflowName, - workflowId = workflowId - ) - launch { sendToWorkflowTagEngine(addWorkflowTag) } - } - - // dispatch workflow - val dispatchWorkflow = DispatchWorkflow( - workflowId = workflowId, - clientName = clientName, - clientWaiting = isSync, - workflowName = workflowName, - methodName = MethodName(handler.methodName), - methodParameterTypes = MethodParameterTypes.from(handler.method), - methodParameters = MethodParameters.from(handler.method, handler.methodArgs), - parentWorkflowId = null, - parentWorkflowName = null, - parentMethodRunId = null, - workflowTags = handler.workflowTags!!, - workflowMeta = handler.workflowMeta!!, - workflowOptions = handler.workflowOptions!! - ) - launch { sendToWorkflowEngine(dispatchWorkflow) } - - Unit - } - - // handler now target an existing task - handler.perWorkflowId = workflowId - // reset isSync only - handler.isSync = true - - return DeferredWorkflow(workflowName, workflowId, isSync, this, future) - } - - internal fun await(deferredWorkflow: DeferredWorkflow): T { - val workflowResult = scope.future { - // if task was not initially sync, then send WaitTask message - if (! deferredWorkflow.isSync) { - val waitWorkflow = WaitWorkflow( - workflowId = deferredWorkflow.workflowId, - workflowName = deferredWorkflow.workflowName, - clientName = clientName - ) - launch { sendToWorkflowEngine(waitWorkflow) } - } - - // wait for result - responseFlow.first { - logger.debug { "ResponseFlow: $it" } - (it is WorkflowMessage && it.workflowId == deferredWorkflow.workflowId) - } - }.join() - - @Suppress("UNCHECKED_CAST") - return when (workflowResult) { - is WorkflowCompleted -> workflowResult.workflowReturnValue.get() as T - is WorkflowCanceled -> throw CanceledDeferredException( - "${deferredWorkflow.workflowId}", - "${deferredWorkflow.workflowName}" - ) - is WorkflowFailed -> throw FailedDeferredException( - "${deferredWorkflow.workflowId}", - "${deferredWorkflow.workflowName}", - workflowResult.error - ) - is UnknownWorkflow -> throw UnknownWorkflowException( - "${deferredWorkflow.workflowId}", - "${deferredWorkflow.workflowName}" - ) - is WorkflowAlreadyCompleted -> throw AlreadyCompletedWorkflowException( - "${deferredWorkflow.workflowId}", - "${deferredWorkflow.workflowName}" - ) - else -> throw RuntimeException("Unexpected ${workflowResult::class}") - } - } - - // synchronous send on a channel: existingWorkflow.channel.send() - override fun dispatchAndWait(handler: SendChannelProxyHandler<*>) = dispatch(handler) - - // asynchronous send on a channel: async(existingWorkflow.channel) { send() } - private fun dispatch(handler: SendChannelProxyHandler<*>): CompletableFuture { - val method = handler.method - - if (method.name != SendChannel<*>::send.name) throw UnknownMethodInSendChannelException( - "${handler.workflowName}", - "${handler.channelName}", - method.name - ) - - val event = handler.methodArgs[0] - - return scope.future() { - when { - handler.perTag != null -> { - val sendToChannelPerTag = SendToChannelPerTag( - workflowTag = handler.perTag!!, - workflowName = handler.workflowName, - clientName = clientName, - clientWaiting = handler.isSync, - channelEventId = ChannelEventId(), - channelName = handler.channelName, - channelEvent = ChannelEvent.from(event), - channelEventTypes = ChannelEventType.allFrom(event::class.java) - ) - launch { sendToWorkflowTagEngine(sendToChannelPerTag) } - } - handler.perWorkflowId != null -> { - val sendToChannel = SendToChannel( - workflowId = handler.perWorkflowId!!, - workflowName = handler.workflowName, - clientName = clientName, - channelEventId = ChannelEventId(), - channelName = handler.channelName, - channelEvent = ChannelEvent.from(event), - channelEventTypes = ChannelEventType.allFrom(event::class.java) - ) - launch { sendToWorkflowEngine(sendToChannel) } - } - else -> thisShouldNotHappen() - } - - Unit - } - } -} diff --git a/infinitic-client/src/main/kotlin/io/infinitic/client/worker/startClientWorker.kt b/infinitic-client/src/main/kotlin/io/infinitic/client/worker/startClientWorker.kt index bd6f463ad..415d711e6 100644 --- a/infinitic-client/src/main/kotlin/io/infinitic/client/worker/startClientWorker.kt +++ b/infinitic-client/src/main/kotlin/io/infinitic/client/worker/startClientWorker.kt @@ -44,7 +44,7 @@ fun CoroutineScope.startClientWorker( client: InfiniticClient, inputChannel: ReceiveChannel, outputChannel: SendChannel -) = launch(CoroutineName("client: ${client.clientName}")) { +) = launch(CoroutineName("client: ${client.name}")) { for (message in inputChannel) { try { diff --git a/infinitic-client/src/test/kotlin/io/infinitic/client/ClientTaskTests.kt b/infinitic-client/src/test/kotlin/io/infinitic/client/ClientTaskTests.kt index a2d15a1eb..e1113b706 100644 --- a/infinitic-client/src/test/kotlin/io/infinitic/client/ClientTaskTests.kt +++ b/infinitic-client/src/test/kotlin/io/infinitic/client/ClientTaskTests.kt @@ -25,11 +25,12 @@ package io.infinitic.client +import io.infinitic.client.deferred.DeferredTask import io.infinitic.client.samples.FakeClass import io.infinitic.client.samples.FakeInterface import io.infinitic.client.samples.FakeTask import io.infinitic.client.samples.FooTask -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters @@ -39,49 +40,46 @@ import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.data.TaskOptions import io.infinitic.common.tasks.data.TaskTag -import io.infinitic.common.tasks.engine.messages.CancelTask import io.infinitic.common.tasks.engine.messages.DispatchTask -import io.infinitic.common.tasks.engine.messages.RetryTask import io.infinitic.common.tasks.engine.messages.TaskEngineMessage -import io.infinitic.common.tasks.tags.messages.AddTaskTag -import io.infinitic.common.tasks.tags.messages.CancelTaskPerTag -import io.infinitic.common.tasks.tags.messages.GetTaskIds -import io.infinitic.common.tasks.tags.messages.RetryTaskPerTag +import io.infinitic.common.tasks.tags.messages.AddTagToTask import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineMessage -import io.infinitic.exceptions.clients.CanNotApplyOnNewTaskStubException -import io.infinitic.exceptions.clients.MultipleMethodCallsException -import io.infinitic.exceptions.clients.NoMethodCallException -import io.infinitic.exceptions.clients.NotAStubException -import io.infinitic.exceptions.clients.SuspendMethodNotSupportedException -import io.kotest.assertions.throwables.shouldNotThrow -import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.mockk.slot -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import java.util.UUID import java.util.concurrent.CopyOnWriteArrayList private val taskTagSlots = CopyOnWriteArrayList() // multithread update private val workflowTagSlots = CopyOnWriteArrayList() // multithread update private val taskSlot = slot() private val workflowSlot = slot() +private val clientNameTest = ClientName("clientTest") class ClientTask : InfiniticClient() { - override val sendingScope = CoroutineScope(Dispatchers.IO) - override val clientName = ClientName("clientTest") - override val sendToTaskTagEngine = mockSendToTaskTagEngine(this, taskTagSlots) - override val sendToTaskEngine = mockSendToTaskEngine(this, taskSlot) - override val sendToWorkflowTagEngine = mockSendToWorkflowTagEngine(this, workflowTagSlots) - override val sendToWorkflowEngine = mockSendToWorkflowEngine(this, workflowSlot) + override val clientName = clientNameTest + override val sendToTaskTagEngine = mockSendToTaskTagEngine(this, taskTagSlots, clientName, sendingScope) + override val sendToTaskEngine = mockSendToTaskEngine(this, taskSlot, clientName, sendingScope) + override val sendToWorkflowTagEngine = mockSendToWorkflowTagEngine(this, workflowTagSlots, clientName, sendingScope) + override val sendToWorkflowEngine = mockSendToWorkflowEngine(this, workflowSlot, clientName, sendingScope) } class ClientTaskTests : StringSpec({ val client = ClientTask() + val fakeTask = client.newTask(FakeTask::class.java) + val fooTask = client.newTask(FooTask::class.java) + + val options = TestFactory.random() + val meta: Map = mapOf( + "foo" to TestFactory.random(), + "bar" to TestFactory.random() + ) + val fakeTaskWithOptions = client.newTask(FakeTask::class.java, options = options) + val fakeTaskWithMeta = client.newTask(FakeTask::class.java, meta = meta) + val fakeTaskWithTags = client.newTask(FakeTask::class.java, tags = setOf("foo", "bar")) + beforeTest { taskTagSlots.clear() taskSlot.clear() @@ -89,103 +87,74 @@ class ClientTaskTests : StringSpec({ workflowSlot.clear() } - "Should throw when not using a stub" { - shouldThrow { - client.async("") { } - } - - shouldThrow { - client.retry("") - } - - shouldThrow { - client.cancel("") - } - } - - "Should not throw when re-using a stub" { - // when - val fakeTask = client.newTask() - shouldNotThrow { - client.async(fakeTask) { m1() } - client.async(fakeTask) { m1() } - } - } - - "Should throw when calling 2 methods" { - // when - val fakeTask = client.newTask() - shouldThrow { - client.async(fakeTask) { m1(); m1() } - } + afterTest { + client.join() } - "Should throw when not calling any method" { - // when - val fakeTask = client.newTask() - shouldThrow { - client.async(fakeTask) { } - } + "First call to getLastSyncDeferred() should be null" { + client.lastDeferred shouldBe null } - "Should throw when retrying new stub" { - // when - val fakeTask = client.newTask() - shouldThrow { - client.retry(fakeTask) - } - } + "Call to getLastSyncDeferred() should be provided last deferred" { + fooTask.annotated() - "Should throw when canceling new stub" { - // when - val fakeTask = client.newTask() - shouldThrow { - client.cancel(fakeTask) - } + client.lastDeferred!!::class shouldBe DeferredTask::class } - "Should throw when using a suspend method" { + "Dispatch method without parameter" { // when - val fakeTask = client.newTask() - shouldThrow { - fakeTask.suspendedMethod() - } + val deferred: Deferred = client.dispatch(fakeTask::m0) + // then + taskTagSlots.size shouldBe 0 + taskSlot.captured shouldBe DispatchTask( + taskName = TaskName(FakeTask::class.java.name), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m0"), + methodParameterTypes = MethodParameterTypes(listOf()), + methodParameters = MethodParameters(), + workflowId = null, + workflowName = null, + methodRunId = null, + taskTags = setOf(), + taskMeta = TaskMeta(), + emitterName = clientNameTest + ) } - "dispatch method without parameter" { + "Dispatch method with annotations" { // when - val fakeTask = client.newTask() - val deferred: Deferred = client.async(fakeTask) { m1() }.join() + val deferred: Deferred = client.dispatch(fooTask::m) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, + taskName = TaskName("foo"), taskId = TaskId(deferred.id), - taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("bar"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch method with annotations" { + "Dispatch method with annotations on super" { // when - val fooTask = client.newTask() - val deferred: Deferred = client.async(fooTask) { m() }.join() + val deferred: Deferred = client.dispatch(fooTask::annotated) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName("foo"), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, methodName = MethodName("bar"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), @@ -193,130 +162,124 @@ class ClientTaskTests : StringSpec({ workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch method with annotations on parent" { + "Dispatch method without parameter (Java syntax)" { // when - val fooTask = client.newTask() - val deferred: Deferred = client.async(fooTask) { annotated() }.join() + val deferred: Deferred = client.dispatch(fakeTask::m0) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, + taskName = TaskName(FakeTask::class.java.name), taskId = TaskId(deferred.id), - taskName = TaskName("foo"), - methodName = MethodName("bar"), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m0"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch method without parameter (Java syntax)" { + "Dispatch method with options" { // when - val fakeTask = client.newTask(FakeTask::class.java) - val deferred: Deferred = client.async(fakeTask) { m1() }.join() + val deferred: Deferred = client.dispatch(fakeTaskWithOptions::m0) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), + taskId = TaskId(deferred.id), + taskOptions = options, + clientWaiting = false, + methodName = MethodName("m0"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch method with options and meta" { + "Dispatch method with meta" { // when - val options = TestFactory.random() - val meta = mapOf( - "foo" to TestFactory.random(), - "bar" to TestFactory.random() - ) - val fakeTask = client.newTask(options = options, meta = meta) - val deferred: Deferred = client.async(fakeTask) { m1() }.join() + val deferred: Deferred = client.dispatch(fakeTaskWithMeta::m0) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m0"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = options, - taskMeta = TaskMeta(meta) + taskMeta = TaskMeta(meta), + emitterName = clientNameTest ) } - "dispatch method with tags" { + "Dispatch method with tags" { // when - val fakeTask = client.newTask(tags = setOf("foo", "bar")) - val deferred: Deferred = client.async(fakeTask) { m1() }.join() + val deferred = client.dispatch(fakeTaskWithTags::m0) // then taskTagSlots.toSet() shouldBe setOf( - AddTaskTag( - taskTag = TaskTag("foo"), + AddTagToTask( taskName = TaskName(FakeTask::class.java.name), + taskTag = TaskTag("foo"), taskId = TaskId(deferred.id), + emitterName = clientNameTest, ), - AddTaskTag( - taskTag = TaskTag("bar"), + AddTagToTask( taskName = TaskName(FakeTask::class.java.name), + taskTag = TaskTag("bar"), taskId = TaskId(deferred.id), + emitterName = clientNameTest, ) ) taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m0"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(TaskTag("foo"), TaskTag("bar")), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method with a primitive as parameter" { + "Second method should not overwrite the first one" { // when - val fakeTask = client.newTask() - val deferred: Deferred = client.async(fakeTask) { m1(0) }.join() + client.dispatch(fakeTask::m0) + + val deferred = client.dispatch(fakeTask::m1, 0) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, methodName = MethodName("m1"), methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name)), methodParameters = MethodParameters.from(0), @@ -324,165 +287,157 @@ class ClientTaskTests : StringSpec({ workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method with null as parameter" { + "Dispatch a method with a primitive as parameter" { // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m1(null) }.join() + val deferred: Deferred = client.dispatch(fakeTask::m1, 0) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(String::class.java.name)), - methodParameters = MethodParameters.from(null), + methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name)), + methodParameters = MethodParameters.from(0), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method with multiple definition" { + "Dispatch a method with null as parameter" { // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m1("a") }.join() + val deferred = client.dispatch(fakeTask::m2, null) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m2"), methodParameterTypes = MethodParameterTypes(listOf(String::class.java.name)), - methodParameters = MethodParameters.from("a"), + methodParameters = MethodParameters.from(null), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method with multiple parameters" { + "Dispatch a method with multiple definition" { // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m1(0, "a") }.join() + val deferred = client.dispatch(fakeTask::m2, "a") // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name, String::class.java.name)), - methodParameters = MethodParameters.from(0, "a"), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m2"), + methodParameterTypes = MethodParameterTypes(listOf(String::class.java.name)), + methodParameters = MethodParameters.from("a"), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method with an interface as parameter" { + "Dispatch a method with multiple parameters" { // when - val fakeTask = client.newTask() - val fake = FakeClass() - val deferred = client.async(fakeTask) { m1(fake) }.join() + val deferred = client.dispatch(fakeTask::m3, 0, "a") // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(FakeInterface::class.java.name)), - methodParameters = MethodParameters.from(fake), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m3"), + methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name, String::class.java.name)), +// methodParameters = MethodParameters(listOf(SerializedData.from(0), SerializedData.from("a"))), + methodParameters = MethodParameters.from(0, "a"), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method with a primitive as return value" { + "Dispatch a method with an interface as parameter" { // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m2() }.join() + val fake = FakeClass() + val deferred = client.dispatch(fakeTask::m4, fake) // then taskTagSlots.size shouldBe 0 taskSlot.captured shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = false, - taskId = TaskId(deferred.id), taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m2"), - methodParameterTypes = MethodParameterTypes(listOf()), - methodParameters = MethodParameters(), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m4"), + methodParameterTypes = MethodParameterTypes(listOf(FakeInterface::class.java.name)), + methodParameters = MethodParameters.from(fake), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method synchronously" { + "Dispatch a method with a primitive as return value" { // when - val fakeTask = client.newTask() - val result = fakeTask.m1(0, "a") + val deferred = client.dispatch(fakeTask::m5) // then - result shouldBe "success" taskTagSlots.size shouldBe 0 - val msg = taskSlot.captured - msg shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = true, - taskId = msg.taskId, + taskSlot.captured shouldBe DispatchTask( taskName = TaskName(FakeTask::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name, String::class.java.name)), - methodParameters = MethodParameters.from(0, "a"), + taskId = TaskId(deferred.id), + taskOptions = TaskOptions(), + clientWaiting = false, + methodName = MethodName("m5"), + methodParameterTypes = MethodParameterTypes(listOf()), + methodParameters = MethodParameters(), workflowId = null, workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "dispatch a method from a parent interface" { + "Dispatch a method from a parent interface" { // when - val fakeTask = client.newTask() val result = fakeTask.parent() // then result shouldBe "success" taskTagSlots.size shouldBe 0 val msg = taskSlot.captured msg shouldBe DispatchTask( - clientName = client.clientName, - clientWaiting = true, - taskId = msg.taskId, taskName = TaskName(FakeTask::class.java.name), + taskId = msg.taskId, + taskOptions = TaskOptions(), + clientWaiting = true, methodName = MethodName("parent"), methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters.from(), @@ -490,134 +445,181 @@ class ClientTaskTests : StringSpec({ workflowName = null, methodRunId = null, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = clientNameTest ) } - "await for a task just dispatched" { - // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m1(0, "a") }.join() - val result = deferred.await() - // then - result shouldBe "success" - } - - "cancel task per id" { - // when - val id = UUID.randomUUID() - val fakeTask = client.getTask(id) - client.cancel(fakeTask).join() - // then - taskTagSlots.size shouldBe 0 - taskSlot.captured shouldBe CancelTask( - taskId = TaskId(id), - taskName = TaskName(FakeTask::class.java.name) - ) - } - - "cancel task per id with output" { - // when - val id = UUID.randomUUID() - val fakeTask = client.getTask(id) - client.cancel(fakeTask).join() - // then - taskTagSlots.size shouldBe 0 - taskSlot.captured shouldBe CancelTask( - taskId = TaskId(id), - taskName = TaskName(FakeTask::class.java.name) - ) - } - - "cancel task per tag" { - // when - val fakeTask = client.getTask("foo") - client.cancel(fakeTask).join() - // then - taskTagSlots.size shouldBe 1 - taskTagSlots[0] shouldBe CancelTaskPerTag( - taskTag = TaskTag("foo"), - taskName = TaskName(FakeTask::class.java.name) - ) - taskSlot.isCaptured shouldBe false - } - - "cancel task per tag with output" { - // when - val fakeTask = client.getTask("foo") - client.cancel(fakeTask).join() - // then - taskTagSlots.size shouldBe 1 - taskTagSlots[0] shouldBe CancelTaskPerTag( - taskTag = TaskTag("foo"), - taskName = TaskName(FakeTask::class.java.name) - ) - taskSlot.isCaptured shouldBe false - } - - "cancel task just dispatched" { - // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m1() }.join() - client.cancel(fakeTask).join() - // then - taskTagSlots.size shouldBe 0 - taskSlot.captured shouldBe CancelTask( - taskId = TaskId(deferred.id), - taskName = TaskName(FakeTask::class.java.name) - ) - } - - "retry task per id" { - // when - val id = UUID.randomUUID() - val fakeTask = client.getTask(id) - client.retry(fakeTask).join() - // then - taskTagSlots.size shouldBe 0 - taskSlot.captured shouldBe RetryTask( - taskId = TaskId(id), - taskName = TaskName(FakeTask::class.java.name) - ) - } - - "retry task per tag" { - // when - val fakeTask = client.getTask("foo") - client.retry(fakeTask).join() - // then - taskTagSlots.size shouldBe 1 - taskTagSlots[0] shouldBe RetryTaskPerTag( - taskTag = TaskTag("foo"), - taskName = TaskName(FakeTask::class.java.name) - ) - taskSlot.isCaptured shouldBe false - } - - "retry a task just dispatched " { - // when - val fakeTask = client.newTask() - val deferred = client.async(fakeTask) { m1() }.join() - client.retry(fakeTask).join() - // then - taskTagSlots.size shouldBe 0 - taskSlot.captured shouldBe RetryTask( - taskId = TaskId(deferred.id), - taskName = TaskName(FakeTask::class.java.name) - ) - } - - "get task ids par name and workflow" { - val taskIds = client.getTaskIds("foo") - // then - taskIds.size shouldBe 2 - taskTagSlots.size shouldBe 1 - taskTagSlots[0] shouldBe GetTaskIds( - taskName = TaskName(FakeTask::class.java.name), - taskTag = TaskTag("foo"), - clientName = ClientName("clientTest") - ) - taskSlot.isCaptured shouldBe false - } + "wait fAor a task just dispatched" { + client.dispatch(fakeTask::m3, 0, "a").await() shouldBe "success" + } + +// "Cancel task per id (sync)" { +// // when +// +// val id = UUID.randomUUID().toString() +// val task = client.getTaskById(FakeTask::class.java, id) +// +// client.cancel(task) +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe CancelTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(id), +// emitterName = clientNameTest +// ) +// } +// +// "Cancel task per id (async)" { +// // when +// val id = UUID.randomUUID().toString() +// val task = client.getTaskById(FakeTask::class.java, id) +// +// client.cancelAsync(task).join() +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe CancelTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(id), +// emitterName = clientNameTest +// ) +// } +// +// "Cancel task per tag (sync)" { +// // when +// val task = client.getTaskByTag(FakeTask::class.java, "foo") +// client.cancel(task) +// // then +// taskTagSlots.size shouldBe 1 +// taskTagSlots[0] shouldBe CancelTaskByTag( +// taskName = TaskName(FakeTask::class.java.name), +// taskTag = TaskTag("foo"), +// emitterName = clientNameTest +// ) +// taskSlot.isCaptured shouldBe false +// } +// +// "Cancel task per tag (async)" { +// // when +// val task = client.getTaskByTag(FakeTask::class.java, "foo") +// client.cancelAsync(task).join() +// // then +// taskTagSlots.size shouldBe 1 +// taskTagSlots[0] shouldBe CancelTaskByTag( +// taskName = TaskName(FakeTask::class.java.name), +// taskTag = TaskTag("foo"), +// emitterName = clientNameTest +// ) +// taskSlot.isCaptured shouldBe false +// } +// +// "Cancel task just dispatched (sync)" { +// // when +// val deferred = client.dispatch(fakeTask::m0) +// deferred.cancel() +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe CancelTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(deferred.id), +// emitterName = clientNameTest +// ) +// } +// +// "Cancel task just dispatched (async)" { +// // when +// val deferred = client.dispatch(fakeTask::m0) +// deferred.cancelAsync().join() +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe CancelTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(deferred.id), +// emitterName = clientNameTest +// ) +// } +// +// "Retry task per id (sync)" { +// // when +// val id = UUID.randomUUID().toString() +// val task = client.getTaskById(FakeTask::class.java, id) +// client.retry(task) +// +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe RetryTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(id), +// emitterName = clientNameTest +// ) +// } +// +// "Retry task per id (async)" { +// // when +// val id = UUID.randomUUID().toString() +// val task = client.getTaskById(FakeTask::class.java, id) +// client.retryAsync(task).join() +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe RetryTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(id), +// emitterName = clientNameTest +// ) +// } +// +// "Retry a task just dispatched " { +// // when +// val deferred = client.dispatch(fakeTask::m0) +// deferred.retry() +// // then +// taskTagSlots.size shouldBe 0 +// taskSlot.captured shouldBe RetryTask( +// taskName = TaskName(FakeTask::class.java.name), +// taskId = TaskId(deferred.id), +// emitterName = clientNameTest +// ) +// } +// +// "Retry task per tag (sync)" { +// // when +// val task = client.getTaskByTag(FakeTask::class.java, "foo") +// client.retry(task) +// // then +// taskTagSlots.size shouldBe 1 +// taskTagSlots[0] shouldBe RetryTaskByTag( +// taskName = TaskName(FakeTask::class.java.name), +// taskTag = TaskTag("foo"), +// emitterName = clientNameTest +// ) +// taskSlot.isCaptured shouldBe false +// } +// +// "Retry task per tag (async)" { +// // when +// val task = client.getTaskByTag(FakeTask::class.java, "foo") +// client.retryAsync(task).join() +// // then +// taskTagSlots.size shouldBe 1 +// taskTagSlots[0] shouldBe RetryTaskByTag( +// taskName = TaskName(FakeTask::class.java.name), +// taskTag = TaskTag("foo"), +// emitterName = clientNameTest +// ) +// taskSlot.isCaptured shouldBe false +// } +// +// "Get task ids par name and workflow" { +// val task = client.getTaskByTag(FakeTask::class.java, "foo") +// val taskIds = client.getIds(task) +// // then +// taskIds.size shouldBe 2 +// taskTagSlots.size shouldBe 1 +// taskTagSlots[0] shouldBe GetTaskIdsByTag( +// taskName = TaskName(FakeTask::class.java.name), +// taskTag = TaskTag("foo"), +// emitterName = clientNameTest, +// ) +// taskSlot.isCaptured shouldBe false +// } }) diff --git a/infinitic-client/src/test/kotlin/io/infinitic/client/ClientWorkflowTests.kt b/infinitic-client/src/test/kotlin/io/infinitic/client/ClientWorkflowTests.kt index 94d083d1f..4134591a4 100644 --- a/infinitic-client/src/test/kotlin/io/infinitic/client/ClientWorkflowTests.kt +++ b/infinitic-client/src/test/kotlin/io/infinitic/client/ClientWorkflowTests.kt @@ -27,18 +27,23 @@ package io.infinitic.client import io.infinitic.client.samples.FakeClass import io.infinitic.client.samples.FakeInterface +import io.infinitic.client.samples.FakeTask +import io.infinitic.client.samples.FakeTaskImpl +import io.infinitic.client.samples.FakeTaskParent import io.infinitic.client.samples.FakeWorkflow +import io.infinitic.client.samples.FakeWorkflowImpl import io.infinitic.client.samples.FooWorkflow -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.tasks.engine.messages.TaskEngineMessage import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage -import io.infinitic.common.workflows.data.channels.ChannelEvent -import io.infinitic.common.workflows.data.channels.ChannelEventType import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.workflows.data.channels.ChannelSignal +import io.infinitic.common.workflows.data.channels.ChannelSignalType +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflows.WorkflowCancellationReason import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowMeta @@ -47,535 +52,636 @@ import io.infinitic.common.workflows.data.workflows.WorkflowOptions import io.infinitic.common.workflows.data.workflows.WorkflowTag import io.infinitic.common.workflows.engine.messages.CancelWorkflow import io.infinitic.common.workflows.engine.messages.DispatchWorkflow -import io.infinitic.common.workflows.engine.messages.SendToChannel +import io.infinitic.common.workflows.engine.messages.SendSignal +import io.infinitic.common.workflows.engine.messages.WaitWorkflow import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage -import io.infinitic.common.workflows.tags.messages.AddWorkflowTag -import io.infinitic.common.workflows.tags.messages.CancelWorkflowPerTag -import io.infinitic.common.workflows.tags.messages.GetWorkflowIds -import io.infinitic.common.workflows.tags.messages.SendToChannelPerTag +import io.infinitic.common.workflows.tags.messages.AddTagToWorkflow +import io.infinitic.common.workflows.tags.messages.CancelWorkflowByTag +import io.infinitic.common.workflows.tags.messages.GetWorkflowIdsByTag +import io.infinitic.common.workflows.tags.messages.SendSignalByTag import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineMessage -import io.infinitic.exceptions.clients.CanNotApplyOnNewWorkflowStubException -import io.infinitic.exceptions.clients.CanNotReuseWorkflowStubException -import io.infinitic.exceptions.clients.MultipleMethodCallsException -import io.infinitic.exceptions.clients.NoMethodCallException -import io.infinitic.exceptions.clients.SuspendMethodNotSupportedException +import io.infinitic.exceptions.clients.InvalidChannelUsageException +import io.infinitic.exceptions.clients.InvalidStubException import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.mockk.slot -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import java.util.UUID import java.util.concurrent.CopyOnWriteArrayList -private val taskTagSlots = CopyOnWriteArrayList() // multithread update -private val workflowTagSlots = CopyOnWriteArrayList() // multithread update +private val taskTagSlots = CopyOnWriteArrayList() // multithreading update +private val workflowTagSlots = CopyOnWriteArrayList() // multithreading update private val taskSlot = slot() private val workflowSlot = slot() +private val clientNameTest = ClientName("clientTest") class ClientWorkflow : InfiniticClient() { - override val sendingScope = CoroutineScope(Dispatchers.IO) - override val clientName = ClientName("clientTest") - override val sendToTaskTagEngine = mockSendToTaskTagEngine(this, taskTagSlots) - override val sendToTaskEngine = mockSendToTaskEngine(this, taskSlot) - override val sendToWorkflowTagEngine = mockSendToWorkflowTagEngine(this, workflowTagSlots) - override val sendToWorkflowEngine = mockSendToWorkflowEngine(this, workflowSlot) + override val clientName = clientNameTest + override val sendToTaskTagEngine = mockSendToTaskTagEngine(this, taskTagSlots, clientName, sendingScope) + override val sendToTaskEngine = mockSendToTaskEngine(this, taskSlot, clientName, sendingScope) + override val sendToWorkflowTagEngine = mockSendToWorkflowTagEngine(this, workflowTagSlots, clientName, sendingScope) + override val sendToWorkflowEngine = mockSendToWorkflowEngine(this, workflowSlot, clientName, sendingScope) } class ClientWorkflowTests : StringSpec({ val client = ClientWorkflow() + val options = TestFactory.random() + val meta: Map = mapOf( + "foo" to TestFactory.random(), + "bar" to TestFactory.random() + ) + val tags = setOf("foo", "bar") + + val fakeWorkflow = client.newWorkflow(FakeWorkflow::class.java) + val fooWorkflow = client.newWorkflow(FooWorkflow::class.java) + + val fakeWorkflowWithOptions = client.newWorkflow(FakeWorkflow::class.java, options = options) + val fakeWorkflowWithMeta = client.newWorkflow(FakeWorkflow::class.java, meta = meta) + val fakeWorkflowWithTags = client.newWorkflow(FakeWorkflow::class.java, tags = tags) + beforeTest { - client.join() taskTagSlots.clear() taskSlot.clear() workflowTagSlots.clear() workflowSlot.clear() } - "Should throw when re-using a stub" { - // when - val fakeWorkflow = client.newWorkflow() - shouldThrow { - client.async(fakeWorkflow) { m1() } - client.async(fakeWorkflow) { m1() } - } - } - - "Should throw when calling 2 methods" { - // when - val fakeWorkflow = client.newWorkflow() - shouldThrow { - client.async(fakeWorkflow) { m1(); m1() } - } - } - - "Should throw when not calling any method" { - // when - val fakeWorkflow = client.newWorkflow() - shouldThrow { - client.async(fakeWorkflow) { } - } - } - - "Should throw when retrying new stub" { - // when - val fakeWorkflow = client.newWorkflow() - shouldThrow { - client.retry(fakeWorkflow) - } - } - - "Should throw when canceling new stub" { - // when - val fakeWorkflow = client.newWorkflow() - shouldThrow { - client.cancel(fakeWorkflow) - } - } - - "Should throw when using a suspend method" { - // when - val fakeWorkflow = client.newWorkflow() - shouldThrow { - fakeWorkflow.suspendedMethod() - } + afterTest { + client.join() } - "Should be able to dispatch a workflow without parameter" { + "Should be able to dispatch a workflow" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1() }.join() + val deferred = client.dispatch(fakeWorkflow::m0) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf()), + workflowId = WorkflowId(deferred.id), + methodName = MethodName("m0"), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } "Should be able to dispatch a workflow with annotation" { // when - val fooWorkflow = client.newWorkflow() - val deferred = client.async(fooWorkflow) { m() }.join() + val deferred = client.dispatch(fooWorkflow::m) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName("foo"), + workflowId = WorkflowId(deferred.id), methodName = MethodName("bar"), - methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } "Should be able to dispatch a workflow with annotation on parent" { // when - val fooWorkflow = client.newWorkflow() - val deferred = client.async(fooWorkflow) { annotated() }.join() + val deferred = client.dispatch(fooWorkflow::annotated) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName("foo"), + workflowId = WorkflowId(deferred.id), methodName = MethodName("bar"), - methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } "Should be able to dispatch a workflow from a parent interface" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { parent() }.join() + val deferred = client.dispatch(fakeWorkflow::parent) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), + workflowId = WorkflowId(deferred.id), methodName = MethodName("parent"), - methodParameterTypes = MethodParameterTypes(listOf()), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } - "Should be able to dispatch a workflow with Java syntax" { + "Should be able to dispatch a workflow with options" { // when - val fakeWorkflow = client.newWorkflow(FakeWorkflow::class.java) - val deferred = client.async(fakeWorkflow) { m1() }.join() + val deferred = client.dispatch(fakeWorkflowWithOptions::m0) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf()), + workflowId = WorkflowId(deferred.id), + methodName = MethodName("m0"), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = options, + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } - "Should be able to dispatch a workflow with options and meta" { + "Should be able to dispatch a workflow with meta" { // when - val options = TestFactory.random() - val meta = mapOf( - "foo" to TestFactory.random(), - "bar" to TestFactory.random() - ) - val fakeWorkflow = client.newWorkflow(options = options, meta = meta) - val deferred = client.async(fakeWorkflow) { m1() }.join() + val deferred = client.dispatch(fakeWorkflowWithMeta::m0) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf()), + workflowId = WorkflowId(deferred.id), + methodName = MethodName("m0"), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(meta), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = options, - workflowMeta = WorkflowMeta(meta) + clientWaiting = false, + emitterName = clientNameTest ) } "Should be able to dispatch a workflow with tags" { // when - val tags = setOf("foo", "bar") - val fakeWorkflow = client.newWorkflow(tags = tags) - val deferred = client.async(fakeWorkflow) { m1() }.join() + val deferred = client.dispatch(fakeWorkflowWithTags::m0) // then workflowTagSlots.size shouldBe 2 - workflowTagSlots.toSet() shouldBe setOf( - AddWorkflowTag( - workflowTag = WorkflowTag("foo"), - workflowName = WorkflowName(FakeWorkflow::class.java.name), - workflowId = WorkflowId(deferred.id), - ), - AddWorkflowTag( - workflowTag = WorkflowTag("bar"), + workflowTagSlots.toSet() shouldBe tags.map { + AddTagToWorkflow( workflowName = WorkflowName(FakeWorkflow::class.java.name), + workflowTag = WorkflowTag(it), workflowId = WorkflowId(deferred.id), + emitterName = clientNameTest, ) - ) + }.toSet() workflowSlot.captured shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf()), + workflowId = WorkflowId(deferred.id), + methodName = MethodName("m0"), methodParameters = MethodParameters(), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf()), + workflowOptions = WorkflowOptions(), + workflowTags = tags.map { WorkflowTag(it) }.toSet(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(WorkflowTag("foo"), WorkflowTag("bar")), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } - "Should be able to dispatch a workflow with a primitive as parameter" { + "Should be able to dispatch a workflow with a one primitive as parameter" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1(0) }.join() + val deferred = client.dispatch(fakeWorkflow::m1, 0) // then workflowSlot.isCaptured shouldBe true val msg = workflowSlot.captured msg shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), + workflowId = WorkflowId(deferred.id), methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(Integer::class.java.name)), methodParameters = MethodParameters.from(0), - parentWorkflowId = null, - parentWorkflowName = null, - parentMethodRunId = null, - workflowTags = setOf(), + methodParameterTypes = MethodParameterTypes(listOf(Integer::class.java.name)), workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() - ) - } - - "Should be able to dispatch a workflow with multiple method definition" { - // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1("a") }.join() - // then - workflowSlot.isCaptured shouldBe true - val msg = workflowSlot.captured - msg shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), - workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(String::class.java.name)), - methodParameters = MethodParameters.from("a"), - parentWorkflowId = null, + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } - "Should be able to dispatch a workflow with multiple parameters" { + "Should be able to dispatch a workflow with two primitive parameters" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1(0, "a") }.join() + val deferred = client.dispatch(fakeWorkflow::m3, 0, "a") // then workflowSlot.isCaptured shouldBe true val msg = workflowSlot.captured msg shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name, String::class.java.name)), + workflowId = WorkflowId(deferred.id), + methodName = MethodName("m3"), methodParameters = MethodParameters.from(0, "a"), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name, String::class.java.name)), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } "Should be able to dispatch a workflow with an interface as parameter" { // when val klass = FakeClass() - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1(klass) }.join() + val deferred = client.dispatch(fakeWorkflow::m4, klass) // then workflowSlot.isCaptured shouldBe true val msg = workflowSlot.captured msg shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = false, - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(FakeInterface::class.java.name)), + workflowId = WorkflowId(deferred.id), + methodName = MethodName("m4"), methodParameters = MethodParameters.from(klass), - parentWorkflowId = null, + methodParameterTypes = MethodParameterTypes(listOf(FakeInterface::class.java.name)), + workflowOptions = WorkflowOptions(), + workflowTags = setOf(), + workflowMeta = WorkflowMeta(), parentWorkflowName = null, + parentWorkflowId = null, parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + clientWaiting = false, + emitterName = clientNameTest ) } - "Should be able to dispatch a workflow synchronously" { + "Should be able to wait for a dispatched workflow" { // when - val fakeWorkflow = client.newWorkflow() - val result = fakeWorkflow.m1(0, "a") + val result = client.dispatch(fakeWorkflow::m3, 0, "a").await() // then result shouldBe "success" val msg = workflowSlot.captured - msg shouldBe DispatchWorkflow( - clientName = client.clientName, - clientWaiting = true, - workflowId = msg.workflowId, + msg shouldBe WaitWorkflow( workflowName = WorkflowName(FakeWorkflow::class.java.name), - methodName = MethodName("m1"), - methodParameterTypes = MethodParameterTypes(listOf(Int::class.java.name, String::class.java.name)), - methodParameters = MethodParameters.from(0, "a"), - parentWorkflowId = null, - parentWorkflowName = null, - parentMethodRunId = null, - workflowTags = setOf(), - workflowOptions = WorkflowOptions(), - workflowMeta = WorkflowMeta() + workflowId = msg.workflowId, + methodRunId = MethodRunId.from(msg.workflowId), + emitterName = clientNameTest ) } - "Should be able to wait for a workflow just dispatched" { + "Should throw when calling a channel from a new workflow" { + // when + shouldThrow { + fakeWorkflow.channelString.send("a") + } + } + + "Should be able to send to a channel by id (sync)" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1(0, "a") }.join() - val result = deferred.await() + val id = UUID.randomUUID().toString() + client.getWorkflowById(FakeWorkflow::class.java, id).channelString.send("a") // then - result shouldBe "success" + workflowTagSlots.size shouldBe 0 + val msg = workflowSlot.captured as SendSignal + msg shouldBe SendSignal( + workflowName = WorkflowName(FakeWorkflow::class.java.name), + workflowId = WorkflowId(id), + channelName = ChannelName("getChannelString"), + channelSignalId = msg.channelSignalId, + channelSignal = ChannelSignal.from("a"), + channelSignalTypes = ChannelSignalType.allFrom(String::class.java), + emitterName = clientNameTest + ) } - "Should be able to emit to a channel by id" { + "Should be able to emit to a channel by id (async)" { // when - val id = UUID.randomUUID() - val fakeWorkflow = client.getWorkflow(FakeWorkflow::class.java, id) - fakeWorkflow.channel.send("a").join() + val id = UUID.randomUUID().toString() + val w = client.getWorkflowById(FakeWorkflow::class.java, id) + client.dispatchAsync(w.channelString::send, "a").join() // then workflowTagSlots.size shouldBe 0 - val msg = workflowSlot.captured as SendToChannel - msg shouldBe SendToChannel( + val msg = workflowSlot.captured as SendSignal + msg shouldBe SendSignal( + workflowName = WorkflowName(FakeWorkflow::class.java.name), workflowId = WorkflowId(id), + channelName = ChannelName("getChannelString"), + channelSignalId = msg.channelSignalId, + channelSignal = ChannelSignal.from("a"), + channelSignalTypes = ChannelSignalType.allFrom(String::class.java), + emitterName = clientNameTest + ) + } + + "Should be able to emit to a channel by tag (sync)" { + val tag = "foo" + // when + client.getWorkflowByTag(FakeWorkflow::class.java, tag).channelString.send("a") + // then + val msg = workflowTagSlots[0] as SendSignalByTag + msg shouldBe SendSignalByTag( workflowName = WorkflowName(FakeWorkflow::class.java.name), - clientName = client.clientName, - channelEventId = msg.channelEventId, - channelName = ChannelName("getChannel"), - channelEvent = ChannelEvent.from("a"), - channelEventTypes = ChannelEventType.allFrom(String::class.java) + workflowTag = WorkflowTag(tag), + channelName = ChannelName("getChannelString"), + channelSignalId = msg.channelSignalId, + channelSignal = ChannelSignal.from("a"), + channelSignalTypes = ChannelSignalType.allFrom(String::class.java), + emitterWorkflowId = null, + emitterName = clientNameTest ) } - "Should be able to emit to a channel by tag" { + "Should be able to emit to a channel by tag (async)" { val tag = "foo" // when - val fakeWorkflow = client.getWorkflow(FakeWorkflow::class.java, tag) - fakeWorkflow.channel.send("a").join() + val w = client.getWorkflowByTag(FakeWorkflow::class.java, tag) + client.dispatchAsync(w.channelString::send, "a").join() // then - val msg = workflowTagSlots[0] as SendToChannelPerTag - msg shouldBe SendToChannelPerTag( + val msg = workflowTagSlots[0] as SendSignalByTag + msg shouldBe SendSignalByTag( + workflowName = WorkflowName(FakeWorkflow::class.java.name), workflowTag = WorkflowTag(tag), + channelName = ChannelName("getChannelString"), + channelSignalId = msg.channelSignalId, + channelSignal = ChannelSignal.from("a"), + channelSignalTypes = ChannelSignalType.allFrom(String::class.java), + emitterWorkflowId = null, + emitterName = clientNameTest + ) + } + + "Should be able to send a complex Object to a channel" { + // when + val id = UUID.randomUUID().toString() + val signal = FakeTaskImpl() + client.getWorkflowById(FakeWorkflow::class.java, id).channelFakeTask.send(signal) + // then + workflowTagSlots.size shouldBe 0 + val msg = workflowSlot.captured as SendSignal + msg shouldBe SendSignal( workflowName = WorkflowName(FakeWorkflow::class.java.name), - clientName = client.clientName, - clientWaiting = true, - channelEventId = msg.channelEventId, - channelName = ChannelName("getChannel"), - channelEvent = ChannelEvent.from("a"), - channelEventTypes = ChannelEventType.allFrom(String::class.java) + workflowId = WorkflowId(id), + channelName = ChannelName("getChannelFakeTask"), + channelSignalId = msg.channelSignalId, + channelSignal = ChannelSignal.from(signal), + channelSignalTypes = setOf( + ChannelSignalType.from(FakeTaskImpl::class.java), + ChannelSignalType.from(FakeTask::class.java), + ChannelSignalType.from(FakeTaskParent::class.java) + ), + emitterName = clientNameTest ) } - "Should be able to emit to a channel after workflow dispatch" { + "Should be able to send a complex Object to a channel targeting a parent type" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1(0, "a") }.join() - fakeWorkflow.channel.send("a").join() + val id = UUID.randomUUID().toString() + val signal = FakeTaskImpl() + client.getWorkflowById(FakeWorkflow::class.java, id).channelFakeTaskParent.send(signal) // then workflowTagSlots.size shouldBe 0 - val msg = workflowSlot.captured as SendToChannel - msg shouldBe SendToChannel( - workflowId = WorkflowId(deferred.id), + val msg = workflowSlot.captured as SendSignal + msg shouldBe SendSignal( workflowName = WorkflowName(FakeWorkflow::class.java.name), - clientName = client.clientName, - channelEventId = msg.channelEventId, - channelName = ChannelName("getChannel"), - channelEvent = ChannelEvent.from("a"), - channelEventTypes = ChannelEventType.allFrom(String::class.java) + workflowId = WorkflowId(id), + channelName = ChannelName("getChannelFakeTaskParent"), + channelSignalId = msg.channelSignalId, + channelSignal = ChannelSignal.from(signal), + channelSignalTypes = setOf( + ChannelSignalType.from(FakeTaskImpl::class.java), + ChannelSignalType.from(FakeTask::class.java), + ChannelSignalType.from(FakeTaskParent::class.java) + ), + emitterName = clientNameTest ) } - "Should be able to cancel workflow per id" { + "Should be able to cancel workflow per id (sync)" { // when - val id = UUID.randomUUID() - val fakeWorkflow = client.getWorkflow(FakeWorkflow::class.java, id) - client.cancel(fakeWorkflow).join() + val id = UUID.randomUUID().toString() + val workflow = client.getWorkflowById(FakeWorkflow::class.java, id) + client.cancel(workflow) // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe CancelWorkflow( + workflowName = WorkflowName(FakeWorkflow::class.java.name), workflowId = WorkflowId(id), + methodRunId = null, + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterName = clientNameTest + ) + } + + "Should be able to cancel workflow per id (async)" { + // when + val id = UUID.randomUUID().toString() + val workflow = client.getWorkflowById(FakeWorkflow::class.java, id) + client.cancelAsync(workflow).join() + // then + workflowTagSlots.size shouldBe 0 + workflowSlot.captured shouldBe CancelWorkflow( workflowName = WorkflowName(FakeWorkflow::class.java.name), - reason = WorkflowCancellationReason.CANCELED_BY_CLIENT + workflowId = WorkflowId(id), + methodRunId = null, + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterName = clientNameTest ) } - "Should be able to cancel workflow per tag" { + "Should be able to cancel workflow per tag (sync)" { // when - val fakeWorkflow = client.getWorkflow("foo") - client.cancel(fakeWorkflow).join() + val tag = "foo" + val workflow = client.getWorkflowByTag(FakeWorkflow::class.java, tag) + client.cancel(workflow) // then workflowTagSlots.size shouldBe 1 - workflowTagSlots[0] shouldBe CancelWorkflowPerTag( - workflowTag = WorkflowTag("foo"), + workflowTagSlots[0] shouldBe CancelWorkflowByTag( workflowName = WorkflowName(FakeWorkflow::class.java.name), - reason = WorkflowCancellationReason.CANCELED_BY_CLIENT + workflowTag = WorkflowTag(tag), + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterWorkflowId = null, + emitterName = clientNameTest ) workflowSlot.isCaptured shouldBe false } - "Should be able to cancel workflow per tag with output" { + "Should be able to cancel workflow per tag (async)" { // when - val fakeWorkflow = client.getWorkflow("foo") - client.cancel(fakeWorkflow).join() + val tag = "foo" + val workflow = client.getWorkflowByTag(FakeWorkflow::class.java, tag) + client.cancelAsync(workflow).join() // then workflowTagSlots.size shouldBe 1 - workflowTagSlots[0] shouldBe CancelWorkflowPerTag( - workflowTag = WorkflowTag("foo"), + workflowTagSlots[0] shouldBe CancelWorkflowByTag( workflowName = WorkflowName(FakeWorkflow::class.java.name), - reason = WorkflowCancellationReason.CANCELED_BY_CLIENT + workflowTag = WorkflowTag("foo"), + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterWorkflowId = null, + emitterName = clientNameTest ) workflowSlot.isCaptured shouldBe false } "Should be able to cancel workflow just dispatched" { // when - val fakeWorkflow = client.newWorkflow() - val deferred = client.async(fakeWorkflow) { m1() }.join() - client.cancel(fakeWorkflow).join() + val deferred = client.dispatch(fakeWorkflow::m0) + deferred.cancel() // then workflowTagSlots.size shouldBe 0 workflowSlot.captured shouldBe CancelWorkflow( - workflowId = WorkflowId(deferred.id), workflowName = WorkflowName(FakeWorkflow::class.java.name), - reason = WorkflowCancellationReason.CANCELED_BY_CLIENT + workflowId = WorkflowId(deferred.id), + methodRunId = null, + reason = WorkflowCancellationReason.CANCELED_BY_CLIENT, + emitterName = clientNameTest ) } - "get task ids par name and workflow" { - val workflowIds = client.getWorkflowIds("foo") + "Get task ids par name and workflow" { + val tag = "foo" + val workflow = client.getWorkflowByTag(FakeWorkflow::class.java, tag) + val workflowIds = client.getIds(workflow) // then workflowIds.size shouldBe 2 workflowTagSlots.size shouldBe 1 - workflowTagSlots[0] shouldBe GetWorkflowIds( + workflowTagSlots[0] shouldBe GetWorkflowIdsByTag( workflowName = WorkflowName(FakeWorkflow::class.java.name), workflowTag = WorkflowTag("foo"), - clientName = ClientName("clientTest") + emitterName = clientNameTest ) workflowSlot.isCaptured shouldBe false } + + "Wait a channel should throw" { + shouldThrow { + client.await(fakeWorkflow.channelString) + } + + val byId = client.getWorkflowById(FakeWorkflow::class.java, UUID.randomUUID().toString()) + shouldThrow { + client.await(byId.channelString) + } + + val byTag = client.getWorkflowByTag(FakeWorkflow::class.java, "foo") + shouldThrow { + client.await(byTag.channelString) + } + } + + "Wait a channel method should throw" { + shouldThrow { + client.await(fakeWorkflow.channelString, UUID.randomUUID().toString()) + } + + val byId = client.getWorkflowById(FakeWorkflow::class.java, UUID.randomUUID().toString()) + shouldThrow { + client.await(byId.channelString, UUID.randomUUID().toString()) + } + + val byTag = client.getWorkflowByTag(FakeWorkflow::class.java, "foo") + shouldThrow { + client.await(byTag.channelString, UUID.randomUUID().toString()) + } + } + + "Retry a channel should throw" { + shouldThrow { + client.retry(fakeWorkflow.channelString) + } + + val byId = client.getWorkflowById(FakeWorkflow::class.java, UUID.randomUUID().toString()) + shouldThrow { + client.retry(byId.channelString) + } + + val byTag = client.getWorkflowByTag(FakeWorkflow::class.java, "foo") + shouldThrow { + client.retry(byTag.channelString) + } + } + + "Cancel a channel should throw" { + shouldThrow { + client.cancel(fakeWorkflow.channelString) + } + + val byId = client.getWorkflowById(FakeWorkflow::class.java, UUID.randomUUID().toString()) + shouldThrow { + client.cancel(byId.channelString) + } + + val byTag = client.getWorkflowByTag(FakeWorkflow::class.java, "foo") + shouldThrow { + client.cancel(byTag.channelString) + } + } + + "Complete a channel should throw" { + shouldThrow { + client.complete(fakeWorkflow.channelString, null) + } + + val byId = client.getWorkflowById(FakeWorkflow::class.java, UUID.randomUUID().toString()) + shouldThrow { + client.complete(byId.channelString, null) + } + + val byTag = client.getWorkflowByTag(FakeWorkflow::class.java, "foo") + shouldThrow { + client.complete(byTag.channelString, null) + } + } + + "Get ids from channel should throw" { + shouldThrow { + client.getIds(fakeWorkflow.channelString) + } + + val byId = client.getWorkflowById(FakeWorkflow::class.java, UUID.randomUUID().toString()) + shouldThrow { + client.getIds(byId.channelString) + } + + val byTag = client.getWorkflowByTag(FakeWorkflow::class.java, "foo") + shouldThrow { + client.getIds(byTag.channelString) + } + } + + "Should throw when using an instance" { + val fake = FakeWorkflowImpl() + shouldThrow { + client.dispatch(fake::m0) + } + } }) diff --git a/infinitic-client/src/test/kotlin/io/infinitic/client/mockClientOutput.kt b/infinitic-client/src/test/kotlin/io/infinitic/client/mockClientOutput.kt index 1959e9dee..048db4916 100644 --- a/infinitic-client/src/test/kotlin/io/infinitic/client/mockClientOutput.kt +++ b/infinitic-client/src/test/kotlin/io/infinitic/client/mockClientOutput.kt @@ -25,11 +25,12 @@ package io.infinitic.client +import io.infinitic.common.clients.messages.MethodCompleted import io.infinitic.common.clients.messages.TaskCompleted -import io.infinitic.common.clients.messages.TaskIdsPerTag -import io.infinitic.common.clients.messages.WorkflowCompleted -import io.infinitic.common.clients.messages.WorkflowIdsPerTag -import io.infinitic.common.data.methods.MethodReturnValue +import io.infinitic.common.clients.messages.TaskIdsByTag +import io.infinitic.common.clients.messages.WorkflowIdsByTag +import io.infinitic.common.data.ClientName +import io.infinitic.common.data.ReturnValue import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.tasks.engine.SendToTaskEngine @@ -37,40 +38,45 @@ import io.infinitic.common.tasks.engine.messages.DispatchTask import io.infinitic.common.tasks.engine.messages.TaskEngineMessage import io.infinitic.common.tasks.engine.messages.WaitTask import io.infinitic.common.tasks.tags.SendToTaskTagEngine -import io.infinitic.common.tasks.tags.messages.GetTaskIds +import io.infinitic.common.tasks.tags.messages.GetTaskIdsByTag import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.messages.DispatchWorkflow import io.infinitic.common.workflows.engine.messages.WaitWorkflow import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine -import io.infinitic.common.workflows.tags.messages.GetWorkflowIds +import io.infinitic.common.workflows.tags.messages.GetWorkflowIdsByTag import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineMessage import io.mockk.CapturingSlot import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.future.future import java.util.concurrent.CopyOnWriteArrayList fun mockSendToTaskTagEngine( client: InfiniticClient, - taskTagSlots: CopyOnWriteArrayList + taskTagSlots: CopyOnWriteArrayList, + clientName: ClientName, + sendingScope: CoroutineScope ): SendToTaskTagEngine { val sendToTaskTagEngine = mockk() every { sendToTaskTagEngine(capture(taskTagSlots)) } answers { taskTagSlots.forEach { - if (it is GetTaskIds) { - val taskIdsPerTag = TaskIdsPerTag( - clientName = client.clientName, + if (it is GetTaskIdsByTag) { + val taskIdsByTag = TaskIdsByTag( + recipientName = clientName, taskName = it.taskName, taskTag = it.taskTag, - taskIds = setOf(TaskId(), TaskId()) + taskIds = setOf(TaskId(), TaskId()), + emitterName = ClientName("mockk") ) - client.sendingScope.future { + sendingScope.future { delay(100) - client.handle(taskIdsPerTag) + client.handle(taskIdsByTag) } } } @@ -80,21 +86,24 @@ fun mockSendToTaskTagEngine( fun mockSendToWorkflowTagEngine( client: InfiniticClient, - workflowTagSlots: CopyOnWriteArrayList + workflowTagSlots: CopyOnWriteArrayList, + clientName: ClientName, + sendingScope: CoroutineScope ): SendToWorkflowTagEngine { val sendToWorkflowTagEngine = mockk() every { sendToWorkflowTagEngine(capture(workflowTagSlots)) } answers { workflowTagSlots.forEach { - if (it is GetWorkflowIds) { - val workflowIdsPerTag = WorkflowIdsPerTag( - clientName = client.clientName, + if (it is GetWorkflowIdsByTag) { + val workflowIdsByTag = WorkflowIdsByTag( + recipientName = clientName, workflowName = it.workflowName, workflowTag = it.workflowTag, - workflowIds = setOf(WorkflowId(), WorkflowId()) + workflowIds = setOf(WorkflowId(), WorkflowId()), + emitterName = ClientName("mockk") ) - client.sendingScope.future { + sendingScope.future { delay(100) - client.handle(workflowIdsPerTag) + client.handle(workflowIdsByTag) } } } @@ -104,19 +113,22 @@ fun mockSendToWorkflowTagEngine( fun mockSendToTaskEngine( client: InfiniticClient, - message: CapturingSlot + message: CapturingSlot, + clientName: ClientName, + sendingScope: CoroutineScope ): SendToTaskEngine { val sendToTaskEngine = mockk() every { sendToTaskEngine(capture(message)) } answers { val msg = message.captured if ((msg is DispatchTask && msg.clientWaiting) || (msg is WaitTask)) { val taskCompleted = TaskCompleted( - clientName = client.clientName, + recipientName = clientName, + emitterName = ClientName("mockk"), taskId = msg.taskId, - taskReturnValue = MethodReturnValue.from("success"), + taskReturnValue = ReturnValue.from("success"), taskMeta = TaskMeta() ) - client.sendingScope.future { + sendingScope.future { delay(100) client.handle(taskCompleted) } @@ -128,18 +140,22 @@ fun mockSendToTaskEngine( fun mockSendToWorkflowEngine( client: InfiniticClient, - message: CapturingSlot + message: CapturingSlot, + clientName: ClientName, + sendingScope: CoroutineScope ): SendToWorkflowEngine { val sendToWorkflowEngine = mockk() every { sendToWorkflowEngine(capture(message)) } answers { - val msg = message.captured + val msg: WorkflowEngineMessage = message.captured if (msg is DispatchWorkflow && msg.clientWaiting || msg is WaitWorkflow) { - val workflowCompleted = WorkflowCompleted( - clientName = client.clientName, + val workflowCompleted = MethodCompleted( + recipientName = clientName, workflowId = msg.workflowId, - workflowReturnValue = MethodReturnValue.from("success") + methodRunId = MethodRunId.from(msg.workflowId), + methodReturnValue = ReturnValue.from("success"), + emitterName = ClientName("mockk") ) - client.sendingScope.future { + sendingScope.future { delay(100) client.handle(workflowCompleted) } diff --git a/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeTask.kt b/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeTask.kt index f2c249a21..30750c9dc 100644 --- a/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeTask.kt +++ b/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeTask.kt @@ -26,7 +26,6 @@ package io.infinitic.client.samples import io.infinitic.annotations.Name -import io.infinitic.common.tasks.data.TaskId internal interface FakeTaskParent { fun parent(): String @@ -36,12 +35,13 @@ internal interface FakeTaskParent { } internal interface FakeTask : FakeTaskParent { - fun m1() + fun m0() + fun m0String(): String fun m1(i: Int): String - fun m1(str: String?): Any? - fun m1(p1: Int, p2: String): String - fun m1(id: FakeInterface): TaskId - fun m2(): Boolean + fun m2(str: String?): Any? + fun m3(p1: Int, p2: String): String + fun m4(id: FakeInterface): FooTask + fun m5(): Boolean suspend fun suspendedMethod() } @@ -50,3 +50,25 @@ internal interface FooTask : FakeTaskParent { @Name("bar") fun m() } + +internal class FakeTaskImpl : FakeTask { + override fun m0() { } + + override fun m0String(): String = "Not needed" + + override fun m1(i: Int): String = "Not needed" + + override fun m2(str: String?): Any? = "Not needed" + + override fun m3(p1: Int, p2: String): String = "Not needed" + + override fun m4(id: FakeInterface): FooTask = throw Exception() + + override fun m5(): Boolean = true + + override suspend fun suspendedMethod() { } + + override fun parent() = "Not needed" + + override fun annotated(): String = "Not needed" +} diff --git a/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeWorkflow.kt b/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeWorkflow.kt index 55955f447..c22acf63f 100644 --- a/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeWorkflow.kt +++ b/infinitic-client/src/test/kotlin/io/infinitic/client/samples/FakeWorkflow.kt @@ -26,7 +26,8 @@ package io.infinitic.client.samples import io.infinitic.annotations.Name -import io.infinitic.workflows.Channel +import io.infinitic.workflows.SendChannel +import io.infinitic.workflows.Workflow internal interface FakeWorkflowParent { fun parent(): String @@ -36,14 +37,29 @@ internal interface FakeWorkflowParent { } internal interface FakeWorkflow : FakeWorkflowParent { - fun m1() + fun m0() fun m1(i: Int?): String - fun m1(str: String): Any? - fun m1(p1: Int, p2: String): String - fun m1(id: FakeInterface): String + fun m2(str: String): Any? + fun m3(p1: Int, p2: String): String + fun m4(id: FakeInterface): String suspend fun suspendedMethod() + val channelString: SendChannel + val channelFakeTask: SendChannel + val channelFakeTaskParent: SendChannel +} - val channel: Channel +internal class FakeWorkflowImpl : Workflow(), FakeWorkflow { + override fun m0() { } + override fun m1(i: Int?): String = "$i" + override fun m2(str: String): Any? = str + override fun m3(p1: Int, p2: String): String = "$p1$p2" + override fun m4(id: FakeInterface): String = "$id" + override suspend fun suspendedMethod() { } + override val channelString = channel() + override val channelFakeTask = channel() + override val channelFakeTaskParent = channel() + override fun parent(): String = "foo" + override fun annotated(): String = "foo" } @Name(name = "foo") diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientEnvelope.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientEnvelope.kt index b9e387dd7..369f04343 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientEnvelope.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientEnvelope.kt @@ -25,7 +25,7 @@ package io.infinitic.common.clients.messages -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.messages.Envelope import io.infinitic.common.serDe.avro.AvroSerDe import kotlinx.serialization.Serializable @@ -37,91 +37,91 @@ data class ClientEnvelope( private val taskCompleted: TaskCompleted? = null, private val taskCanceled: TaskCanceled? = null, private val taskFailed: TaskFailed? = null, - private val unknownTask: UnknownTask? = null, - private val taskIdsPerTag: TaskIdsPerTag? = null, - private val workflowCompleted: WorkflowCompleted? = null, - private val workflowCanceled: WorkflowCanceled? = null, - private val workflowFailed: WorkflowFailed? = null, - private val unknownWorkflow: UnknownWorkflow? = null, - private val workflowAlreadyCompleted: WorkflowAlreadyCompleted? = null, - private val workflowIdsPerTag: WorkflowIdsPerTag? = null + private val taskUnknown: TaskUnknown? = null, + private val taskIdsByTag: TaskIdsByTag? = null, + private val workflowCompleted: MethodCompleted? = null, + private val workflowCanceled: MethodCanceled? = null, + private val workflowFailed: MethodFailed? = null, + private val unknownWorkflow: MethodRunUnknown? = null, + private val methodAlreadyCompleted: MethodAlreadyCompleted? = null, + private val workflowIdsByTag: WorkflowIdsByTag? = null ) : Envelope { init { val noNull = listOfNotNull( taskCompleted, taskCanceled, taskFailed, - unknownTask, - taskIdsPerTag, + taskUnknown, + taskIdsByTag, workflowCompleted, workflowCanceled, workflowFailed, unknownWorkflow, - workflowAlreadyCompleted, - workflowIdsPerTag + methodAlreadyCompleted, + workflowIdsByTag ) require(noNull.size == 1) require(noNull.first() == message()) - require(noNull.first().clientName == clientName) + require(noNull.first().emitterName == clientName) } companion object { fun from(msg: ClientMessage) = when (msg) { is TaskCompleted -> ClientEnvelope( - msg.clientName, + msg.emitterName, ClientMessageType.TASK_COMPLETED, taskCompleted = msg ) is TaskCanceled -> ClientEnvelope( - msg.clientName, + msg.emitterName, ClientMessageType.TASK_CANCELED, taskCanceled = msg ) is TaskFailed -> ClientEnvelope( - msg.clientName, + msg.emitterName, ClientMessageType.TASK_FAILED, taskFailed = msg ) - is UnknownTask -> ClientEnvelope( - msg.clientName, + is TaskUnknown -> ClientEnvelope( + msg.emitterName, ClientMessageType.UNKNOWN_TASK, - unknownTask = msg + taskUnknown = msg ) - is TaskIdsPerTag -> ClientEnvelope( - msg.clientName, + is TaskIdsByTag -> ClientEnvelope( + msg.emitterName, ClientMessageType.TASK_IDS_PER_TAG, - taskIdsPerTag = msg + taskIdsByTag = msg ) - is WorkflowCompleted -> ClientEnvelope( - msg.clientName, + is MethodCompleted -> ClientEnvelope( + msg.emitterName, ClientMessageType.WORKFLOW_COMPLETED, workflowCompleted = msg ) - is WorkflowCanceled -> ClientEnvelope( - msg.clientName, + is MethodCanceled -> ClientEnvelope( + msg.emitterName, ClientMessageType.WORKFLOW_CANCELED, workflowCanceled = msg ) - is WorkflowFailed -> ClientEnvelope( - msg.clientName, + is MethodFailed -> ClientEnvelope( + msg.emitterName, ClientMessageType.WORKFLOW_FAILED, workflowFailed = msg ) - is UnknownWorkflow -> ClientEnvelope( - msg.clientName, + is MethodRunUnknown -> ClientEnvelope( + msg.emitterName, ClientMessageType.UNKNOWN_WORKFLOW, unknownWorkflow = msg ) - is WorkflowAlreadyCompleted -> ClientEnvelope( - msg.clientName, + is MethodAlreadyCompleted -> ClientEnvelope( + msg.emitterName, ClientMessageType.WORKFLOW_ALREADY_COMPLETED, - workflowAlreadyCompleted = msg + methodAlreadyCompleted = msg ) - is WorkflowIdsPerTag -> ClientEnvelope( - msg.clientName, + is WorkflowIdsByTag -> ClientEnvelope( + msg.emitterName, ClientMessageType.WORKFLOW_IDS_PER_TAG, - workflowIdsPerTag = msg + workflowIdsByTag = msg ) } @@ -129,17 +129,17 @@ data class ClientEnvelope( } override fun message(): ClientMessage = when (type) { - ClientMessageType.UNKNOWN_TASK -> unknownTask!! + ClientMessageType.UNKNOWN_TASK -> taskUnknown!! ClientMessageType.TASK_COMPLETED -> taskCompleted!! ClientMessageType.TASK_CANCELED -> taskCanceled!! ClientMessageType.TASK_FAILED -> taskFailed!! - ClientMessageType.TASK_IDS_PER_TAG -> taskIdsPerTag!! + ClientMessageType.TASK_IDS_PER_TAG -> taskIdsByTag!! ClientMessageType.WORKFLOW_COMPLETED -> workflowCompleted!! ClientMessageType.WORKFLOW_CANCELED -> workflowCanceled!! ClientMessageType.WORKFLOW_FAILED -> workflowFailed!! ClientMessageType.UNKNOWN_WORKFLOW -> unknownWorkflow!! - ClientMessageType.WORKFLOW_ALREADY_COMPLETED -> workflowAlreadyCompleted!! - ClientMessageType.WORKFLOW_IDS_PER_TAG -> workflowIdsPerTag!! + ClientMessageType.WORKFLOW_ALREADY_COMPLETED -> methodAlreadyCompleted!! + ClientMessageType.WORKFLOW_IDS_PER_TAG -> workflowIdsByTag!! } fun toByteArray() = AvroSerDe.writeBinary(this, serializer()) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientMessage.kt index 1e1aa5e42..f500c59cc 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/ClientMessage.kt @@ -25,17 +25,19 @@ package io.infinitic.common.clients.messages -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.clients.messages.interfaces.MethodMessage import io.infinitic.common.clients.messages.interfaces.TaskMessage -import io.infinitic.common.clients.messages.interfaces.WorkflowMessage +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId -import io.infinitic.common.data.methods.MethodReturnValue -import io.infinitic.common.errors.Error +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.errors.DeferredError +import io.infinitic.common.errors.WorkerError import io.infinitic.common.messages.Message import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.data.TaskTag +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.data.workflows.WorkflowTag @@ -44,83 +46,99 @@ import kotlinx.serialization.Serializable @Serializable sealed class ClientMessage : Message { val messageId: MessageId = MessageId() - abstract val clientName: ClientName + abstract val emitterName: ClientName + abstract val recipientName: ClientName override fun envelope() = ClientEnvelope.from(this) } @Serializable data class TaskCompleted( - override val clientName: ClientName, + override val emitterName: ClientName, + override val recipientName: ClientName, override val taskId: TaskId, - val taskReturnValue: MethodReturnValue, + val taskReturnValue: ReturnValue, val taskMeta: TaskMeta ) : ClientMessage(), TaskMessage @Serializable data class TaskFailed( - override val clientName: ClientName, + override val recipientName: ClientName, override val taskId: TaskId, - val error: Error, + val error: WorkerError, + override val emitterName: ClientName, ) : ClientMessage(), TaskMessage @Serializable data class TaskCanceled( - override val clientName: ClientName, + override val recipientName: ClientName, override val taskId: TaskId, - val taskMeta: TaskMeta + override val emitterName: ClientName ) : ClientMessage(), TaskMessage @Serializable -data class UnknownTask( - override val clientName: ClientName, - override val taskId: TaskId +data class TaskUnknown( + override val recipientName: ClientName, + override val taskId: TaskId, + override val emitterName: ClientName ) : ClientMessage(), TaskMessage @Serializable -data class TaskIdsPerTag( - override val clientName: ClientName, +data class TaskIdsByTag( + override val recipientName: ClientName, val taskName: TaskName, val taskTag: TaskTag, - val taskIds: Set + val taskIds: Set, + override val emitterName: ClientName ) : ClientMessage() @Serializable -data class WorkflowCompleted( - override val clientName: ClientName, +data class MethodCompleted( + override val recipientName: ClientName, override val workflowId: WorkflowId, - val workflowReturnValue: MethodReturnValue -) : ClientMessage(), WorkflowMessage + override val methodRunId: MethodRunId, + val methodReturnValue: ReturnValue, + override val emitterName: ClientName +) : ClientMessage(), MethodMessage @Serializable -data class WorkflowFailed( - override val clientName: ClientName, +data class MethodFailed( + override val recipientName: ClientName, override val workflowId: WorkflowId, - val error: Error -) : ClientMessage(), WorkflowMessage + override val methodRunId: MethodRunId, + val cause: DeferredError, + override val emitterName: ClientName +) : ClientMessage(), MethodMessage @Serializable -data class WorkflowCanceled( - override val clientName: ClientName, - override val workflowId: WorkflowId -) : ClientMessage(), WorkflowMessage +data class MethodCanceled( + override val recipientName: ClientName, + override val workflowId: WorkflowId, + override val methodRunId: MethodRunId, + override val emitterName: ClientName, +) : ClientMessage(), MethodMessage @Serializable -data class UnknownWorkflow( - override val clientName: ClientName, - override val workflowId: WorkflowId -) : ClientMessage(), WorkflowMessage +data class MethodRunUnknown( + override val recipientName: ClientName, + override val workflowId: WorkflowId, + override val methodRunId: MethodRunId, + override val emitterName: ClientName, +) : ClientMessage(), MethodMessage @Serializable -data class WorkflowAlreadyCompleted( - override val clientName: ClientName, - override val workflowId: WorkflowId -) : ClientMessage(), WorkflowMessage +data class MethodAlreadyCompleted( + override val recipientName: ClientName, + override val workflowId: WorkflowId, + override val methodRunId: MethodRunId, + override val emitterName: ClientName, +) : ClientMessage(), MethodMessage @Serializable -data class WorkflowIdsPerTag( - override val clientName: ClientName, +data class WorkflowIdsByTag( + override val recipientName: ClientName, val workflowName: WorkflowName, val workflowTag: WorkflowTag, - val workflowIds: Set + val workflowIds: Set, + override val emitterName: ClientName ) : ClientMessage() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/interfaces/MethodMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/interfaces/MethodMessage.kt new file mode 100644 index 000000000..1fab486b9 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/clients/messages/interfaces/MethodMessage.kt @@ -0,0 +1,32 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.clients.messages.interfaces + +import io.infinitic.common.workflows.data.methodRuns.MethodRunId + +interface MethodMessage : WorkflowMessage { + val methodRunId: MethodRunId +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/data/ClientName.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/ClientName.kt similarity index 58% rename from infinitic-common/src/main/kotlin/io/infinitic/common/clients/data/ClientName.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/data/ClientName.kt index e4c31bfd9..7e1d0b342 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/clients/data/ClientName.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/data/ClientName.kt @@ -23,27 +23,16 @@ * Licensor: infinitic.io */ -package io.infinitic.common.clients.data +package io.infinitic.common.data -import io.infinitic.common.data.Name -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.lang.reflect.Method -@Serializable(with = ClientNameSerializer::class) -data class ClientName(override val name: String) : Name(name) { +@JvmInline @Serializable +value class ClientName(private val name: String) { companion object { fun from(method: Method) = ClientName(method.declaringClass.name) } -} -object ClientNameSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ClientName", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: ClientName) { encoder.encodeString(value.name) } - override fun deserialize(decoder: Decoder) = ClientName(decoder.decodeString()) + override fun toString() = name } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/Data.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/JobOptions.kt similarity index 82% rename from infinitic-common/src/main/kotlin/io/infinitic/common/data/Data.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/data/JobOptions.kt index 24599b4c6..d8574a62f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/Data.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/data/JobOptions.kt @@ -25,10 +25,4 @@ package io.infinitic.common.data -import io.infinitic.common.serDe.SerializedData - -abstract class Data(open val serializedData: SerializedData) { - final override fun toString() = "$serializedData" - - fun get(): Any? = serializedData.deserialize() -} +interface JobOptions diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/MessageId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/MessageId.kt index a6b499dc9..4e8bfc3df 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/MessageId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/data/MessageId.kt @@ -25,28 +25,16 @@ package io.infinitic.common.data -import io.infinitic.common.data.UUIDConversion.toByteArray -import io.infinitic.common.data.UUIDConversion.toUUID -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = MessageIdSerializer::class) -data class MessageId(override val id: UUID = UUID.randomUUID()) : Id(id) { +@JvmInline @Serializable +value class MessageId(private val id: String = UUID.randomUUID().toString()) { companion object { - fun fromByteArray(bytes: ByteArray) = MessageId(bytes.toUUID()) + fun fromByteArray(bytes: ByteArray) = MessageId(bytes.decodeToString()) } - fun toByteArray() = id.toByteArray() -} + override fun toString() = id -object MessageIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MessageId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: MessageId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = MessageId(UUID.fromString(decoder.decodeString())) + fun toByteArray() = id.toByteArray() } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/Parameters.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/Parameters.kt deleted file mode 100644 index ee8982d29..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/Parameters.kt +++ /dev/null @@ -1,62 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.data - -import com.fasterxml.jackson.core.JsonProcessingException -import io.infinitic.common.serDe.SerializedData -import io.infinitic.exceptions.serialization.ParameterSerializationException -import java.lang.reflect.Method - -abstract class Parameters(open vararg val serializedDataArray: SerializedData) { - companion object { - fun getSerializedParameter(method: Method, index: Int, parameterValue: Any?) = try { - SerializedData.from(parameterValue) - } catch (e: JsonProcessingException) { - val parameterName = method.parameters[index].name - val parameterType = method.parameterTypes[index] - val methodName = method.name - val className = method.declaringClass.name - throw ParameterSerializationException(parameterName, parameterType.name, methodName, className) - } - } - - final override fun toString() = "${serializedDataArray.toList()}" - - final override fun hashCode() = serializedDataArray.contentHashCode() - - final override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Parameters - - if (!serializedDataArray.contentDeepEquals(other.serializedDataArray)) return false - - return true - } - - fun get() = serializedDataArray.map { it.deserialize() } -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepReturnValue.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/ReturnValue.kt similarity index 75% rename from infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepReturnValue.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/data/ReturnValue.kt index 93e33219b..189df1c4e 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepReturnValue.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/data/ReturnValue.kt @@ -23,9 +23,8 @@ * Licensor: infinitic.io */ -package io.infinitic.common.workflows.data.steps +package io.infinitic.common.data -import io.infinitic.common.data.Data import io.infinitic.common.serDe.SerializedData import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -33,18 +32,22 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -@Serializable(with = StepOutputSerializer::class) -data class StepReturnValue(override val serializedData: SerializedData) : Data(serializedData) { +@Serializable(with = ReturnValueSerializer::class) +data class ReturnValue(val serializedData: SerializedData) { companion object { - fun from(data: Any?) = StepReturnValue(SerializedData.from(data)) + fun from(data: Any?) = ReturnValue(SerializedData.from(data)) } + + override fun toString() = serializedData.toString() + + fun value(): Any? = serializedData.deserialize() } -object StepOutputSerializer : KSerializer { +object ReturnValueSerializer : KSerializer { override val descriptor: SerialDescriptor = SerializedData.serializer().descriptor - override fun serialize(encoder: Encoder, value: StepReturnValue) { + override fun serialize(encoder: Encoder, value: ReturnValue) { SerializedData.serializer().serialize(encoder, value.serializedData) } override fun deserialize(decoder: Decoder) = - StepReturnValue(SerializedData.serializer().deserialize(decoder)) + ReturnValue(SerializedData.serializer().deserialize(decoder)) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodName.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodName.kt index ce87f79a6..985fbf0ec 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodName.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodName.kt @@ -25,20 +25,9 @@ package io.infinitic.common.data.methods -import io.infinitic.common.data.Name -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -@Serializable(with = MethodNameSerializer::class) -data class MethodName(override val name: String) : Name(name) - -object MethodNameSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MethodName", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: MethodName) { encoder.encodeString(value.name) } - override fun deserialize(decoder: Decoder) = MethodName(decoder.decodeString()) +@JvmInline @Serializable +value class MethodName(private val name: String) { + override fun toString() = name } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodParameters.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodParameters.kt index 4db66eaf2..2973d913d 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodParameters.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodParameters.kt @@ -25,34 +25,48 @@ package io.infinitic.common.data.methods -import io.infinitic.common.data.Parameters +import com.fasterxml.jackson.core.JsonProcessingException import io.infinitic.common.serDe.SerializedData +import io.infinitic.exceptions.serialization.ParameterSerializationException import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import org.jetbrains.annotations.TestOnly import java.lang.reflect.Method -@Serializable(with = MethodInputSerializer::class) -class MethodParameters(override vararg val serializedDataArray: SerializedData) : Parameters(*serializedDataArray), Collection by serializedDataArray.toList() { +@Serializable(with = MethodParametersSerializer::class) +data class MethodParameters(val parameters: List = listOf()) : Collection by parameters { companion object { - fun from(method: Method, data: Array) = MethodParameters( - *data.mapIndexed { index, value -> getSerializedParameter(method, index, value) }.toTypedArray() + fun from(method: Method, data: Array<*>) = MethodParameters( + data.mapIndexed { index, value -> + try { + SerializedData.from(value) + } catch (e: JsonProcessingException) { + throw ParameterSerializationException( + method.parameters[index].name, + method.parameterTypes[index].kotlin.javaObjectType.name, + method.name, + method.declaringClass.name + ) + } + }.toList() ) + @TestOnly fun from(vararg data: Any?) = MethodParameters( - *data.map { SerializedData.from(it) }.toTypedArray() + data.map { SerializedData.from(it) }.toList() ) } } -object MethodInputSerializer : KSerializer { +object MethodParametersSerializer : KSerializer { override val descriptor: SerialDescriptor = ListSerializer(SerializedData.serializer()).descriptor override fun serialize(encoder: Encoder, value: MethodParameters) { - ListSerializer(SerializedData.serializer()).serialize(encoder, value.serializedDataArray.toList()) + ListSerializer(SerializedData.serializer()).serialize(encoder, value.parameters.toList()) } override fun deserialize(decoder: Decoder) = - MethodParameters(*(ListSerializer(SerializedData.serializer()).deserialize(decoder).toTypedArray())) + MethodParameters(ListSerializer(SerializedData.serializer()).deserialize(decoder).toList()) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/errors/DeferredError.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/errors/DeferredError.kt new file mode 100644 index 000000000..28e0648cb --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/errors/DeferredError.kt @@ -0,0 +1,389 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.errors + +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.tasks.data.TaskId +import io.infinitic.common.tasks.data.TaskName +import io.infinitic.common.workflows.data.methodRuns.MethodRunId +import io.infinitic.common.workflows.data.workflows.WorkflowId +import io.infinitic.common.workflows.data.workflows.WorkflowName +import io.infinitic.exceptions.CanceledDeferredException +import io.infinitic.exceptions.CanceledTaskException +import io.infinitic.exceptions.CanceledWorkflowException +import io.infinitic.exceptions.DeferredException +import io.infinitic.exceptions.FailedDeferredException +import io.infinitic.exceptions.FailedTaskException +import io.infinitic.exceptions.FailedWorkflowException +import io.infinitic.exceptions.FailedWorkflowTaskException +import io.infinitic.exceptions.TimedOutDeferredException +import io.infinitic.exceptions.TimedOutTaskException +import io.infinitic.exceptions.TimedOutWorkflowException +import io.infinitic.exceptions.UnknownDeferredException +import io.infinitic.exceptions.UnknownTaskException +import io.infinitic.exceptions.UnknownWorkflowException +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +sealed class DeferredError { + companion object { + fun from(exception: DeferredException) = when (exception) { + is FailedDeferredException -> FailedDeferredError.from(exception) + is CanceledDeferredException -> CanceledDeferredError.from(exception) + is TimedOutDeferredException -> TimedOutDeferredError.from(exception) + is UnknownDeferredException -> UnknownDeferredError.from(exception) + } + } +} + +@Serializable +sealed class FailedDeferredError : DeferredError() { + companion object { + fun from(exception: FailedDeferredException) = when (exception) { + is FailedTaskException -> FailedTaskError.from(exception) + is FailedWorkflowException -> FailedWorkflowError.from(exception) + is FailedWorkflowTaskException -> FailedWorkflowTaskError.from(exception) + } + } +} + +@Serializable +sealed class CanceledDeferredError : DeferredError() { + companion object { + fun from(exception: CanceledDeferredException) = when (exception) { + is CanceledTaskException -> CanceledTaskError.from(exception) + is CanceledWorkflowException -> CanceledWorkflowError.from(exception) + } + } +} + +@Serializable +sealed class TimedOutDeferredError : DeferredError() { + companion object { + fun from(exception: TimedOutDeferredException) = when (exception) { + is TimedOutTaskException -> TimedOutTaskError.from(exception) + is TimedOutWorkflowException -> TimedOutWorkflowError.from(exception) + } + } +} + +@Serializable +sealed class UnknownDeferredError : DeferredError() { + companion object { + fun from(exception: UnknownDeferredException) = when (exception) { + is UnknownTaskException -> UnknownTaskError.from(exception) + is UnknownWorkflowException -> UnknownWorkflowError.from(exception) + } + } +} + +/** + * Error occurring when waiting for an unknown task + */ +@Serializable @SerialName("UnknownTaskError") +data class UnknownTaskError( + /** + * Name of the unknown task + */ + val taskName: TaskName, + + /** + * Id of the unknown task + */ + val taskId: TaskId +) : UnknownDeferredError() { + companion object { + fun from(exception: UnknownTaskException) = UnknownTaskError( + taskName = TaskName(exception.taskName), + taskId = TaskId(exception.taskId) + ) + } +} + +/** + * Error occurring when waiting for an unknown workflow + */ +@Serializable @SerialName("UnknownWorkflowError") +data class UnknownWorkflowError( + /** + * Name of the unknown workflow + */ + val workflowName: WorkflowName, + + /** + * Id of the unknown workflow + */ + val workflowId: WorkflowId, + + /** + * Id of the methodRun + */ + val methodRunId: MethodRunId? +) : UnknownDeferredError() { + companion object { + fun from(exception: UnknownWorkflowException) = UnknownWorkflowError( + workflowName = WorkflowName(exception.workflowName), + workflowId = WorkflowId(exception.workflowId), + methodRunId = exception.methodRunId?.let { MethodRunId(it) } + ) + } +} + +/** + * Error occurring when waiting a timed-out task + */ +@Serializable @SerialName("TimedOutTaskError") +data class TimedOutTaskError( + /** + * Name of the canceled task + */ + val taskName: TaskName, + + /** + * Id of the canceled task + */ + val taskId: TaskId, + + /** + * Method called + */ + val methodName: MethodName + +) : TimedOutDeferredError() { + companion object { + fun from(exception: TimedOutTaskException) = TimedOutTaskError( + taskName = TaskName(exception.taskName), + taskId = TaskId(exception.taskId), + methodName = MethodName(exception.methodName) + ) + } +} + +/** + * Error occurring when waiting a timed-out child workflow + */ +@Serializable @SerialName("TimedOutWorkflowError") +data class TimedOutWorkflowError( + /** + * Name of the canceled child workflow + */ + val workflowName: WorkflowName, + + /** + * Id of the canceled child workflow + */ + val workflowId: WorkflowId, + + /** + * Method called + */ + val methodName: MethodName, + + /** + * Id of the methodRun + */ + val methodRunId: MethodRunId? +) : TimedOutDeferredError() { + companion object { + fun from(exception: TimedOutWorkflowException) = TimedOutWorkflowError( + workflowName = WorkflowName(exception.workflowName), + workflowId = WorkflowId(exception.workflowId), + methodName = MethodName(exception.methodName), + methodRunId = exception.methodRunId?.let { MethodRunId(it) } + ) + } +} + +/** + * Error occurring when waiting a canceled task + */ +@Serializable @SerialName("CanceledTaskError") +data class CanceledTaskError( + /** + * Name of the canceled task + */ + val taskName: TaskName, + + /** + * Id of the canceled task + */ + val taskId: TaskId, + + /** + * Method called + */ + val methodName: MethodName, + +) : CanceledDeferredError() { + companion object { + fun from(exception: CanceledTaskException) = CanceledTaskError( + taskName = TaskName(exception.taskName), + taskId = TaskId(exception.taskId), + methodName = MethodName(exception.methodName) + ) + } +} + +/** + * Error occurring when waiting a failed child workflow + */ +@Serializable @SerialName("CanceledWorkflowError") +data class CanceledWorkflowError( + /** + * Name of the canceled child workflow + */ + val workflowName: WorkflowName, + + /** + * Id of the canceled child workflow + */ + val workflowId: WorkflowId, + + /** + * Id of the methodRun + */ + val methodRunId: MethodRunId? +) : CanceledDeferredError() { + companion object { + fun from(exception: CanceledWorkflowException) = CanceledWorkflowError( + workflowName = WorkflowName(exception.workflowName), + workflowId = WorkflowId(exception.workflowId), + methodRunId = exception.methodRunId?.let { MethodRunId(it) } + ) + } +} + +/** + * Error occurring when waiting a failed task + */ +@Serializable @SerialName("FailedTaskError") +data class FailedTaskError( + /** + * Name of the task where the error occurred + */ + val taskName: TaskName, + + /** + * Id of the task where the error occurred + */ + val taskId: TaskId, + + /** + * Method called where the error occurred + */ + val methodName: MethodName, + + /** + * cause of the error + */ + val cause: WorkerError +) : FailedDeferredError() { + companion object { + fun from(exception: FailedTaskException) = FailedTaskError( + taskName = TaskName(exception.taskName), + taskId = TaskId(exception.taskId), + methodName = MethodName(exception.methodName), + cause = WorkerError.from(exception.workerException) + ) + } +} + +/** + * Error occurring when waiting a failed workflow + */ +@Serializable @SerialName("FailedWorkflowError") +data class FailedWorkflowError( + /** + * Name of the child workflow where the error occurred + */ + val workflowName: WorkflowName, + + /** + * Method called where the error occurred + */ + val methodName: MethodName, + + /** + * Id of the child workflow where the error occurred + */ + val workflowId: WorkflowId, + + /** + * Id of the methodRun where the error occurred + */ + val methodRunId: MethodRunId?, + + /** + * error + */ + val deferredError: DeferredError +) : FailedDeferredError() { + companion object { + fun from(exception: FailedWorkflowException): FailedWorkflowError = FailedWorkflowError( + workflowName = WorkflowName(exception.workflowName), + workflowId = WorkflowId(exception.workflowId), + methodName = MethodName(exception.methodName), + methodRunId = exception.methodRunId?.let { MethodRunId(it) }, + deferredError = from(exception.deferredException) + ) + } +} + +/** + * Error occurring when waiting a failed workflow + */ +@Serializable @SerialName("FailedWorkflowTaskError") +data class FailedWorkflowTaskError( + /** + * Name of the child workflow where the error occurred + */ + val workflowName: WorkflowName, + + /** + * Id of the child workflow where the error occurred + */ + val workflowId: WorkflowId, + + /** + * Id of the workflow task where the error occurred + */ + val workflowTaskId: TaskId, + + /** + * cause of the error + */ + val cause: WorkerError +) : FailedDeferredError() { + companion object { + fun from(exception: FailedWorkflowTaskException): FailedWorkflowTaskError = FailedWorkflowTaskError( + workflowName = WorkflowName(exception.workflowName), + workflowId = WorkflowId(exception.workflowId), + workflowTaskId = TaskId(exception.workflowTaskId), + cause = WorkerError.from(exception.workerException) + ) + } +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/errors/Error.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/errors/Error.kt deleted file mode 100644 index 51cc04593..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/errors/Error.kt +++ /dev/null @@ -1,104 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.errors - -import io.infinitic.common.serDe.kserializer.UUIDSerializer -import io.infinitic.exceptions.workflows.CanceledDeferredException -import io.infinitic.exceptions.workflows.FailedDeferredException -import io.infinitic.exceptions.workflows.TimedOutDeferredException -import kotlinx.serialization.Serializable -import java.util.UUID - -/** - * Data class representing an error - */ -@Serializable -data class Error( - /** - * Name of the error - */ - val errorName: String, - - /** - * Message of the error - */ - val errorMessage: String?, - - /** - * Except for CanceledDeferredException, FailedDeferredException, TimedOutDeferredException - * String version of the stack trace - */ - val errorStackTraceToString: String?, - - /** - * for CanceledDeferredException, FailedDeferredException, TimedOutDeferredException - * id of the failing task or child workflow where the error occurred - */ - @Serializable(with = UUIDSerializer::class) val whereId: UUID? = null, - - /** - * for CanceledDeferredException, FailedDeferredException, TimedOutDeferredException - * name of the failing task or child workflow where the error occurred - */ - val whereName: String? = null, - - /** - * cause of the error - */ - val errorCause: Error? -) { - companion object { - fun from(throwable: Throwable): Error { - val whereId = when (throwable) { - is CanceledDeferredException -> throwable.id - is FailedDeferredException -> throwable.id - is TimedOutDeferredException -> throwable.id - else -> null - } - - val whereName = when (throwable) { - is CanceledDeferredException -> throwable.name - is FailedDeferredException -> throwable.name - is TimedOutDeferredException -> throwable.name - else -> null - } - - return Error( - errorName = throwable::class.java.name, - errorMessage = throwable.message, - errorStackTraceToString = if (whereId != null) throwable.stackTraceToString() else null, - errorCause = run { - val cause = throwable.cause - if (cause == throwable || cause == null) null else from(cause) - }, - whereId = whereId, - whereName = whereName - ) - } - } - - override fun toString(): String = errorStackTraceToString ?: "$errorName: $errorMessage" -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/errors/WorkerError.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/errors/WorkerError.kt new file mode 100644 index 000000000..708e2ce0e --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/errors/WorkerError.kt @@ -0,0 +1,89 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.errors + +import io.infinitic.common.data.ClientName +import io.infinitic.exceptions.WorkerException +import kotlinx.serialization.Serializable + +/** + * Data class representing an error + */ +@Serializable +data class WorkerError( + /** + * Name of the worker + */ + val workerName: ClientName, + + /** + * Name of the error + */ + val name: String, + + /** + * Message of the error + */ + val message: String?, + + /** + * String version of the stack trace + */ + val stackTraceToString: String, + + /** + * cause of the error + */ + val cause: WorkerError? +) { + companion object { + fun from(exception: WorkerException): WorkerError = WorkerError( + workerName = ClientName(exception.workerName), + name = exception.name, + message = exception.message, + stackTraceToString = exception.stackTraceToString, + cause = exception.cause?.let { from(it) } + ) + + fun from(workerName: ClientName, throwable: Throwable): WorkerError = WorkerError( + workerName = workerName, + name = throwable::class.java.name, + message = throwable.message, + stackTraceToString = throwable.stackTraceToString(), + cause = when (val cause = throwable.cause) { + null, throwable -> null + else -> from(workerName, cause) + } + ) + } + + // removing stackTraceToString of the output to preserve logs + override fun toString(): String = this::class.java.simpleName + "(" + listOf( + "name" to name, + "message" to message, + "cause" to cause + ).joinToString() { "${it.first}=${it.second}" } + ")" +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/thisShouldNotHappen.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/exceptions/thisShouldNotHappen.kt similarity index 96% rename from infinitic-common/src/main/kotlin/io/infinitic/exceptions/thisShouldNotHappen.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/exceptions/thisShouldNotHappen.kt index 378d70042..c4c9fe528 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/thisShouldNotHappen.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/exceptions/thisShouldNotHappen.kt @@ -23,7 +23,7 @@ * Licensor: infinitic.io */ -package io.infinitic.exceptions +package io.infinitic.common.exceptions fun thisShouldNotHappen(reason: String? = null): Nothing = throw RuntimeException( "this should not happen${ diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/global/messages/MetricsGlobalMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/global/messages/MetricsGlobalMessage.kt index 804f7b55f..b9b8bb5bd 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/global/messages/MetricsGlobalMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/global/messages/MetricsGlobalMessage.kt @@ -25,6 +25,7 @@ package io.infinitic.common.metrics.global.messages +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.messages.Message import io.infinitic.common.tasks.data.TaskName @@ -33,11 +34,12 @@ import kotlinx.serialization.Serializable @Serializable sealed class MetricsGlobalMessage : Message { val messageId = MessageId() - + abstract val emitterName: ClientName override fun envelope() = MetricsGlobalEnvelope.from(this) } @Serializable data class TaskCreated( - val taskName: TaskName + val taskName: TaskName, + override val emitterName: ClientName ) : MetricsGlobalMessage() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/perName/messages/MetricsPerNameMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/perName/messages/MetricsPerNameMessage.kt index 0982da811..64d2ed12f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/perName/messages/MetricsPerNameMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/metrics/perName/messages/MetricsPerNameMessage.kt @@ -25,6 +25,7 @@ package io.infinitic.common.metrics.perName.messages +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.messages.Message import io.infinitic.common.tasks.data.TaskId @@ -35,15 +36,17 @@ import kotlinx.serialization.Serializable @Serializable sealed class MetricsPerNameMessage : Message { val messageId = MessageId() + abstract val emitterName: ClientName abstract val taskName: TaskName override fun envelope() = MetricsPerNameEnvelope.from(this) } @Serializable -data class TaskStatusUpdated constructor( +data class TaskStatusUpdated( override val taskName: TaskName, val taskId: TaskId, val oldStatus: TaskStatus?, - val newStatus: TaskStatus + val newStatus: TaskStatus, + override val emitterName: ClientName ) : MetricsPerNameMessage() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/parser/Parser.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/parser/Parser.kt index 227aebe32..e58a7ce33 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/parser/Parser.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/parser/Parser.kt @@ -47,6 +47,18 @@ fun getMethodPerNameAndParameters( ?: throw NoMethodFoundWithParameterTypesException(klass.name, name, parameterTypes) } +fun classForName(name: String): Class = when (name) { + "long" -> Long::class.java + "int" -> Int::class.java + "short" -> Short::class.java + "byte" -> Byte::class.java + "double" -> Double::class.java + "float" -> Float::class.java + "char" -> Char::class.java + "boolean" -> Boolean::class.java + else -> Class.forName(name) +} + private fun getMethodPerAnnotationAndParameterTypes(klass: Class<*>, name: String, parameterTypes: List): Method? { var clazz = klass @@ -54,8 +66,7 @@ private fun getMethodPerAnnotationAndParameterTypes(klass: Class<*>, name: Strin // has current class a method with @Name annotation and right parameters? val methods = clazz.methods.filter { method -> method.isAccessible = true - method.isAnnotationPresent(Name::class.java) && - method.parameterTypes.map { it.name } == parameterTypes + method.isAnnotationPresent(Name::class.java) && method.parameterTypes.map { it.name } == parameterTypes } when (methods.size) { 0 -> Unit @@ -75,17 +86,10 @@ private fun getMethodPerAnnotationAndParameterTypes(klass: Class<*>, name: Strin return null } -private fun getMethodPerNameAndParameterTypes(klass: Class<*>, name: String, parameterTypes: List): Method? { - val methods = klass.methods.filter { method -> - method.isAccessible = true - method.name == name && method.parameterTypes.map { it.name } == parameterTypes - } - - return when (methods.size) { - 0 -> null - 1 -> methods[0] - else -> throw TooManyMethodsFoundWithParameterTypesException(klass.name, name, parameterTypes) - } +private fun getMethodPerNameAndParameterTypes(klass: Class<*>, name: String, parameterTypes: List): Method? = try { + klass.getMethod(name, *(parameterTypes.map { classForName(it) }.toTypedArray())) +} catch (e: NoSuchMethodException) { + null } private fun getMethodPerAnnotationAndParametersCount(klass: Class<*>, name: String, parameterCount: Int): Method? { @@ -95,9 +99,9 @@ private fun getMethodPerAnnotationAndParametersCount(klass: Class<*>, name: Stri // has current class a method with @Name annotation and right count of parameters? val methods = clazz.methods.filter { method -> method.isAccessible = true - method.isAnnotationPresent(Name::class.java) && - method.parameterTypes.size == parameterCount + method.isAnnotationPresent(Name::class.java) && method.parameterTypes.size == parameterCount } + when (methods.size) { 0 -> Unit 1 -> return methods[0] diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ChannelProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ChannelProxyHandler.kt new file mode 100644 index 000000000..bd575cb30 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ChannelProxyHandler.kt @@ -0,0 +1,53 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.proxies + +import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.workflows.data.channels.ChannelSignal +import io.infinitic.common.workflows.data.channels.ChannelSignalType +import io.infinitic.exceptions.clients.InvalidChannelGetterException +import io.infinitic.workflows.SendChannel + +@Suppress("UNCHECKED_CAST") +class ChannelProxyHandler>( + handler: GetWorkflowProxyHandler<*>, +) : ProxyHandler( + handler.method.returnType as Class, + handler.dispatcherFn +) { + init { + if (handler.method.returnType != SendChannel::class.java) throw InvalidChannelGetterException(handler.method.returnType.name) + } + + val workflowName = handler.workflowName + val channelName = ChannelName.from(handler.methodName) + val workflowId = handler.workflowId + val workflowTag = handler.workflowTag + + val channelSignalTypes by lazy { ChannelSignalType.allFrom(methodArgs.first()::class.java) } + val channelSignal by lazy { ChannelSignal.from(methodArgs.first()) } + val channelType by lazy { handler.method.returnType } +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/GetTaskProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/GetTaskProxyHandler.kt new file mode 100644 index 000000000..28326d948 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/GetTaskProxyHandler.kt @@ -0,0 +1,47 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.proxies + +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.tasks.data.TaskId +import io.infinitic.common.tasks.data.TaskName +import io.infinitic.common.tasks.data.TaskTag + +class GetTaskProxyHandler( + override val klass: Class, + val taskId: TaskId?, + val taskTag: TaskTag?, + override val dispatcherFn: () -> ProxyDispatcher +) : ProxyHandler(klass, dispatcherFn) { + + init { + require((taskId == null && taskTag != null) || (taskId != null && taskTag == null)) { + thisShouldNotHappen() + } + } + + val taskName = TaskName(name) +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/SendChannelProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/GetWorkflowProxyHandler.kt similarity index 60% rename from infinitic-common/src/main/kotlin/io/infinitic/common/proxies/SendChannelProxyHandler.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/proxies/GetWorkflowProxyHandler.kt index 382ad6c3a..5b1ce0d0f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/SendChannelProxyHandler.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/GetWorkflowProxyHandler.kt @@ -25,36 +25,23 @@ package io.infinitic.common.proxies -import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.data.workflows.WorkflowTag -import java.lang.reflect.Method -class SendChannelProxyHandler( - override val klass: Class, - val workflowName: WorkflowName, - val channelName: ChannelName, - val perWorkflowId: WorkflowId? = null, - val perTag: WorkflowTag? = null, - private val dispatcherFn: () -> Dispatcher -) : MethodProxyHandler(klass) { +class GetWorkflowProxyHandler( + override val klass: Class, + val workflowId: WorkflowId?, + val workflowTag: WorkflowTag?, + override val dispatcherFn: () -> ProxyDispatcher +) : ProxyHandler(klass, dispatcherFn) { init { - require(perWorkflowId == null || perTag == null) - } - - override val method: Method - get() = methods.last() - - override fun invoke(proxy: Any, method: Method, args: Array?): Any? { - val any = super.invoke(proxy, method, args) - - if (method.declaringClass == Object::class.java) return any - - return when (isSync) { - true -> dispatcherFn().dispatchAndWait(this) - false -> any + require((workflowId == null && workflowTag != null) || (workflowId != null && workflowTag == null)) { + thisShouldNotHappen() } } + + val workflowName = WorkflowName(name) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/Method.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/Method.kt new file mode 100644 index 000000000..457b5b3ca --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/Method.kt @@ -0,0 +1,36 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.proxies + +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.data.methods.MethodParameterTypes +import io.infinitic.common.data.methods.MethodParameters + +data class Method( + var methodName: MethodName, + val methodParameterTypes: MethodParameterTypes, + val methodParameters: MethodParameters, +) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/NewTaskProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/NewTaskProxyHandler.kt new file mode 100644 index 000000000..f5369b788 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/NewTaskProxyHandler.kt @@ -0,0 +1,42 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.proxies + +import io.infinitic.common.tasks.data.TaskMeta +import io.infinitic.common.tasks.data.TaskName +import io.infinitic.common.tasks.data.TaskOptions +import io.infinitic.common.tasks.data.TaskTag + +class NewTaskProxyHandler( + override val klass: Class, + val taskTags: Set, + val taskOptions: TaskOptions, + val taskMeta: TaskMeta, + override val dispatcherFn: () -> ProxyDispatcher +) : ProxyHandler(klass, dispatcherFn) { + + val taskName = TaskName(name) +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/NewWorkflowProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/NewWorkflowProxyHandler.kt new file mode 100644 index 000000000..8a3c97e73 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/NewWorkflowProxyHandler.kt @@ -0,0 +1,42 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.proxies + +import io.infinitic.common.workflows.data.workflows.WorkflowMeta +import io.infinitic.common.workflows.data.workflows.WorkflowName +import io.infinitic.common.workflows.data.workflows.WorkflowOptions +import io.infinitic.common.workflows.data.workflows.WorkflowTag + +class NewWorkflowProxyHandler( + override val klass: Class, + val workflowTags: Set, + val workflowOptions: WorkflowOptions, + val workflowMeta: WorkflowMeta, + override val dispatcherFn: () -> ProxyDispatcher +) : ProxyHandler(klass, dispatcherFn) { + + val workflowName = WorkflowName(name) +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ProxyDispatcher.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ProxyDispatcher.kt new file mode 100644 index 000000000..9291cee8d --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ProxyDispatcher.kt @@ -0,0 +1,30 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.proxies + +interface ProxyDispatcher { + fun dispatchAndWait(handler: ProxyHandler<*>): R +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/MethodProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ProxyHandler.kt similarity index 50% rename from infinitic-common/src/main/kotlin/io/infinitic/common/proxies/MethodProxyHandler.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ProxyHandler.kt index 9006c8c8d..989fe2210 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/MethodProxyHandler.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/ProxyHandler.kt @@ -26,30 +26,64 @@ package io.infinitic.common.proxies import io.infinitic.annotations.Name +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.data.methods.MethodParameterTypes +import io.infinitic.common.data.methods.MethodParameters +import io.infinitic.exceptions.workflows.InvalidInlineException +import io.infinitic.workflows.SendChannel import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy +import kotlin.reflect.full.isSubclassOf + +sealed class ProxyHandler( + open val klass: Class, + open val dispatcherFn: () -> ProxyDispatcher +) : InvocationHandler { + + companion object { + @JvmStatic private val isInlined: ThreadLocal = ThreadLocal.withInitial { false } + @JvmStatic private val isInvocationAsync: ThreadLocal = ThreadLocal.withInitial { false } + @JvmStatic private val invocationHandler: ThreadLocal?> = ThreadLocal.withInitial { null } + + fun async(fct: () -> R): ProxyHandler<*>? { + if (isInlined.get()) throw InvalidInlineException + + // set invocation flag to Async + isInvocationAsync.set(true) + // call the method reference + fct() + val handler = invocationHandler.get() + // reset default value + isInvocationAsync.set(false) + invocationHandler.set(null) + + return handler + } -abstract class MethodProxyHandler(protected open val klass: Class) : InvocationHandler { - /** - * isSync is true, if the last method was called with a synchronous syntax - */ - var isSync: Boolean = true - - /** - * history of methods called on this stub - */ - protected var methods: MutableList = mutableListOf() + /** + * This method can be used to get the result of fct() + * an InvalidInlineException is thrown if `fct` uses a proxy + */ + fun inline(fct: () -> R): R { + isInlined.set(true) + return try { + fct() + } finally { + isInlined.set(false) + } + } + } /** - * history of arguments of methods called on this stub + * Method called */ - private var args: MutableList> = mutableListOf() + lateinit var method: Method /** - * Implemented by task, workflow or channel stubs + * Args of method called */ - abstract val method: Method + lateinit var methodArgs: Array /** * Name provided by @Name annotation, if any @@ -61,7 +95,7 @@ abstract class MethodProxyHandler(protected open val klass: Class) : Invoc /** * Class name provided by @Name annotation, or java class name by default */ - protected val className: String by lazy { + protected val name: String by lazy { classAnnotatedName ?: klass.name } @@ -69,41 +103,38 @@ abstract class MethodProxyHandler(protected open val klass: Class) : Invoc * SimpleName provided by @Name annotation, or class name by default */ val simpleName: String + // MUST be a get() as this.methodName can change when reusing instance get() = "${classAnnotatedName ?: klass.simpleName}::$methodName" /** - * Method name provided by @Name annotation, or java method name by default + * MethodName provided by @Name annotation, or java method name by default */ - val methodName: String - get() = findMethodNamePerAnnotation(klass, method) ?: method.name + val methodName: MethodName + // MUST be a get() as this.method changes + get() = MethodName(findMethodNamePerAnnotation(klass, method) ?: method.name) - val methodArgs: Array - get() { - // ensure checks are performed - method - - return args.last() - } - - /* - * invoke method is called when a method is applied to the proxy instance + /** + * MethodParameterTypes from method */ - override fun invoke(proxy: Any, method: Method, args: Array?): Any? { - val default = getAsyncReturnValue(method) - - if (method.declaringClass == Object::class.java) return when (method.name) { - "toString" -> klass.name - else -> default - } + val methodParameterTypes: MethodParameterTypes + // MUST be a get() as this.method changes + get() = MethodParameterTypes.from(method) - // store method and args - this.methods.add(method) - this.args.add(args ?: arrayOf()) + /** + * ReturnType from method + */ + val returnType: Class<*> + // MUST be a get() as this.method changes + get() = method.returnType - return default - } + /** + * MethodParameters from method + */ + val methodParameters: MethodParameters + // MUST be a get() as this.method changes + get() = MethodParameters.from(method, methodArgs) - /* + /** * provides a stub of type T */ @Suppress("UNCHECKED_CAST") @@ -113,19 +144,39 @@ abstract class MethodProxyHandler(protected open val klass: Class) : Invoc this ) as T - /* - * Prepare for reuse - */ - fun reset() { - isSync = true - methods = mutableListOf() - args = mutableListOf() + override fun invoke(proxy: Any, method: Method, args: Array?): Any? { + if (isInlined.get()) throw InvalidInlineException + + val any = getAsyncReturnValue(method) + + if (method.declaringClass == Object::class.java) return when (method.name) { + "toString" -> klass.name + else -> any + } + + this.method = method + this.methodArgs = args ?: arrayOf() + + return when (isInvocationAsync.get()) { + // sync => run directly from dispatcher + false -> dispatcherFn().dispatchAndWait(this) + // store current instance to get retrieved from ProxyHandler.async + true -> { + // set current handler + invocationHandler.set(this) + // return fake value + any + } + } } /** - * Returns a type-compatible value to avoid a java runtime exception + * Check if method is a getter on a SendChannel */ - protected fun getAsyncReturnValue(method: Method) = when (method.returnType.name) { + fun isChannelGetter(): Boolean = method.returnType.kotlin.isSubclassOf(SendChannel::class) + + // Returns a type-compatible value to avoid an exception at runtime + private fun getAsyncReturnValue(method: Method) = when (method.returnType.name) { "long" -> 0L "int" -> 0 "short" -> 0.toShort() @@ -137,6 +188,7 @@ abstract class MethodProxyHandler(protected open val klass: Class) : Invoc else -> null } + // search for a @Name annotation given by user to this method, in the class, its interfaces, or its parent private fun findMethodNamePerAnnotation(klass: Class<*>, method: Method): String? { var clazz = klass @@ -163,6 +215,7 @@ abstract class MethodProxyHandler(protected open val klass: Class) : Invoc return null } + // search for a @Name annotation given by user to this class, its interfaces, or its parent private fun findClassNamePerAnnotation(klass: Class<*>): String? { var clazz = klass @@ -177,7 +230,7 @@ abstract class MethodProxyHandler(protected open val klass: Class) : Invoc // if not, inspect the superclass clazz = clazz.superclass ?: break - } while ("java.lang.Object" != clazz.canonicalName) + } while (Object::class.java.name != clazz.canonicalName) return null } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/TaskProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/TaskProxyHandler.kt deleted file mode 100644 index 208199184..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/TaskProxyHandler.kt +++ /dev/null @@ -1,78 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.proxies - -import io.infinitic.common.tasks.data.TaskId -import io.infinitic.common.tasks.data.TaskMeta -import io.infinitic.common.tasks.data.TaskName -import io.infinitic.common.tasks.data.TaskOptions -import io.infinitic.common.tasks.data.TaskTag -import io.infinitic.exceptions.clients.MultipleMethodCallsException -import io.infinitic.exceptions.clients.NoMethodCallException -import java.lang.reflect.Method - -class TaskProxyHandler( - override val klass: Class, - val taskTags: Set? = null, - val taskOptions: TaskOptions? = null, - val taskMeta: TaskMeta? = null, - var perTaskId: TaskId? = null, - var perTag: TaskTag? = null, - private val dispatcherFn: () -> Dispatcher -) : MethodProxyHandler(klass) { - - val taskName = TaskName(className) - - override val method: Method - get() { - if (methods.isEmpty()) { - throw NoMethodCallException(klass.name) - } - - if (methods.size > 1) { - throw MultipleMethodCallsException(klass.name, methods.first().name, methods.last().name) - } - - return methods.last() - } - - init { - require(perTaskId == null || perTag == null) - } - - override fun invoke(proxy: Any, method: Method, args: Array?): Any? { - val any = super.invoke(proxy, method, args) - - if (method.declaringClass == Object::class.java) return any - - return when (isSync) { - true -> dispatcherFn().dispatchAndWait(this) - false -> any - } - } - - fun isNew() = perTaskId == null && perTag == null -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/WorkflowProxyHandler.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/WorkflowProxyHandler.kt deleted file mode 100644 index 8d1abe440..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/proxies/WorkflowProxyHandler.kt +++ /dev/null @@ -1,89 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.proxies - -import io.infinitic.common.workflows.data.workflows.WorkflowId -import io.infinitic.common.workflows.data.workflows.WorkflowMeta -import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.common.workflows.data.workflows.WorkflowOptions -import io.infinitic.common.workflows.data.workflows.WorkflowTag -import io.infinitic.exceptions.clients.ChannelUsedOnNewWorkflowException -import io.infinitic.exceptions.clients.MultipleMethodCallsException -import io.infinitic.exceptions.clients.NoMethodCallException -import io.infinitic.workflows.SendChannel -import java.lang.reflect.Method -import kotlin.reflect.full.isSubclassOf - -class WorkflowProxyHandler( - override val klass: Class, - val workflowTags: Set? = null, - val workflowOptions: WorkflowOptions? = null, - val workflowMeta: WorkflowMeta? = null, - var perWorkflowId: WorkflowId? = null, - var perTag: WorkflowTag? = null, - private val dispatcherFn: () -> Dispatcher -) : MethodProxyHandler(klass) { - - val workflowName = WorkflowName(className) - - override val method: Method - get() { - if (methods.isEmpty()) { - throw NoMethodCallException(klass.name) - } - - val method = methods.last() - - val isChannel = method.returnType.kotlin.isSubclassOf(SendChannel::class) - - if (isChannel && isNew()) { - throw ChannelUsedOnNewWorkflowException("$workflowName") - } - - if (!isChannel && methods.size > 1) { - throw MultipleMethodCallsException(klass.name, methods.first().name, methods.last().name) - } - - return method - } - - init { - require(perWorkflowId == null || perTag == null) - } - - override fun invoke(proxy: Any, method: Method, args: Array?): Any? { - val any = super.invoke(proxy, method, args) - - if (method.declaringClass == Object::class.java) return any - - return when (isSync) { - true -> dispatcherFn().dispatchAndWait(this) - false -> any - } - } - - fun isNew() = perWorkflowId == null && perTag == null -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedData.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedData.kt index fafe1cbfe..8944b3134 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedData.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedData.kt @@ -26,16 +26,16 @@ package io.infinitic.common.serDe import com.fasterxml.jackson.core.JsonProcessingException -import io.infinitic.common.serDe.kserializer.getKSerializerOrNull import io.infinitic.exceptions.serialization.ClassNotFoundException import io.infinitic.exceptions.serialization.JsonDeserializationException import io.infinitic.exceptions.serialization.KotlinDeserializationException import io.infinitic.exceptions.serialization.MissingMetaJavaClassException import io.infinitic.exceptions.serialization.SerializerNotFoundException -import io.infinitic.exceptions.serialization.WrongSerializationTypeException +import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException +import kotlinx.serialization.serializerOrNull import java.math.BigInteger import java.security.MessageDigest import io.infinitic.common.serDe.json.Json as JsonJackson @@ -56,62 +56,63 @@ data class SerializedData( /** * @return serialized value */ + @OptIn(InternalSerializationApi::class) fun from(value: T?): SerializedData { val bytes: ByteArray val type: SerializedDataType - val meta = mapOf(META_JAVA_CLASS to (value ?: "")::class.java.name.toByteArray(charset = Charsets.UTF_8)) + val meta: Map when (value) { null -> { - bytes = ByteArray(0) + bytes = "null".toByteArray() type = SerializedDataType.NULL - } - is ByteArray -> { - bytes = value - type = SerializedDataType.BYTES + meta = mapOf() } else -> { - val serializer = getKSerializerOrNull(value::class.java) - if (serializer == null) { - bytes = toJsonJacksonByteArray(value) - type = SerializedDataType.JSON_JACKSON - } else { - @Suppress("UNCHECKED_CAST") - bytes = toJsonKotlinByteArray(serializer as KSerializer, value) - type = SerializedDataType.JSON_KOTLIN + @Suppress("UNCHECKED_CAST") + when (val serializer = value::class.serializerOrNull()?. let { it as KSerializer }) { + null -> { + bytes = JsonJackson.stringify(value).toByteArray() + type = SerializedDataType.JSON_JACKSON + } + else -> { + bytes = jsonKotlin.encodeToString(serializer, value).toByteArray() + type = SerializedDataType.JSON_KOTLIN + } } + meta = mapOf(META_JAVA_CLASS to value::class.java.name.toByteArray(charset = Charsets.UTF_8)) } } return SerializedData(bytes, type, meta) } - - private fun toJsonJacksonByteArray(value: Any): ByteArray = - JsonJackson.stringify(value).toByteArray(charset = Charsets.UTF_8) - - private fun toJsonKotlinByteArray(serializer: KSerializer, value: T): ByteArray = - jsonKotlin.encodeToString(serializer, value).toByteArray(charset = Charsets.UTF_8) } /** * @return deserialized value */ - fun deserialize(): Any? { - val klassName = getClassName() ?: throw MissingMetaJavaClassException - - val klass = try { - Class.forName(klassName) - } catch (e: java.lang.ClassNotFoundException) { - throw ClassNotFoundException(klassName) + @OptIn(InternalSerializationApi::class) + fun deserialize(): Any? = when (type) { + SerializedDataType.NULL -> null + SerializedDataType.JSON_JACKSON -> { + val klass = getClassObject() + try { + JsonJackson.parse(getJson(), klass) + } catch (e: JsonProcessingException) { + throw JsonDeserializationException(klass.name, causeString = e.toString()) + } + } + SerializedDataType.JSON_KOTLIN -> { + val klass = getClassObject() + val serializer = klass.kotlin.serializerOrNull() ?: throw SerializerNotFoundException(klass.name) + try { + jsonKotlin.decodeFromString(serializer, getJson()) + } catch (e: SerializationException) { + throw KotlinDeserializationException(klass.name, causeString = e.toString()) + } } - - return deserialize(klass) } - fun getJson(): String = when (type) { - SerializedDataType.JSON_KOTLIN, - SerializedDataType.JSON_JACKSON -> String(bytes, Charsets.UTF_8) - else -> throw WrongSerializationTypeException(getClassName(), type) - } + fun getJson(): String = String(bytes, Charsets.UTF_8) fun hash(): String { // MD5 implementation, enough to avoid collision in practical cases @@ -119,9 +120,14 @@ data class SerializedData( return BigInteger(1, md.digest(bytes)).toString(16).padStart(32, '0') } - override fun toString() = try { "${deserialize()}" } catch (e: Throwable) { - "** SerializedData - can't display due to deserialization error :**\n$e" - } + /** + * Readable version + */ + override fun toString() = mapOf( + "bytes" to String(bytes), + "type" to type, + "meta" to meta.mapValues { String(it.value) } + ).toString() override fun equals(other: Any?): Boolean { if (this === other) return true @@ -132,36 +138,23 @@ data class SerializedData( if (!bytes.contentEquals(other.bytes)) return false if (type != other.type) return false + if (meta.keys != other.meta.keys) return false + if (meta.map { it.value.contentEquals(other.meta[it.key]!!) }.any { !it }) return false + return true } - override fun hashCode(): Int { - return bytes.contentHashCode() - } + override fun hashCode(): Int = bytes.contentHashCode() private fun getClassName(): String? = meta[META_JAVA_CLASS]?.let { String(it, charset = Charsets.UTF_8) } - private fun deserialize(klass: Class<*>) = when (type) { - SerializedDataType.NULL -> null - SerializedDataType.BYTES -> bytes - SerializedDataType.JSON_JACKSON -> fromJsonJackson(klass) - SerializedDataType.JSON_KOTLIN -> fromJsonKotlin(klass) - SerializedDataType.CUSTOM -> throw RuntimeException("Can't deserialize data with CUSTOM serialization") - } - - private fun fromJsonJackson(klass: Class): T = try { - JsonJackson.parse(getJson(), klass) - } catch (e: JsonProcessingException) { - throw JsonDeserializationException(klass.name, causeString = e.toString()) - } - - private fun fromJsonKotlin(klass: Class<*>): Any? { - val serializer = getKSerializerOrNull(klass) ?: throw SerializerNotFoundException(klass.name) + private fun getClassObject(): Class { + val klassName = getClassName() ?: throw MissingMetaJavaClassException return try { - jsonKotlin.decodeFromString(serializer, getJson()) - } catch (e: SerializationException) { - throw KotlinDeserializationException(klass.name, causeString = e.toString()) + Class.forName(klassName) + } catch (e: java.lang.ClassNotFoundException) { + throw ClassNotFoundException(klassName) } } } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedDataType.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedDataType.kt index aa6264d81..02fe9c753 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedDataType.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/SerializedDataType.kt @@ -27,8 +27,6 @@ package io.infinitic.common.serDe enum class SerializedDataType { NULL, - BYTES, JSON_JACKSON, - JSON_KOTLIN, - CUSTOM + JSON_KOTLIN } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/json/Json.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/json/Json.kt index 68ba129cd..dfa91400f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/json/Json.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/json/Json.kt @@ -51,7 +51,7 @@ object Json { false -> mapper.writeValueAsString(msg) } - fun parse(json: String, klass: Class): T = mapper.readValue(json, klass) + fun parse(json: String, klass: Class): T = mapper.readValue(json, klass) /** * Cause should not be included to the json, as it triggers diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/kserializer/kserializer.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/kserializer/kserializer.kt index 9eb52f02d..2fa471465 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/kserializer/kserializer.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/serDe/kserializer/kserializer.kt @@ -26,6 +26,7 @@ package io.infinitic.common.serDe.kserializer import io.infinitic.common.clients.messages.ClientEnvelope +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.metrics.global.messages.MetricsGlobalEnvelope import io.infinitic.common.metrics.perName.messages.MetricsPerNameEnvelope import io.infinitic.common.tasks.engine.messages.TaskEngineEnvelope @@ -33,9 +34,9 @@ import io.infinitic.common.tasks.executors.messages.TaskExecutorEnvelope import io.infinitic.common.tasks.tags.messages.TaskTagEngineEnvelope import io.infinitic.common.workflows.engine.messages.WorkflowEngineEnvelope import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineEnvelope -import io.infinitic.exceptions.thisShouldNotHappen +import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer -import java.lang.reflect.Modifier.isStatic +import kotlinx.serialization.serializerOrNull import kotlin.reflect.KClass // @OptIn(ExperimentalStdlibApi::class) @@ -46,21 +47,23 @@ import kotlin.reflect.KClass // null // } -fun getKSerializerOrNull(klass: Class<*>): KSerializer<*>? { - val companionField = klass.declaredFields.find { - it.name == "Companion" && isStatic(it.modifiers) - } ?: return null - val companion = companionField.get(klass) - val serializerMethod = try { - companion::class.java.getMethod("serializer") - } catch (e: NoSuchMethodException) { - return null - } - if (serializerMethod.returnType.name != KSerializer::class.qualifiedName) { - return null - } - @Suppress("UNCHECKED_CAST") - return serializerMethod.invoke(companion) as KSerializer<*> +@OptIn(InternalSerializationApi::class) +fun getKSerializerOrNull(klass: Class): KSerializer? { + return klass.kotlin.serializerOrNull() +// val companionField = klass.declaredFields.find { +// it.name == "Companion" && isStatic(it.modifiers) +// } ?: return null +// val companion = companionField.get(klass) +// val serializerMethod = try { +// companion::class.java.getMethod("serializer") +// } catch (e: NoSuchMethodException) { +// return null +// } +// if (serializerMethod.returnType.name != KSerializer::class.qualifiedName) { +// return null +// } +// @Suppress("UNCHECKED_CAST") +// return serializerMethod.invoke(companion) as KSerializer } @Suppress("UNCHECKED_CAST") diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskAttemptId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskAttemptId.kt index 2a74804bc..00a1736c4 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskAttemptId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskAttemptId.kt @@ -25,21 +25,10 @@ package io.infinitic.common.tasks.data -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = TaskAttemptIdSerializer::class) -data class TaskAttemptId(override val id: UUID = UUID.randomUUID()) : Id(id) - -object TaskAttemptIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("TaskAttemptId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: TaskAttemptId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = TaskAttemptId(UUID.fromString(decoder.decodeString())) +@JvmInline @Serializable +value class TaskAttemptId(private val id: String = UUID.randomUUID().toString()) { + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskId.kt index de0a5436f..55f3d8445 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskId.kt @@ -25,21 +25,15 @@ package io.infinitic.common.tasks.data -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer +import io.infinitic.common.workflows.data.commands.CommandId import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = TaskIdSerializer::class) -data class TaskId(override val id: UUID = UUID.randomUUID()) : Id(id) +@JvmInline @Serializable +value class TaskId(private val id: String = UUID.randomUUID().toString()) { + companion object { + fun from(commandId: CommandId) = TaskId(commandId.toString()) + } -object TaskIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("TaskId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: TaskId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = TaskId(UUID.fromString(decoder.decodeString())) + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskMeta.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskMeta.kt index ce622d85f..b0e78ae2d 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskMeta.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskMeta.kt @@ -35,7 +35,17 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(with = TaskMetaSerializer::class) -data class TaskMeta(val map: Map = mapOf()) : Map by map +data class TaskMeta(val map: Map = mapOf()) : Map by map { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as TaskMeta + if (map.keys != other.map.keys) return false + if (map.map { it.value.contentEquals(other.map[it.key]!!) }.any { !it }) return false + + return true + } +} object TaskMetaSerializer : KSerializer { val ser = MapSerializer(String.serializer(), ByteArraySerializer()) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskOptions.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskOptions.kt index 4ffb10c77..1a5226d74 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskOptions.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskOptions.kt @@ -25,9 +25,10 @@ package io.infinitic.common.tasks.data +import io.infinitic.common.data.JobOptions import kotlinx.serialization.Serializable @Serializable data class TaskOptions( val runningTimeout: Float? = null -) +) : JobOptions diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandType.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskReturnValue.kt similarity index 78% rename from infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandType.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskReturnValue.kt index 67485dc9f..37622d5c1 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandType.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/data/TaskReturnValue.kt @@ -23,20 +23,14 @@ * Licensor: infinitic.io */ -package io.infinitic.common.workflows.data.commands +package io.infinitic.common.tasks.data +import io.infinitic.common.data.ReturnValue import kotlinx.serialization.Serializable @Serializable -enum class CommandType { - DISPATCH_TASK, - DISPATCH_CHILD_WORKFLOW, - START_DURATION_TIMER, - START_INSTANT_TIMER, - RECEIVE_IN_CHANNEL, - SENT_TO_CHANNEL, - START_ASYNC, - END_ASYNC, - START_INLINE_TASK, - END_INLINE_TASK -} +data class TaskReturnValue( + val taskId: TaskId, + val taskName: TaskName, + val returnValue: ReturnValue +) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/messages/TaskEngineMessages.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/messages/TaskEngineMessages.kt index a2130afbd..630208869 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/messages/TaskEngineMessages.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/messages/TaskEngineMessages.kt @@ -25,14 +25,15 @@ package io.infinitic.common.tasks.engine.messages -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.data.MillisDuration +import io.infinitic.common.data.ReturnValue import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.data.methods.MethodReturnValue -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.DeferredError +import io.infinitic.common.errors.WorkerError import io.infinitic.common.messages.Message import io.infinitic.common.tasks.data.TaskAttemptId import io.infinitic.common.tasks.data.TaskId @@ -52,6 +53,7 @@ import kotlinx.serialization.Serializable @Serializable sealed class TaskEngineMessage : Message { val messageId = MessageId() + abstract val emitterName: ClientName abstract val taskId: TaskId abstract val taskName: TaskName @@ -60,9 +62,9 @@ sealed class TaskEngineMessage : Message { @Serializable data class DispatchTask( - override val taskId: TaskId, override val taskName: TaskName, - val clientName: ClientName, + override val taskId: TaskId, + val taskOptions: TaskOptions, val clientWaiting: Boolean, val methodName: MethodName, val methodParameterTypes: MethodParameterTypes?, @@ -72,63 +74,70 @@ data class DispatchTask( val methodRunId: MethodRunId?, val taskTags: Set, val taskMeta: TaskMeta, - val taskOptions: TaskOptions + override val emitterName: ClientName ) : TaskEngineMessage() @Serializable data class WaitTask( - override val taskId: TaskId, override val taskName: TaskName, - val clientName: ClientName + override val taskId: TaskId, + override val emitterName: ClientName, ) : TaskEngineMessage() @Serializable data class RetryTask( + override val taskName: TaskName, override val taskId: TaskId, - override val taskName: TaskName + override val emitterName: ClientName ) : TaskEngineMessage() @Serializable data class CancelTask( + override val taskName: TaskName, override val taskId: TaskId, - override val taskName: TaskName + override val emitterName: ClientName ) : TaskEngineMessage() @Serializable data class CompleteTask( - override val taskId: TaskId, override val taskName: TaskName, - val taskReturnValue: MethodReturnValue + override val taskId: TaskId, + val taskReturnValue: ReturnValue, + override val emitterName: ClientName ) : TaskEngineMessage() @Serializable data class RetryTaskAttempt( - override val taskId: TaskId, override val taskName: TaskName, + override val taskId: TaskId, + override val taskRetryIndex: TaskRetryIndex, override val taskAttemptId: TaskAttemptId, override val taskRetrySequence: TaskRetrySequence, - override val taskRetryIndex: TaskRetryIndex + override val emitterName: ClientName ) : TaskEngineMessage(), TaskAttemptMessage @Serializable data class TaskAttemptCompleted( - override val taskId: TaskId, override val taskName: TaskName, + override val taskId: TaskId, + override val taskRetryIndex: TaskRetryIndex, override val taskAttemptId: TaskAttemptId, override val taskRetrySequence: TaskRetrySequence, - override val taskRetryIndex: TaskRetryIndex, - val taskReturnValue: MethodReturnValue, - val taskMeta: TaskMeta + val taskReturnValue: ReturnValue, + val taskMeta: TaskMeta, + override val emitterName: ClientName ) : TaskEngineMessage(), TaskAttemptMessage @Serializable data class TaskAttemptFailed( - override val taskId: TaskId, override val taskName: TaskName, + override val taskId: TaskId, + override val taskAttemptDelayBeforeRetry: MillisDuration?, override val taskAttemptId: TaskAttemptId, override val taskRetrySequence: TaskRetrySequence, override val taskRetryIndex: TaskRetryIndex, - override val taskAttemptDelayBeforeRetry: MillisDuration?, - val taskAttemptError: Error, - val taskMeta: TaskMeta + val deferredError: DeferredError?, + val workerError: WorkerError?, + val taskMeta: TaskMeta, + override val emitterName: ClientName ) : TaskEngineMessage(), FailingTaskAttemptMessage diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/state/TaskState.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/state/TaskState.kt index 54f5100aa..e9be392e3 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/state/TaskState.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/engine/state/TaskState.kt @@ -25,13 +25,13 @@ package io.infinitic.common.tasks.engine.state -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId +import io.infinitic.common.data.ReturnValue import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.data.methods.MethodReturnValue -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.WorkerError import io.infinitic.common.serDe.avro.AvroSerDe import io.infinitic.common.tasks.data.TaskAttemptId import io.infinitic.common.tasks.data.TaskId @@ -53,7 +53,7 @@ data class TaskState( var lastMessageId: MessageId, val taskId: TaskId, val taskName: TaskName, - var taskReturnValue: MethodReturnValue?, + var taskReturnValue: ReturnValue?, val methodName: MethodName, val methodParameterTypes: MethodParameterTypes?, val methodParameters: MethodParameters, @@ -64,7 +64,7 @@ data class TaskState( var taskRetrySequence: TaskRetrySequence = TaskRetrySequence(0), var taskAttemptId: TaskAttemptId, var taskRetryIndex: TaskRetryIndex = TaskRetryIndex(0), - var lastError: Error? = null, + var lastError: WorkerError? = null, val taskTags: Set, val taskOptions: TaskOptions, var taskMeta: TaskMeta diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/executors/messages/TaskExecutorMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/executors/messages/TaskExecutorMessage.kt index 4834d2973..37b9a2cd1 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/executors/messages/TaskExecutorMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/executors/messages/TaskExecutorMessage.kt @@ -25,11 +25,12 @@ package io.infinitic.common.tasks.executors.messages +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.WorkerError import io.infinitic.common.messages.Message import io.infinitic.common.tasks.data.TaskAttemptId import io.infinitic.common.tasks.data.TaskId @@ -39,6 +40,7 @@ import io.infinitic.common.tasks.data.TaskOptions import io.infinitic.common.tasks.data.TaskRetryIndex import io.infinitic.common.tasks.data.TaskRetrySequence import io.infinitic.common.tasks.data.TaskTag +import io.infinitic.common.workflows.data.workflowTasks.WorkflowTask import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowName import kotlinx.serialization.Serializable @@ -46,26 +48,30 @@ import kotlinx.serialization.Serializable @Serializable sealed class TaskExecutorMessage : Message { val messageId = MessageId() + abstract val emitterName: ClientName abstract val taskId: TaskId abstract val taskName: TaskName + fun isWorkflowTask() = (taskName == TaskName(WorkflowTask::class.java.name)) + override fun envelope() = TaskExecutorEnvelope.from(this) } @Serializable data class ExecuteTaskAttempt( - override val taskId: TaskId, override val taskName: TaskName, + override val taskId: TaskId, + val taskTags: Set, val workflowId: WorkflowId?, val workflowName: WorkflowName?, val taskAttemptId: TaskAttemptId, val taskRetrySequence: TaskRetrySequence, val taskRetryIndex: TaskRetryIndex, - val lastError: Error?, + val lastError: WorkerError?, val methodName: MethodName, val methodParameterTypes: MethodParameterTypes?, val methodParameters: MethodParameters, val taskOptions: TaskOptions, val taskMeta: TaskMeta, - val taskTags: Set + override val emitterName: ClientName ) : TaskExecutorMessage() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineEnvelope.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineEnvelope.kt index f8d89ee58..a3825e82c 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineEnvelope.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineEnvelope.kt @@ -33,19 +33,19 @@ import kotlinx.serialization.Serializable data class TaskTagEngineEnvelope( val name: String, val type: TaskTagEngineMessageType, - val addTaskTag: AddTaskTag? = null, - val removeTaskTag: RemoveTaskTag? = null, - val cancelTaskPerTag: CancelTaskPerTag? = null, - val retryTaskPerTag: RetryTaskPerTag? = null, - val getTaskIds: GetTaskIds? = null + val addTagToTask: AddTagToTask? = null, + val removeTagFromTask: RemoveTagFromTask? = null, + val cancelTaskByTag: CancelTaskByTag? = null, + val retryTaskByTag: RetryTaskByTag? = null, + val getTaskIdsByTag: GetTaskIdsByTag? = null ) : Envelope { init { val noNull = listOfNotNull( - addTaskTag, - removeTaskTag, - cancelTaskPerTag, - retryTaskPerTag, - getTaskIds + addTagToTask, + removeTagFromTask, + cancelTaskByTag, + retryTaskByTag, + getTaskIdsByTag ) require(noNull.size == 1) @@ -55,30 +55,30 @@ data class TaskTagEngineEnvelope( companion object { fun from(msg: TaskTagEngineMessage) = when (msg) { - is AddTaskTag -> TaskTagEngineEnvelope( + is AddTagToTask -> TaskTagEngineEnvelope( "${msg.taskName}", - TaskTagEngineMessageType.ADD_TASK_TAG, - addTaskTag = msg + TaskTagEngineMessageType.ADD_TAG_TO_TASK, + addTagToTask = msg ) - is RemoveTaskTag -> TaskTagEngineEnvelope( + is RemoveTagFromTask -> TaskTagEngineEnvelope( "${msg.taskName}", - TaskTagEngineMessageType.REMOVE_TASK_TAG, - removeTaskTag = msg + TaskTagEngineMessageType.REMOVE_TAG_FROM_TASK, + removeTagFromTask = msg ) - is CancelTaskPerTag -> TaskTagEngineEnvelope( + is CancelTaskByTag -> TaskTagEngineEnvelope( "${msg.taskName}", - TaskTagEngineMessageType.CANCEL_TASK_PER_TAG, - cancelTaskPerTag = msg + TaskTagEngineMessageType.CANCEL_TASK_BY_TAG, + cancelTaskByTag = msg ) - is RetryTaskPerTag -> TaskTagEngineEnvelope( + is RetryTaskByTag -> TaskTagEngineEnvelope( "${msg.taskName}", - TaskTagEngineMessageType.RETRY_TASK_PER_TAG, - retryTaskPerTag = msg + TaskTagEngineMessageType.RETRY_TASK_BY_TAG, + retryTaskByTag = msg ) - is GetTaskIds -> TaskTagEngineEnvelope( + is GetTaskIdsByTag -> TaskTagEngineEnvelope( "${msg.taskName}", - TaskTagEngineMessageType.GET_TASK_IDS, - getTaskIds = msg + TaskTagEngineMessageType.GET_TASK_IDS_BY_TAG, + getTaskIdsByTag = msg ) } @@ -86,11 +86,11 @@ data class TaskTagEngineEnvelope( } override fun message() = when (type) { - TaskTagEngineMessageType.ADD_TASK_TAG -> addTaskTag!! - TaskTagEngineMessageType.REMOVE_TASK_TAG -> removeTaskTag!! - TaskTagEngineMessageType.CANCEL_TASK_PER_TAG -> cancelTaskPerTag!! - TaskTagEngineMessageType.RETRY_TASK_PER_TAG -> retryTaskPerTag!! - TaskTagEngineMessageType.GET_TASK_IDS -> getTaskIds!! + TaskTagEngineMessageType.ADD_TAG_TO_TASK -> addTagToTask!! + TaskTagEngineMessageType.REMOVE_TAG_FROM_TASK -> removeTagFromTask!! + TaskTagEngineMessageType.CANCEL_TASK_BY_TAG -> cancelTaskByTag!! + TaskTagEngineMessageType.RETRY_TASK_BY_TAG -> retryTaskByTag!! + TaskTagEngineMessageType.GET_TASK_IDS_BY_TAG -> getTaskIdsByTag!! } fun toByteArray() = AvroSerDe.writeBinary(this, serializer()) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessage.kt index 0bc81e546..1df7a788c 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessage.kt @@ -25,7 +25,7 @@ package io.infinitic.common.tasks.tags.messages -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.messages.Message import io.infinitic.common.tasks.data.TaskId @@ -36,6 +36,7 @@ import kotlinx.serialization.Serializable @Serializable sealed class TaskTagEngineMessage : Message { val messageId = MessageId() + abstract val emitterName: ClientName abstract val taskTag: TaskTag abstract val taskName: TaskName @@ -43,34 +44,38 @@ sealed class TaskTagEngineMessage : Message { } @Serializable -data class RetryTaskPerTag( +data class RetryTaskByTag( + override val taskName: TaskName, override val taskTag: TaskTag, - override val taskName: TaskName + override val emitterName: ClientName ) : TaskTagEngineMessage() @Serializable -data class CancelTaskPerTag( +data class CancelTaskByTag( + override val taskName: TaskName, override val taskTag: TaskTag, - override val taskName: TaskName + override val emitterName: ClientName ) : TaskTagEngineMessage() @Serializable -data class AddTaskTag( - override val taskTag: TaskTag, +data class AddTagToTask( override val taskName: TaskName, + override val taskTag: TaskTag, val taskId: TaskId, + override val emitterName: ClientName, ) : TaskTagEngineMessage() @Serializable -data class RemoveTaskTag( - override val taskTag: TaskTag, +data class RemoveTagFromTask( override val taskName: TaskName, + override val taskTag: TaskTag, val taskId: TaskId, + override val emitterName: ClientName, ) : TaskTagEngineMessage() @Serializable -data class GetTaskIds( - override val taskTag: TaskTag, +data class GetTaskIdsByTag( override val taskName: TaskName, - val clientName: ClientName + override val taskTag: TaskTag, + override val emitterName: ClientName ) : TaskTagEngineMessage() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessageType.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessageType.kt index c77390a6b..be7652909 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessageType.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/tasks/tags/messages/TaskTagEngineMessageType.kt @@ -26,9 +26,9 @@ package io.infinitic.common.tasks.tags.messages enum class TaskTagEngineMessageType { - ADD_TASK_TAG, - REMOVE_TASK_TAG, - CANCEL_TASK_PER_TAG, - RETRY_TASK_PER_TAG, - GET_TASK_IDS + ADD_TAG_TO_TASK, + REMOVE_TAG_FROM_TASK, + CANCEL_TASK_BY_TAG, + RETRY_TASK_BY_TAG, + GET_TASK_IDS_BY_TAG } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventFilter.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventFilter.kt index 02904420f..a92bc926b 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventFilter.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventFilter.kt @@ -58,7 +58,7 @@ data class ChannelEventFilter(val jsonPath: String, val filter: String? = null) * true if this event should be caught * false either */ - fun check(event: ChannelEvent): Boolean { + fun check(event: ChannelSignal): Boolean { // get Json of the provided event val json = event.serializedData.getJson() // is this json filtered by the provided jsonPath diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventId.kt deleted file mode 100644 index 66ac90efa..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventId.kt +++ /dev/null @@ -1,45 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.workflows.data.channels - -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import java.util.UUID - -@Serializable(with = SendIdSerializer::class) -data class ChannelEventId(override val id: UUID = UUID.randomUUID()) : Id(id) - -object SendIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SendId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: ChannelEventId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = ChannelEventId(UUID.fromString(decoder.decodeString())) -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelImpl.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelImpl.kt deleted file mode 100644 index 1204506b3..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelImpl.kt +++ /dev/null @@ -1,58 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.workflows.data.channels - -import com.jayway.jsonpath.Criteria -import io.infinitic.exceptions.workflows.NameNotInitializedInChannelException -import io.infinitic.workflows.Channel -import io.infinitic.workflows.Deferred -import io.infinitic.workflows.WorkflowDispatcher -import java.util.concurrent.CompletableFuture - -class ChannelImpl( - private val context: () -> WorkflowDispatcher -) : Channel { - lateinit var name: String - - fun isNameInitialized() = ::name.isInitialized - - fun getNameOrThrow() = when (isNameInitialized()) { - true -> name - else -> throw NameNotInitializedInChannelException - } - - override fun send(event: T): CompletableFuture { - context().sendToChannel(this, event) - - return CompletableFuture.completedFuture(Unit) - } - - override fun receive(jsonPath: String?, criteria: Criteria?): Deferred = - context().receiveFromChannel(this, jsonPath, criteria) - - override fun receive(klass: Class, jsonPath: String?, criteria: Criteria?): Deferred = - context().receiveFromChannel(this, klass, jsonPath, criteria) -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelName.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelName.kt index 98f8b06e0..004530602 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelName.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelName.kt @@ -25,25 +25,16 @@ package io.infinitic.common.workflows.data.channels -import io.infinitic.common.data.Name -import kotlinx.serialization.KSerializer +import io.infinitic.common.data.methods.MethodName import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.lang.reflect.Method -@Serializable(with = ChannelNameSerializer::class) -data class ChannelName(override val name: String) : Name(name) { +@JvmInline @Serializable +value class ChannelName(private val name: String) { companion object { fun from(method: Method) = ChannelName(method.name) + fun from(methodName: MethodName) = ChannelName(methodName.toString()) } -} -object ChannelNameSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ChannelName", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: ChannelName) { encoder.encodeString(value.name) } - override fun deserialize(decoder: Decoder) = ChannelName(decoder.decodeString()) + override fun toString() = name } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEvent.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignal.kt similarity index 78% rename from infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEvent.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignal.kt index f251341f9..94a1190e4 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEvent.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignal.kt @@ -25,7 +25,6 @@ package io.infinitic.common.workflows.data.channels -import io.infinitic.common.data.Data import io.infinitic.common.serDe.SerializedData import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -34,17 +33,21 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(with = ChannelEventSerializer::class) -data class ChannelEvent(override val serializedData: SerializedData) : Data(serializedData) { +data class ChannelSignal(val serializedData: SerializedData) { companion object { - fun from(data: Any?) = ChannelEvent(SerializedData.from(data)) + fun from(data: Any?) = ChannelSignal(SerializedData.from(data)) } + + override fun toString() = serializedData.toString() + + fun signal(): Any? = serializedData.deserialize() } -object ChannelEventSerializer : KSerializer { +object ChannelEventSerializer : KSerializer { override val descriptor: SerialDescriptor = SerializedData.serializer().descriptor - override fun serialize(encoder: Encoder, value: ChannelEvent) { + override fun serialize(encoder: Encoder, value: ChannelSignal) { SerializedData.serializer().serialize(encoder, value.serializedData) } override fun deserialize(decoder: Decoder) = - ChannelEvent(SerializedData.serializer().deserialize(decoder)) + ChannelSignal(SerializedData.serializer().deserialize(decoder)) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowRunException.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignalId.kt similarity index 59% rename from infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowRunException.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignalId.kt index fa5c09d7e..26de3bc91 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowRunException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignalId.kt @@ -23,26 +23,17 @@ * Licensor: infinitic.io */ -package io.infinitic.exceptions.workflows +package io.infinitic.common.workflows.data.channels -import io.infinitic.common.errors.Error -import io.infinitic.exceptions.RunException +import io.infinitic.common.workflows.data.commands.CommandId +import kotlinx.serialization.Serializable import java.util.UUID -sealed class WorkflowRunException( - msg: String, - causeError: Error? = null -) : RunException(msg, causeError) +@JvmInline @Serializable +value class ChannelSignalId(private val id: String = UUID.randomUUID().toString()) { + companion object { + fun from(command: CommandId) = ChannelSignalId(command.toString()) + } -class CanceledDeferredException(val name: String?, val id: UUID) : WorkflowRunException( - msg = "Waiting for a canceled deferred: $name ($id)", -) - -class FailedDeferredException(val name: String?, val id: UUID, error: Error? = null) : WorkflowRunException( - msg = "Waiting for a failed deferred: $name ($id)", - causeError = error -) - -class TimedOutDeferredException(val name: String?, val id: UUID) : WorkflowRunException( - msg = "Waiting for a timed-out deferred: $name ($id)", -) + override fun toString() = id +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventType.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignalType.kt similarity index 81% rename from infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventType.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignalType.kt index 2509bfdb4..e74f90975 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelEventType.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ChannelSignalType.kt @@ -35,15 +35,15 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(with = ChannelEventTypeSerializer::class) -data class ChannelEventType(override val name: String) : Name(name) { +data class ChannelSignalType(override val name: String) : Name(name) { companion object { - fun from(klass: Class) = ChannelEventType(klass.name) + fun from(klass: Class) = ChannelSignalType(klass.name) fun allFrom(klass: Class) = getAllExtendedOrImplementedTypes(klass) } } -object ChannelEventTypeSerializer : KSerializer { +object ChannelEventTypeSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ChannelEventType", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: ChannelEventType) { encoder.encodeString(value.name) } - override fun deserialize(decoder: Decoder) = ChannelEventType(decoder.decodeString()) + override fun serialize(encoder: Encoder, value: ChannelSignalType) { encoder.encodeString(value.name) } + override fun deserialize(decoder: Decoder) = ChannelSignalType(decoder.decodeString()) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ReceivingChannel.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ReceivingChannel.kt index a9067b53f..85e4f5ae0 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ReceivingChannel.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/ReceivingChannel.kt @@ -32,7 +32,7 @@ import kotlinx.serialization.Serializable @Serializable data class ReceivingChannel( val channelName: ChannelName, - val channelEventType: ChannelEventType?, + val channelSignalType: ChannelSignalType?, val channelEventFilter: ChannelEventFilter?, val methodRunId: MethodRunId, val commandId: CommandId diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/getAllExtendedOrImplementedTypes.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/getAllExtendedOrImplementedTypes.kt index ffd3d5489..d24fd8cf8 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/getAllExtendedOrImplementedTypes.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/channels/getAllExtendedOrImplementedTypes.kt @@ -25,16 +25,16 @@ package io.infinitic.common.workflows.data.channels -internal fun getAllExtendedOrImplementedTypes(klass: Class<*>): Set { +internal fun getAllExtendedOrImplementedTypes(klass: Class<*>): Set { var clazz = klass - val all: MutableList = mutableListOf() + val all: MutableList = mutableListOf() do { - all.add(ChannelEventType(clazz.name)) + all.add(ChannelSignalType(clazz.name)) // First, add all the interfaces implemented by this class val interfaces = clazz.interfaces.toList() if (interfaces.isNotEmpty()) { - all.addAll(interfaces.map { ChannelEventType(it.name) }) + all.addAll(interfaces.map { ChannelSignalType(it.name) }) for (interfaze in interfaces) { all.addAll(getAllExtendedOrImplementedTypes(interfaze)) } @@ -43,7 +43,7 @@ internal fun getAllExtendedOrImplementedTypes(klass: Class<*>): Set, val taskMeta: TaskMeta, val taskOptions: TaskOptions -) : Command() +) : Command() { + override fun isSameThan(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as DispatchTaskCommand -@Serializable -data class DispatchChildWorkflow( - val childWorkflowName: WorkflowName, - val childMethodName: MethodName, - val childMethodParameterTypes: MethodParameterTypes, - val childMethodParameters: MethodParameters, + return taskName == other.taskName && + methodName == other.methodName && + methodParameterTypes == methodParameterTypes && + methodParameters == other.methodParameters + } +} + +@Serializable @SerialName("DispatchWorkflowCommand") +data class DispatchWorkflowCommand( + val workflowName: WorkflowName, + val methodName: MethodName, + val methodParameterTypes: MethodParameterTypes, + val methodParameters: MethodParameters, val workflowTags: Set, val workflowMeta: WorkflowMeta, val workflowOptions: WorkflowOptions -) : Command() +) : Command() { + override fun isSameThan(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as DispatchWorkflowCommand -@Serializable -object StartAsync : Command() { - override fun equals(other: Any?) = javaClass == other?.javaClass + return workflowName == other.workflowName && + methodName == other.methodName && + methodParameterTypes == methodParameterTypes && + methodParameters == other.methodParameters + } } -@Serializable -data class EndAsync( - @JsonProperty("output") - val asyncReturnValue: CommandReturnValue -) : Command() +@Serializable @SerialName("DispatchMethodCommand") +data class DispatchMethodCommand( + val workflowName: WorkflowName, + val workflowId: WorkflowId?, + val workflowTag: WorkflowTag?, + val methodName: MethodName, + val methodParameterTypes: MethodParameterTypes, + val methodParameters: MethodParameters +) : Command() { + override fun isSameThan(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as DispatchMethodCommand -@Serializable -object StartInlineTask : Command() { - // as we can not define a data class without parameter, we add manually the equals func - override fun equals(other: Any?) = javaClass == other?.javaClass + return workflowName == other.workflowName && + workflowId == other.workflowId && + workflowTag == other.workflowTag && + methodName == other.methodName && + methodParameterTypes == methodParameterTypes && + methodParameters == other.methodParameters + } } -@Serializable -data class EndInlineTask( - @JsonProperty("output") - val inlineTaskReturnValue: CommandReturnValue -) : Command() - -@Serializable -data class StartDurationTimer( - val duration: MillisDuration -) : Command() +@Serializable @SerialName("SendSignalCommand") +data class SendSignalCommand( + val workflowName: WorkflowName, + val workflowId: WorkflowId?, + val workflowTag: WorkflowTag?, + val channelName: ChannelName, + val channelSignal: ChannelSignal, + val channelSignalTypes: Set +) : Command() { + companion object { + fun simpleName() = CommandSimpleName("SEND_SIGNAL") + } -@Serializable -data class StartInstantTimer( - val instant: MillisInstant -) : Command() + override fun isSameThan(other: Any?) = other == this +} -@Serializable -data class ReceiveInChannel( +@Serializable @SerialName("ReceiveSignalCommand") +data class ReceiveSignalCommand( val channelName: ChannelName, - val channelEventType: ChannelEventType?, + val channelSignalType: ChannelSignalType?, val channelEventFilter: ChannelEventFilter? -) : Command() +) : Command() { + companion object { + fun simpleName() = CommandSimpleName("RECEIVE_SIGNAL") + } -@Serializable -data class SendToChannel( - val channelName: ChannelName, - val channelEvent: ChannelEvent, - val channelEventTypes: Set -) : Command() + override fun isSameThan(other: Any?) = other == this +} + +@Serializable @SerialName("InlineTaskCommand") +data class InlineTaskCommand( + val task: String = "inline" +) : Command() { + companion object { + fun simpleName() = CommandSimpleName("INLINE_TASK") + } + + override fun equals(other: Any?) = other is InlineTaskCommand + + override fun isSameThan(other: Any?) = other == this +} + +@Serializable @SerialName("StartDurationTimerCommand") +data class StartDurationTimerCommand( + val duration: MillisDuration +) : Command() { + companion object { + fun simpleName() = CommandSimpleName("START_DURATION_TIMER") + } + + override fun isSameThan(other: Any?) = other == this +} + +@Serializable @SerialName("StartInstantTimerCommand") +data class StartInstantTimerCommand( + val instant: MillisInstant +) : Command() { + companion object { + fun simpleName() = CommandSimpleName("START_INSTANT_TIMER") + } + + override fun isSameThan(other: Any?) = other == this +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandId.kt index f6dc546d4..956da7071 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandId.kt @@ -25,30 +25,21 @@ package io.infinitic.common.workflows.data.commands -import io.infinitic.common.data.Id import io.infinitic.common.tasks.data.TaskId -import io.infinitic.common.workflows.data.channels.ChannelEventId +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.timers.TimerId import io.infinitic.common.workflows.data.workflows.WorkflowId -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = CommandIdSerializer::class) -data class CommandId(override val id: UUID = UUID.randomUUID()) : Id(id) { - constructor(taskId: TaskId) : this(taskId.id) - constructor(timerId: TimerId) : this(timerId.id) - constructor(workflowId: WorkflowId) : this(workflowId.id) - constructor(eventId: ChannelEventId) : this(eventId.id) -} +@JvmInline @Serializable +value class CommandId(private val id: String = UUID.randomUUID().toString()) { + companion object { + fun from(taskId: TaskId) = CommandId(taskId.toString()) + fun from(workflowId: WorkflowId) = CommandId(workflowId.toString()) + fun from(methodRunId: MethodRunId) = CommandId(methodRunId.toString()) + fun from(timerId: TimerId) = CommandId(timerId.toString()) + } -object CommandIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CommandId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: CommandId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = CommandId(UUID.fromString(decoder.decodeString())) + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/Id.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandName.kt similarity index 56% rename from infinitic-common/src/main/kotlin/io/infinitic/common/data/Id.kt rename to infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandName.kt index 39bf5a609..4221c4772 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/Id.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandName.kt @@ -23,24 +23,21 @@ * Licensor: infinitic.io */ -package io.infinitic.common.data +package io.infinitic.common.workflows.data.commands -import kotlinx.serialization.KSerializer +import io.infinitic.common.exceptions.thisShouldNotHappen import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import java.util.UUID -@Serializable(with = IdSerializer::class) -open class Id(open val id: UUID) { - final override fun toString() = id.toString() -} - -object IdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Id", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: Id) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = Id(UUID.fromString(decoder.decodeString())) +@JvmInline @Serializable +value class CommandName private constructor(private val name: String) { + companion object { + fun from(command: Command) = when (command) { + is DispatchTaskCommand -> CommandName("${command.taskName}::${command.methodName}") + is DispatchWorkflowCommand -> CommandName("${command.workflowName}::${command.methodName}") + is DispatchMethodCommand -> CommandName("${command.workflowName}::${command.methodName}") + is SendSignalCommand -> CommandName("${command.workflowName}::${command.channelName}") + else -> thisShouldNotHappen() + } + } + override fun toString() = name } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandReturnValue.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandReturnValue.kt deleted file mode 100644 index 06a8c08d2..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandReturnValue.kt +++ /dev/null @@ -1,52 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.workflows.data.commands - -import io.infinitic.common.data.Data -import io.infinitic.common.serDe.SerializedData -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import java.time.Instant - -@Serializable(with = CommandOutputSerializer::class) -data class CommandReturnValue(override val serializedData: SerializedData) : Data(serializedData) { - companion object { - fun now() = CommandReturnValue(SerializedData.from(Instant.now())) - fun from(data: Any?) = CommandReturnValue(SerializedData.from(data)) - } -} - -object CommandOutputSerializer : KSerializer { - override val descriptor: SerialDescriptor = SerializedData.serializer().descriptor - override fun serialize(encoder: Encoder, value: CommandReturnValue) { - SerializedData.serializer().serialize(encoder, value.serializedData) - } - override fun deserialize(decoder: Decoder) = - CommandReturnValue(SerializedData.serializer().deserialize(decoder)) -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandSimpleName.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandSimpleName.kt index f9e96f00a..d607d6f9f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandSimpleName.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandSimpleName.kt @@ -25,20 +25,9 @@ package io.infinitic.common.workflows.data.commands -import io.infinitic.common.data.Name -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -@Serializable(with = CommandSimpleNameSerializer::class) -data class CommandSimpleName(override val name: String) : Name(name) - -object CommandSimpleNameSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CommandSimpleName", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: CommandSimpleName) { encoder.encodeString(value.name) } - override fun deserialize(decoder: Decoder) = CommandSimpleName(decoder.decodeString()) +@JvmInline @Serializable +value class CommandSimpleName(private val name: String) { + override fun toString() = name } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandStatus.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandStatus.kt index 603c54b41..5673d6915 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandStatus.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/CommandStatus.kt @@ -25,37 +25,51 @@ package io.infinitic.common.workflows.data.commands -import io.infinitic.common.errors.Error +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonTypeInfo +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.errors.CanceledDeferredError +import io.infinitic.common.errors.FailedDeferredError +import io.infinitic.common.errors.UnknownDeferredError import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@klass") sealed class CommandStatus { /** * A command is terminated if canceled or completed, failed is a transient state */ - fun isTerminated() = this is Completed || this is Canceled + @JsonIgnore fun isTerminated() = this is Completed || this is Canceled - @Serializable + @Serializable @SerialName("CommandStatus.Running") object Running : CommandStatus() { override fun equals(other: Any?) = javaClass == other?.javaClass override fun toString(): String = Running::class.java.name } - @Serializable - data class Completed( - val returnValue: CommandReturnValue, - val completionWorkflowTaskIndex: WorkflowTaskIndex + @Serializable @SerialName("CommandStatus.Unknown") + data class Unknown( + val unknownDeferredError: UnknownDeferredError, + val unknowingWorkflowTaskIndex: WorkflowTaskIndex ) : CommandStatus() - @Serializable + @Serializable @SerialName("CommandStatus.Canceled") data class Canceled( + val canceledDeferredError: CanceledDeferredError, val cancellationWorkflowTaskIndex: WorkflowTaskIndex ) : CommandStatus() - @Serializable + @Serializable @SerialName("CommandStatus.CurrentlyFailed") data class CurrentlyFailed( - val error: Error, + val failedDeferredError: FailedDeferredError, val failureWorkflowTaskIndex: WorkflowTaskIndex ) : CommandStatus() + + @Serializable @SerialName("CommandStatus.Completed") + data class Completed( + val returnValue: ReturnValue, + val completionWorkflowTaskIndex: WorkflowTaskIndex + ) : CommandStatus() } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/NewCommand.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/NewCommand.kt deleted file mode 100644 index 68486c14b..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/NewCommand.kt +++ /dev/null @@ -1,56 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.common.workflows.data.commands - -import io.infinitic.common.data.Name -import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition -import kotlinx.serialization.Serializable - -@Serializable -data class NewCommand( - val commandId: CommandId = CommandId(), - val command: Command, - val commandName: Name?, - val commandSimpleName: CommandSimpleName, - val commandPosition: MethodRunPosition -) { - val commandStatus: CommandStatus = CommandStatus.Running - - val commandHash: CommandHash = command.hash() - - val commandType: CommandType = when (command) { - is DispatchTask -> CommandType.DISPATCH_TASK - is DispatchChildWorkflow -> CommandType.DISPATCH_CHILD_WORKFLOW - is StartDurationTimer -> CommandType.START_DURATION_TIMER - is StartInstantTimer -> CommandType.START_INSTANT_TIMER - is StartAsync -> CommandType.START_ASYNC - is EndAsync -> CommandType.END_ASYNC - is StartInlineTask -> CommandType.START_INLINE_TASK - is EndInlineTask -> CommandType.END_INLINE_TASK - is ReceiveInChannel -> CommandType.RECEIVE_IN_CHANNEL - is SendToChannel -> CommandType.SENT_TO_CHANNEL - } -} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/PastCommand.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/PastCommand.kt index 504cd8fe1..6bc25dc0f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/PastCommand.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/commands/PastCommand.kt @@ -25,37 +25,137 @@ package io.infinitic.common.workflows.data.commands -import io.infinitic.common.data.Name import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition -import io.infinitic.common.workflows.data.properties.PropertyHash -import io.infinitic.common.workflows.data.properties.PropertyName -import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex import io.infinitic.common.workflows.data.workflows.WorkflowChangeCheckMode +import io.infinitic.common.workflows.data.workflows.WorkflowChangeCheckMode.NONE +import io.infinitic.common.workflows.data.workflows.WorkflowChangeCheckMode.SIMPLE +import io.infinitic.common.workflows.data.workflows.WorkflowChangeCheckMode.STRICT +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class PastCommand( - val commandPosition: MethodRunPosition, - val commandType: CommandType, - val commandId: CommandId, - val commandHash: CommandHash, - val commandName: Name?, - val commandSimpleName: CommandSimpleName, - var commandStatus: CommandStatus, - // property below are used only for start_async command - var propertiesNameHashAtStart: Map? = null, - var workflowTaskIndexAtStart: WorkflowTaskIndex? = null -) { +sealed class PastCommand { + abstract val commandId: CommandId + abstract val commandPosition: MethodRunPosition + abstract val commandSimpleName: CommandSimpleName + abstract var commandStatus: CommandStatus + abstract val command: Command + + companion object { + fun from( + commandPosition: MethodRunPosition, + commandSimpleName: CommandSimpleName, + commandStatus: CommandStatus, + command: Command + ) = when (command) { + is DispatchMethodCommand -> { + DispatchMethodPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is DispatchTaskCommand -> { + DispatchTaskPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is DispatchWorkflowCommand -> { + DispatchWorkflowPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is InlineTaskCommand -> { + InlineTaskPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is ReceiveSignalCommand -> { + ReceiveSignalPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is SendSignalCommand -> { + SendSignalPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is StartDurationTimerCommand -> { + StartDurationTimerPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + is StartInstantTimerCommand -> { + StartInstantTimerPastCommand(CommandId(), commandPosition, commandSimpleName, commandStatus, command) + } + } + } fun isTerminated() = commandStatus.isTerminated() - fun isSameThan(newCommand: NewCommand, mode: WorkflowChangeCheckMode): Boolean = - newCommand.commandPosition == commandPosition && + fun isSameThan(other: PastCommand, mode: WorkflowChangeCheckMode): Boolean = + other.commandPosition == commandPosition && when (mode) { - WorkflowChangeCheckMode.NONE -> true - WorkflowChangeCheckMode.SIMPLE_NAME_ONLY -> - newCommand.commandType == commandType && - newCommand.commandSimpleName == commandSimpleName - WorkflowChangeCheckMode.ALL -> - newCommand.commandHash == commandHash + NONE -> + true + SIMPLE -> + other.command::class == command::class && other.commandSimpleName == commandSimpleName + STRICT -> + command.isSameThan(other.command) } } + +@Serializable @SerialName("DispatchTaskPastCommand") +data class DispatchTaskPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: DispatchTaskCommand +) : PastCommand() + +@Serializable @SerialName("DispatchWorkflowPastCommand") +data class DispatchWorkflowPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: DispatchWorkflowCommand +) : PastCommand() + +@Serializable @SerialName("DispatchMethodPastCommand") +data class DispatchMethodPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: DispatchMethodCommand +) : PastCommand() + +@Serializable @SerialName("InlineTaskPastCommand") +data class InlineTaskPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: InlineTaskCommand +) : PastCommand() + +@Serializable @SerialName("ReceiveSignalPastCommand") +data class ReceiveSignalPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: ReceiveSignalCommand +) : PastCommand() + +@Serializable @SerialName("SendSignalPastCommand") +data class SendSignalPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: SendSignalCommand +) : PastCommand() + +@Serializable @SerialName("StartDurationTimerPastCommand") +data class StartDurationTimerPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: StartDurationTimerCommand +) : PastCommand() + +@Serializable @SerialName("StartInstantTimerPastCommand") +data class StartInstantTimerPastCommand( + override val commandId: CommandId, + override val commandPosition: MethodRunPosition, + override val commandSimpleName: CommandSimpleName, + override var commandStatus: CommandStatus, + override val command: StartInstantTimerCommand +) : PastCommand() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRun.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRun.kt index 9199a4f87..bd6b3eed1 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRun.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRun.kt @@ -25,13 +25,15 @@ package io.infinitic.common.workflows.data.methodRuns -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName +import io.infinitic.common.data.ReturnValue import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.data.methods.MethodReturnValue import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandType +import io.infinitic.common.workflows.data.commands.DispatchMethodCommand +import io.infinitic.common.workflows.data.commands.DispatchTaskCommand +import io.infinitic.common.workflows.data.commands.DispatchWorkflowCommand import io.infinitic.common.workflows.data.commands.PastCommand import io.infinitic.common.workflows.data.properties.PropertyHash import io.infinitic.common.workflows.data.properties.PropertyName @@ -55,42 +57,39 @@ data class MethodRun( val methodName: MethodName, val methodParameterTypes: MethodParameterTypes?, val methodParameters: MethodParameters, - var methodReturnValue: MethodReturnValue? = null, - val workflowTaskIndexAtStart: WorkflowTaskIndex = WorkflowTaskIndex(0), + var methodReturnValue: ReturnValue? = null, + val workflowTaskIndexAtStart: WorkflowTaskIndex, val propertiesNameHashAtStart: Map, val pastCommands: MutableList = mutableListOf(), - val pastSteps: MutableList = mutableListOf() + val pastSteps: MutableList = mutableListOf(), + var currentStep: PastStep? = null ) { /** * Retrieve step by position - * This value could be null (eg. for starting position of async command) */ fun getStepByPosition(position: MethodRunPosition): PastStep? = pastSteps.firstOrNull { it.stepPosition == position } - /** - * Retrieve step by position - * This value could be null (eg. for starting position of async command) - */ - fun getCommandByPosition(position: MethodRunPosition): PastCommand? = - pastCommands.firstOrNull { it.commandPosition == position } - /** * Retrieve pastCommand per commandId. - * This value should always be present. */ - fun getPastCommand(commandId: CommandId): PastCommand = - pastCommands.first { it.commandId == commandId } + fun getPastCommand(commandId: CommandId): PastCommand? = + pastCommands.firstOrNull { it.commandId == commandId } /** * To be terminated, a method should provide a return value and have all steps and branches terminated. - * We add a constraint on child-workflows as well to ensure that this methodRun is not deleted - * before child workflows are completed, so that child workflows can be canceled - * if the workflow itself is canceled + * We ensure that this methodRun is not deleted before children are terminated + * - child workflows should be canceled if the workflow itself is canceled + * - other methods could use reference to deferred owned by current method */ fun isTerminated() = methodReturnValue != null && pastSteps.all { it.isTerminated() } && - pastCommands - .filter { it.commandType == CommandType.DISPATCH_CHILD_WORKFLOW || it.commandType == CommandType.START_ASYNC } - .all { it.isTerminated() } + pastCommands.filter { + when (it.command) { + is DispatchWorkflowCommand, + is DispatchMethodCommand, + is DispatchTaskCommand -> true + else -> false + } + }.all { it.isTerminated() } } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunId.kt index e94d0087f..eae2c71bc 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunId.kt @@ -25,21 +25,18 @@ package io.infinitic.common.workflows.data.methodRuns -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer +import io.infinitic.common.workflows.data.commands.CommandId +import io.infinitic.common.workflows.data.workflows.WorkflowId import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = MethodRunIdSerializer::class) -data class MethodRunId(override val id: UUID = UUID.randomUUID()) : Id(id) +@JvmInline @Serializable +value class MethodRunId(private val id: String = UUID.randomUUID().toString()) { + companion object { + fun from(workflowId: WorkflowId) = MethodRunId(workflowId.toString()) -object MethodRunIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MethodRunId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: MethodRunId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = MethodRunId(UUID.fromString(decoder.decodeString())) + fun from(commandId: CommandId) = MethodRunId(commandId.toString()) + } + + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunPosition.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunPosition.kt index e7cd0294a..7a087e194 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunPosition.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/methodRuns/MethodRunPosition.kt @@ -25,27 +25,15 @@ package io.infinitic.common.workflows.data.methodRuns -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -@Serializable(with = MethodRunPositionSerializer::class) -data class MethodRunPosition(val position: String) { +@JvmInline @Serializable +value class MethodRunPosition private constructor(private val index: Int) { companion object { - const val POSITION_SEPARATOR = "." + fun new() = MethodRunPosition(-1) } - override fun toString() = position + override fun toString() = "$index" - fun isOnMainPath() = ! position.contains(POSITION_SEPARATOR) -} - -object MethodRunPositionSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MethodRunPosition", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: MethodRunPosition) { encoder.encodeString(value.position) } - override fun deserialize(decoder: Decoder) = MethodRunPosition(decoder.decodeString()) + fun next() = MethodRunPosition(index + 1) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/properties/PropertyValue.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/properties/PropertyValue.kt index ebc0b6c61..6f5051514 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/properties/PropertyValue.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/properties/PropertyValue.kt @@ -25,7 +25,6 @@ package io.infinitic.common.workflows.data.properties -import io.infinitic.common.data.Data import io.infinitic.common.serDe.SerializedData import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -34,11 +33,15 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(with = PropertyValueSerializer::class) -data class PropertyValue(override val serializedData: SerializedData) : Data(serializedData) { +data class PropertyValue(val serializedData: SerializedData) { companion object { fun from(data: Any?) = PropertyValue(SerializedData.from(data)) } + override fun toString() = serializedData.toString() + + fun value(): Any? = serializedData.deserialize() + fun hash() = PropertyHash(serializedData.hash()) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/PastStep.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/PastStep.kt index 2cdc92931..818152499 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/PastStep.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/PastStep.kt @@ -32,8 +32,9 @@ import io.infinitic.common.workflows.data.properties.PropertyHash import io.infinitic.common.workflows.data.properties.PropertyName import io.infinitic.common.workflows.data.steps.StepStatus.Canceled import io.infinitic.common.workflows.data.steps.StepStatus.Completed +import io.infinitic.common.workflows.data.steps.StepStatus.CurrentlyFailed import io.infinitic.common.workflows.data.steps.StepStatus.Failed -import io.infinitic.common.workflows.data.steps.StepStatus.OngoingFailure +import io.infinitic.common.workflows.data.steps.StepStatus.Unknown import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex import kotlinx.serialization.Serializable @@ -42,25 +43,34 @@ data class PastStep( val stepPosition: MethodRunPosition, val step: Step, val stepHash: StepHash, + val workflowTaskIndexAtStart: WorkflowTaskIndex, var stepStatus: StepStatus = StepStatus.Waiting, var propertiesNameHashAtTermination: Map? = null, var workflowTaskIndexAtTermination: WorkflowTaskIndex? = null ) { @JsonIgnore fun isTerminated() = - stepStatus is Completed || - stepStatus is Canceled || - stepStatus is Failed + stepStatus is Completed || stepStatus is Canceled || stepStatus is Failed || stepStatus is Unknown + + fun updateWith(pastCommand: PastCommand) { + step.updateWith(pastCommand.commandId, pastCommand.commandStatus) + stepStatus = step.status() + } fun isTerminatedBy(pastCommand: PastCommand): Boolean { // returns false if already terminated if (isTerminated()) return false - // apply update - step.update(pastCommand.commandId, pastCommand.commandStatus) - stepStatus = step.status() - // returns true only if newly terminated - return isTerminated() || stepStatus is OngoingFailure + + val isWaiting = stepStatus is StepStatus.Waiting + + // working on a copy to check without updating + with(copy()) { + // apply update + updateWith(pastCommand) + + return isTerminated() || (stepStatus is CurrentlyFailed && isWaiting) + } } - fun isSimilarTo(newStep: NewStep) = newStep.stepHash == stepHash + fun isSameThan(newStep: NewStep) = newStep.stepHash == stepHash } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/Step.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/Step.kt index 137415440..39b1503b7 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/Step.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/Step.kt @@ -25,6 +25,11 @@ package io.infinitic.common.workflows.data.steps +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonTypeInfo +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.serDe.SerializedData import io.infinitic.common.workflows.data.commands.CommandId import io.infinitic.common.workflows.data.commands.CommandStatus @@ -32,82 +37,93 @@ import io.infinitic.common.workflows.data.commands.CommandStatus.Canceled import io.infinitic.common.workflows.data.commands.CommandStatus.Completed import io.infinitic.common.workflows.data.commands.CommandStatus.CurrentlyFailed import io.infinitic.common.workflows.data.commands.CommandStatus.Running -import io.infinitic.common.workflows.data.commands.NewCommand +import io.infinitic.common.workflows.data.commands.CommandStatus.Unknown import io.infinitic.common.workflows.data.commands.PastCommand import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex -import io.infinitic.exceptions.thisShouldNotHappen +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.Int.Companion.MAX_VALUE @Serializable +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@class") sealed class Step { - fun isTerminated() = isTerminatedAt(WorkflowTaskIndex(MAX_VALUE)) + @JsonIgnore fun isTerminated() = isTerminatedAt(WorkflowTaskIndex(MAX_VALUE)) fun status() = statusAt(WorkflowTaskIndex(MAX_VALUE)) - abstract fun isTerminatedAt(index: WorkflowTaskIndex): Boolean + @JsonIgnore abstract fun isTerminatedAt(index: WorkflowTaskIndex): Boolean abstract fun statusAt(index: WorkflowTaskIndex): StepStatus - /* - * hash function is defined to exclude commandStatus and provide a hopefully unique hash linked to the structure of the step + /** + * hash function excludes commandStatus and provide a hopefully unique hash linked to the structure of the step */ abstract fun hash(): StepHash - @Serializable + @Serializable @SerialName("Step.Id") data class Id( - val commandId: CommandId, - var commandStatus: CommandStatus + val commandId: CommandId ) : Step() { + @JsonIgnore var commandStatus: CommandStatus = Running + + companion object { + fun from(pastCommand: PastCommand) = Id(pastCommand.commandId) + .apply { commandStatus = pastCommand.commandStatus } + + @JsonCreator @JvmStatic + // This is needed for Jackson deserialization, CommandId being an inline type + fun new(commandId: String) = Id(CommandId(commandId)) + } override fun hash() = StepHash(SerializedData.from(commandId).hash()) - override fun isTerminatedAt(index: WorkflowTaskIndex) = when (statusAt(index)) { + @JsonIgnore override fun isTerminatedAt(index: WorkflowTaskIndex) = when (statusAt(index)) { is StepStatus.Waiting -> false - is StepStatus.OngoingFailure -> true - is StepStatus.Completed -> true + is StepStatus.Unknown -> true is StepStatus.Canceled -> true + is StepStatus.CurrentlyFailed -> true is StepStatus.Failed -> thisShouldNotHappen() + is StepStatus.Completed -> true } override fun statusAt(index: WorkflowTaskIndex) = when (val status = commandStatus) { is Running -> StepStatus.Waiting - is Completed -> when (index >= status.completionWorkflowTaskIndex) { - true -> StepStatus.Completed(StepReturnValue.from(status.returnValue.get()), status.completionWorkflowTaskIndex) + is Unknown -> when (index >= status.unknowingWorkflowTaskIndex) { + true -> StepStatus.Unknown(status.unknownDeferredError, status.unknowingWorkflowTaskIndex) false -> StepStatus.Waiting } is Canceled -> when (index >= status.cancellationWorkflowTaskIndex) { - true -> StepStatus.Canceled(commandId, status.cancellationWorkflowTaskIndex) + true -> StepStatus.Canceled(status.canceledDeferredError, status.cancellationWorkflowTaskIndex) false -> StepStatus.Waiting } is CurrentlyFailed -> when (index >= status.failureWorkflowTaskIndex) { - true -> StepStatus.OngoingFailure(commandId, status.failureWorkflowTaskIndex) + true -> StepStatus.CurrentlyFailed(status.failedDeferredError, status.failureWorkflowTaskIndex) + false -> StepStatus.Waiting + } + is Completed -> when (index >= status.completionWorkflowTaskIndex) { + true -> StepStatus.Completed(status.returnValue, status.completionWorkflowTaskIndex) false -> StepStatus.Waiting } - } - - companion object { - fun from(newCommand: NewCommand) = Id(newCommand.commandId, Running) - fun from(pastCommand: PastCommand) = Id(pastCommand.commandId, pastCommand.commandStatus) } } - @Serializable + @Serializable @SerialName("Step.And") data class And(var steps: List) : Step() { override fun hash() = StepHash(SerializedData.from(steps.map { it.hash() }).hash()) - override fun isTerminatedAt(index: WorkflowTaskIndex) = + @JsonIgnore override fun isTerminatedAt(index: WorkflowTaskIndex) = this.steps.all { it.isTerminatedAt(index) } override fun statusAt(index: WorkflowTaskIndex): StepStatus { val statuses = steps.map { it.statusAt(index) } - // if at least one step is canceled or ongoingFailure, then And(...steps) is the first of them + // if at least one step is canceled or currentlyFailed, then And(...steps) is the first of them val firstTerminated = statuses - .filter { it is StepStatus.OngoingFailure && it is StepStatus.Canceled } + .filter { it is StepStatus.CurrentlyFailed || it is StepStatus.Canceled || it is StepStatus.Unknown } .minByOrNull { when (it) { - is StepStatus.OngoingFailure -> it.failureWorkflowTaskIndex + is StepStatus.CurrentlyFailed -> it.failureWorkflowTaskIndex is StepStatus.Canceled -> it.cancellationWorkflowTaskIndex + is StepStatus.Unknown -> it.unknowingWorkflowTaskIndex is StepStatus.Completed, is StepStatus.Failed, is StepStatus.Waiting -> thisShouldNotHappen() } } @@ -119,21 +135,21 @@ sealed class Step { // if all steps are completed, then And(...steps) is completed if (statuses.all { it is StepStatus.Completed }) { val maxIndex = statuses.maxOf { (it as StepStatus.Completed).completionWorkflowTaskIndex } - val results = statuses.map { (it as StepStatus.Completed).returnValue.get() } + val results = statuses.map { (it as StepStatus.Completed).returnValue.value() } - return StepStatus.Completed(StepReturnValue.from(results), maxIndex) + return StepStatus.Completed(ReturnValue.from(results), maxIndex) } thisShouldNotHappen() } } - @Serializable + @Serializable @SerialName("Step.Or") data class Or(var steps: List) : Step() { override fun hash() = StepHash(SerializedData.from(steps.map { it.hash() }).hash()) - override fun isTerminatedAt(index: WorkflowTaskIndex) = + @JsonIgnore override fun isTerminatedAt(index: WorkflowTaskIndex) = this.steps.any { it.isTerminatedAt(index) } override fun statusAt(index: WorkflowTaskIndex): StepStatus { @@ -151,8 +167,9 @@ sealed class Step { // all steps are neither completed, neither ongoing => canceled, failed based on last one val lastTerminated = statuses.maxByOrNull { when (it) { - is StepStatus.OngoingFailure -> it.failureWorkflowTaskIndex + is StepStatus.CurrentlyFailed -> it.failureWorkflowTaskIndex is StepStatus.Canceled -> it.cancellationWorkflowTaskIndex + is StepStatus.Unknown -> it.unknowingWorkflowTaskIndex is StepStatus.Completed, is StepStatus.Failed, is StepStatus.Waiting -> thisShouldNotHappen() } } @@ -165,11 +182,11 @@ sealed class Step { /* * Used in engine to update a step after having cancelled or completed a command */ - fun update(commandId: CommandId, commandStatus: CommandStatus): Step { + fun updateWith(commandId: CommandId, commandStatus: CommandStatus): Step { when (this) { is Id -> if (this.commandId == commandId) this.commandStatus = commandStatus - is And -> steps = steps.map { it.update(commandId, commandStatus) } - is Or -> steps = steps.map { it.update(commandId, commandStatus) } + is And -> steps = steps.map { it.updateWith(commandId, commandStatus) } + is Or -> steps = steps.map { it.updateWith(commandId, commandStatus) } } return this.resolveOr().compose() } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepId.kt index 2982e071c..9c217f2e1 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepId.kt @@ -25,21 +25,10 @@ package io.infinitic.common.workflows.data.steps -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = StepIdIdSerializer::class) -data class StepId(override val id: UUID = UUID.randomUUID()) : Id(id) - -object StepIdIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("StepId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: StepId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = StepId(UUID.fromString(decoder.decodeString())) +@JvmInline @Serializable +value class StepId(private val id: String = UUID.randomUUID().toString()) { + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepStatus.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepStatus.kt index d46eb9a12..437fffd4f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepStatus.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/steps/StepStatus.kt @@ -25,8 +25,12 @@ package io.infinitic.common.workflows.data.steps -import io.infinitic.common.workflows.data.commands.CommandId +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.errors.CanceledDeferredError +import io.infinitic.common.errors.FailedDeferredError +import io.infinitic.common.errors.UnknownDeferredError import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -37,32 +41,38 @@ sealed class StepStatus { override fun equals(other: Any?) = javaClass == other?.javaClass } - @Serializable - data class Completed( - val returnValue: StepReturnValue, - val completionWorkflowTaskIndex: WorkflowTaskIndex + @Serializable @SerialName("StepStatus.Unknown") + data class Unknown( + val unknownDeferredError: UnknownDeferredError, + val unknowingWorkflowTaskIndex: WorkflowTaskIndex ) : StepStatus() - @Serializable + @Serializable @SerialName("StepStatus.Canceled") data class Canceled( - val commandId: CommandId, + val canceledDeferredError: CanceledDeferredError, val cancellationWorkflowTaskIndex: WorkflowTaskIndex ) : StepStatus() - @Serializable + @Serializable @SerialName("StepStatus.Failed") data class Failed( - val commandId: CommandId, + val failedDeferredError: FailedDeferredError, val failureWorkflowTaskIndex: WorkflowTaskIndex ) : StepStatus() + @Serializable @SerialName("StepStatus.Completed") + data class Completed( + val returnValue: ReturnValue, + val completionWorkflowTaskIndex: WorkflowTaskIndex + ) : StepStatus() + /** * OngoingFailure is a transient status given when the failure of a task triggers the failure of a step * - if next workflowTask related to this branch run correctly (error caught in workflow code), status will eventually be Failed * - if not, task can be retried and status can transition to Completed */ - @Serializable - data class OngoingFailure( - val commandId: CommandId, + @Serializable @SerialName("StepStatus.CurrentlyFailed") + data class CurrentlyFailed( + val failedDeferredError: FailedDeferredError, val failureWorkflowTaskIndex: WorkflowTaskIndex ) : StepStatus() } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/timers/TimerId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/timers/TimerId.kt index d267dd22a..96957af2d 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/timers/TimerId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/timers/TimerId.kt @@ -25,21 +25,15 @@ package io.infinitic.common.workflows.data.timers -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer +import io.infinitic.common.workflows.data.commands.CommandId import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = TimerIdSerializer::class) -data class TimerId(override val id: UUID = UUID.randomUUID()) : Id(id) +@JvmInline @Serializable +value class TimerId(private val id: String = UUID.randomUUID().toString()) { + companion object { + fun from(commandId: CommandId) = TimerId(commandId.toString()) + } -object TimerIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("TimerId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: TimerId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = TimerId(UUID.fromString(decoder.decodeString())) + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskParameters.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskParameters.kt index 4152987da..407016335 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskParameters.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskParameters.kt @@ -26,7 +26,6 @@ package io.infinitic.common.workflows.data.workflowTasks import io.infinitic.common.workflows.data.methodRuns.MethodRun -import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition import io.infinitic.common.workflows.data.properties.PropertyHash import io.infinitic.common.workflows.data.properties.PropertyValue import io.infinitic.common.workflows.data.workflows.WorkflowId @@ -45,9 +44,5 @@ data class WorkflowTaskParameters( val workflowMeta: WorkflowMeta, val workflowPropertiesHashValue: Map, val workflowTaskIndex: WorkflowTaskIndex, - - val methodRun: MethodRun, - val targetPosition: MethodRunPosition = MethodRunPosition("") -) { - fun getFullMethodName() = "$workflowName::${methodRun.methodName}" -} + val methodRun: MethodRun +) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskReturnValue.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskReturnValue.kt index dffa56b8f..b2c1d4cc6 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskReturnValue.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflowTasks/WorkflowTaskReturnValue.kt @@ -25,8 +25,8 @@ package io.infinitic.common.workflows.data.workflowTasks -import io.infinitic.common.data.methods.MethodReturnValue -import io.infinitic.common.workflows.data.commands.NewCommand +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.workflows.data.commands.PastCommand import io.infinitic.common.workflows.data.properties.PropertyName import io.infinitic.common.workflows.data.properties.PropertyValue import io.infinitic.common.workflows.data.steps.NewStep @@ -34,8 +34,8 @@ import kotlinx.serialization.Serializable @Serializable data class WorkflowTaskReturnValue( - val newCommands: List, - val newSteps: List, + val newCommands: List, + val newStep: NewStep?, val properties: Map, - val methodReturnValue: MethodReturnValue? + val methodReturnValue: ReturnValue? ) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowId.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowId.kt index 5ee4ec86a..feca2de6b 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowId.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowId.kt @@ -25,21 +25,15 @@ package io.infinitic.common.workflows.data.workflows -import io.infinitic.common.data.Id -import kotlinx.serialization.KSerializer +import io.infinitic.common.workflows.data.commands.CommandId import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import java.util.UUID -@Serializable(with = WorkflowIdSerializer::class) -data class WorkflowId(override val id: UUID = UUID.randomUUID()) : Id(id) +@JvmInline @Serializable +value class WorkflowId(private val id: String = UUID.randomUUID().toString()) { + companion object { + fun from(commandId: CommandId) = WorkflowId(commandId.toString()) + } -object WorkflowIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("WorkflowId", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: WorkflowId) { encoder.encodeString("${value.id}") } - override fun deserialize(decoder: Decoder) = WorkflowId(UUID.fromString(decoder.decodeString())) + override fun toString() = id } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowMeta.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowMeta.kt index 74888329e..5c0efe8b7 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowMeta.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowMeta.kt @@ -35,7 +35,17 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(with = WorkflowMetaSerializer::class) -data class WorkflowMeta(val map: Map = mapOf()) : Map by map +data class WorkflowMeta(val map: Map = mapOf()) : Map by map { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as WorkflowMeta + if (map.keys != other.map.keys) return false + if (map.map { it.value.contentEquals(other.map[it.key]!!) }.any { !it }) return false + + return true + } +} object WorkflowMetaSerializer : KSerializer { val ser = MapSerializer(String.serializer(), ByteArraySerializer()) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowName.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowName.kt index ceebff009..d9651ea8f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowName.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowName.kt @@ -26,6 +26,7 @@ package io.infinitic.common.workflows.data.workflows import io.infinitic.common.data.Name +import io.infinitic.common.workflows.data.commands.CommandName import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -35,7 +36,11 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(with = WorkflowNameSerializer::class) -data class WorkflowName(override val name: String) : Name(name) +data class WorkflowName(override val name: String) : Name(name) { + companion object { + fun from(commandName: CommandName) = WorkflowName(commandName.toString()) + } +} object WorkflowNameSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("WorkflowName", PrimitiveKind.STRING) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowOptions.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowOptions.kt index 6e31560ef..3709288fb 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowOptions.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowOptions.kt @@ -25,14 +25,15 @@ package io.infinitic.common.workflows.data.workflows +import io.infinitic.common.data.JobOptions import kotlinx.serialization.Serializable @Serializable data class WorkflowOptions( - val workflowChangeCheckMode: WorkflowChangeCheckMode = WorkflowChangeCheckMode.ALL -) + val workflowChangeCheckMode: WorkflowChangeCheckMode = WorkflowChangeCheckMode.STRICT +) : JobOptions @Serializable enum class WorkflowChangeCheckMode { - NONE, SIMPLE_NAME_ONLY, ALL + NONE, SIMPLE, STRICT } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowReturnValue.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowReturnValue.kt new file mode 100644 index 000000000..137c084e8 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/data/workflows/WorkflowReturnValue.kt @@ -0,0 +1,37 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.workflows.data.workflows + +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.workflows.data.methodRuns.MethodRunId +import kotlinx.serialization.Serializable + +@Serializable +data class WorkflowReturnValue( + val workflowId: WorkflowId, + val methodRunId: MethodRunId, + val returnValue: ReturnValue +) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineEnvelope.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineEnvelope.kt index 562d1c4f1..25869ce99 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineEnvelope.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineEnvelope.kt @@ -34,34 +34,40 @@ import kotlinx.serialization.Serializable data class WorkflowEngineEnvelope( val workflowId: WorkflowId, val type: WorkflowEngineMessageType, + val dispatchWorkflow: DispatchWorkflow? = null, + val dispatchMethod: DispatchMethod? = null, val waitWorkflow: WaitWorkflow? = null, val cancelWorkflow: CancelWorkflow? = null, val retryWorkflowTask: RetryWorkflowTask? = null, val completeWorkflow: CompleteWorkflow? = null, - val sendToChannel: SendToChannel? = null, - val childWorkflowFailed: ChildWorkflowFailed? = null, - val childWorkflowCanceled: ChildWorkflowCanceled? = null, - val childWorkflowCompleted: ChildWorkflowCompleted? = null, + val sendSignal: SendSignal? = null, val timerCompleted: TimerCompleted? = null, - val dispatchWorkflow: DispatchWorkflow? = null, - val taskFailed: TaskFailed? = null, + val childMethodUnknown: ChildMethodUnknown? = null, + val childMethodCanceled: ChildMethodCanceled? = null, + val childMethodFailed: ChildMethodFailed? = null, + val childMethodCompleted: ChildMethodCompleted? = null, + val taskUnknown: TaskUnknown? = null, val taskCanceled: TaskCanceled? = null, + val taskFailed: TaskFailed? = null, val taskCompleted: TaskCompleted? = null ) : Envelope { init { val noNull = listOfNotNull( + dispatchWorkflow, + dispatchMethod, waitWorkflow, cancelWorkflow, retryWorkflowTask, completeWorkflow, - sendToChannel, - childWorkflowFailed, - childWorkflowCanceled, - childWorkflowCompleted, + sendSignal, timerCompleted, - dispatchWorkflow, - taskFailed, + childMethodUnknown, + childMethodFailed, + childMethodCanceled, + childMethodCompleted, + taskUnknown, taskCanceled, + taskFailed, taskCompleted ) @@ -84,6 +90,16 @@ data class WorkflowEngineEnvelope( companion object { fun from(msg: WorkflowEngineMessage) = when (msg) { + is DispatchWorkflow -> WorkflowEngineEnvelope( + msg.workflowId, + WorkflowEngineMessageType.DISPATCH_WORKFLOW, + dispatchWorkflow = msg + ) + is DispatchMethod -> WorkflowEngineEnvelope( + msg.workflowId, + WorkflowEngineMessageType.DISPATCH_METHOD, + dispatchMethod = msg + ) is WaitWorkflow -> WorkflowEngineEnvelope( msg.workflowId, WorkflowEngineMessageType.WAIT_WORKFLOW, @@ -104,46 +120,51 @@ data class WorkflowEngineEnvelope( WorkflowEngineMessageType.COMPLETE_WORKFLOW, completeWorkflow = msg ) - is SendToChannel -> WorkflowEngineEnvelope( + is SendSignal -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.EMIT_TO_CHANNEL, - sendToChannel = msg + WorkflowEngineMessageType.SEND_SIGNAL, + sendSignal = msg ) - is ChildWorkflowFailed -> WorkflowEngineEnvelope( + is TimerCompleted -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.CHILD_WORKFLOW_FAILED, - childWorkflowFailed = msg + WorkflowEngineMessageType.TIMER_COMPLETED, + timerCompleted = msg ) - is ChildWorkflowCanceled -> WorkflowEngineEnvelope( + is ChildMethodUnknown -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.CHILD_WORKFLOW_CANCELED, - childWorkflowCanceled = msg + WorkflowEngineMessageType.CHILD_WORKFLOW_UNKNOWN, + childMethodUnknown = msg ) - is ChildWorkflowCompleted -> WorkflowEngineEnvelope( + is ChildMethodCanceled -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.CHILD_WORKFLOW_COMPLETED, - childWorkflowCompleted = msg + WorkflowEngineMessageType.CHILD_WORKFLOW_CANCELED, + childMethodCanceled = msg ) - is TimerCompleted -> WorkflowEngineEnvelope( + is ChildMethodFailed -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.TIMER_COMPLETED, - timerCompleted = msg + WorkflowEngineMessageType.CHILD_WORKFLOW_FAILED, + childMethodFailed = msg ) - is DispatchWorkflow -> WorkflowEngineEnvelope( + is ChildMethodCompleted -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.DISPATCH_WORKFLOW, - dispatchWorkflow = msg + WorkflowEngineMessageType.CHILD_WORKFLOW_COMPLETED, + childMethodCompleted = msg ) - is TaskFailed -> WorkflowEngineEnvelope( + is TaskUnknown -> WorkflowEngineEnvelope( msg.workflowId, - WorkflowEngineMessageType.TASK_FAILED, - taskFailed = msg + WorkflowEngineMessageType.TASK_UNKNOWN, + taskUnknown = msg ) is TaskCanceled -> WorkflowEngineEnvelope( msg.workflowId, WorkflowEngineMessageType.TASK_CANCELED, taskCanceled = msg ) + is TaskFailed -> WorkflowEngineEnvelope( + msg.workflowId, + WorkflowEngineMessageType.TASK_FAILED, + taskFailed = msg + ) is TaskCompleted -> WorkflowEngineEnvelope( msg.workflowId, WorkflowEngineMessageType.TASK_COMPLETED, @@ -155,18 +176,21 @@ data class WorkflowEngineEnvelope( } override fun message(): WorkflowEngineMessage = when (type) { + WorkflowEngineMessageType.DISPATCH_WORKFLOW -> dispatchWorkflow!! + WorkflowEngineMessageType.DISPATCH_METHOD -> dispatchMethod!! WorkflowEngineMessageType.WAIT_WORKFLOW -> waitWorkflow!! WorkflowEngineMessageType.CANCEL_WORKFLOW -> cancelWorkflow!! WorkflowEngineMessageType.RETRY_WORKFLOW_TASK -> retryWorkflowTask!! WorkflowEngineMessageType.COMPLETE_WORKFLOW -> completeWorkflow!! - WorkflowEngineMessageType.EMIT_TO_CHANNEL -> sendToChannel!! - WorkflowEngineMessageType.CHILD_WORKFLOW_FAILED -> childWorkflowFailed!! - WorkflowEngineMessageType.CHILD_WORKFLOW_CANCELED -> childWorkflowCanceled!! - WorkflowEngineMessageType.CHILD_WORKFLOW_COMPLETED -> childWorkflowCompleted!! + WorkflowEngineMessageType.SEND_SIGNAL -> sendSignal!! WorkflowEngineMessageType.TIMER_COMPLETED -> timerCompleted!! - WorkflowEngineMessageType.DISPATCH_WORKFLOW -> dispatchWorkflow!! - WorkflowEngineMessageType.TASK_FAILED -> taskFailed!! + WorkflowEngineMessageType.CHILD_WORKFLOW_UNKNOWN -> childMethodUnknown!! + WorkflowEngineMessageType.CHILD_WORKFLOW_CANCELED -> childMethodCanceled!! + WorkflowEngineMessageType.CHILD_WORKFLOW_FAILED -> childMethodFailed!! + WorkflowEngineMessageType.CHILD_WORKFLOW_COMPLETED -> childMethodCompleted!! + WorkflowEngineMessageType.TASK_UNKNOWN -> taskUnknown!! WorkflowEngineMessageType.TASK_CANCELED -> taskCanceled!! + WorkflowEngineMessageType.TASK_FAILED -> taskFailed!! WorkflowEngineMessageType.TASK_COMPLETED -> taskCompleted!! } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessage.kt index 01e7b697a..47e0782f4 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessage.kt @@ -25,20 +25,26 @@ package io.infinitic.common.workflows.engine.messages -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId +import io.infinitic.common.data.ReturnValue import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.data.methods.MethodReturnValue -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.CanceledTaskError +import io.infinitic.common.errors.CanceledWorkflowError +import io.infinitic.common.errors.DeferredError +import io.infinitic.common.errors.FailedTaskError +import io.infinitic.common.errors.FailedWorkflowError +import io.infinitic.common.errors.UnknownTaskError +import io.infinitic.common.errors.UnknownWorkflowError import io.infinitic.common.messages.Message -import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskName -import io.infinitic.common.workflows.data.channels.ChannelEvent -import io.infinitic.common.workflows.data.channels.ChannelEventId -import io.infinitic.common.workflows.data.channels.ChannelEventType +import io.infinitic.common.tasks.data.TaskReturnValue import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.workflows.data.channels.ChannelSignal +import io.infinitic.common.workflows.data.channels.ChannelSignalId +import io.infinitic.common.workflows.data.channels.ChannelSignalType import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.timers.TimerId import io.infinitic.common.workflows.data.workflowTasks.WorkflowTask @@ -47,6 +53,7 @@ import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowMeta import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.data.workflows.WorkflowOptions +import io.infinitic.common.workflows.data.workflows.WorkflowReturnValue import io.infinitic.common.workflows.data.workflows.WorkflowTag import io.infinitic.common.workflows.engine.messages.interfaces.MethodRunMessage import io.infinitic.common.workflows.engine.messages.interfaces.TaskMessage @@ -55,129 +62,180 @@ import kotlinx.serialization.Serializable @Serializable sealed class WorkflowEngineMessage : Message { val messageId: MessageId = MessageId() + abstract val emitterName: ClientName abstract val workflowId: WorkflowId abstract val workflowName: WorkflowName override fun envelope() = WorkflowEngineEnvelope.from(this) - fun isWorkflowTask() = (this is TaskMessage) && this.taskName == TaskName(WorkflowTask::class.java.name) + fun isWorkflowTask() = (this is TaskMessage) && this.taskName() == TaskName(WorkflowTask::class.java.name) } @Serializable data class DispatchWorkflow( - override val workflowId: WorkflowId, override val workflowName: WorkflowName, - val clientName: ClientName, - val clientWaiting: Boolean, - var parentWorkflowId: WorkflowId?, - var parentWorkflowName: WorkflowName?, - var parentMethodRunId: MethodRunId?, + override val workflowId: WorkflowId, val methodName: MethodName, - val methodParameterTypes: MethodParameterTypes?, val methodParameters: MethodParameters, + val methodParameterTypes: MethodParameterTypes?, + val workflowOptions: WorkflowOptions, val workflowTags: Set, val workflowMeta: WorkflowMeta, - val workflowOptions: WorkflowOptions + var parentWorkflowName: WorkflowName?, + var parentWorkflowId: WorkflowId?, + var parentMethodRunId: MethodRunId?, + val clientWaiting: Boolean, + override val emitterName: ClientName ) : WorkflowEngineMessage() @Serializable -data class WaitWorkflow( +data class DispatchMethod( + override val workflowName: WorkflowName, override val workflowId: WorkflowId, + val methodRunId: MethodRunId, + val methodName: MethodName, + val methodParameters: MethodParameters, + val methodParameterTypes: MethodParameterTypes?, + var parentWorkflowId: WorkflowId?, + var parentWorkflowName: WorkflowName?, + var parentMethodRunId: MethodRunId?, + val clientWaiting: Boolean, + override val emitterName: ClientName, +) : WorkflowEngineMessage() + +@Serializable +data class WaitWorkflow( override val workflowName: WorkflowName, - val clientName: ClientName + override val workflowId: WorkflowId, + val methodRunId: MethodRunId, + override val emitterName: ClientName, ) : WorkflowEngineMessage() @Serializable data class CancelWorkflow( - override val workflowId: WorkflowId, override val workflowName: WorkflowName, - val reason: WorkflowCancellationReason + override val workflowId: WorkflowId, + val methodRunId: MethodRunId?, + val reason: WorkflowCancellationReason, + override val emitterName: ClientName ) : WorkflowEngineMessage() @Serializable data class RetryWorkflowTask( + override val workflowName: WorkflowName, override val workflowId: WorkflowId, - override val workflowName: WorkflowName + override val emitterName: ClientName ) : WorkflowEngineMessage() @Serializable data class CompleteWorkflow( - override val workflowId: WorkflowId, override val workflowName: WorkflowName, - val workflowReturnValue: MethodReturnValue + override val workflowId: WorkflowId, + val workflowReturnValue: ReturnValue, + override val emitterName: ClientName ) : WorkflowEngineMessage() @Serializable -data class SendToChannel( - val clientName: ClientName, - override val workflowId: WorkflowId, +data class SendSignal( override val workflowName: WorkflowName, - val channelEventId: ChannelEventId, + override val workflowId: WorkflowId, val channelName: ChannelName, - val channelEvent: ChannelEvent, - val channelEventTypes: Set + val channelSignalId: ChannelSignalId, + val channelSignal: ChannelSignal, + val channelSignalTypes: Set, + override val emitterName: ClientName ) : WorkflowEngineMessage() @Serializable -data class ChildWorkflowCanceled( - override val workflowId: WorkflowId, +data class TimerCompleted( override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - val childWorkflowId: WorkflowId, - val childWorkflowName: WorkflowName + val timerId: TimerId, + override val emitterName: ClientName ) : WorkflowEngineMessage(), MethodRunMessage @Serializable -data class ChildWorkflowFailed( - override val workflowId: WorkflowId, +data class ChildMethodUnknown( override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - val childWorkflowId: WorkflowId, - val childWorkflowError: Error + val childUnknownWorkflowError: UnknownWorkflowError, + override val emitterName: ClientName ) : WorkflowEngineMessage(), MethodRunMessage @Serializable -data class ChildWorkflowCompleted( - override val workflowId: WorkflowId, +data class ChildMethodCanceled( override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - val childWorkflowId: WorkflowId, - val childWorkflowReturnValue: MethodReturnValue + val childCanceledWorkflowError: CanceledWorkflowError, + override val emitterName: ClientName ) : WorkflowEngineMessage(), MethodRunMessage @Serializable -data class TimerCompleted( - override val workflowId: WorkflowId, +data class ChildMethodFailed( override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - val timerId: TimerId + val childFailedWorkflowError: FailedWorkflowError, + override val emitterName: ClientName ) : WorkflowEngineMessage(), MethodRunMessage @Serializable -data class TaskFailed( +data class ChildMethodCompleted( + override val workflowName: WorkflowName, override val workflowId: WorkflowId, + override val methodRunId: MethodRunId, + val childWorkflowReturnValue: WorkflowReturnValue, + override val emitterName: ClientName +) : WorkflowEngineMessage(), MethodRunMessage + +@Serializable +data class TaskUnknown( override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - override val taskId: TaskId, - override val taskName: TaskName, - val error: Error -) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage + val unknownTaskError: UnknownTaskError, + override val emitterName: ClientName +) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage { + override fun taskId() = unknownTaskError.taskId + override fun taskName() = unknownTaskError.taskName +} @Serializable data class TaskCanceled( + override val workflowName: WorkflowName, override val workflowId: WorkflowId, + override val methodRunId: MethodRunId, + val canceledTaskError: CanceledTaskError, + override val emitterName: ClientName +) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage { + override fun taskId() = canceledTaskError.taskId + override fun taskName() = canceledTaskError.taskName +} + +@Serializable +data class TaskFailed( override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - override val taskId: TaskId, - override val taskName: TaskName -) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage + val failedTaskError: FailedTaskError, + val deferredError: DeferredError?, + override val emitterName: ClientName +) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage { + override fun taskId() = failedTaskError.taskId + override fun taskName() = failedTaskError.taskName +} @Serializable data class TaskCompleted( - override val workflowId: WorkflowId, override val workflowName: WorkflowName, + override val workflowId: WorkflowId, override val methodRunId: MethodRunId, - override val taskId: TaskId, - override val taskName: TaskName, - val taskReturnValue: MethodReturnValue -) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage + val taskReturnValue: TaskReturnValue, + override val emitterName: ClientName +) : WorkflowEngineMessage(), TaskMessage, MethodRunMessage { + override fun taskId() = taskReturnValue.taskId + override fun taskName() = taskReturnValue.taskName +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessageType.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessageType.kt index 23118b8d7..c92595086 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessageType.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/WorkflowEngineMessageType.kt @@ -33,13 +33,16 @@ enum class WorkflowEngineMessageType { CANCEL_WORKFLOW, RETRY_WORKFLOW_TASK, COMPLETE_WORKFLOW, - EMIT_TO_CHANNEL, - CHILD_WORKFLOW_FAILED, + SEND_SIGNAL, + DISPATCH_WORKFLOW, + DISPATCH_METHOD, + TIMER_COMPLETED, + CHILD_WORKFLOW_UNKNOWN, CHILD_WORKFLOW_CANCELED, + CHILD_WORKFLOW_FAILED, CHILD_WORKFLOW_COMPLETED, - TIMER_COMPLETED, - DISPATCH_WORKFLOW, - TASK_FAILED, + TASK_UNKNOWN, TASK_CANCELED, + TASK_FAILED, TASK_COMPLETED } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/interfaces/TaskMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/interfaces/TaskMessage.kt index 25960c8a1..87fc68b39 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/interfaces/TaskMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/messages/interfaces/TaskMessage.kt @@ -29,6 +29,6 @@ import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskName interface TaskMessage { - val taskId: TaskId - val taskName: TaskName + fun taskId(): TaskId + fun taskName(): TaskName } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/state/WorkflowState.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/state/WorkflowState.kt index 72f6ca4b6..7ac4182e3 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/state/WorkflowState.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/engine/state/WorkflowState.kt @@ -27,11 +27,12 @@ package io.infinitic.common.workflows.engine.state import io.infinitic.common.data.MessageId import io.infinitic.common.data.MillisInstant +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.serDe.avro.AvroSerDe import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.workflows.data.channels.ReceivingChannel import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandType +import io.infinitic.common.workflows.data.commands.PastCommand import io.infinitic.common.workflows.data.methodRuns.MethodRun import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition @@ -45,7 +46,6 @@ import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.data.workflows.WorkflowOptions import io.infinitic.common.workflows.data.workflows.WorkflowTag import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage -import io.infinitic.exceptions.thisShouldNotHappen import kotlinx.serialization.Serializable @Serializable @@ -98,10 +98,9 @@ data class WorkflowState( /** * In some situations, we know that multiples branches must be processed. * As WorkflowTask handles branch one by one, we orderly buffer these branches here - * (it happens when a workflowTask decide to launch more than one async branch, - * or when more than one branch' steps are completed by the same message) + * (it happens when a terminated command completes currentStep on multiple MethodRuns) */ - val runningMethodRunBufferedCommands: MutableList = mutableListOf(), + val runningTerminatedCommands: MutableList = mutableListOf(), /** * Instant when WorkflowTask currently running was triggered @@ -150,6 +149,13 @@ data class WorkflowState( fun getMethodRun(methodRunId: MethodRunId) = methodRuns.firstOrNull() { it.methodRunId == methodRunId } + fun getPastCommand(commandId: CommandId, methodRun: MethodRun): PastCommand = + methodRun.getPastCommand(commandId) + // if we do not find in this methodRun, then search within others + ?: methodRuns.map { it.getPastCommand(commandId) }.firstOrNull() { it != null } + // methodRun should not be deleted if a step is still running + ?: thisShouldNotHappen() + fun removeMethodRun(methodRun: MethodRun) { methodRuns.remove(methodRun) @@ -170,18 +176,6 @@ data class WorkflowState( removeUnusedPropertyHash() } - fun getMainMethodRun() = methodRuns.find { it.methodRunId.id == workflowId.id } - - /** - * true if the current workflow task is on main path - */ - fun isRunningWorkflowTaskOnMainPath(): Boolean { - val p = runningMethodRunPosition ?: throw thisShouldNotHappen() - - return p.isOnMainPath() && - getRunningMethodRun().getCommandByPosition(p)?.commandType != CommandType.START_ASYNC - } - /** * After completion or cancellation of methodRuns, we can clean up the properties not used anymore */ @@ -193,9 +187,6 @@ data class WorkflowState( methodRun.pastSteps.forEach { pastStep -> pastStep.propertiesNameHashAtTermination?.forEach { propertyHashes.add(it.value) } } - methodRun.pastCommands.forEach { pastCommand -> - pastCommand.propertiesNameHashAtStart?.forEach { propertyHashes.add(it.value) } - } methodRun.propertiesNameHashAtStart.forEach { propertyHashes.add(it.value) } } currentPropertiesNameHash.forEach { propertyHashes.add(it.value) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/executors/parser/Parser.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/executors/parser/Parser.kt index 12cda2e63..f1593b60f 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/executors/parser/Parser.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/executors/parser/Parser.kt @@ -25,9 +25,9 @@ package io.infinitic.common.workflows.executors.parser +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.workflows.data.properties.PropertyName import io.infinitic.common.workflows.data.properties.PropertyValue -import io.infinitic.exceptions.thisShouldNotHappen import kotlin.reflect.KProperty1 import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.javaField @@ -36,7 +36,7 @@ fun setPropertiesToObject(obj: T, values: Map properties.find { it.name == name.name } - ?.let { setProperty(obj, it, value.get()) } + ?.let { setProperty(obj, it, value.value()) } ?: thisShouldNotHappen("Trying to set unknown property ${obj::class.java.name}:${name.name}") } } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineEnvelope.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineEnvelope.kt index a4acf84c0..4495f3b42 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineEnvelope.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineEnvelope.kt @@ -33,21 +33,24 @@ import kotlinx.serialization.Serializable data class WorkflowTagEngineEnvelope( val name: String, val type: WorkflowTagEngineMessageType, - val addWorkflowTag: AddWorkflowTag? = null, - val removeWorkflowTag: RemoveWorkflowTag? = null, - val sendToChannelPerTag: SendToChannelPerTag? = null, - val cancelWorkflowPerTag: CancelWorkflowPerTag? = null, - val retryWorkflowTaskPerTag: RetryWorkflowTaskPerTag? = null, - val getWorkflowIds: GetWorkflowIds? = null, + val addTagToWorkflow: AddTagToWorkflow? = null, + val removeTagFromWorkflow: RemoveTagFromWorkflow? = null, + val sendSignalByTag: SendSignalByTag? = null, + val cancelWorkflowByTag: CancelWorkflowByTag? = null, + val retryWorkflowTaskByTag: RetryWorkflowTaskByTag? = null, + val dispatchMethodByTag: DispatchMethodByTag? = null, + val getWorkflowIdsByTag: GetWorkflowIdsByTag? = null, ) : Envelope { + init { val noNull = listOfNotNull( - addWorkflowTag, - removeWorkflowTag, - sendToChannelPerTag, - cancelWorkflowPerTag, - retryWorkflowTaskPerTag, - getWorkflowIds + addTagToWorkflow, + removeTagFromWorkflow, + sendSignalByTag, + cancelWorkflowByTag, + retryWorkflowTaskByTag, + dispatchMethodByTag, + getWorkflowIdsByTag ) require(noNull.size == 1) @@ -57,35 +60,40 @@ data class WorkflowTagEngineEnvelope( companion object { fun from(msg: WorkflowTagEngineMessage) = when (msg) { - is AddWorkflowTag -> WorkflowTagEngineEnvelope( + is AddTagToWorkflow -> WorkflowTagEngineEnvelope( + "${msg.workflowName}", + WorkflowTagEngineMessageType.ADD_TAG_TO_WORKFLOW, + addTagToWorkflow = msg + ) + is RemoveTagFromWorkflow -> WorkflowTagEngineEnvelope( "${msg.workflowName}", - WorkflowTagEngineMessageType.ADD_WORKFLOW_TAG, - addWorkflowTag = msg + WorkflowTagEngineMessageType.REMOVE_TAG_FROM_WORKFLOW, + removeTagFromWorkflow = msg ) - is RemoveWorkflowTag -> WorkflowTagEngineEnvelope( + is SendSignalByTag -> WorkflowTagEngineEnvelope( "${msg.workflowName}", - WorkflowTagEngineMessageType.REMOVE_WORKFLOW_TAG, - removeWorkflowTag = msg + WorkflowTagEngineMessageType.SEND_SIGNAL_BY_TAG, + sendSignalByTag = msg ) - is SendToChannelPerTag -> WorkflowTagEngineEnvelope( + is CancelWorkflowByTag -> WorkflowTagEngineEnvelope( "${msg.workflowName}", - WorkflowTagEngineMessageType.SEND_TO_CHANNEL_PER_TAG, - sendToChannelPerTag = msg + WorkflowTagEngineMessageType.CANCEL_WORKFLOW_BY_TAG, + cancelWorkflowByTag = msg ) - is CancelWorkflowPerTag -> WorkflowTagEngineEnvelope( + is RetryWorkflowTaskByTag -> WorkflowTagEngineEnvelope( "${msg.workflowName}", - WorkflowTagEngineMessageType.CANCEL_WORKFLOW_PER_TAG, - cancelWorkflowPerTag = msg + WorkflowTagEngineMessageType.RETRY_WORKFLOW_TASK_BY_TAG, + retryWorkflowTaskByTag = msg ) - is RetryWorkflowTaskPerTag -> WorkflowTagEngineEnvelope( + is DispatchMethodByTag -> WorkflowTagEngineEnvelope( "${msg.workflowName}", - WorkflowTagEngineMessageType.RETRY_WORKFLOW_TASK_PER_TAG, - retryWorkflowTaskPerTag = msg + WorkflowTagEngineMessageType.DISPATCH_METHOD_BY_TAG, + dispatchMethodByTag = msg ) - is GetWorkflowIds -> WorkflowTagEngineEnvelope( + is GetWorkflowIdsByTag -> WorkflowTagEngineEnvelope( "${msg.workflowName}", - WorkflowTagEngineMessageType.GET_WORKFLOW_IDS, - getWorkflowIds = msg + WorkflowTagEngineMessageType.GET_WORKFLOW_IDS_BY_TAG, + getWorkflowIdsByTag = msg ) } @@ -93,12 +101,13 @@ data class WorkflowTagEngineEnvelope( } override fun message() = when (type) { - WorkflowTagEngineMessageType.ADD_WORKFLOW_TAG -> addWorkflowTag!! - WorkflowTagEngineMessageType.REMOVE_WORKFLOW_TAG -> removeWorkflowTag!! - WorkflowTagEngineMessageType.SEND_TO_CHANNEL_PER_TAG -> sendToChannelPerTag!! - WorkflowTagEngineMessageType.CANCEL_WORKFLOW_PER_TAG -> cancelWorkflowPerTag!! - WorkflowTagEngineMessageType.RETRY_WORKFLOW_TASK_PER_TAG -> retryWorkflowTaskPerTag!! - WorkflowTagEngineMessageType.GET_WORKFLOW_IDS -> getWorkflowIds!! + WorkflowTagEngineMessageType.ADD_TAG_TO_WORKFLOW -> addTagToWorkflow!! + WorkflowTagEngineMessageType.REMOVE_TAG_FROM_WORKFLOW -> removeTagFromWorkflow!! + WorkflowTagEngineMessageType.SEND_SIGNAL_BY_TAG -> sendSignalByTag!! + WorkflowTagEngineMessageType.CANCEL_WORKFLOW_BY_TAG -> cancelWorkflowByTag!! + WorkflowTagEngineMessageType.RETRY_WORKFLOW_TASK_BY_TAG -> retryWorkflowTaskByTag!! + WorkflowTagEngineMessageType.DISPATCH_METHOD_BY_TAG -> dispatchMethodByTag!! + WorkflowTagEngineMessageType.GET_WORKFLOW_IDS_BY_TAG -> getWorkflowIdsByTag!! } fun toByteArray() = AvroSerDe.writeBinary(this, serializer()) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessage.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessage.kt index 82363c1df..d9a14e783 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessage.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessage.kt @@ -25,13 +25,17 @@ package io.infinitic.common.workflows.tags.messages -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId +import io.infinitic.common.data.methods.MethodName +import io.infinitic.common.data.methods.MethodParameterTypes +import io.infinitic.common.data.methods.MethodParameters import io.infinitic.common.messages.Message -import io.infinitic.common.workflows.data.channels.ChannelEvent -import io.infinitic.common.workflows.data.channels.ChannelEventId -import io.infinitic.common.workflows.data.channels.ChannelEventType import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.workflows.data.channels.ChannelSignal +import io.infinitic.common.workflows.data.channels.ChannelSignalId +import io.infinitic.common.workflows.data.channels.ChannelSignalType +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflows.WorkflowCancellationReason import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowName @@ -41,6 +45,7 @@ import kotlinx.serialization.Serializable @Serializable sealed class WorkflowTagEngineMessage : Message { val messageId = MessageId() + abstract val emitterName: ClientName abstract val workflowTag: WorkflowTag abstract val workflowName: WorkflowName @@ -48,47 +53,68 @@ sealed class WorkflowTagEngineMessage : Message { } @Serializable -data class SendToChannelPerTag( - override val workflowTag: WorkflowTag, +data class SendSignalByTag( override val workflowName: WorkflowName, - val clientName: ClientName, - val clientWaiting: Boolean, - val channelEventId: ChannelEventId, + override val workflowTag: WorkflowTag, val channelName: ChannelName, - val channelEvent: ChannelEvent, - val channelEventTypes: Set + val channelSignalId: ChannelSignalId, + val channelSignal: ChannelSignal, + val channelSignalTypes: Set, + var emitterWorkflowId: WorkflowId?, + override val emitterName: ClientName ) : WorkflowTagEngineMessage() @Serializable -data class CancelWorkflowPerTag( - override val workflowTag: WorkflowTag, +data class CancelWorkflowByTag( override val workflowName: WorkflowName, - val reason: WorkflowCancellationReason + override val workflowTag: WorkflowTag, + val reason: WorkflowCancellationReason, + var emitterWorkflowId: WorkflowId?, + override val emitterName: ClientName ) : WorkflowTagEngineMessage() @Serializable -data class RetryWorkflowTaskPerTag( +data class RetryWorkflowTaskByTag( + override val workflowName: WorkflowName, override val workflowTag: WorkflowTag, - override val workflowName: WorkflowName + var emitterWorkflowId: WorkflowId?, + override val emitterName: ClientName, ) : WorkflowTagEngineMessage() @Serializable -data class AddWorkflowTag( - override val workflowTag: WorkflowTag, +data class AddTagToWorkflow( override val workflowName: WorkflowName, + override val workflowTag: WorkflowTag, val workflowId: WorkflowId, + override val emitterName: ClientName, ) : WorkflowTagEngineMessage() @Serializable -data class RemoveWorkflowTag( - override val workflowTag: WorkflowTag, +data class RemoveTagFromWorkflow( override val workflowName: WorkflowName, + override val workflowTag: WorkflowTag, val workflowId: WorkflowId, + override val emitterName: ClientName, ) : WorkflowTagEngineMessage() @Serializable -data class GetWorkflowIds( +data class GetWorkflowIdsByTag( + override val workflowName: WorkflowName, override val workflowTag: WorkflowTag, + override val emitterName: ClientName, +) : WorkflowTagEngineMessage() + +@Serializable +data class DispatchMethodByTag( override val workflowName: WorkflowName, - val clientName: ClientName + override val workflowTag: WorkflowTag, + var parentWorkflowId: WorkflowId?, + var parentWorkflowName: WorkflowName?, + var parentMethodRunId: MethodRunId?, + val methodRunId: MethodRunId, + val methodName: MethodName, + val methodParameterTypes: MethodParameterTypes?, + val methodParameters: MethodParameters, + val clientWaiting: Boolean, + override val emitterName: ClientName, ) : WorkflowTagEngineMessage() diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessageType.kt b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessageType.kt index 3b4a28e09..741a48096 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessageType.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/common/workflows/tags/messages/WorkflowTagEngineMessageType.kt @@ -26,10 +26,11 @@ package io.infinitic.common.workflows.tags.messages enum class WorkflowTagEngineMessageType { - ADD_WORKFLOW_TAG, - REMOVE_WORKFLOW_TAG, - SEND_TO_CHANNEL_PER_TAG, - CANCEL_WORKFLOW_PER_TAG, - RETRY_WORKFLOW_TASK_PER_TAG, - GET_WORKFLOW_IDS + ADD_TAG_TO_WORKFLOW, + REMOVE_TAG_FROM_WORKFLOW, + SEND_SIGNAL_BY_TAG, + CANCEL_WORKFLOW_BY_TAG, + RETRY_WORKFLOW_TASK_BY_TAG, + DISPATCH_METHOD_BY_TAG, + GET_WORKFLOW_IDS_BY_TAG } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/DeferredException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/DeferredException.kt new file mode 100644 index 000000000..4f4dfac5e --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/DeferredException.kt @@ -0,0 +1,367 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.exceptions + +import io.infinitic.common.errors.CanceledDeferredError +import io.infinitic.common.errors.CanceledTaskError +import io.infinitic.common.errors.CanceledWorkflowError +import io.infinitic.common.errors.DeferredError +import io.infinitic.common.errors.FailedDeferredError +import io.infinitic.common.errors.FailedTaskError +import io.infinitic.common.errors.FailedWorkflowError +import io.infinitic.common.errors.FailedWorkflowTaskError +import io.infinitic.common.errors.TimedOutDeferredError +import io.infinitic.common.errors.TimedOutTaskError +import io.infinitic.common.errors.TimedOutWorkflowError +import io.infinitic.common.errors.UnknownDeferredError +import io.infinitic.common.errors.UnknownTaskError +import io.infinitic.common.errors.UnknownWorkflowError + +sealed class DeferredException : kotlin.RuntimeException() { + companion object { + fun from(error: DeferredError) = when (error) { + is UnknownDeferredError -> UnknownDeferredException.from(error) + is TimedOutDeferredError -> TimedOutDeferredException.from(error) + is CanceledDeferredError -> CanceledDeferredException.from(error) + is FailedDeferredError -> FailedDeferredException.from(error) + } + } +} + +sealed class UnknownDeferredException : DeferredException() { + companion object { + fun from(error: UnknownDeferredError) = when (error) { + is UnknownTaskError -> UnknownTaskException.from(error) + is UnknownWorkflowError -> UnknownWorkflowException.from(error) + } + } +} + +sealed class TimedOutDeferredException : DeferredException() { + companion object { + fun from(error: TimedOutDeferredError) = when (error) { + is TimedOutTaskError -> TimedOutTaskException.from(error) + is TimedOutWorkflowError -> TimedOutWorkflowException.from(error) + } + } +} + +sealed class CanceledDeferredException : DeferredException() { + companion object { + fun from(error: CanceledDeferredError) = when (error) { + is CanceledTaskError -> CanceledTaskException.from(error) + is CanceledWorkflowError -> CanceledWorkflowException.from(error) + } + } +} + +sealed class FailedDeferredException : DeferredException() { + companion object { + fun from(error: FailedDeferredError) = when (error) { + is FailedTaskError -> FailedTaskException.from(error) + is FailedWorkflowError -> FailedWorkflowException.from(error) + is FailedWorkflowTaskError -> FailedWorkflowTaskException.from(error) + } + } +} + +/** + * Exception occurring when waiting for an unknown task + */ +class UnknownTaskException( + /** + * Name of the canceled task + */ + val taskName: String, + + /** + * Id of the canceled task + */ + val taskId: String, +) : UnknownDeferredException() { + companion object { + fun from(error: UnknownTaskError) = UnknownTaskException( + taskName = error.taskName.toString(), + taskId = error.taskId.toString() + ) + } +} + +/** + * Exception occurring when waiting for an unknown workflow + */ +class UnknownWorkflowException( + /** + * Name of the canceled child workflow + */ + val workflowName: String, + + /** + * Id of the canceled child workflow + */ + val workflowId: String, + + /** + * Id of the methodRun + */ + val methodRunId: String? +) : UnknownDeferredException() { + companion object { + fun from(error: UnknownWorkflowError) = UnknownWorkflowException( + workflowName = error.workflowName.toString(), + workflowId = error.workflowId.toString(), + methodRunId = error.methodRunId?.toString() + ) + } +} + +/** + * Exception occurring when waiting for a timed-out task + */ +class TimedOutTaskException( + /** + * Name of the canceled task + */ + val taskName: String, + + /** + * Id of the canceled task + */ + val taskId: String, + + /** + * Method called + */ + val methodName: String, + +) : TimedOutDeferredException() { + companion object { + fun from(error: TimedOutTaskError) = TimedOutTaskException( + taskName = error.taskName.toString(), + taskId = error.taskId.toString(), + methodName = error.methodName.toString() + ) + } +} + +/** + * Error occurring when waiting for an timed-out workflow + */ +class TimedOutWorkflowException( + /** + * Name of the canceled child workflow + */ + val workflowName: String, + + /** + * Id of the canceled child workflow + */ + val workflowId: String, + + /** + * Method called + */ + val methodName: String, + + /** + * Id of the methodRun + */ + val methodRunId: String? +) : TimedOutDeferredException() { + companion object { + fun from(error: TimedOutWorkflowError) = TimedOutWorkflowException( + workflowName = error.workflowName.toString(), + workflowId = error.workflowId.toString(), + methodName = error.methodName.toString(), + methodRunId = error.methodRunId?.toString(), + ) + } +} + +/** + * Exception occurring when waiting for a canceled task + */ +class CanceledTaskException( + /** + * Name of the canceled task + */ + val taskName: String, + + /** + * Id of the canceled task + */ + val taskId: String, + + /** + * Method called + */ + val methodName: String, + +) : CanceledDeferredException() { + companion object { + fun from(error: CanceledTaskError) = CanceledTaskException( + taskName = error.taskName.toString(), + taskId = error.taskId.toString(), + methodName = error.methodName.toString() + ) + } +} + +/** + * Exception occurring when waiting for a canceled workflow + */ +class CanceledWorkflowException( + /** + * Name of the canceled child workflow + */ + val workflowName: String, + + /** + * Id of the canceled child workflow + */ + val workflowId: String, + + /** + * Id of the methodRun + */ + val methodRunId: String? +) : CanceledDeferredException() { + companion object { + fun from(error: CanceledWorkflowError) = CanceledWorkflowException( + workflowName = error.workflowName.toString(), + workflowId = error.workflowId.toString(), + methodRunId = error.methodRunId?.toString(), + ) + } +} + +/** + * Exception occurring when waiting fora failed task + */ +class FailedTaskException( + /** + * Name of the task where the error occurred + */ + val taskName: String, + + /** + * Id of the task where the error occurred + */ + val taskId: String, + + /** + * Method called where the error occurred + */ + val methodName: String, + + /** + * cause of the error + */ + val workerException: WorkerException +) : FailedDeferredException() { + companion object { + fun from(error: FailedTaskError) = FailedTaskException( + taskName = error.taskName.toString(), + taskId = error.taskId.toString(), + methodName = error.methodName.toString(), + workerException = WorkerException.from(error.cause) + ) + } +} + +/** + * Exception occurring when waiting fora failed task + */ +class FailedWorkflowException( + /** + * Name of the workflow where the error occurred + */ + val workflowName: String, + + /** + * Id of the workflow where the error occurred + */ + val workflowId: String, + + /** + * Method called where the error occurred + */ + val methodName: String, + + /** + * Id of the methodRun + */ + val methodRunId: String?, + + /** + * cause of the error + */ + val deferredException: DeferredException +) : FailedDeferredException() { + companion object { + fun from(error: FailedWorkflowError): FailedWorkflowException = FailedWorkflowException( + workflowName = error.workflowName.toString(), + workflowId = error.workflowId.toString(), + methodName = error.methodName.toString(), + methodRunId = error.methodRunId.toString(), + deferredException = from(error.deferredError) + ) + } +} + +/** + * Exception occurred during a workflow task + */ +class FailedWorkflowTaskException( + /** + * Name of the workflow for which the error occurred + */ + val workflowName: String, + + /** + * Id of the workflow for which the error occurred + */ + val workflowId: String, + + /** + * Id of the workflow task for which the error occurred + */ + val workflowTaskId: String, + + /** + * cause of the error + */ + val workerException: WorkerException +) : FailedDeferredException() { + companion object { + fun from(error: FailedWorkflowTaskError) = FailedWorkflowTaskException( + workflowName = error.workflowName.toString(), + workflowId = error.workflowId.toString(), + workflowTaskId = error.workflowTaskId.toString(), + workerException = WorkerException.from(error.cause) + ) + } +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/RunException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/RunException.kt index 4b623ce62..22d4f2b3d 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/RunException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/RunException.kt @@ -25,10 +25,10 @@ package io.infinitic.exceptions -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.DeferredError import java.lang.RuntimeException open class RunException( message: String, - val causeError: Error? -) : RuntimeException(message) + val causeError: DeferredError? = null +) : kotlin.RuntimeException(message) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/UserException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/UserException.kt index 62b98219c..ff590fbe7 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/UserException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/UserException.kt @@ -25,4 +25,4 @@ package io.infinitic.exceptions -open class UserException(override val message: String) : RuntimeException(message) +open class UserException(override val message: String) : kotlin.RuntimeException(message) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/WorkerException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/WorkerException.kt new file mode 100644 index 000000000..de065a640 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/WorkerException.kt @@ -0,0 +1,78 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.exceptions + +import io.infinitic.common.data.ClientName +import io.infinitic.common.errors.WorkerError + +class WorkerException( + /** + * Name of the worker + */ + val workerName: String, + + /** + * Name of the error + */ + val name: String, + + /** + * Message of the error + */ + override val message: String?, + + /** + * String version of the stack trace + */ + val stackTraceToString: String, + + /** + * cause of the error + */ + override val cause: WorkerException? + +) : kotlin.RuntimeException() { + companion object { + fun from(error: WorkerError): WorkerException = WorkerException( + workerName = error.workerName.toString(), + name = error.name, + message = error.message, + stackTraceToString = error.stackTraceToString, + cause = error.cause?.let { from(it) } + ) + + fun from(workerName: ClientName, throwable: Throwable): WorkerException = WorkerException( + workerName = workerName.toString(), + name = throwable::class.java.name, + message = throwable.message, + stackTraceToString = throwable.stackTraceToString(), + cause = when (val cause = throwable.cause) { + null, throwable -> null + else -> from(workerName, cause) + } + ) + } +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientRunException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientRunException.kt deleted file mode 100644 index 3863f84a4..000000000 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientRunException.kt +++ /dev/null @@ -1,85 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.exceptions.clients - -import io.infinitic.common.errors.Error -import io.infinitic.exceptions.RunException - -sealed class ClientRunException( - msg: String, - help: String, - causeError: Error? = null -) : RunException("$msg.\n$help", causeError) - -class CanceledDeferredException( - val id: String, - val name: String, -) : ClientRunException( - msg = "canceled deferred: $name ($id)", - help = "" -) - -class FailedDeferredException( - val id: String, - val name: String, - causeError: Error -) : ClientRunException( - msg = "failed deferred: $name ($id)", - help = "", - causeError = causeError -) - -data class TimedOutDeferredException( - val id: String, - val name: String -) : ClientRunException( - msg = "timed out deferred: $name ($id)", - help = "" -) - -class UnknownTaskException( - taskId: String, - taskName: String, -) : ClientRunException( - msg = "Unknown task instance: $taskName ($taskId)", - help = "This instance is probably already completed" -) - -class UnknownWorkflowException( - workflowId: String, - workflowName: String -) : ClientRunException( - msg = "Unknown workflow instance: $workflowName ($workflowId)", - help = "This instance is probably already completed" -) - -class AlreadyCompletedWorkflowException( - workflowId: String, - workflowName: String -) : ClientRunException( - msg = "Workflow instance's main method already completed: $workflowName ($workflowId)", - help = "" -) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientUserException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientUserException.kt index 432036c93..ba75e9798 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientUserException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/clients/ClientUserException.kt @@ -33,87 +33,62 @@ sealed class ClientUserException( help: String, ) : UserException("$msg.\n$help") -class NotAStubException( - name: String, - async: String +class InvalidStubException( + klass: String? = null ) : ClientUserException( - msg = "First parameter of client.$async function should be a stub", - help = "Make sure to provide the stub returned by client.newTask($name) or client.newWorkflow($name) function" + msg = when (klass) { + null -> "Instance used" + else -> "$klass is not the stub of a class or of a workflow" + }, + help = "Make sure to use a stub returned by taskStub(Class<*>) or workflowStub(Class<*>)" ) -class CanNotApplyOnChannelException( - action: String +class InvalidCommandException( + msg: String, + help: String = "" ) : ClientUserException( - msg = "First parameter of client.$action should be the stub of an existing task or workflow", - help = "Make sure to provide the stub returned by client.getTask or client.getWorkflow function" + msg = msg, + help = msg ) -class CanNotReuseWorkflowStubException( - name: String +class InvalidChannelGetterException( + klass: String ) : ClientUserException( - msg = "You can not reuse a workflow stub ($name) already dispatched", - help = "Please create a new stub using `newWorkflow()` for each workflow dispatch`" + msg = "Invalid channel getter", + help = "When defining getters of channels in your workflow interface, " + + "make sure to have ${SendChannel::class.java.name} as return type, not $klass" ) -class CanNotApplyOnNewTaskStubException( - name: String, - action: String +class InvalidRunningTaskException( + klass: String ) : ClientUserException( - msg = "You can not `$action` on the stub of a new task", - help = "Please target an existing $name task using `client.getTask()` " + msg = "$klass is not the stub of a running task", + help = "Make sure to use a stub returned by workflowStub(Class<*>)" ) -class CanNotApplyOnNewWorkflowStubException( - name: String, - action: String +class InvalidWorkflowException( + klass: String ) : ClientUserException( - msg = "You can not `$action` on the stub of a new workflow", - help = "Please target an existing $name workflow using `client.getWorkflow()` " + msg = "$klass is not the stub of a workflow", + help = "Make sure to use a stub returned by workflowStub(Class<*>)" ) -class SuspendMethodNotSupportedException( - klass: String, +class InvalidInterfaceException( method: String ) : ClientUserException( - msg = "method \"$method\" in class \"$klass\" is a suspend function", - help = "Suspend functions are not supporteds" -) - -class NoMethodCallException( - klass: String -) : ClientUserException( - msg = "The method to call for your task or workflow is missing", - help = "Make sure to call a method of \"$klass\"" + msg = "$method must be a method declared from an interface", + help = "Make sure to provide a method defined from an interface, not from an actual instance." ) -class MultipleMethodCallsException( +class SuspendMethodNotSupportedException( klass: String, - method1: String, - method2: String -) : ClientUserException( - msg = "Only one method of \"$klass\" can be called at a time. You can not call \"$method2\" method as you have already called \"$method1\"", - help = "Make sure you call only one method of \"$klass\" - multiple calls in the provided lambda is forbidden" -) - -class ChannelUsedOnNewWorkflowException( - workflow: String -) : ClientUserException( - msg = "Channels can only be used for an existing instance of $workflow workflow", - help = "Make sure you target a running workflow, by providing and id when defining your workflow stub" -) - -class UnknownMethodInSendChannelException( - workflow: String, - channel: String, method: String ) : ClientUserException( - msg = "Unknown method $method used on channel $channel in $workflow", - help = "Make sure to use the ${SendChannel<*>::send.name} method" + msg = "Method \"$klass:$method\" is a suspend function", + help = "Suspend functions are not supported yet" ) -class CanNotAwaitStubPerTag( - klass: String -) : ClientUserException( - msg = "You can not await a task or workflow ($klass) based on a tag", - help = "Please target the task or workflow per id" +class InvalidChannelUsageException() : ClientUserException( + msg = "send method of channels can not be used directly", + help = "Make sure to use the send(workflow, id) function" ) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/serialization/SerializationException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/serialization/SerializationException.kt index 6cb571188..f23ec4e52 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/serialization/SerializationException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/serialization/SerializationException.kt @@ -89,5 +89,5 @@ class ParameterSerializationException( msg = "Error during Json serialization of parameter $parameterName of $className::$methodName", help = "We are using Jackson library per default to serialize object through the ${Json::class.java.name} wrapper. If an exception is thrown during serialization, please consider those options:\n" + "- modifying $parameterType to make it serializable by ${Json::class.java.name}\n" + - "- replacing $parameterType per simpler parameters in $className::$methodName\n" + "- replacing $parameterType by more simple parameters in $className::$methodName\n" ) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/tasks/TaskRunException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/tasks/TaskRunException.kt index 2a4fe8dc9..27cb3fd18 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/tasks/TaskRunException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/tasks/TaskRunException.kt @@ -26,12 +26,12 @@ package io.infinitic.exceptions.tasks import io.infinitic.common.tasks.data.TaskOptions -import io.infinitic.exceptions.UserException +import io.infinitic.exceptions.RunException sealed class TaskRunException( msg: String, help: String -) : UserException("$msg.\n$help") +) : RunException("$msg.\n$help") class ProcessingTimeoutException( klass: String, diff --git a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowUserException.kt b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowUserException.kt index e955825b4..fa6a44508 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowUserException.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/exceptions/workflows/WorkflowUserException.kt @@ -33,67 +33,34 @@ sealed class WorkflowUserException( help: String ) : UserException("$msg.\n$help") -class NoMethodCallAtAsyncException( - klass: String -) : WorkflowUserException( - msg = "You must use a method of \"$klass\" when using \"async\" method", - help = "Make sure to call exactly one method of \"$klass\" within the curly braces - example: async(foo) { bar(*args) }" -) - -class MultipleMethodCallsAtAsyncException( - klass: String, - method1: String?, - method2: String -) : WorkflowUserException( - msg = "Only one method of \"$klass\" can be called at a time. You can not call \"$method2\" method as you have already called \"$method1\"", - help = "Make sure you call only one method of \"$klass\" - multiple calls in the provided lambda is forbidden" -) - -class ShouldNotWaitInsideInlinedTaskException( - method: String -) : WorkflowUserException( - msg = "Asynchronous computation inside an inlined task in forbidden", - help = "In $method, make sure you do not wait for task or child workflow completion inside `inline { ... }`" -) - -class ShouldNotUseAsyncFunctionInsideInlinedTaskException( - method: String -) : WorkflowUserException( - msg = "Asynchronous computation inside an inlined task in forbidden", - help = "In $method, make sure you do not use `async { ... }` function inside `task { ... }`" -) - -class ParametersInChannelMethodException( - workflow: String, - method: String -) : WorkflowUserException( - msg = "in workflow $workflow, method $method returning a ${Channel::class.simpleName} should NOT have any parameter", +object InvalidInlineException : WorkflowUserException( + msg = "Task or workflow must not be used inside an inline function", help = "" ) -class NonUniqueChannelFromChannelMethodException( +class NonIdempotentChannelGetterException( workflow: String, method: String ) : WorkflowUserException( - msg = "in workflow $workflow, method $method should return the same ${Channel::class.simpleName} instance when called multiple times", + msg = "in workflow $workflow, method $method should return the same object when called multiple times", help = "" ) -class MultipleNamesForChannelException( +class MultipleGettersForSameChannelException( workflow: String, method: String, otherMethod: String ) : WorkflowUserException( - msg = "in workflow $workflow, method $method return a ${Channel::class.simpleName} instance already associated with name $otherMethod", - help = "Make sure to not have multiple methods returning the same channel" + msg = "in workflow $workflow, getter $method and $otherMethod return the same channel", + help = "Make sure to not have multiple getters returning the same channel" ) -object NameNotInitializedInChannelException : WorkflowUserException( - msg = "A ${Channel::class.simpleName} is used without name", - help = "Make sure to have a method that returns this channel." +object ChannelWithoutGetterException : WorkflowUserException( + msg = "A ${Channel::class.simpleName} is used without getter", + help = "Make sure to add a getter of this channel to the workflow interface" ) -class WorkflowUpdatedWhileRunningException( +class WorkflowUpdatedException( workflow: String, method: String, position: String diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Channel.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Channel.kt index c7def1f04..7c96f13f9 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Channel.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Channel.kt @@ -25,4 +25,33 @@ package io.infinitic.workflows -interface Channel : SendChannel, ReceiveChannel +import com.jayway.jsonpath.Criteria +import io.infinitic.exceptions.workflows.ChannelWithoutGetterException + +class Channel( + private val dispatcherFn: () -> WorkflowDispatcher +) : SendChannel { + private lateinit var _name: String + + internal fun setName(name: String) { _name = name } + + internal fun hasName() = ::_name.isInitialized + + val name by lazy { + when (hasName()) { + true -> _name + else -> throw ChannelWithoutGetterException + } + } + + override fun send(signal: T) = + dispatcherFn().sendSignal(this, signal) + + @JvmOverloads + fun receive(jsonPath: String? = null, criteria: Criteria? = null): Deferred = + dispatcherFn().receiveSignal(this, null, jsonPath, criteria) + + @JvmOverloads + fun receive(klass: Class, jsonPath: String? = null, criteria: Criteria? = null): Deferred = + dispatcherFn().receiveSignal(this, klass, jsonPath, criteria) +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Consumer.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Consumer.kt new file mode 100644 index 000000000..fd2233ab6 --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Consumer.kt @@ -0,0 +1,66 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.workflows + +interface Consumer0 { + fun apply() +} + +interface Consumer1 { + fun apply(p1: P1) +} + +interface Consumer2 { + fun apply(p1: P1, p2: P2) +} + +interface Consumer3 { + fun apply(p1: P1, p2: P2, p3: P3) +} + +interface Consumer4 { + fun apply(p1: P1, p2: P2, p3: P3, p4: P4) +} + +interface Consumer5 { + fun apply(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) +} + +interface Consumer6 { + fun apply(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) +} + +interface Consumer7 { + fun apply(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) +} + +interface Consumer8 { + fun apply(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) +} + +interface Consumer9 { + fun apply(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Deferred.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Deferred.kt index 5b634ac2f..5fbd001d8 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Deferred.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Deferred.kt @@ -25,40 +25,65 @@ package io.infinitic.workflows +import com.fasterxml.jackson.annotation.JsonIgnore import io.infinitic.common.workflows.data.steps.Step -import io.infinitic.common.workflows.data.steps.StepStatus -import java.util.UUID +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import io.infinitic.common.workflows.data.steps.and as stepAnd import io.infinitic.common.workflows.data.steps.or as stepOr -@Suppress("unused", "MemberVisibilityCanBePrivate") -data class Deferred ( - val step: Step, - internal val workflowDispatcher: WorkflowDispatcher -) { - val id: UUID? - get() = when (step) { - is Step.Id -> step.commandId.id - else -> null - } - - lateinit var stepStatus: StepStatus - - /* - * Use this method to wait the completion or cancellation of a deferred and get its result +@Serializable(with = DeferredSerializer::class) +data class Deferred (val step: Step) { + @Transient @JsonIgnore lateinit var workflowDispatcher: WorkflowDispatcher + + @Transient @JsonIgnore val id: String? = when (step) { + is Step.Id -> step.commandId.toString() + else -> null + } + + companion object { + private val workflowDispatcherLocal: ThreadLocal = ThreadLocal() + + fun setWorkflowDispatcher(workflowDispatcher: WorkflowDispatcher) = + workflowDispatcherLocal.set(workflowDispatcher) + + fun delWorkflowDispatcher() = workflowDispatcherLocal.set(null) + } + + init { + // special way to initialize workflowDispatcher when deserializing Deferred in WorkflowTaskImpl + workflowDispatcherLocal.get()?.let { workflowDispatcher = it } + } + + /** + * Wait the completion or cancellation of a deferred and get its result */ fun await(): T = workflowDispatcher.await(this) - /* - * Use this method to get the status of a deferred + /** + * Status of a deferred */ fun status(): DeferredStatus = workflowDispatcher.status(this) - fun isCompleted() = status() == DeferredStatus.COMPLETED + @JsonIgnore fun isOngoing() = status() == DeferredStatus.ONGOING + + @JsonIgnore fun isUnknown() = status() == DeferredStatus.UNKNOWN + + @JsonIgnore fun isCanceled() = status() == DeferredStatus.CANCELED - fun isCanceled() = status() == DeferredStatus.CANCELED + @JsonIgnore fun isFailed() = status() == DeferredStatus.FAILED + + @JsonIgnore fun isCompleted() = status() == DeferredStatus.COMPLETED +} - fun isOngoing() = status() == DeferredStatus.ONGOING +object DeferredSerializer : KSerializer> { + override val descriptor: SerialDescriptor = Step.serializer().descriptor + override fun serialize(encoder: Encoder, value: Deferred<*>) { encoder.encodeSerializableValue(Step.serializer(), value.step) } + override fun deserialize(decoder: Decoder): Deferred<*> = Deferred(decoder.decodeSerializableValue(Step.serializer())) } fun or(vararg others: Deferred<*>) = others.reduce { acc, deferred -> acc or deferred } @@ -67,40 +92,40 @@ fun and(vararg others: Deferred<*>) = others.reduce { acc, deferred -> acc and d @JvmName("orT0") infix fun Deferred.or(other: Deferred) = - Deferred(stepOr(this.step, other.step), this.workflowDispatcher) + Deferred(stepOr(step, other.step)).apply { workflowDispatcher = this@or.workflowDispatcher } @JvmName("orT1") infix fun Deferred>.or(other: Deferred) = - Deferred(stepOr(this.step, other.step), this.workflowDispatcher) + Deferred(stepOr(step, other.step)).apply { workflowDispatcher = this@or.workflowDispatcher } @JvmName("orT2") infix fun Deferred>.or(other: Deferred>) = - Deferred>(stepOr(this.step, other.step), this.workflowDispatcher) + Deferred>(stepOr(step, other.step)).apply { workflowDispatcher = this@or.workflowDispatcher } @JvmName("orT3") infix fun Deferred.or(other: Deferred>) = - Deferred(stepOr(this.step, other.step), this.workflowDispatcher) + Deferred(stepOr(step, other.step)).apply { workflowDispatcher = this@or.workflowDispatcher } @JvmName("andT0") infix fun Deferred.and(other: Deferred) = - Deferred>(stepAnd(this.step, other.step), this.workflowDispatcher) + Deferred>(stepAnd(step, other.step)).apply { workflowDispatcher = this@and.workflowDispatcher } @JvmName("andT1") infix fun Deferred>.and(other: Deferred) = - Deferred>(stepAnd(this.step, other.step), this.workflowDispatcher) + Deferred>(stepAnd(step, other.step)).apply { workflowDispatcher = this@and.workflowDispatcher } @JvmName("andT2") infix fun Deferred>.and(other: Deferred>) = - Deferred>(stepAnd(this.step, other.step), this.workflowDispatcher) + Deferred>(stepAnd(step, other.step)).apply { workflowDispatcher = this@and.workflowDispatcher } @JvmName("andT3") infix fun Deferred.and(other: Deferred>) = - Deferred>(stepAnd(this.step, other.step), this.workflowDispatcher) + Deferred>(stepAnd(step, other.step)).apply { workflowDispatcher = this@and.workflowDispatcher } // extension function to apply AND to a List> fun List>.and() = - Deferred>(Step.And(this.map { it.step }), this.first().workflowDispatcher) + Deferred>(Step.And(map { it.step })).apply { workflowDispatcher = first().workflowDispatcher } // extension function to apply OR to a List> fun List>.or() = - Deferred(Step.Or(this.map { it.step }), this.first().workflowDispatcher) + Deferred(Step.Or(map { it.step })).apply { workflowDispatcher = first().workflowDispatcher } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/DeferredStatus.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/DeferredStatus.kt index 7011c3379..479af5070 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/DeferredStatus.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/DeferredStatus.kt @@ -27,7 +27,8 @@ package io.infinitic.workflows enum class DeferredStatus { ONGOING, - COMPLETED, + UNKNOWN, CANCELED, - FAILED + FAILED, + COMPLETED, } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/ReceiveChannel.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/ReceiveChannel.kt index acdb1e07f..edf642e06 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/ReceiveChannel.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/ReceiveChannel.kt @@ -26,13 +26,13 @@ package io.infinitic.workflows import com.jayway.jsonpath.Criteria -import kotlin.reflect.KClass interface ReceiveChannel { - fun receive(jsonPath: String? = null, criteria: Criteria? = null): Deferred + fun receive(): Deferred - fun receive(klass: Class, jsonPath: String? = null, criteria: Criteria? = null): Deferred + fun receive(jsonPath: String): Deferred + + fun receive(jsonPath: String, criteria: Criteria? = null): Deferred - fun receive(klass: KClass, jsonPath: String? = null, criteria: Criteria? = null) = - receive(klass.java, jsonPath, criteria) + fun receive(klass: Class, jsonPath: String? = null, criteria: Criteria? = null): Deferred } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/SendChannel.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/SendChannel.kt index 5830a34b4..0d62beab3 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/SendChannel.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/SendChannel.kt @@ -25,8 +25,6 @@ package io.infinitic.workflows -import java.util.concurrent.CompletableFuture - interface SendChannel { - fun send(event: T): CompletableFuture + fun send(signal: T) } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/With.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/With.kt new file mode 100644 index 000000000..f37ddd3bc --- /dev/null +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/With.kt @@ -0,0 +1,93 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.workflows + +class With0(val f: () -> Deferred) { + fun with() = f() +} + +class With1( + val f: (p1: P1) -> Deferred +) { + fun with(p1: P1) = + f(p1) +} + +class With2( + val f: (p1: P1, p2: P2) -> Deferred +) { + fun with(p1: P1, p2: P2) = + f(p1, p2) +} + +class With3( + val f: (p1: P1, p2: P2, p3: P3) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3) = + f(p1, p2, p3) +} + +class With4( + val f: (p1: P1, p2: P2, p3: P3, p4: P4) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3, p4: P4) = + f(p1, p2, p3, p4) +} + +class With5( + val f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) = + f(p1, p2, p3, p4, p5) +} + +class With6( + val f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) = + f(p1, p2, p3, p4, p5, p6) +} + +class With7( + val f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) = + f(p1, p2, p3, p4, p5, p6, p7) +} + +class With8( + val f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) = + f(p1, p2, p3, p4, p5, p6, p7, p8) +} + +class With9( + val f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) -> Deferred +) { + fun with(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) = + f(p1, p2, p3, p4, p5, p6, p7, p8, p9) +} diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Workflow.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Workflow.kt index aeb985d3a..20d8f7ae3 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/Workflow.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/Workflow.kt @@ -25,15 +25,21 @@ package io.infinitic.workflows -import io.infinitic.common.proxies.TaskProxyHandler -import io.infinitic.common.proxies.WorkflowProxyHandler +import io.infinitic.annotations.Name +import io.infinitic.common.proxies.GetWorkflowProxyHandler +import io.infinitic.common.proxies.NewTaskProxyHandler +import io.infinitic.common.proxies.NewWorkflowProxyHandler +import io.infinitic.common.proxies.ProxyHandler import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.tasks.data.TaskOptions import io.infinitic.common.tasks.data.TaskTag -import io.infinitic.common.workflows.data.channels.ChannelImpl +import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowMeta import io.infinitic.common.workflows.data.workflows.WorkflowOptions import io.infinitic.common.workflows.data.workflows.WorkflowTag +import io.infinitic.exceptions.clients.InvalidStubException +import io.infinitic.exceptions.workflows.MultipleGettersForSameChannelException +import io.infinitic.exceptions.workflows.NonIdempotentChannelGetterException import java.time.Duration import java.time.Instant @@ -42,82 +48,404 @@ abstract class Workflow { lateinit var context: WorkflowContext lateinit var dispatcher: WorkflowDispatcher - /* - * Create a channel - */ - fun channel(): Channel = ChannelImpl { dispatcher } - - /* - * Stub task + /** + * Create a stub for a task */ - @JvmOverloads fun newTask( + @JvmOverloads + protected fun newTask( klass: Class, tags: Set = setOf(), options: TaskOptions = TaskOptions(), meta: Map = mapOf() - ): T = TaskProxyHandler( + ): T = NewTaskProxyHandler( klass = klass, taskTags = tags.map { TaskTag(it) }.toSet(), taskOptions = options, taskMeta = TaskMeta(meta) ) { dispatcher }.stub() - /* - * (Kotlin) Stub task - */ - inline fun newTask( - tags: Set = setOf(), - options: TaskOptions = TaskOptions(), - meta: Map = mapOf() - ): T = newTask(T::class.java, tags, options, meta) - - /* - * Stub workflow + /** + * Create a stub for a workflow */ - @JvmOverloads fun newWorkflow( + @JvmOverloads + protected fun newWorkflow( klass: Class, tags: Set = setOf(), options: WorkflowOptions = WorkflowOptions(), meta: Map = mapOf() - ): T = WorkflowProxyHandler( + ): T = NewWorkflowProxyHandler( klass = klass, workflowTags = tags.map { WorkflowTag(it) }.toSet(), workflowOptions = options, workflowMeta = WorkflowMeta(meta) ) { dispatcher }.stub() - /* - * Stub workflow - * (Kotlin way) +// /** +// * Create a stub for an existing task targeted by id +// */ +// fun getTaskById( +// klass: Class, +// id: String +// ): T = GetTaskProxyHandler( +// klass = klass, +// TaskId(id), +// null +// ) { dispatcher }.stub() +// +// /** +// * Create a stub for existing task targeted by tag +// */ +// fun getTaskByTag( +// klass: Class, +// tag: String +// ): T = GetTaskProxyHandler( +// klass = klass, +// null, +// TaskTag(tag) +// ) { dispatcher }.stub() + + /** + * Create a stub for an existing workflow targeted by id */ - inline fun newWorkflow( - tags: Set = setOf(), - options: WorkflowOptions = WorkflowOptions(), - meta: Map = mapOf() - ): T = newWorkflow(T::class.java, tags, options, meta) + protected fun getWorkflowById( + klass: Class, + id: String + ): T = GetWorkflowProxyHandler( + klass = klass, + WorkflowId(id), + null + ) { dispatcher }.stub() + + /** + * Create a stub for existing workflow targeted by tag + */ + protected fun getWorkflowByTag( + klass: Class, + tag: String + ): T = GetWorkflowProxyHandler( + klass = klass, + null, + WorkflowTag(tag) + ) { dispatcher }.stub() + + /** + * Dispatch without parameter a task or workflow returning an object + */ + protected fun dispatch( + method: () -> R + ): Deferred = start { method.invoke() } + + /** + * Dispatch with 1 parameter a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1) -> R, + p1: P1 + ): Deferred = start { method.invoke(p1) } + + /** + * Dispatch with 2 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2) -> R, + p1: P1, + p2: P2 + ): Deferred = start { method.invoke(p1, p2) } + + /** + * Dispatch with 3 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3) -> R, + p1: P1, + p2: P2, + p3: P3 + ): Deferred = start { method.invoke(p1, p2, p3) } + + /** + * Dispatch with 4 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4 + ): Deferred = start { method.invoke(p1, p2, p3, p4) } + + /** + * Dispatch with 5 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5 + ): Deferred = start { method.invoke(p1, p2, p3, p4, p5) } + + /** + * Dispatch with 6 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6 + ): Deferred = start { method.invoke(p1, p2, p3, p4, p5, p6) } + + /** + * Dispatch with 7 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7 + ): Deferred = start { method.invoke(p1, p2, p3, p4, p5, p6, p7) } + + /** + * Dispatch with 8 parameters a task or workflow returning an object + */ + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8 + ): Deferred = start { method.invoke(p1, p2, p3, p4, p5, p6, p7, p8) } - /* - * Dispatch a task or a workflow asynchronously + /** + * Dispatch with 9 parameters a task or workflow returning an object */ - fun async(proxy: T, method: T.() -> S): Deferred = dispatcher.dispatch(proxy, method) + protected fun dispatch( + method: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) -> R, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9 + ): Deferred = start { method.invoke(p1, p2, p3, p4, p5, p6, p7, p8, p9) } - /* - * Create an async branch + /** + * Dispatch without parameter a task or workflow returning void */ - fun async(branch: () -> S): Deferred = dispatcher.async(branch) + protected fun dispatchVoid( + method: Consumer0 + ): Deferred = startVoid { method.apply() } - /* - * Create an inline task + /** + * Dispatch with 1 parameter a task or workflow returning void */ - fun inline(task: () -> S): S = dispatcher.inline(task) + protected fun dispatchVoid( + method: Consumer1, + p1: P1 + ): Deferred = startVoid { method.apply(p1) } - /* + /** + * Dispatch with 2 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer2, + p1: P1, + p2: P2 + ): Deferred = startVoid { method.apply(p1, p2) } + + /** + * Dispatch with 3 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer3, + p1: P1, + p2: P2, + p3: P3 + ): Deferred = startVoid { method.apply(p1, p2, p3) } + + /** + * Dispatch with 4 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer4, + p1: P1, + p2: P2, + p3: P3, + p4: P4 + ): Deferred = startVoid { method.apply(p1, p2, p3, p4) } + + /** + * Dispatch with 5 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer5, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5 + ): Deferred = startVoid { method.apply(p1, p2, p3, p4, p5) } + + /** + * Dispatch with 6 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer6, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6 + ): Deferred = startVoid { method.apply(p1, p2, p3, p4, p5, p6) } + + /** + * Dispatch with 7 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer7, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7 + ): Deferred = startVoid { method.apply(p1, p2, p3, p4, p5, p6, p7) } + + /** + * Dispatch with 8 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer8, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8 + ): Deferred = startVoid { method.apply(p1, p2, p3, p4, p5, p6, p7, p8) } + + /** + * Dispatch with 9 parameters a task or workflow returning void + */ + protected fun dispatchVoid( + method: Consumer9, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, + p6: P6, + p7: P7, + p8: P8, + p9: P9 + ): Deferred = startVoid { method.apply(p1, p2, p3, p4, p5, p6, p7, p8, p9) } + + /** + * Create a channel + */ + protected fun channel(): Channel = Channel { dispatcher } + + /** + * Run inline task + */ + protected fun inline(task: () -> S): S = dispatcher.inline(task) + + /** + * Run inline task returning void + */ + protected fun inlineVoid(task: Consumer0): Unit = dispatcher.inline { task.apply() } + + /** * Create a timer from a duration */ - fun timer(duration: Duration): Deferred = dispatcher.timer(duration) + protected fun timer(duration: Duration): Deferred = dispatcher.timer(duration) - /* + /** * Create a timer from an instant */ - fun timer(instant: Instant): Deferred = dispatcher.timer(instant) + protected fun timer(instant: Instant): Deferred = dispatcher.timer(instant) + + private fun start( + invoke: () -> R + ): Deferred { + val handler = ProxyHandler.async(invoke) ?: throw InvalidStubException() + + return dispatcher.dispatch(handler, false) + } + + private fun startVoid( + invoke: () -> Unit + ): Deferred { + val handler = ProxyHandler.async(invoke) ?: throw InvalidStubException() + + return dispatcher.dispatch(handler, false) + } + + // from klass for the given workflow name + private fun findClassPerWorkflowName() = try { + Class.forName(context.name) + } catch (e: ClassNotFoundException) { + findClassPerAnnotationName(this::class.java, context.name) + } + + // from klass, search for a given @Name annotation + private fun findClassPerAnnotationName(klass: Class<*>, name: String): Class<*>? { + var clazz = klass + + do { + // has current clazz the right @Name annotation? + if (clazz.getAnnotation(Name::class.java)?.name == name) return clazz + + // has any of the interfaces the right @Name annotation? + clazz.interfaces.forEach { interfaze -> + findClassPerAnnotationName(interfaze, name)?.also { return it } + } + + // if not, inspect the superclass + clazz = clazz.superclass ?: break + } while (Object::class.java.name != clazz.canonicalName) + + return null + } +} + +/** + * Set names of all channels in this workflow + */ +fun Workflow.setChannelNames() { + this::class.java.declaredMethods + .filter { it.returnType.name == Channel::class.java.name && it.parameterCount == 0 } + .map { + // channel must be created only once per method + it.isAccessible = true + val channel = it.invoke(this) + if (channel !== it.invoke(this)) { + throw NonIdempotentChannelGetterException(this::class.java.name, it.name) + } + // this channel must not have a name already + channel as Channel<*> + if (channel.hasName()) { + throw MultipleGettersForSameChannelException(this::class.java.name, it.name, channel.name) + } + // set channel name + channel.setName(it.name) + } } diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowContext.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowContext.kt index 273e7cbac..8b80b9e44 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowContext.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowContext.kt @@ -25,10 +25,10 @@ package io.infinitic.workflows -import java.util.UUID - interface WorkflowContext { - val id: UUID + val name: String + + val id: String val tags: Set diff --git a/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowDispatcher.kt b/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowDispatcher.kt index 1fd48f1bb..517cf808b 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowDispatcher.kt +++ b/infinitic-common/src/main/kotlin/io/infinitic/workflows/WorkflowDispatcher.kt @@ -26,18 +26,14 @@ package io.infinitic.workflows import com.jayway.jsonpath.Criteria -import io.infinitic.common.proxies.Dispatcher -import io.infinitic.common.proxies.TaskProxyHandler -import io.infinitic.common.proxies.WorkflowProxyHandler -import io.infinitic.common.workflows.data.channels.ChannelImpl +import io.infinitic.common.proxies.ProxyDispatcher +import io.infinitic.common.proxies.ProxyHandler import java.time.Duration import java.time.Instant -interface WorkflowDispatcher : Dispatcher { +interface WorkflowDispatcher : ProxyDispatcher { - fun dispatch(proxy: T, method: T.() -> S): Deferred - - fun async(branch: () -> T): Deferred + fun dispatch(handler: ProxyHandler<*>, clientWaiting: Boolean): Deferred fun inline(task: () -> T): T @@ -45,17 +41,13 @@ interface WorkflowDispatcher : Dispatcher { fun status(deferred: Deferred): DeferredStatus - fun dispatchTask(handler: TaskProxyHandler<*>): Deferred - - fun dispatchWorkflow(handler: WorkflowProxyHandler<*>): Deferred - fun timer(duration: Duration): Deferred fun timer(instant: Instant): Deferred - fun receiveFromChannel(channel: ChannelImpl, jsonPath: String?, criteria: Criteria?): Deferred +// fun receiveSignal(channel: Channel, jsonPath: String?, criteria: Criteria?): Deferred - fun receiveFromChannel(channel: ChannelImpl, klass: Class, jsonPath: String?, criteria: Criteria?): Deferred + fun receiveSignal(channel: Channel, klass: Class?, jsonPath: String?, criteria: Criteria?): Deferred - fun sendToChannel(channel: ChannelImpl, event: T) + fun sendSignal(channel: Channel, signal: T) } diff --git a/infinitic-common/src/test/kotlin/io/infinitic/common/data/DataTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/common/data/DataTests.kt new file mode 100644 index 000000000..d921e8e44 --- /dev/null +++ b/infinitic-common/src/test/kotlin/io/infinitic/common/data/DataTests.kt @@ -0,0 +1,38 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.common.data + +import io.infinitic.common.fixtures.TestFactory +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class DataTests : StringSpec({ + "MessageId should be reversible with ByteArray" { + val messageId = TestFactory.random() + + MessageId.fromByteArray(messageId.toByteArray()) shouldBe messageId + } +}) diff --git a/infinitic-common/src/test/kotlin/io/infinitic/common/serDe/SerDeTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/common/serDe/SerDeTests.kt index 5fbd61343..551f2d824 100644 --- a/infinitic-common/src/test/kotlin/io/infinitic/common/serDe/SerDeTests.kt +++ b/infinitic-common/src/test/kotlin/io/infinitic/common/serDe/SerDeTests.kt @@ -26,98 +26,137 @@ package io.infinitic.common.serDe import io.infinitic.common.fixtures.TestFactory +import io.infinitic.common.workflows.data.steps.Step import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import kotlinx.serialization.Serializable class SerDeTests : StringSpec({ + + "Null (Obj) should be serializable / deserializable" { + val obj1: Obj1? = null + val obj2 = SerializedData.from(obj1).deserialize() + + obj2 shouldBe obj1 + } + "Primitive (ByteArray) should be serializable / deserializable" { - val bytes1: ByteArray = TestFactory.random().toByteArray() + val bytes1: ByteArray = TestFactory.random() val bytes2 = SerializedData.from(bytes1).deserialize() as ByteArray bytes1.contentEquals(bytes2) shouldBe true } "Primitive (Short) should be serializable / deserializable" { - val val1: Short = 42 - val val2 = SerializedData.from(val1).deserialize() as Short + val val1: Short = TestFactory.random() + println(SerializedData.from(val1)) + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Primitive (Int) should be serializable / deserializable" { - val val1: Int = 42 - val val2 = SerializedData.from(val1).deserialize() as Int + val val1: Int = TestFactory.random() + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Primitive (Long) should be serializable / deserializable" { - val val1: Long = 42 - val val2 = SerializedData.from(val1).deserialize() as Long + val val1: Long = TestFactory.random() + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Primitive (Float) should be serializable / deserializable" { - val val1: Float = 42.0F - val val2 = SerializedData.from(val1).deserialize() as Float + val val1: Float = TestFactory.random() + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Primitive (Double) should be serializable / deserializable" { - val val1: Double = 42.0 - val val2 = SerializedData.from(val1).deserialize() as Double + val val1: Double = TestFactory.random() + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Primitive (Boolean) should be serializable / deserializable" { - val val1: Boolean = true - val val2 = SerializedData.from(val1).deserialize() as Boolean + val val1: Boolean = TestFactory.random() + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Primitive (Char) should be serializable / deserializable" { - val val1: Char = 'w' - val val2 = SerializedData.from(val1).deserialize() as Char + val val1: Char = TestFactory.random() + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "String should be serializable / deserializable" { val val1: String = TestFactory.random() - val val2 = SerializedData.from(val1).deserialize() as String + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "List Of primitives should be serializable / deserializable" { val val1 = listOf(42F, true, "!@#%") - val val2 = SerializedData.from(val1).deserialize() as List<*> + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Simple Object should be serializable / deserializable" { val val1 = Obj1("42", 42, Type.TYPE_1) - val val2 = SerializedData.from(val1).deserialize() as Obj1 + val val2 = SerializedData.from(val1).deserialize() - val1 shouldBe val2 + val2 shouldBe val1 } "Object containing set of sealed should be serializable / deserializable (even with a 'type' property)" { val val1 = Objs(setOf(Obj1("42", 42, Type.TYPE_1))) - val val2 = SerializedData.from(val1).deserialize() as Objs + val val2 = SerializedData.from(val1).deserialize() + + val2 shouldBe val1 + } + + "Step.Id should be serializable / deserializable" { + val step1 = TestFactory.random() + val step2 = SerializedData.from(step1).deserialize() + + step2 shouldBe step1 + } + + "Step.Or should be serializable / deserializable" { + val step1 = TestFactory.random() + val step2 = SerializedData.from(step1).deserialize() + + step2 shouldBe step1 + } + + "Step.And should be serializable / deserializable" { + val step1 = TestFactory.random() + val step2 = SerializedData.from(step1).deserialize() + + step2 shouldBe step1 + } + + "Comparing SerializedData" { + val step = TestFactory.random() + val s1 = SerializedData.from(step) + val s2 = SerializedData.from(step) - val1 shouldBe val2 + s1 shouldBe s2 } // "List of simple Objects should be serializable / deserializable" { -// val val1 = listOf(Obj1("42", 42), Obj1("24", 24)) -// val val2 = SerializedData.from(val1).deserialize() as List +// val val1 = listOf(Obj1("42", 42, Type.TYPE_1), Obj1("24", 24, Type.TYPE_2)) +// val val2 = SerializedData.from(val1).deserialize() // // val1 shouldBe val2 // } diff --git a/infinitic-common/src/test/kotlin/io/infinitic/common/tasks/DataTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/common/tasks/DataTests.kt index 35f6843b6..ccfa10216 100644 --- a/infinitic-common/src/test/kotlin/io/infinitic/common/tasks/DataTests.kt +++ b/infinitic-common/src/test/kotlin/io/infinitic/common/tasks/DataTests.kt @@ -26,10 +26,10 @@ package io.infinitic.common.tasks import io.infinitic.common.data.Name +import io.infinitic.common.data.ReturnValue import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.data.methods.MethodReturnValue import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.serDe.SerializedData import io.infinitic.common.tasks.data.TaskAttemptId @@ -38,6 +38,7 @@ import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.data.TaskRetryIndex import io.infinitic.common.tasks.data.TaskRetrySequence +import io.infinitic.common.tasks.engine.state.TaskState import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import kotlinx.serialization.ExperimentalSerializationApi @@ -47,45 +48,52 @@ import kotlinx.serialization.json.Json @OptIn(ExperimentalSerializationApi::class) class DataTests : StringSpec({ - "TaskId should be stringify as id" { - val taskId = TestFactory.random() + "TaskId should be json-serializable" { + val id = TestFactory.random() + val m1 = TaskId(id) + val json = Json.encodeToString(m1) + val m2 = Json.decodeFromString(json) - "$taskId" shouldBe taskId.id.toString() + json shouldBe Json.encodeToString(id) + m2 shouldBe m1 } - "TaskAttemptId should be stringify as id" { - val taskAttemptId = TestFactory.random() + "TaskAttemptId should be json-serializable" { + val id = TestFactory.random() + val m1 = TaskAttemptId(id) + val json = Json.encodeToString(m1) + val m2 = Json.decodeFromString(json) - "$taskAttemptId" shouldBe taskAttemptId.id.toString() + json shouldBe Json.encodeToString(id) + m2 shouldBe m1 } "MethodInput should be serialized as List" { val m = MethodParameters.from("a", "b") - val json = Json.encodeToString(m) - json shouldBe Json.encodeToString(listOf(SerializedData.from("a"), SerializedData.from("b"))) - val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(listOf(SerializedData.from("a"), SerializedData.from("b"))) m2 shouldBe m } "MethodName should be serialized as String" { - val m = MethodName("qwerty") - + val id = TestFactory.random() + val m = MethodName(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" - val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "MethodReturnValue should be serialized as SerializedData" { - val m = MethodReturnValue.from("qwerty") - + "ReturnValue should be serialized as SerializedData" { + val id = TestFactory.random() + val m = ReturnValue.from(id) val json = Json.encodeToString(m) - json shouldBe Json.encodeToString(SerializedData.from(m.get())) + val m2 = Json.decodeFromString(json) - val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(SerializedData.from(id)) m2 shouldBe m } @@ -100,67 +108,64 @@ class DataTests : StringSpec({ } "TaskAttemptId should be serialized as String" { - val m = TaskAttemptId() + val id = TestFactory.random() + val m = TaskAttemptId(id) val json = Json.encodeToString(m) val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(id) m2 shouldBe m } "TaskRetry should be serialized as Int" { val m = TaskRetrySequence(42) - val json = Json.encodeToString(m) - json shouldBe "42" - val m2 = Json.decodeFromString(json) + + json shouldBe "42" m2 shouldBe m } "TaskAttemptRetry should be serialized as Int" { val m = TaskRetryIndex(42) - val json = Json.encodeToString(m) - json shouldBe "42" - val m2 = Json.decodeFromString(json) - m2 shouldBe m - } - - "TaskId should be serialized as String" { - val m = TaskId() - val json = Json.encodeToString(m) - val m2 = Json.decodeFromString(json) + json shouldBe "42" m2 shouldBe m } "TaskMeta should be serialized as Map" { val m = TaskMeta(mapOf("a" to "1".toByteArray())) - val json = Json.encodeToString(m) - val m2 = Json.decodeFromString(json) + m2 shouldBe m } "TaskName should be serialized as String" { - val m = TaskName("qwerty") - + val id = TestFactory.random() + val m = TaskName(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" - val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } "Name should be serialized as String" { - val m = Name("qwerty") - + val id = TestFactory.random() + val m = Name(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" - val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } + + "TaskState can be serDe to byteArray" { + val taskState = TestFactory.random() + + TaskState.fromByteArray(taskState.toByteArray()) shouldBe taskState + } }) diff --git a/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/DataTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/DataTests.kt index 7413eae51..6f578b0b0 100644 --- a/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/DataTests.kt +++ b/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/DataTests.kt @@ -25,16 +25,17 @@ package io.infinitic.common.workflows +import io.infinitic.common.data.ReturnValue import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.serDe.SerializedData -import io.infinitic.common.workflows.data.channels.ChannelEvent -import io.infinitic.common.workflows.data.channels.ChannelEventId -import io.infinitic.common.workflows.data.channels.ChannelEventType import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.workflows.data.channels.ChannelSignal +import io.infinitic.common.workflows.data.channels.ChannelSignalId +import io.infinitic.common.workflows.data.channels.ChannelSignalType import io.infinitic.common.workflows.data.commands.CommandHash import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandReturnValue import io.infinitic.common.workflows.data.commands.CommandSimpleName +import io.infinitic.common.workflows.data.commands.DispatchWorkflowPastCommand import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition import io.infinitic.common.workflows.data.properties.PropertyHash @@ -42,7 +43,6 @@ import io.infinitic.common.workflows.data.properties.PropertyName import io.infinitic.common.workflows.data.properties.PropertyValue import io.infinitic.common.workflows.data.steps.StepHash import io.infinitic.common.workflows.data.steps.StepId -import io.infinitic.common.workflows.data.steps.StepReturnValue import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex import io.infinitic.common.workflows.data.workflows.WorkflowId import io.infinitic.common.workflows.data.workflows.WorkflowMeta @@ -56,190 +56,209 @@ import kotlinx.serialization.json.Json @OptIn(ExperimentalSerializationApi::class) class DataTests : StringSpec({ - "WorkflowId should be stringify as id" { - val workflowId = TestFactory.random() + "WorkflowId should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = WorkflowId(id) + val json = Json.encodeToString(m) + val m2 = Json.decodeFromString(json) - "$workflowId" shouldBe workflowId.id.toString() + json shouldBe Json.encodeToString(id) + m2 shouldBe m } - "CommandHash should be serialized as String" { - val m = CommandHash("qwerty") + "CommandHash should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = CommandHash(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } "CommandId should be serialized as String" { - val m = CommandId() + val id = TestFactory.random() + val m = CommandId(id) val json = Json.encodeToString(m) val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "CommandOutput should be serialized as SerializedData" { - val m = CommandReturnValue.from("qwerty") - + "CommandOutput should be serialized as SerializedData and reversible in json" { + val id = TestFactory.random() + val m = ReturnValue.from(id) val json = Json.encodeToString(m) - json shouldBe Json.encodeToString(SerializedData.from(m.get())) + val m2 = Json.decodeFromString(json) - val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(SerializedData.from(id)) m2 shouldBe m } - "CommandSimpleName should be serialized as String" { - val m = CommandSimpleName("qwerty") - + "CommandSimpleName should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = CommandSimpleName(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" - val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "ChannelEvent should be serialized as SerializedData" { - val m = ChannelEvent(SerializedData.from("qwerty")) - + "ChannelSignal should be serialized as SerializedData and reversible in json" { + val id = TestFactory.random() + val m = ChannelSignal(SerializedData.from(id)) val json = Json.encodeToString(m) - json shouldBe Json.encodeToString(SerializedData.from("qwerty")) + val m2 = Json.decodeFromString(json) - val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(SerializedData.from(id)) m2 shouldBe m } - "ChannelEventId should be serialized as String" { - val m = ChannelEventId() + "ChannelSignalId should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = ChannelSignalId(id) val json = Json.encodeToString(m) - val m2 = Json.decodeFromString(json) + val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "ChannelEventType should be serialized as String" { - val m = ChannelEventType("qwerty") - + "ChannelSignalType should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = ChannelSignalType(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" - val m2 = Json.decodeFromString(json) + val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "ChannelName should be serialized as String" { - val m = ChannelName("qwerty") - + "ChannelName should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = ChannelName(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "MethodRunId should be serialized as String" { - val m = MethodRunId() + "MethodRunId should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = MethodRunId(id) val json = Json.encodeToString(m) val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "MethodRunPosition should be serialized as String" { - val m = MethodRunPosition("qwerty") - + "MethodRunPosition should be serialized as String and reversible in json" { + val m = MethodRunPosition.new() val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(-1) m2 shouldBe m } - "PropertyHash should be serialized as String" { - val m = PropertyHash("qwerty") - + "PropertyHash should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = PropertyHash(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "PropertyName should be serialized as String" { - val m = PropertyName("qwerty") - + "PropertyName should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = PropertyName(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "PropertyValue should be serialized as SerializedData" { - val m = PropertyValue.from("qwerty") - + "PropertyValue should be serialized as SerializedData and reversible in json" { + val id = TestFactory.random() + val m = PropertyValue.from(id) val json = Json.encodeToString(m) - json shouldBe Json.encodeToString(SerializedData.from(m.get())) - val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(SerializedData.from(id)) m2 shouldBe m } - "StepHash should be serialized as String" { - val m = StepHash("qwerty") - + "StepHash should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = StepHash(id) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" val m2 = Json.decodeFromString(json) + + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "StepId should be serialized as String" { - val m = StepId() + "StepId should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = StepId(id) val json = Json.encodeToString(m) val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "StepOutput should be serialized as SerializedData" { - val m = StepReturnValue.from("qwerty") - + "StepOutput should be serialized as SerializedData and reversible in json" { + val id = TestFactory.random() + val m = ReturnValue.from(id) val json = Json.encodeToString(m) - json shouldBe Json.encodeToString(SerializedData.from(m.get())) + val m2 = Json.decodeFromString(json) - val m2 = Json.decodeFromString(json) + json shouldBe Json.encodeToString(SerializedData.from(id)) m2 shouldBe m } - "WorkflowId should be serialized as String" { - val m = WorkflowId() + "WorkflowMeta should be serializable and reversible in json" { + val id = TestFactory.random() + val m = WorkflowMeta(mapOf("a" to id.toByteArray())) val json = Json.encodeToString(m) - val m2 = Json.decodeFromString(json) + val m2 = Json.decodeFromString(json) m2 shouldBe m } - "WorkflowMeta should be serializable" { - val m = WorkflowMeta(mapOf("a" to "1".toByteArray())) - + "WorkflowName should be serialized as String and reversible in json" { + val id = TestFactory.random() + val m = WorkflowName(id) val json = Json.encodeToString(m) + val m2 = Json.decodeFromString(json) - val m2 = Json.decodeFromString(json) - + json shouldBe Json.encodeToString(id) m2 shouldBe m } - "WorkflowName should be serialized as String" { - val m = WorkflowName("qwerty") - + "WorkflowTaskIndex should be serialized as Int and reversible in json" { + val m = WorkflowTaskIndex(42) val json = Json.encodeToString(m) - json shouldBe "\"qwerty\"" - val m2 = Json.decodeFromString(json) + val m2 = Json.decodeFromString(json) + + json shouldBe "42" m2 shouldBe m } - "WorkflowTaskIndex should be serialized as Int" { - val m = WorkflowTaskIndex(42) - + "DispatchWorkflowPastCommand should reversible in json" { + val m = TestFactory.random() val json = Json.encodeToString(m) - json shouldBe "42" - val m2 = Json.decodeFromString(json) + val m2 = Json.decodeFromString(json) + m2 shouldBe m } }) diff --git a/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/StateTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/StateTests.kt index a69229b53..a552461b4 100644 --- a/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/StateTests.kt +++ b/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/StateTests.kt @@ -28,15 +28,18 @@ package io.infinitic.common.workflows import com.github.avrokotlin.avro4k.Avro import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.workflows.engine.state.WorkflowState +import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe class StateTests : StringSpec({ "WorkflowState should be avro-convertible" { - val msg = TestFactory.random() - val ser = WorkflowState.serializer() - val byteArray = Avro.default.encodeToByteArray(ser, msg) - val msg2 = Avro.default.decodeFromByteArray(ser, byteArray) - msg shouldBe msg2 + shouldNotThrowAny { + val msg = TestFactory.random() + val ser = WorkflowState.serializer() + val byteArray = Avro.default.encodeToByteArray(ser, msg) + val msg2 = Avro.default.decodeFromByteArray(ser, byteArray) + msg shouldBe msg2 + } } }) diff --git a/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/data/steps/StepTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/data/steps/StepTests.kt index 3a6ff5505..b9d045ea9 100644 --- a/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/data/steps/StepTests.kt +++ b/infinitic-common/src/test/kotlin/io/infinitic/common/workflows/data/steps/StepTests.kt @@ -25,20 +25,19 @@ package io.infinitic.common.workflows.data.steps +import io.infinitic.common.data.ReturnValue import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandReturnValue import io.infinitic.common.workflows.data.commands.CommandStatus.Completed -import io.infinitic.common.workflows.data.commands.CommandStatus.Running import io.infinitic.common.workflows.data.steps.Step.And import io.infinitic.common.workflows.data.steps.Step.Or import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe -fun getStepId() = Step.Id(CommandId(), Running) +fun getStepId() = Step.Id(CommandId()) fun getCompletedStatus(output: Any? = null, index: Int = 0) = Completed( - returnValue = CommandReturnValue.from(output), + returnValue = ReturnValue.from(output), completionWorkflowTaskIndex = WorkflowTaskIndex(index) ) @@ -54,7 +53,7 @@ class StepTests : StringSpec({ val step = Or(listOf(stepA)) step.isTerminated() shouldBe false - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step.isTerminated() shouldBe true } @@ -63,7 +62,7 @@ class StepTests : StringSpec({ val step = And(listOf(stepA)) step.isTerminated() shouldBe false - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step.isTerminated() shouldBe true } @@ -73,9 +72,9 @@ class StepTests : StringSpec({ val step = And(listOf(stepA, stepB)) step.isTerminated() shouldBe false - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step.isTerminated() shouldBe false - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step.isTerminated() shouldBe true step shouldBe And(listOf(stepA, stepB)) } @@ -86,7 +85,7 @@ class StepTests : StringSpec({ val step = Or(listOf(stepA, stepB)) step.isTerminated() shouldBe false - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step shouldBe Or(listOf(stepA)) } @@ -97,7 +96,7 @@ class StepTests : StringSpec({ val step = Or(listOf(stepA, Or(listOf(stepB, stepC)))) step.isTerminated() shouldBe false - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step shouldBe Or(listOf(stepB)) } @@ -108,9 +107,9 @@ class StepTests : StringSpec({ val step = And(listOf(stepA, Or(listOf(stepB, stepC)))) step.isTerminated() shouldBe false - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step.isTerminated() shouldBe false - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step shouldBe And(listOf(stepA, stepB)) } @@ -121,11 +120,11 @@ class StepTests : StringSpec({ val step = And(listOf(stepA, And(listOf(stepB, stepC)))) step.isTerminated() shouldBe false - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step.isTerminated() shouldBe false - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step.isTerminated() shouldBe false - step.update(stepC.commandId, getCompletedStatus()) + step.updateWith(stepC.commandId, getCompletedStatus()) step.isTerminated() shouldBe true step shouldBe And(listOf(stepA, stepB, stepC)) } @@ -135,7 +134,7 @@ class StepTests : StringSpec({ val stepB = getStepId() val step = Or(listOf(stepA, stepB)) - step.update(stepA.commandId, getCompletedStatus()) + step.updateWith(stepA.commandId, getCompletedStatus()) step shouldBe Or(listOf(stepA)) } @@ -145,7 +144,7 @@ class StepTests : StringSpec({ val stepC = getStepId() val step = Or(listOf(stepA, Or(listOf(stepB, stepC)))) - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step shouldBe Or(listOf(stepB)) } @@ -155,8 +154,8 @@ class StepTests : StringSpec({ val stepC = getStepId() val step = Or(listOf(stepA, And(listOf(stepB, stepC)))) - step.update(stepB.commandId, getCompletedStatus()) - step.update(stepC.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) + step.updateWith(stepC.commandId, getCompletedStatus()) step.isTerminated() shouldBe true step shouldBe Or(listOf(And(listOf(stepB, stepC)))) } @@ -167,7 +166,7 @@ class StepTests : StringSpec({ val stepC = getStepId() val step = And(listOf(stepA, Or(listOf(stepB, stepC)))) - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step shouldBe And(listOf(stepA, stepB)) } @@ -178,8 +177,8 @@ class StepTests : StringSpec({ val stepD = getStepId() val step = Or(listOf(stepA, And(listOf(stepB, Or(listOf(stepC, stepD)))))) - step.update(stepC.commandId, getCompletedStatus()) - step.update(stepB.commandId, getCompletedStatus()) + step.updateWith(stepC.commandId, getCompletedStatus()) + step.updateWith(stepB.commandId, getCompletedStatus()) step shouldBe Or(listOf(And(listOf(stepB, stepC)))) } }) diff --git a/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodReturnValue.kt b/infinitic-common/src/test/kotlin/io/infinitic/workflows/DataTests.kt similarity index 50% rename from infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodReturnValue.kt rename to infinitic-common/src/test/kotlin/io/infinitic/workflows/DataTests.kt index b26f679bd..3da87c59b 100644 --- a/infinitic-common/src/main/kotlin/io/infinitic/common/data/methods/MethodReturnValue.kt +++ b/infinitic-common/src/test/kotlin/io/infinitic/workflows/DataTests.kt @@ -23,28 +23,36 @@ * Licensor: infinitic.io */ -package io.infinitic.common.data.methods +package io.infinitic.workflows -import io.infinitic.common.data.Data +import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.serDe.SerializedData -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder +import io.infinitic.common.workflows.data.steps.Step +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.mockk.mockk +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json -@Serializable(with = MethodReturnValueSerializer::class) -data class MethodReturnValue(override val serializedData: SerializedData) : Data(serializedData) { - companion object { - fun from(data: Any?) = MethodReturnValue(SerializedData.from(data)) +@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class) +class DataTests : StringSpec({ + "Deferred should be serDe with SerializedData" { + val step = TestFactory.random() + val m1 = Deferred(step).apply { this.workflowDispatcher = mockk(); } + + val data = SerializedData.from(m1) + val m2 = data.deserialize() + + m2 shouldBe m1 } -} -object MethodReturnValueSerializer : KSerializer { - override val descriptor: SerialDescriptor = SerializedData.serializer().descriptor - override fun serialize(encoder: Encoder, value: MethodReturnValue) { - SerializedData.serializer().serialize(encoder, value.serializedData) + "Deferred should be json-serializable by kotlinx.serialization" { + val step = TestFactory.random() + val m1 = Deferred(step).apply { this.workflowDispatcher = mockk() } + val json = Json.encodeToString(m1) + val m2 = Json.decodeFromString>(json) + + m2 shouldBe m1 } - override fun deserialize(decoder: Decoder) = - MethodReturnValue(SerializedData.serializer().deserialize(decoder)) -} +}) diff --git a/infinitic-common/src/test/kotlin/io/infinitic/workflows/WorkflowTests.kt b/infinitic-common/src/test/kotlin/io/infinitic/workflows/WorkflowTests.kt new file mode 100644 index 000000000..b008a1981 --- /dev/null +++ b/infinitic-common/src/test/kotlin/io/infinitic/workflows/WorkflowTests.kt @@ -0,0 +1,115 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.workflows + +import io.infinitic.common.proxies.NewTaskProxyHandler +import io.infinitic.common.proxies.NewWorkflowProxyHandler +import io.infinitic.common.tasks.data.TaskMeta +import io.infinitic.common.tasks.data.TaskName +import io.infinitic.common.tasks.data.TaskTag +import io.infinitic.common.workflows.data.workflows.WorkflowMeta +import io.infinitic.common.workflows.data.workflows.WorkflowName +import io.infinitic.common.workflows.data.workflows.WorkflowTag +import io.infinitic.workflows.samples.TaskA +import io.infinitic.workflows.samples.WorkflowA +import io.infinitic.workflows.samples.WorkflowAImpl +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import kotlinx.serialization.ExperimentalSerializationApi + +@OptIn(ExperimentalSerializationApi::class) +class WorkflowTests : StringSpec({ + val dispatcher = mockk() + val newTaskSlot = slot>() + val newWorkflowSlot = slot>() + every { dispatcher.dispatchAndWait(capture(newTaskSlot)) } returns 100L + every { dispatcher.dispatch(capture(newTaskSlot), false) } returns mockk() + every { dispatcher.dispatchAndWait(capture(newWorkflowSlot)) } returns 100L + every { dispatcher.dispatch(capture(newWorkflowSlot), false) } returns mockk() + + "Workflow should trigger synchronously a task" { + val w = WorkflowAImpl().apply { this.dispatcher = dispatcher } + + w.syncTask(100) + + val proxy = newTaskSlot.captured + proxy.taskName shouldBe TaskName(TaskA::class.java.name) + proxy.methodName.toString() shouldBe TaskA::await.name + proxy.methodArgs.toList() shouldBe listOf(100) + proxy.taskTags shouldBe setOf(TaskTag("foo"), TaskTag("bar")) + proxy.taskMeta shouldBe TaskMeta(mapOf("foo" to "bar".toByteArray())) + } + + "Workflow should trigger asynchronously a task" { + val w = WorkflowAImpl().apply { this.dispatcher = dispatcher } + + w.asyncTask(100) + + val proxy = newTaskSlot.captured + proxy.taskName shouldBe TaskName(TaskA::class.java.name) + proxy.methodName.toString() shouldBe TaskA::await.name + proxy.methodArgs.toList() shouldBe listOf(100) + proxy.taskTags shouldBe setOf(TaskTag("foo"), TaskTag("bar")) + proxy.taskMeta shouldBe TaskMeta(mapOf("foo" to "bar".toByteArray())) + } + + "Workflow should trigger synchronously a child-workflow" { + val w = WorkflowAImpl().apply { this.dispatcher = dispatcher } + + w.syncWorkflow(100) + + val proxy = newWorkflowSlot.captured + proxy.workflowName shouldBe WorkflowName(WorkflowA::class.java.name) + proxy.methodName.toString() shouldBe WorkflowA::syncTask.name + proxy.methodArgs.toList() shouldBe listOf(100) + proxy.workflowTags shouldBe setOf(WorkflowTag("foo"), WorkflowTag("bar")) + proxy.workflowMeta shouldBe WorkflowMeta(mapOf("foo" to "bar".toByteArray())) + } + + "Workflow should trigger asynchronously a child-workflow" { + val w = WorkflowAImpl().apply { this.dispatcher = dispatcher } + + w.asyncWorkflow(100) + + val proxy = newWorkflowSlot.captured + proxy.workflowName shouldBe WorkflowName(WorkflowA::class.java.name) + proxy.methodName.toString() shouldBe WorkflowA::syncTask.name + proxy.methodArgs.toList() shouldBe listOf(100) + proxy.workflowTags shouldBe setOf(WorkflowTag("foo"), WorkflowTag("bar")) + proxy.workflowMeta shouldBe WorkflowMeta(mapOf("foo" to "bar".toByteArray())) + } + +// "Workflow should throw when dispatching its own method" { +// val w = WorkflowAImpl().apply { this.dispatcher = dispatcher } +// +// w.dispatchSelf() +// +// // println(newTaskSlot.captured.method()) +// } +}) diff --git a/infinitic-common/src/test/kotlin/io/infinitic/workflows/samples/TaskA.kt b/infinitic-common/src/test/kotlin/io/infinitic/workflows/samples/TaskA.kt new file mode 100644 index 000000000..7203e4b64 --- /dev/null +++ b/infinitic-common/src/test/kotlin/io/infinitic/workflows/samples/TaskA.kt @@ -0,0 +1,36 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.workflows.samples + +interface ParentInterface { + fun parent(): String +} + +interface TaskA : ParentInterface { + fun concat(str1: String, str2: String): String + fun reverse(str: String): String + fun await(delay: Long): Long +} diff --git a/infinitic-common/src/test/kotlin/io/infinitic/workflows/samples/WorkflowA.kt b/infinitic-common/src/test/kotlin/io/infinitic/workflows/samples/WorkflowA.kt new file mode 100644 index 000000000..8bda7cc7d --- /dev/null +++ b/infinitic-common/src/test/kotlin/io/infinitic/workflows/samples/WorkflowA.kt @@ -0,0 +1,69 @@ +/** + * "Commons Clause" License Condition v1.0 + * + * The Software is provided to you by the Licensor under the License, as defined + * below, subject to the following condition. + * + * Without limiting other conditions in the License, the grant of rights under the + * License will not include, and the License does not grant to you, the right to + * Sell the Software. + * + * For purposes of the foregoing, “Sell” means practicing any or all of the rights + * granted to you under the License to provide to third parties, for a fee or + * other consideration (including without limitation fees for hosting or + * consulting/ support services related to the Software), a product or service + * whose value derives, entirely or substantially, from the functionality of the + * Software. Any license notice or attribution required by the License must also + * include this Commons Clause License Condition notice. + * + * Software: Infinitic + * + * License: MIT License (https://opensource.org/licenses/MIT) + * + * Licensor: infinitic.io + */ + +package io.infinitic.workflows.samples + +import io.infinitic.workflows.Deferred +import io.infinitic.workflows.SendChannel +import io.infinitic.workflows.Workflow +import kotlinx.serialization.Serializable + +sealed class Obj +@Serializable +data class Obj1(val foo: String, val bar: Int) : Obj() +@Serializable +data class Obj2(val foo: String, val bar: Int) : Obj() + +interface WorkflowA { + val channelObj: SendChannel + val channelString: SendChannel + + fun empty(): String + fun syncTask(duration: Long): Long + fun asyncTask(duration: Long): Deferred + fun syncWorkflow(duration: Long): Long + fun asyncWorkflow(duration: Long): Deferred + fun dispatchSelf(): Deferred +} + +class WorkflowAImpl : Workflow(), WorkflowA { + val self: WorkflowA = getWorkflowById(WorkflowA::class.java, "id") + override val channelObj = channel() + override val channelString = channel() + private val taskA = newTask(TaskA::class.java, tags = setOf("foo", "bar"), meta = mapOf("foo" to "bar".toByteArray())) + private val workflowA = newWorkflow(WorkflowA::class.java, tags = setOf("foo", "bar"), meta = mapOf("foo" to "bar".toByteArray())) + + override fun empty() = "void" + override fun syncTask(duration: Long) = taskA.await(duration) + override fun asyncTask(duration: Long) = dispatch(taskA::await, duration) + override fun syncWorkflow(duration: Long) = workflowA.syncTask(duration) + override fun asyncWorkflow(duration: Long) = dispatch(workflowA::syncTask, duration) + + override fun dispatchSelf(): Deferred = dispatch(self::empty) + + fun seqBis() { + taskA.await(100) + } +} diff --git a/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/TestFactory.kt b/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/TestFactory.kt index 7eebb6996..7dda28296 100644 --- a/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/TestFactory.kt +++ b/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/TestFactory.kt @@ -26,7 +26,8 @@ package io.infinitic.common.fixtures import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.DeferredError +import io.infinitic.common.errors.WorkerError import io.infinitic.common.metrics.global.messages.MetricsGlobalEnvelope import io.infinitic.common.metrics.global.messages.MetricsGlobalMessage import io.infinitic.common.metrics.perName.messages.MetricsPerNameEnvelope @@ -35,7 +36,6 @@ import io.infinitic.common.serDe.SerializedData import io.infinitic.common.tasks.engine.messages.TaskEngineEnvelope import io.infinitic.common.tasks.engine.messages.TaskEngineMessage import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandStatus.Running import io.infinitic.common.workflows.data.steps.Step import io.infinitic.common.workflows.engine.messages.WorkflowEngineEnvelope import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage @@ -72,8 +72,9 @@ object TestFactory { .randomize(String::class.java) { String(random(), Charsets.UTF_8) } .randomize(ByteArray::class.java) { Random(seed).nextBytes(10) } .randomize(ByteBuffer::class.java) { ByteBuffer.wrap(random()) } - .randomize(MethodParameters::class.java) { MethodParameters.from(random(), random()) } + .randomize(WorkerError::class.java) { WorkerError(random(), random(), random(), random(), null) } .randomize(SerializedData::class.java) { SerializedData.from(random()) } + .randomize(MethodParameters::class.java) { MethodParameters.from(random(), random()) } .randomize(WorkflowEngineEnvelope::class.java) { val sub = WorkflowEngineMessage::class.sealedSubclasses.shuffled().first() WorkflowEngineEnvelope.from(random(sub)) @@ -90,15 +91,9 @@ object TestFactory { val sub = MetricsGlobalMessage::class.sealedSubclasses.shuffled().first() MetricsGlobalEnvelope.from(random(sub)) } - .randomize(Error::class.java) { - Error( - errorName = random(), - errorStackTraceToString = random(), - errorMessage = random(), - errorCause = null, - whereId = null, - whereName = null - ) + .randomize(DeferredError::class.java) { + val sub = DeferredError::class.sealedSubclasses.shuffled().first() + random(sub) } values?.forEach { @@ -109,7 +104,7 @@ object TestFactory { } private fun steps(): Map { - fun getStepId() = Step.Id(CommandId(), Running) + fun getStepId() = Step.Id(CommandId()) val stepA = getStepId() val stepB = getStepId() val stepC = getStepId() diff --git a/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/after.kt b/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/after.kt index 402d72e03..c79b9981e 100644 --- a/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/after.kt +++ b/infinitic-common/src/testFixtures/kotlin/io/infinitic/common/fixtures/after.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -fun after(delay: Long = 100L, f: suspend CoroutineScope.() -> Unit) = CoroutineScope(Dispatchers.IO).launch { +fun later(delay: Long = 100L, f: suspend CoroutineScope.() -> Unit) = CoroutineScope(Dispatchers.IO).launch { delay(delay) f() } diff --git a/infinitic-dashboard/src/main/kotlin/io/infinitic/dashboard/main.kt b/infinitic-dashboard/src/main/kotlin/io/infinitic/dashboard/main.kt index 91425c626..dec384c31 100644 --- a/infinitic-dashboard/src/main/kotlin/io/infinitic/dashboard/main.kt +++ b/infinitic-dashboard/src/main/kotlin/io/infinitic/dashboard/main.kt @@ -25,7 +25,7 @@ package io.infinitic.dashboard -import io.infinitic.exceptions.thisShouldNotHappen +import io.infinitic.common.exceptions.thisShouldNotHappen fun main(args: Array) { // get name of config file diff --git a/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticClientFactory.kt b/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticClientFactory.kt index 27484a786..e373cdfbc 100644 --- a/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticClientFactory.kt +++ b/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticClientFactory.kt @@ -34,9 +34,8 @@ import io.infinitic.worker.config.WorkerConfig @Suppress("unused") object InfiniticClientFactory { - /** - * Create InfiniticWorker from file in resources directory + * Create InfiniticClient with config from resources directory */ @JvmStatic fun fromConfigResource(vararg resources: String): InfiniticClient { @@ -49,7 +48,7 @@ object InfiniticClientFactory { } /** - * Create InfiniticWorker from file in system file + * Create InfiniticClient with config from system file */ @JvmStatic fun fromConfigFile(vararg files: String): InfiniticClient { diff --git a/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticWorkerFactory.kt b/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticWorkerFactory.kt index d517e07ab..eb70b610e 100644 --- a/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticWorkerFactory.kt +++ b/infinitic-factory/src/main/kotlin/io/infinitic/factory/InfiniticWorkerFactory.kt @@ -34,13 +34,13 @@ import io.infinitic.worker.config.WorkerConfig @Suppress("unused") object InfiniticWorkerFactory { /** - * Create InfiniticWorker from file in resources directory + * Create InfiniticWorker with config from resources directory */ @JvmStatic fun fromConfigResource(vararg resources: String): InfiniticWorker = fromConfig(WorkerConfig.fromResource(*resources)) /** - * Create InfiniticWorker from file in system file + * Create InfiniticWorker with config from system file */ @JvmStatic fun fromConfigFile(vararg files: String): InfiniticWorker = fromConfig(WorkerConfig.fromFile(*files)) diff --git a/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticClient.kt b/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticClient.kt index a9852852a..97d289f12 100644 --- a/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticClient.kt +++ b/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticClient.kt @@ -27,7 +27,7 @@ package io.infinitic.inMemory import io.infinitic.client.InfiniticClient import io.infinitic.client.worker.startClientWorker -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.inMemory.transport.InMemoryOutput import io.infinitic.worker.config.WorkerConfig import kotlinx.coroutines.CoroutineName @@ -36,7 +36,7 @@ import kotlinx.coroutines.launch @Suppress("MemberVisibilityCanBePrivate") class InMemoryInfiniticClient( workerConfig: WorkerConfig, - val name: String? = null + name: String? = null ) : InfiniticClient() { private val inMemoryOutput = InMemoryOutput(sendingScope) @@ -45,7 +45,7 @@ class InMemoryInfiniticClient( InMemoryInfiniticWorker(workerConfig).apply { output = inMemoryOutput client = this@InMemoryInfiniticClient - name = clientName.name + this.name = clientName.toString() } } @@ -75,7 +75,7 @@ class InMemoryInfiniticClient( ) } - worker.runningScope.launch { worker.start() } + worker.startAsync() } override fun close() { diff --git a/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticWorker.kt b/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticWorker.kt index b6a6f561b..3767fc162 100644 --- a/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticWorker.kt +++ b/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/InMemoryInfiniticWorker.kt @@ -26,10 +26,10 @@ package io.infinitic.inMemory import io.infinitic.common.data.Name +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.workflows.data.workflowTasks.isWorkflowTask import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.inMemory.transport.InMemoryOutput import io.infinitic.metrics.global.engine.storage.MetricsGlobalStateStorage import io.infinitic.metrics.global.engine.worker.startMetricsGlobalEngine @@ -52,8 +52,9 @@ import io.infinitic.worker.config.WorkerConfig import io.infinitic.workflows.engine.storage.WorkflowStateStorage import io.infinitic.workflows.engine.worker.WorkflowEngineMessageToProcess import io.infinitic.workflows.engine.worker.startWorkflowEngine -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import java.util.concurrent.CompletableFuture @Suppress("MemberVisibilityCanBePrivate") class InMemoryInfiniticWorker( @@ -64,15 +65,15 @@ class InMemoryInfiniticWorker( lateinit var client: InMemoryInfiniticClient override lateinit var name: String - override fun start() { + override fun startAsync(): CompletableFuture = if (this::output.isInitialized && this::client.isInitialized && this::name.isInitialized) { - super.start() + super.startAsync() } else { - logger.warn { "Can not start ${InMemoryInfiniticWorker::class.java.name} outside of an in-memory client - Closing" } + logger.warn { "Should not start ${InMemoryInfiniticWorker::class.java.name} outside of an in-memory client - Closing" } + CompletableFuture.completedFuture(null) } - } - override fun CoroutineScope.startTaskExecutors(name: Name, concurrency: Int) { + override fun startTaskExecutors(name: Name, concurrency: Int) { when (name) { is TaskName -> { @@ -80,14 +81,15 @@ class InMemoryInfiniticWorker( output.taskExecutorChannel[name] = channel repeat(concurrency) { - startTaskExecutor( - "in-memory-task-executor-$it: $name", - taskExecutorRegister, - inputChannel = channel, - outputChannel = output.logChannel, - output.sendEventsToTaskEngine(name), - { client } - ) + runningScope.launch { + startTaskExecutor( + "in-memory-task-executor-$it: $name", + taskExecutorRegister, + inputChannel = channel, + outputChannel = output.logChannel, + output.sendEventsToTaskEngine(name) + ) { client } + } } } is WorkflowName -> @@ -96,163 +98,178 @@ class InMemoryInfiniticWorker( output.workflowTaskExecutorChannel[name] = channel repeat(concurrency) { - startTaskExecutor( - "in-memory-workflow-task-executor-$it: $name", - taskExecutorRegister, - inputChannel = channel, - outputChannel = output.logChannel, - output.sendEventsToTaskEngine(name), - { client } - ) + runningScope.launch { + startTaskExecutor( + "in-memory-workflow-task-executor-$it: $name", + taskExecutorRegister, + inputChannel = channel, + outputChannel = output.logChannel, + output.sendEventsToTaskEngine(name) + ) { client } + } } } else -> thisShouldNotHappen() } } - override fun CoroutineScope.startTaskTagEngines(taskName: TaskName, concurrency: Int, storage: TaskTagStorage) { + override fun startTaskTagEngines(taskName: TaskName, concurrency: Int, storage: TaskTagStorage) { val commandsChannel = Channel() val eventsChannel = Channel() output.taskTagCommandsChannel[taskName] = commandsChannel output.taskTagEventsChannel[taskName] = eventsChannel - startTaskTagEngine( - "in-memory-task-tag-engine: $taskName", - storage, - eventsInputChannel = eventsChannel, - eventsOutputChannel = output.logChannel, - commandsInputChannel = commandsChannel, - commandsOutputChannel = output.logChannel, - output.sendCommandsToTaskEngine(taskName), - output.sendToClient - ) + runningScope.launch { + startTaskTagEngine( + "task-tag-engine: $name", + storage, + eventsInputChannel = eventsChannel, + eventsOutputChannel = output.logChannel, + commandsInputChannel = commandsChannel, + commandsOutputChannel = output.logChannel, + output.sendCommandsToTaskEngine(taskName), + output.sendToClient + ) + } } - override fun CoroutineScope.startTaskEngines(taskName: TaskName, concurrency: Int, storage: TaskStateStorage) { + override fun startTaskEngines(taskName: TaskName, concurrency: Int, storage: TaskStateStorage) { val commandsChannel = Channel() val eventsChannel = Channel() output.taskCommandsChannel[taskName] = commandsChannel output.taskEventsChannel[taskName] = eventsChannel - startTaskEngine( - "in-memory-task-engine: $taskName", - storage, - eventsInputChannel = eventsChannel, - eventsOutputChannel = output.logChannel, - commandsInputChannel = commandsChannel, - commandsOutputChannel = output.logChannel, - output.sendToClient, - output.sendEventsToTaskTagEngine, - output.sendToTaskEngineAfter(taskName), - output.sendEventsToWorkflowEngine, - output.sendToTaskExecutors(taskName), - output.sendToMetricsPerName(taskName) - ) + runningScope.launch { + startTaskEngine( + "in-memory-task-engine: $taskName", + storage, + eventsInputChannel = eventsChannel, + eventsOutputChannel = output.logChannel, + commandsInputChannel = commandsChannel, + commandsOutputChannel = output.logChannel, + output.sendToClient, + output.sendEventsToTaskTagEngine, + output.sendToTaskEngineAfter(taskName), + output.sendEventsToWorkflowEngine, + output.sendToTaskExecutors(taskName), + output.sendToMetricsPerName(taskName) + ) + } } - override fun CoroutineScope.startTaskDelayEngines(taskName: TaskName, concurrency: Int) { - // Implementation not needed + override fun startTaskDelayEngines(taskName: TaskName, concurrency: Int) { + // not needed } - override fun CoroutineScope.startWorkflowTagEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowTagStorage) { + override fun startWorkflowTagEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowTagStorage) { val commandsChannel = Channel() val eventsChannel = Channel() output.workflowTagCommandsChannel[workflowName] = commandsChannel output.workflowTagEventsChannel[workflowName] = eventsChannel - startWorkflowTagEngine( - "in-memory-workflow-tag-engine: $workflowName", - storage, - eventsInputChannel = eventsChannel, - eventsOutputChannel = output.logChannel, - commandsInputChannel = commandsChannel, - commandsOutputChannel = output.logChannel, - output.sendCommandsToWorkflowEngine, - output.sendToClient - ) + runningScope.launch { + startWorkflowTagEngine( + "workflow-tag-engine: $name", + storage, + eventsInputChannel = eventsChannel, + eventsOutputChannel = output.logChannel, + commandsInputChannel = commandsChannel, + commandsOutputChannel = output.logChannel, + output.sendCommandsToWorkflowEngine, + output.sendToClient + ) + } } - override fun CoroutineScope.startWorkflowEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowStateStorage) { + override fun startWorkflowEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowStateStorage) { val commandsChannel = Channel() val eventsChannel = Channel() output.workflowCommandsChannel[workflowName] = commandsChannel output.workflowEventsChannel[workflowName] = eventsChannel - startWorkflowEngine( - "in-memory-workflow-engine", - storage, - eventsInputChannel = eventsChannel, - eventsOutputChannel = output.logChannel, - commandsInputChannel = commandsChannel, - commandsOutputChannel = output.logChannel, - output.sendToClient, - output.sendCommandsToTaskTagEngine, - { - when (it.isWorkflowTask()) { - true -> output.sendCommandsToTaskEngine(workflowName)(it) - false -> output.sendCommandsToTaskEngine(it.taskName)(it) - } - }, - output.sendEventsToWorkflowTagEngine, - output.sendEventsToWorkflowEngine, - output.sendToWorkflowEngineAfter - ) + runningScope.launch { + startWorkflowEngine( + "workflow-engine: $name", + storage, + eventsInputChannel = eventsChannel, + eventsOutputChannel = output.logChannel, + commandsInputChannel = commandsChannel, + commandsOutputChannel = output.logChannel, + output.sendToClient, + output.sendCommandsToTaskTagEngine, + { + when (it.isWorkflowTask()) { + true -> output.sendCommandsToTaskEngine(workflowName)(it) + false -> output.sendCommandsToTaskEngine(it.taskName)(it) + } + }, + output.sendEventsToWorkflowTagEngine, + output.sendEventsToWorkflowEngine, + output.sendToWorkflowEngineAfter + ) + } } - override fun CoroutineScope.startWorkflowDelayEngines(workflowName: WorkflowName, concurrency: Int) { + override fun startWorkflowDelayEngines(workflowName: WorkflowName, concurrency: Int) { // Implementation not needed } - override fun CoroutineScope.startTaskEngines(workflowName: WorkflowName, concurrency: Int, storage: TaskStateStorage) { + override fun startTaskEngines(workflowName: WorkflowName, concurrency: Int, storage: TaskStateStorage) { val commandsChannel = Channel() val eventsChannel = Channel() output.workflowTaskCommandsChannel[workflowName] = commandsChannel output.workflowTaskEventsChannel[workflowName] = eventsChannel - startTaskEngine( - "in-memory-workflow-task-engine: $workflowName", - storage, - eventsInputChannel = eventsChannel, - eventsOutputChannel = output.logChannel, - commandsInputChannel = commandsChannel, - commandsOutputChannel = output.logChannel, - output.sendToClient, - output.sendEventsToTaskTagEngine, - output.sendToTaskEngineAfter(workflowName), - output.sendEventsToWorkflowEngine, - output.sendToTaskExecutors(workflowName), - output.sendToMetricsPerName(workflowName) - ) + runningScope.launch { + startTaskEngine( + "in-memory-workflow-task-engine: $workflowName", + storage, + eventsInputChannel = eventsChannel, + eventsOutputChannel = output.logChannel, + commandsInputChannel = commandsChannel, + commandsOutputChannel = output.logChannel, + output.sendToClient, + output.sendEventsToTaskTagEngine, + output.sendToTaskEngineAfter(workflowName), + output.sendEventsToWorkflowEngine, + output.sendToTaskExecutors(workflowName), + output.sendToMetricsPerName(workflowName) + ) + } } - override fun CoroutineScope.startTaskDelayEngines(workflowName: WorkflowName, concurrency: Int) { + override fun startTaskDelayEngines(workflowName: WorkflowName, concurrency: Int) { // Implementation not needed } - override fun CoroutineScope.startMetricsPerNameEngines(taskName: TaskName, storage: MetricsPerNameStateStorage) { + override fun startMetricsPerNameEngines(taskName: TaskName, storage: MetricsPerNameStateStorage) { val channel = Channel() output.taskMetricsPerNameChannel[taskName] = channel - startMetricsPerNameEngine( - "in-memory-metrics-per-name-engine", - storage, - inputChannel = channel, - outputChannel = output.logChannel, - output.sendToMetricsGlobal - ) + runningScope.launch { + startMetricsPerNameEngine( + "metrics-per-name-engine", + storage, + inputChannel = channel, + outputChannel = output.logChannel, + output.sendToMetricsGlobal + ) + } } - override fun CoroutineScope.startMetricsGlobalEngine(storage: MetricsGlobalStateStorage) { - startMetricsGlobalEngine( - "in-memory-metrics-global-engine", - storage, - inputChannel = output.metricsGlobalChannel, - outputChannel = output.logChannel - ) + override fun startMetricsGlobalEngine(storage: MetricsGlobalStateStorage) { + runningScope.launch { + startMetricsGlobalEngine( + "in-memory-metrics-global-engine", + storage, + inputChannel = output.metricsGlobalChannel, + outputChannel = output.logChannel + ) + } } } diff --git a/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/transport/InMemoryOutput.kt b/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/transport/InMemoryOutput.kt index 4f17eeb7a..aa6a918b4 100644 --- a/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/transport/InMemoryOutput.kt +++ b/infinitic-inMemory/src/main/kotlin/io/infinitic/inMemory/transport/InMemoryOutput.kt @@ -29,6 +29,7 @@ import io.infinitic.common.clients.messages.ClientMessage import io.infinitic.common.clients.transport.ClientMessageToProcess import io.infinitic.common.clients.transport.SendToClient import io.infinitic.common.data.Name +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.metrics.global.messages.MetricsGlobalMessage import io.infinitic.common.metrics.global.transport.SendToMetricsGlobal import io.infinitic.common.metrics.perName.messages.MetricsPerNameMessage @@ -45,7 +46,6 @@ import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.SendToWorkflowEngineAfter import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.metrics.global.engine.worker.MetricsGlobalMessageToProcess import io.infinitic.metrics.perName.engine.worker.MetricsPerNameMessageToProcess import io.infinitic.tags.tasks.worker.TaskTagEngineMessageToProcess diff --git a/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/global/engine/worker/StartMetricsGlobalEngine.kt b/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/global/engine/worker/StartMetricsGlobalEngine.kt index 07ac845b9..6fcf21889 100644 --- a/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/global/engine/worker/StartMetricsGlobalEngine.kt +++ b/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/global/engine/worker/StartMetricsGlobalEngine.kt @@ -45,11 +45,11 @@ private fun logError(messageToProcess: MetricsGlobalMessageToProcess, e: Throwab } fun CoroutineScope.startMetricsGlobalEngine( - coroutineName: String, + name: String, metricsGlobalStateStorage: MetricsGlobalStateStorage, inputChannel: ReceiveChannel, outputChannel: SendChannel -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { val metricsGlobalEngine = MetricsGlobalEngine( metricsGlobalStateStorage diff --git a/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameEngine.kt b/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameEngine.kt index 5444fa37c..2de5c8297 100644 --- a/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameEngine.kt +++ b/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameEngine.kt @@ -25,6 +25,7 @@ package io.infinitic.metrics.perName.engine +import io.infinitic.common.data.ClientName import io.infinitic.common.metrics.global.messages.TaskCreated import io.infinitic.common.metrics.global.transport.SendToMetricsGlobal import io.infinitic.common.metrics.perName.messages.MetricsPerNameMessage @@ -36,6 +37,7 @@ import io.infinitic.metrics.perName.engine.storage.MetricsPerNameStateStorage import mu.KotlinLogging class MetricsPerNameEngine( + private val clientName: ClientName, storage: MetricsPerNameStateStorage, val sendToMetricsGlobal: SendToMetricsGlobal ) { @@ -64,7 +66,10 @@ class MetricsPerNameEngine( // It's a new task type if (oldState == null) { - val taskCreated = TaskCreated(taskName = message.taskName) + val taskCreated = TaskCreated( + taskName = message.taskName, + emitterName = clientName + ) sendToMetricsGlobal(taskCreated) } diff --git a/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/worker/StartMetricsPerNameEngine.kt b/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/worker/StartMetricsPerNameEngine.kt index 140950936..a8e5f0161 100644 --- a/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/worker/StartMetricsPerNameEngine.kt +++ b/infinitic-metrics-engines/src/main/kotlin/io/infinitic/metrics/perName/engine/worker/StartMetricsPerNameEngine.kt @@ -25,6 +25,7 @@ package io.infinitic.metrics.perName.engine.worker +import io.infinitic.common.data.ClientName import io.infinitic.common.metrics.global.transport.SendToMetricsGlobal import io.infinitic.common.metrics.perName.messages.MetricsPerNameMessage import io.infinitic.common.workers.MessageToProcess @@ -46,14 +47,15 @@ private fun logError(messageToProcess: MetricsPerNameMessageToProcess, e: Throwa } fun CoroutineScope.startMetricsPerNameEngine( - coroutineName: String, + name: String, metricsPerNameStateStorage: MetricsPerNameStateStorage, inputChannel: ReceiveChannel, outputChannel: SendChannel, sendToMetricsGlobal: SendToMetricsGlobal -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { val metricsPerNameEngine = MetricsPerNameEngine( + ClientName(name), metricsPerNameStateStorage, sendToMetricsGlobal ) diff --git a/infinitic-metrics-engines/src/test/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameTests.kt b/infinitic-metrics-engines/src/test/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameTests.kt index 693a05db4..b4ebb1cd5 100644 --- a/infinitic-metrics-engines/src/test/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameTests.kt +++ b/infinitic-metrics-engines/src/test/kotlin/io/infinitic/metrics/perName/engine/MetricsPerNameTests.kt @@ -25,6 +25,7 @@ package io.infinitic.metrics.perName.engine +import io.infinitic.common.data.ClientName import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.metrics.global.messages.TaskCreated import io.infinitic.common.metrics.global.transport.SendToMetricsGlobal @@ -42,6 +43,8 @@ import io.mockk.runs import io.mockk.slot class MetricsPerNameTests : ShouldSpec({ + val clientName = ClientName("clientMetricsPerNameTests") + context("TaskMetrics.handle") { should("should update TaskMetricsState when receiving TaskStatusUpdate message") { val storage = mockk() @@ -58,6 +61,7 @@ class MetricsPerNameTests : ShouldSpec({ coEvery { storage.putState(msg.taskName, capture(stateOutSlot)) } just runs val metricsPerName = MetricsPerNameEngine( + clientName, storage, mockSendToMetricsGlobal() ) @@ -86,7 +90,7 @@ class MetricsPerNameTests : ShouldSpec({ coEvery { storage.getState(msg.taskName) } returns null coEvery { storage.putState(msg.taskName, capture(stateOutSlot)) } just runs val sendToMetricsGlobal = mockSendToMetricsGlobal() - val metricsPerName = MetricsPerNameEngine(storage, sendToMetricsGlobal) + val metricsPerName = MetricsPerNameEngine(clientName, storage, sendToMetricsGlobal) // when metricsPerName.handle(msg) diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticClient.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticClient.kt index ca493d696..eebf8bafa 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticClient.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticClient.kt @@ -26,7 +26,7 @@ package io.infinitic.pulsar import io.infinitic.client.InfiniticClient -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.pulsar.config.ClientConfig import io.infinitic.pulsar.topics.TopicName diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticWorker.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticWorker.kt index 2bf8e94af..82563773f 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticWorker.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/PulsarInfiniticWorker.kt @@ -61,6 +61,7 @@ import org.apache.pulsar.client.admin.PulsarAdminException import org.apache.pulsar.client.admin.Tenants import org.apache.pulsar.client.admin.Topics import org.apache.pulsar.client.api.PulsarClient +import java.util.concurrent.CompletableFuture import kotlin.system.exitProcess @Suppress("MemberVisibilityCanBePrivate", "unused") @@ -133,7 +134,7 @@ class PulsarInfiniticWorker private constructor( /** * Start worker */ - override fun start() { + override fun startAsync(): CompletableFuture { // make sure all needed topics exists runningScope.future { try { @@ -155,7 +156,7 @@ class PulsarInfiniticWorker private constructor( } }.join() - super.start() + return super.startAsync() } private fun Tenants.checkOrCreateTenant() { @@ -261,130 +262,152 @@ class PulsarInfiniticWorker private constructor( } } - override fun CoroutineScope.startTaskExecutors(name: Name, concurrency: Int) { - startPulsarTaskExecutors( - name, - concurrency, - this@PulsarInfiniticWorker.name, - taskExecutorRegister, - pulsarConsumerFactory, - pulsarOutput, - clientFactory - ) + override fun startTaskExecutors(name: Name, concurrency: Int) { + runningScope.launch { + startPulsarTaskExecutors( + name, + concurrency, + this@PulsarInfiniticWorker.name, + taskExecutorRegister, + pulsarConsumerFactory, + pulsarOutput, + clientFactory + ) + } } - override fun CoroutineScope.startWorkflowTagEngines( + override fun startWorkflowTagEngines( workflowName: WorkflowName, concurrency: Int, storage: WorkflowTagStorage ) { - startPulsarWorkflowTagEngines( - workflowName, - concurrency, - storage, - name, - pulsarConsumerFactory, - pulsarOutput - ) + runningScope.launch { + startPulsarWorkflowTagEngines( + name, + concurrency, + storage, + workflowName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startTaskEngines( + override fun startTaskEngines( workflowName: WorkflowName, concurrency: Int, storage: TaskStateStorage ) { - startPulsarTaskEngines( - workflowName, - concurrency, - storage, - name, - pulsarConsumerFactory, - pulsarOutput - ) + runningScope.launch { + startPulsarTaskEngines( + name, + concurrency, + storage, + workflowName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startTaskEngines(taskName: TaskName, concurrency: Int, storage: TaskStateStorage) { - startPulsarTaskEngines( - taskName, - concurrency, - storage, - name, - pulsarConsumerFactory, - pulsarOutput - ) + override fun startTaskEngines(taskName: TaskName, concurrency: Int, storage: TaskStateStorage) { + runningScope.launch { + startPulsarTaskEngines( + name, + concurrency, + storage, + taskName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startTaskDelayEngines(workflowName: WorkflowName, concurrency: Int) { - startPulsarTaskDelayEngines( - workflowName, - concurrency, - name, - pulsarConsumerFactory, - pulsarOutput - ) + override fun startTaskDelayEngines(workflowName: WorkflowName, concurrency: Int) { + runningScope.launch { + startPulsarTaskDelayEngines( + name, + concurrency, + workflowName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startTaskDelayEngines(taskName: TaskName, concurrency: Int) { - startPulsarTaskDelayEngines( - taskName, - concurrency, - name, - pulsarConsumerFactory, - pulsarOutput - ) + override fun startTaskDelayEngines(taskName: TaskName, concurrency: Int) { + runningScope.launch { + startPulsarTaskDelayEngines( + name, + concurrency, + taskName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startWorkflowEngines( + override fun startWorkflowEngines( workflowName: WorkflowName, concurrency: Int, storage: WorkflowStateStorage ) { - startPulsarWorkflowEngines( - workflowName, - concurrency, - storage, - name, - pulsarConsumerFactory, - pulsarOutput - ) + runningScope.launch { + startPulsarWorkflowEngines( + name, + concurrency, + storage, + workflowName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startWorkflowDelayEngines(workflowName: WorkflowName, concurrency: Int) { - startPulsarWorkflowDelayEngines( - workflowName, - concurrency, - name, - pulsarConsumerFactory, - pulsarOutput - ) + override fun startWorkflowDelayEngines(workflowName: WorkflowName, concurrency: Int) { + runningScope.launch { + startPulsarWorkflowDelayEngines( + name, + concurrency, + workflowName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startTaskTagEngines(taskName: TaskName, concurrency: Int, storage: TaskTagStorage) { - startPulsarTaskTagEngines( - taskName, - concurrency, - storage, - name, - pulsarConsumerFactory, - pulsarOutput - ) + override fun startTaskTagEngines(taskName: TaskName, concurrency: Int, storage: TaskTagStorage) { + runningScope.launch { + startPulsarTaskTagEngines( + name, + concurrency, + storage, + taskName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startMetricsPerNameEngines(taskName: TaskName, storage: MetricsPerNameStateStorage) { - startPulsarMetricsPerNameEngines( - taskName, - storage, - name, - pulsarConsumerFactory, - pulsarOutput - ) + override fun startMetricsPerNameEngines(taskName: TaskName, storage: MetricsPerNameStateStorage) { + runningScope.launch { + startPulsarMetricsPerNameEngines( + name, + storage, + taskName, + pulsarConsumerFactory, + pulsarOutput + ) + } } - override fun CoroutineScope.startMetricsGlobalEngine(storage: MetricsGlobalStateStorage) { - startPulsarMetricsGlobalEngine( - storage, - name, - pulsarConsumerFactory - ) + override fun startMetricsGlobalEngine(storage: MetricsGlobalStateStorage) { + runningScope.launch { + startPulsarMetricsGlobalEngine( + name, + storage, + pulsarConsumerFactory + ) + } } } diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/MetricsPerNamePulsarFunction.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/MetricsPerNamePulsarFunction.kt index a3835c9de..0f2354131 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/MetricsPerNamePulsarFunction.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/MetricsPerNamePulsarFunction.kt @@ -27,6 +27,7 @@ package io.infinitic.pulsar.functions import io.infinitic.cache.caffeine.Caffeine import io.infinitic.cache.caffeine.CaffeineKeyValueCache +import io.infinitic.common.data.ClientName import io.infinitic.common.metrics.perName.messages.MetricsPerNameEnvelope import io.infinitic.common.storage.keyValue.CachedKeyValueStorage import io.infinitic.metrics.perName.engine.MetricsPerNameEngine @@ -53,6 +54,7 @@ class MetricsPerNamePulsarFunction : Function { } internal fun getMetricsPerNameEngine(context: Context) = MetricsPerNameEngine( + ClientName(""), BinaryMetricsPerNameStateStorage( // context storage decorated with logging and a 1h cache CachedKeyValueStorage( diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/TaskEnginePulsarFunction.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/TaskEnginePulsarFunction.kt index dc53eb0b9..276d00822 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/TaskEnginePulsarFunction.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/TaskEnginePulsarFunction.kt @@ -27,6 +27,7 @@ package io.infinitic.pulsar.functions import io.infinitic.cache.caffeine.Caffeine import io.infinitic.cache.caffeine.CaffeineKeyValueCache +import io.infinitic.common.data.ClientName import io.infinitic.common.storage.keyValue.CachedKeyValueStorage import io.infinitic.common.tasks.engine.messages.TaskEngineEnvelope import io.infinitic.pulsar.functions.storage.keyValueStorage @@ -57,6 +58,7 @@ class TaskEnginePulsarFunction : Function { val output = PulsarOutput.from(context) return TaskEngine( + ClientName(""), BinaryTaskStateStorage( // context storage decorated with logging and a 1h cache CachedKeyValueStorage( diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/WorkflowEnginePulsarFunction.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/WorkflowEnginePulsarFunction.kt index 03c74f321..d05436146 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/WorkflowEnginePulsarFunction.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/functions/WorkflowEnginePulsarFunction.kt @@ -27,6 +27,7 @@ package io.infinitic.pulsar.functions import io.infinitic.cache.caffeine.Caffeine import io.infinitic.cache.caffeine.CaffeineKeyValueCache +import io.infinitic.common.data.ClientName import io.infinitic.common.storage.keyValue.CachedKeyValueStorage import io.infinitic.common.workflows.engine.messages.WorkflowEngineEnvelope import io.infinitic.pulsar.functions.storage.keyValueStorage @@ -53,10 +54,11 @@ class WorkflowEnginePulsarFunction : Function { null } - internal fun getWorkflowEngine(context: Context): WorkflowEngine { + private fun getWorkflowEngine(context: Context): WorkflowEngine { val output = PulsarOutput.from(context) return WorkflowEngine( + ClientName(""), BinaryWorkflowStateStorage( // context storage decorated with logging and a 1h cache CachedKeyValueStorage( diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/topics/TopicName.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/topics/TopicName.kt index 67dbe37bf..2794f0083 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/topics/TopicName.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/topics/TopicName.kt @@ -25,7 +25,7 @@ package io.infinitic.pulsar.topics -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.data.ClientName const val TOPIC_WITH_DELAYS = "delays" diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarConsumerFactory.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarConsumerFactory.kt index 892f85c6a..babf9fa25 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarConsumerFactory.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarConsumerFactory.kt @@ -25,8 +25,9 @@ package io.infinitic.pulsar.transport -import io.infinitic.common.clients.data.ClientName import io.infinitic.common.clients.messages.ClientEnvelope +import io.infinitic.common.data.ClientName +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.messages.Envelope import io.infinitic.common.metrics.global.messages.MetricsGlobalEnvelope import io.infinitic.common.metrics.perName.messages.MetricsPerNameEnvelope @@ -37,7 +38,6 @@ import io.infinitic.common.tasks.tags.messages.TaskTagEngineEnvelope import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.engine.messages.WorkflowEngineEnvelope import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineEnvelope -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.pulsar.schemas.schemaDefinition import io.infinitic.pulsar.topics.GlobalTopic import io.infinitic.pulsar.topics.TaskTopic @@ -209,7 +209,7 @@ class PulsarConsumerFactory( subscriptionName = subscriptionName, ackTimeout = 60 ) - GlobalTopic.NAMER -> throw thisShouldNotHappen() + GlobalTopic.NAMER -> thisShouldNotHappen() } } diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarOutput.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarOutput.kt index 13c902113..f1b3ece0b 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarOutput.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/transport/PulsarOutput.kt @@ -28,6 +28,7 @@ package io.infinitic.pulsar.transport import io.infinitic.common.clients.transport.SendToClient import io.infinitic.common.data.MillisDuration import io.infinitic.common.data.Name +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.metrics.global.messages.MetricsGlobalMessage import io.infinitic.common.metrics.global.transport.SendToMetricsGlobal import io.infinitic.common.metrics.perName.messages.MetricsPerNameMessage @@ -41,7 +42,6 @@ import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.SendToWorkflowEngineAfter import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.pulsar.messageBuilders.PulsarMessageBuilder import io.infinitic.pulsar.messageBuilders.PulsarMessageBuilderFromClient import io.infinitic.pulsar.messageBuilders.PulsarMessageBuilderFromFunction @@ -92,7 +92,7 @@ class PulsarOutput( } fun sendToClient(): SendToClient = { message -> - val topic = topicName.of(message.clientName) + val topic = topicName.of(message.recipientName) val key = null logger.debug { "topic: $topic, sendToClient: $message" } pulsarMessageBuilder.sendPulsarMessage(topic, message.envelope(), key, zero) @@ -132,7 +132,7 @@ class PulsarOutput( } topicName.of(taskTopic, "${message.taskName}") } - else -> throw thisShouldNotHappen() + else -> thisShouldNotHappen() } val key = "${message.taskId}" logger.debug { "topic: $topic, sendToTaskEngine: $message" } @@ -144,7 +144,7 @@ class PulsarOutput( is WorkflowName -> topicName.of(WorkflowTaskTopic.DELAYS, "$name") is TaskName -> topicName.of(TaskTopic.DELAYS, "$name") null -> topicName.of(TaskTopic.DELAYS, "${message.taskName}") - else -> throw thisShouldNotHappen() + else -> thisShouldNotHappen() } val key = null logger.debug { "topic: $topic, sendToTaskEngineAfter: $message" } @@ -185,7 +185,7 @@ class PulsarOutput( is WorkflowName -> topicName.of(WorkflowTaskTopic.EXECUTORS, "$name") is TaskName -> topicName.of(TaskTopic.EXECUTORS, "$name") null -> topicName.of(TaskTopic.EXECUTORS, "${message.taskName}") - else -> throw thisShouldNotHappen() + else -> thisShouldNotHappen() } val key = null logger.debug { "topic: $topic, sendToTaskExecutors: $message" } @@ -197,7 +197,7 @@ class PulsarOutput( is WorkflowName -> topicName.of(WorkflowTaskTopic.METRICS, "$name") is TaskName -> topicName.of(TaskTopic.METRICS, "$name") null -> topicName.of(TaskTopic.METRICS, "${message.taskName}") - else -> throw thisShouldNotHappen() + else -> thisShouldNotHappen() } // val key = null logger.debug { "topic: $topic, sendToMetricsPerName: $message" } diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsGlobalEngine.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsGlobalEngine.kt index cef5bee3f..154f7f147 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsGlobalEngine.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsGlobalEngine.kt @@ -40,8 +40,8 @@ typealias PulsarMetricsGlobalMessageToProcess = PulsarMessageToProcess() startMetricsGlobalEngine( - "metrics-global-engine", + "metrics-global-engine: $name", storage, inputChannel = inputChannel, outputChannel = outputChannel ) - val consumer = consumerFactory.newConsumer(consumerName, GlobalTopic.METRICS) as Consumer + val consumer = consumerFactory.newConsumer(name, GlobalTopic.METRICS) as Consumer // coroutine pulling pulsar messages pullMessages(consumer, inputChannel) diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsPerNameEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsPerNameEngines.kt index 5d48bfbad..e682c1f96 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsPerNameEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarMetricsPerNameEngines.kt @@ -42,9 +42,9 @@ typealias PulsarMetricsPerNameMessageToProcess = PulsarMessageToProcess() startMetricsPerNameEngine( - "metrics-per-name", + "metrics-per-name: $name", storage, inputChannel = inputChannel, outputChannel = outputChannel, @@ -61,7 +61,7 @@ fun CoroutineScope.startPulsarMetricsPerNameEngines( // Pulsar consumer val consumer = consumerFactory.newConsumer( - consumerName = consumerName, + consumerName = name, taskTopic = TaskTopic.METRICS, taskName = taskName ) as Consumer diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskDelayEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskDelayEngines.kt index 415c24629..b549a5778 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskDelayEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskDelayEngines.kt @@ -26,10 +26,10 @@ package io.infinitic.pulsar.workers import io.infinitic.common.data.Name +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.engine.messages.TaskEngineEnvelope import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.pulsar.topics.TaskTopic import io.infinitic.pulsar.topics.TopicType import io.infinitic.pulsar.topics.WorkflowTaskTopic @@ -42,9 +42,9 @@ import org.apache.pulsar.client.api.Consumer @Suppress("UNCHECKED_CAST") fun CoroutineScope.startPulsarTaskDelayEngines( - name: Name, + name: String, concurrency: Int, - consumerName: String, + jobName: Name, consumerFactory: PulsarConsumerFactory, output: PulsarOutput ) { @@ -53,7 +53,7 @@ fun CoroutineScope.startPulsarTaskDelayEngines( repeat(concurrency) { startTaskDelayEngine( - consumerName, + name, inputChannel, outputChannel, output.sendToTaskEngine(TopicType.EXISTING) @@ -61,16 +61,16 @@ fun CoroutineScope.startPulsarTaskDelayEngines( } // Pulsar consumer - val consumer = when (name) { + val consumer = when (jobName) { is TaskName -> consumerFactory.newConsumer( - consumerName = consumerName, + consumerName = name, taskTopic = TaskTopic.DELAYS, - taskName = name + taskName = jobName ) is WorkflowName -> consumerFactory.newConsumer( - consumerName = consumerName, + consumerName = name, workflowTaskTopic = WorkflowTaskTopic.DELAYS, - workflowName = name + workflowName = jobName ) else -> thisShouldNotHappen() } as Consumer diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskEngines.kt index fe19f7344..e03808766 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskEngines.kt @@ -26,11 +26,11 @@ package io.infinitic.pulsar.workers import io.infinitic.common.data.Name +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.engine.messages.TaskEngineEnvelope import io.infinitic.common.tasks.engine.messages.TaskEngineMessage import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.pulsar.topics.TaskTopic import io.infinitic.pulsar.topics.TopicType import io.infinitic.pulsar.topics.WorkflowTaskTopic @@ -47,10 +47,10 @@ typealias PulsarTaskEngineMessageToProcess = PulsarMessageToProcess() startTaskEngine( - "task-engine:$it", + "task-engine-$it: $name", storage, eventsInputChannel = eventsInputChannel, eventsOutputChannel = eventsOutputChannel, @@ -70,39 +70,39 @@ fun CoroutineScope.startPulsarTaskEngines( commandsOutputChannel = commandsOutputChannel, output.sendToClient(), output.sendToTaskTagEngine(TopicType.EXISTING), - output.sendToTaskEngineAfter(name), + output.sendToTaskEngineAfter(jobName), output.sendToWorkflowEngine(TopicType.EXISTING), - output.sendToTaskExecutors(name), + output.sendToTaskExecutors(jobName), output.sendToMetricsPerName() ) // Pulsar consumers - val existingConsumer = when (name) { + val existingConsumer = when (jobName) { is TaskName -> consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", taskTopic = TaskTopic.ENGINE_EXISTING, - taskName = name + taskName = jobName ) is WorkflowName -> consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", workflowTaskTopic = WorkflowTaskTopic.ENGINE_EXISTING, - workflowName = name + workflowName = jobName ) - else -> throw thisShouldNotHappen() + else -> thisShouldNotHappen() } as Consumer - val newConsumer = when (name) { + val newConsumer = when (jobName) { is TaskName -> consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", taskTopic = TaskTopic.ENGINE_NEW, - taskName = name + taskName = jobName ) is WorkflowName -> consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", workflowTaskTopic = WorkflowTaskTopic.ENGINE_NEW, - workflowName = name + workflowName = jobName ) - else -> throw thisShouldNotHappen() + else -> thisShouldNotHappen() } as Consumer // coroutine pulling pulsar events messages diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskExecutors.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskExecutors.kt index a145baf26..bf1ba6bf5 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskExecutors.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskExecutors.kt @@ -26,11 +26,11 @@ package io.infinitic.pulsar.workers import io.infinitic.common.data.Name +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.executors.messages.TaskExecutorEnvelope import io.infinitic.common.tasks.executors.messages.TaskExecutorMessage import io.infinitic.common.workflows.data.workflows.WorkflowName -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.pulsar.PulsarInfiniticClient import io.infinitic.pulsar.topics.TaskTopic import io.infinitic.pulsar.topics.TopicType diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskTagEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskTagEngines.kt index 42d16f501..c3f9ebd26 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskTagEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarTaskTagEngines.kt @@ -43,10 +43,10 @@ typealias PulsarTaskTagEngineMessageToProcess = PulsarMessageToProcess() startTaskTagEngine( - "task-tag-engine:$it", + "task-tag-engine-$it: $name", storage, eventsInputChannel = eventsInputChannel, eventsOutputChannel = eventsOutputChannel, @@ -70,13 +70,13 @@ fun CoroutineScope.startPulsarTaskTagEngines( // Pulsar consumers val existingConsumer = consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", taskTopic = TaskTopic.TAG_EXISTING, taskName = taskName ) as Consumer val newConsumer = consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", taskTopic = TaskTopic.TAG_NEW, taskName = taskName ) as Consumer diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowDelayEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowDelayEngines.kt index a0c88ce1c..8b15ec0d6 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowDelayEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowDelayEngines.kt @@ -38,9 +38,9 @@ import org.apache.pulsar.client.api.Consumer @Suppress("UNCHECKED_CAST") fun CoroutineScope.startPulsarWorkflowDelayEngines( - workflowName: WorkflowName, + name: String, concurrency: Int, - consumerName: String, + workflowName: WorkflowName, consumerFactory: PulsarConsumerFactory, output: PulsarOutput ) { @@ -49,7 +49,7 @@ fun CoroutineScope.startPulsarWorkflowDelayEngines( repeat(concurrency) { startWorkflowDelayEngine( - consumerName, + name, inputChannel, outputChannel, output.sendToWorkflowEngine(TopicType.EXISTING) @@ -58,7 +58,7 @@ fun CoroutineScope.startPulsarWorkflowDelayEngines( // Pulsar consumer val consumer = consumerFactory.newConsumer( - consumerName = consumerName, + consumerName = name, workflowTopic = WorkflowTopic.DELAYS, workflowName = workflowName ) as Consumer diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowEngines.kt index 88ce40394..ea1bd8adb 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowEngines.kt @@ -44,10 +44,10 @@ typealias PulsarWorkflowEngineMessageToProcess = PulsarMessageToProcess val newConsumer = consumerFactory.newConsumer( - consumerName = "$consumerName:$count", + consumerName = "$name:$count", workflowTopic = WorkflowTopic.ENGINE_NEW, workflowName = workflowName ) as Consumer diff --git a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowTagEngines.kt b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowTagEngines.kt index af6b2eb3c..1148b2d53 100644 --- a/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowTagEngines.kt +++ b/infinitic-pulsar/src/main/kotlin/io/infinitic/pulsar/workers/startPulsarWorkflowTagEngines.kt @@ -43,10 +43,10 @@ typealias PulsarWorkflowTagEngineMessageToProcess = PulsarMessageToProcess() startWorkflowTagEngine( - "workflow-tag-engine:$it", + "workflow-tag-engine-$it: $name", storage, eventsInputChannel = eventsInputChannel, eventsOutputChannel = eventsOutputChannel, @@ -70,13 +70,13 @@ fun CoroutineScope.startPulsarWorkflowTagEngines( // Pulsar consumers val eventsConsumer = consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", workflowTopic = WorkflowTopic.TAG_EXISTING, workflowName = workflowName ) as Consumer val commandsConsumer = consumerFactory.newConsumer( - consumerName = "$consumerName:$it", + consumerName = "$name:$it", workflowTopic = WorkflowTopic.TAG_NEW, workflowName = workflowName ) as Consumer diff --git a/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowA.kt b/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowA.kt index d4c93795a..9ad9fa22c 100644 --- a/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowA.kt +++ b/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowA.kt @@ -57,18 +57,16 @@ interface WorkflowA { } class WorkflowAImpl : Workflow(), WorkflowA { - private val taskA = newTask() - private val workflowB = newWorkflow() + private val taskA = newTask(TaskA::class.java) + private val workflowB = newWorkflow(WorkflowB::class.java) private var p1 = "" override fun empty() = "void" override fun seq1(): String { var str = "" - str = taskA.concat(str, "1") str = taskA.concat(str, "2") - str = taskA.concat(str, "3") return str // should be "123" @@ -76,8 +74,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun seq2(): String { var str = "" - - val d = async(taskA) { reverse("ab") } + val d = dispatch(taskA::reverse, "ab") str = taskA.concat(str, "2") str = taskA.concat(str, "3") @@ -86,75 +83,72 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun seq3(): String { var str = "" - - val d = async { taskA.reverse("ab") } + val d = dispatch(::seq3bis) str = taskA.concat(str, "2") str = taskA.concat(str, "3") return str + d.await() // should be "23ba" } + private fun seq3bis() { taskA.reverse("ab") } + override fun seq4(): String { var str = "" - - val d = async { - val s = taskA.reverse("ab") - taskA.concat(s, "c") - } + val d = dispatch(::seq4bis) str = taskA.concat(str, "2") str = taskA.concat(str, "3") return str + d.await() // should be "23bac" } + private fun seq4bis() { val s = taskA.reverse("ab"); taskA.concat(s, "c") } + override fun or1(): String { - val d1 = async(taskA) { reverse("ab") } - val d2 = async(taskA) { reverse("cd") } - val d3 = async(taskA) { reverse("ef") } + val d1 = dispatch(taskA::reverse, "ab") + val d2 = dispatch(taskA::reverse, "cd") + val d3 = dispatch(taskA::reverse, "ef") return (d1 or d2 or d3).await() // should be "ba" or "dc" or "fe" } override fun or2(): Any { - val d1 = async(taskA) { reverse("ab") } - val d2 = async(taskA) { reverse("cd") } - val d3 = async(taskA) { reverse("ef") } + val d1 = dispatch(taskA::reverse, "ab") + val d2 = dispatch(taskA::reverse, "cd") + val d3 = dispatch(taskA::reverse, "ef") return ((d1 and d2) or d3).await() // should be listOf("ba","dc") or "fe" } override fun or3(): String { val list: MutableList> = mutableListOf() - list.add(async(taskA) { reverse("ab") }) - list.add(async(taskA) { reverse("cd") }) - list.add(async(taskA) { reverse("ef") }) + list.add(dispatch(taskA::reverse, "ab")) + list.add(dispatch(taskA::reverse, "cd")) + list.add(dispatch(taskA::reverse, "ef")) return list.or().await() // should be "ba" or "dc" or "fe" } override fun and1(): List { - val d1 = async(taskA) { reverse("ab") } - val d2 = async(taskA) { reverse("cd") } - val d3 = async(taskA) { reverse("ef") } + val d1 = dispatch(taskA::reverse, "ab") + val d2 = dispatch(taskA::reverse, "cd") + val d3 = dispatch(taskA::reverse, "ef") return (d1 and d2 and d3).await() // should be listOf("ba","dc","fe") } override fun and2(): List { - val list: MutableList> = mutableListOf() - list.add(async(taskA) { reverse("ab") }) - list.add(async(taskA) { reverse("cd") }) - list.add(async(taskA) { reverse("ef") }) + list.add(dispatch(taskA::reverse, "ab")) + list.add(dispatch(taskA::reverse, "cd")) + list.add(dispatch(taskA::reverse, "ef")) return list.and().await() // should be listOf("ba","dc","fe") } override fun and3(): List { - val list: MutableList> = mutableListOf() for (i in 1..1_00) { - list.add(async(taskA) { reverse("ab") }) + list.add(dispatch(taskA::reverse, "ab")) } return list.and().await() // should be listOf("ba","dc","fe") } @@ -166,7 +160,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun inline2(): String { val date = inline { - async(taskA) { reverse("ab") } + dispatch(taskA::reverse, "ab") LocalDateTime.now() } @@ -182,7 +176,6 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun child1(): String { - var str: String = workflowB.concat("-") str = taskA.concat(str, "-") @@ -191,28 +184,24 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun child2(): String { val str = taskA.reverse("12") - val d = async(workflowB) { concat(str) } + val d = dispatch(workflowB::concat, str) return taskA.concat(d.await(), str) // should be "21abc21" } override fun prop1(): String { p1 = "a" - - async { - p1 += "b" - } + dispatch(::prop1bis) p1 += "c" return p1 // should be "ac" } + private fun prop1bis() { p1 += "b" } + override fun prop2(): String { p1 = "a" - - async { - p1 += "b" - } + dispatch(::prop2Bis) p1 += "c" taskA.await(100) p1 += "d" @@ -220,13 +209,11 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acbd" } + fun prop2Bis() { p1 += "b" } + override fun prop3(): String { p1 = "a" - - async { - taskA.await(50) - p1 += "b" - } + dispatch(::prop3bis) p1 += "c" taskA.await(100) p1 += "d" @@ -234,13 +221,11 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acbd" } + fun prop3bis() { taskA.await(50); p1 += "b" } + override fun prop4(): String { p1 = "a" - - async { - taskA.await(150) - p1 += "b" - } + dispatch(::prop4bis) p1 += "c" taskA.await(100) p1 += "d" @@ -248,30 +233,24 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acd" } + private fun prop4bis() { taskA.await(150); p1 += "b" } + override fun prop5(): String { p1 = "a" - - async { - p1 += "b" - } - - async { - p1 += "c" - } + dispatch(::prop5bis) + dispatch(::prop5ter) p1 += "d" taskA.await(100) return p1 // should be "adbc" } - override fun prop6(): String { - val d1 = async(taskA) { reverse("12") } + private fun prop5bis() { p1 += "b" } + private fun prop5ter() { p1 += "c" } - val d2 = async { - d1.await() - p1 += "b" - p1 - } + override fun prop6(): String { + val d1 = dispatch(taskA::reverse, "12") + val d2 = dispatch(::prop6bis, d1) d1.await() p1 += "a" p1 = d2.await() + p1 @@ -281,4 +260,6 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "abab" } + + private fun prop6bis(d1: Deferred<*>): String { d1.await(); p1 += "b"; return p1 } } diff --git a/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowB.kt b/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowB.kt index 5a4ad96df..a88d0160f 100644 --- a/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowB.kt +++ b/infinitic-pulsar/src/test/kotlin/io/infinitic/pulsar/samples/WorkflowB.kt @@ -33,8 +33,8 @@ interface WorkflowB { } class WorkflowBImpl() : Workflow(), WorkflowB { - private val task = newTask() - private val workflow = newWorkflow() + private val task = newTask(TaskA::class.java) + private val workflow = newWorkflow(WorkflowB::class.java) override fun concat(input: String): String { var str = input @@ -43,7 +43,7 @@ class WorkflowBImpl() : Workflow(), WorkflowB { str = task.concat(str, "b") str = task.concat(str, "c") - return str // should be "${input}123" + return str // should be "${input}abc" } override fun factorial(n: Long): Long { diff --git a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/TaskTagEngine.kt b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/TaskTagEngine.kt index 2bf1886b4..2caa6139c 100644 --- a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/TaskTagEngine.kt +++ b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/TaskTagEngine.kt @@ -25,16 +25,17 @@ package io.infinitic.tags.tasks -import io.infinitic.common.clients.messages.TaskIdsPerTag +import io.infinitic.common.clients.messages.TaskIdsByTag import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.engine.messages.CancelTask import io.infinitic.common.tasks.engine.messages.RetryTask -import io.infinitic.common.tasks.tags.messages.AddTaskTag -import io.infinitic.common.tasks.tags.messages.CancelTaskPerTag -import io.infinitic.common.tasks.tags.messages.GetTaskIds -import io.infinitic.common.tasks.tags.messages.RemoveTaskTag -import io.infinitic.common.tasks.tags.messages.RetryTaskPerTag +import io.infinitic.common.tasks.tags.messages.AddTagToTask +import io.infinitic.common.tasks.tags.messages.CancelTaskByTag +import io.infinitic.common.tasks.tags.messages.GetTaskIdsByTag +import io.infinitic.common.tasks.tags.messages.RemoveTagFromTask +import io.infinitic.common.tasks.tags.messages.RetryTaskByTag import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage import io.infinitic.tags.tasks.storage.LoggedTaskTagStorage import io.infinitic.tags.tasks.storage.TaskTagStorage @@ -44,6 +45,7 @@ import kotlinx.coroutines.launch import mu.KotlinLogging class TaskTagEngine( + val clientName: ClientName, storage: TaskTagStorage, val sendToTaskEngine: SendToTaskEngine, val sendToClient: SendToClient @@ -68,36 +70,23 @@ class TaskTagEngine( scope = this when (message) { - is AddTaskTag -> addTaskTag(message) - is RemoveTaskTag -> removeTaskTag(message) - is CancelTaskPerTag -> cancelTaskPerTag(message) - is RetryTaskPerTag -> retryTaskPerTag(message) - is GetTaskIds -> getTaskIds(message) + is AddTagToTask -> addTagToTask(message) + is RemoveTagFromTask -> removeTagFromTask(message) + is CancelTaskByTag -> cancelTaskByTag(message) + is RetryTaskByTag -> retryTaskByTag(message) + is GetTaskIdsByTag -> getTaskIds(message) } } - private suspend fun getTaskIds(message: GetTaskIds) { - val taskIds = storage.getTaskIds(message.taskTag, message.taskName) - - val taskIdsPerTag = TaskIdsPerTag( - message.clientName, - message.taskName, - message.taskTag, - taskIds = taskIds - ) - - scope.launch { sendToClient(taskIdsPerTag) } - } - - private suspend fun addTaskTag(message: AddTaskTag) { + private suspend fun addTagToTask(message: AddTagToTask) { storage.addTaskId(message.taskTag, message.taskName, message.taskId) } - private suspend fun removeTaskTag(message: RemoveTaskTag) { + private suspend fun removeTagFromTask(message: RemoveTagFromTask) { storage.removeTaskId(message.taskTag, message.taskName, message.taskId) } - private suspend fun retryTaskPerTag(message: RetryTaskPerTag) { + private suspend fun retryTaskByTag(message: RetryTaskByTag) { // is not an idempotent action if (hasMessageAlreadyBeenHandled(message)) return @@ -108,15 +97,16 @@ class TaskTagEngine( } false -> taskIds.forEach { val retryTask = RetryTask( + taskName = message.taskName, taskId = it, - taskName = message.taskName + emitterName = clientName ) scope.launch { sendToTaskEngine(retryTask) } } } } - private suspend fun cancelTaskPerTag(message: CancelTaskPerTag) { + private suspend fun cancelTaskByTag(message: CancelTaskByTag) { // is not an idempotent action if (hasMessageAlreadyBeenHandled(message)) return @@ -127,14 +117,29 @@ class TaskTagEngine( } false -> ids.forEach { val cancelTask = CancelTask( + taskName = message.taskName, taskId = it, - taskName = message.taskName + emitterName = clientName ) scope.launch { sendToTaskEngine(cancelTask) } } } } + private suspend fun getTaskIds(message: GetTaskIdsByTag) { + val taskIds = storage.getTaskIds(message.taskTag, message.taskName) + + val taskIdsByTag = TaskIdsByTag( + recipientName = message.emitterName, + message.taskName, + message.taskTag, + taskIds, + emitterName = clientName + ) + + scope.launch { sendToClient(taskIdsByTag) } + } + private suspend fun hasMessageAlreadyBeenHandled(message: TaskTagEngineMessage) = when (storage.getLastMessageId(message.taskTag, message.taskName)) { message.messageId -> { diff --git a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/storage/BinaryTaskTagStorage.kt b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/storage/BinaryTaskTagStorage.kt index 4cfa55e05..6bde617a8 100644 --- a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/storage/BinaryTaskTagStorage.kt +++ b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/storage/BinaryTaskTagStorage.kt @@ -26,8 +26,6 @@ package io.infinitic.tags.tasks.storage import io.infinitic.common.data.MessageId -import io.infinitic.common.data.UUIDConversion.toByteArray -import io.infinitic.common.data.UUIDConversion.toUUID import io.infinitic.common.storage.Flushable import io.infinitic.common.storage.keySet.KeySetStorage import io.infinitic.common.storage.keyValue.KeyValueStorage @@ -61,18 +59,18 @@ class BinaryTaskTagStorage( val key = getTagSetIdsKey(tag, taskName) return keySetStorage .get(key) - .map { TaskId(it.toUUID()) } + .map { TaskId(String(it)) } .toSet() } override suspend fun addTaskId(tag: TaskTag, taskName: TaskName, taskId: TaskId) { val key = getTagSetIdsKey(tag, taskName) - keySetStorage.add(key, taskId.id.toByteArray()) + keySetStorage.add(key, taskId.toString().toByteArray()) } override suspend fun removeTaskId(tag: TaskTag, taskName: TaskName, taskId: TaskId) { val key = getTagSetIdsKey(tag, taskName) - keySetStorage.remove(key, taskId.id.toByteArray()) + keySetStorage.remove(key, taskId.toString().toByteArray()) } private fun getTagMessageIdKey(tag: TaskTag, taskName: TaskName) = "task:$taskName|tag:$tag|messageId" diff --git a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/worker/startTaskTagEngine.kt b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/worker/startTaskTagEngine.kt index 53d0036b6..acc8a73dc 100644 --- a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/worker/startTaskTagEngine.kt +++ b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/tasks/worker/startTaskTagEngine.kt @@ -26,6 +26,7 @@ package io.infinitic.tags.tasks.worker import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage import io.infinitic.common.workers.MessageToProcess @@ -49,7 +50,7 @@ private fun logError(messageToProcess: TaskTagEngineMessageToProcess, e: Throwab } fun CoroutineScope.startTaskTagEngine( - coroutineName: String, + name: String, taskTagStorage: TaskTagStorage, eventsInputChannel: ReceiveChannel, eventsOutputChannel: SendChannel, @@ -57,9 +58,10 @@ fun CoroutineScope.startTaskTagEngine( commandsOutputChannel: SendChannel, sendToTaskEngine: SendToTaskEngine, sendToClient: SendToClient, -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { val tagEngine = TaskTagEngine( + ClientName(name), taskTagStorage, sendToTaskEngine, sendToClient diff --git a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/WorkflowTagEngine.kt b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/WorkflowTagEngine.kt index 3eea984e7..974bd326c 100644 --- a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/WorkflowTagEngine.kt +++ b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/WorkflowTagEngine.kt @@ -25,18 +25,22 @@ package io.infinitic.tags.workflows -import io.infinitic.common.clients.messages.WorkflowIdsPerTag +import io.infinitic.common.clients.messages.WorkflowIdsByTag import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.messages.CancelWorkflow +import io.infinitic.common.workflows.engine.messages.DispatchMethod import io.infinitic.common.workflows.engine.messages.RetryWorkflowTask -import io.infinitic.common.workflows.engine.messages.SendToChannel -import io.infinitic.common.workflows.tags.messages.AddWorkflowTag -import io.infinitic.common.workflows.tags.messages.CancelWorkflowPerTag -import io.infinitic.common.workflows.tags.messages.GetWorkflowIds -import io.infinitic.common.workflows.tags.messages.RemoveWorkflowTag -import io.infinitic.common.workflows.tags.messages.RetryWorkflowTaskPerTag -import io.infinitic.common.workflows.tags.messages.SendToChannelPerTag +import io.infinitic.common.workflows.engine.messages.SendSignal +import io.infinitic.common.workflows.tags.messages.AddTagToWorkflow +import io.infinitic.common.workflows.tags.messages.CancelWorkflowByTag +import io.infinitic.common.workflows.tags.messages.DispatchMethodByTag +import io.infinitic.common.workflows.tags.messages.GetWorkflowIdsByTag +import io.infinitic.common.workflows.tags.messages.RemoveTagFromWorkflow +import io.infinitic.common.workflows.tags.messages.RetryWorkflowTaskByTag +import io.infinitic.common.workflows.tags.messages.SendSignalByTag import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineMessage import io.infinitic.tags.workflows.storage.LoggedWorkflowTagStorage import io.infinitic.tags.workflows.storage.WorkflowTagStorage @@ -46,6 +50,7 @@ import kotlinx.coroutines.launch import mu.KotlinLogging class WorkflowTagEngine( + private val clientName: ClientName, storage: WorkflowTagStorage, val sendToWorkflowEngine: SendToWorkflowEngine, val sendToClient: SendToClient @@ -72,28 +77,48 @@ class WorkflowTagEngine( @Suppress("UNUSED_VARIABLE") val o = when (message) { - is AddWorkflowTag -> addWorkflowTag(message) - is RemoveWorkflowTag -> removeWorkflowTag(message) - is SendToChannelPerTag -> sendToChannelPerTag(message) - is CancelWorkflowPerTag -> cancelWorkflowPerTag(message) - is RetryWorkflowTaskPerTag -> retryWorkflowTaskPerTag(message) - is GetWorkflowIds -> getWorkflowIds(message) + is AddTagToWorkflow -> addWorkflowTag(message) + is RemoveTagFromWorkflow -> removeWorkflowTag(message) + is SendSignalByTag -> sendToChannelPerTag(message) + is CancelWorkflowByTag -> cancelWorkflowPerTag(message) + is RetryWorkflowTaskByTag -> retryWorkflowTaskPerTag(message) + is DispatchMethodByTag -> dispatchMethodRunPerTag(message) + is GetWorkflowIdsByTag -> getWorkflowIds(message) } } - private suspend fun getWorkflowIds(message: GetWorkflowIds) { - val workflowIds = storage.getWorkflowIds(message.workflowTag, message.workflowName) + private suspend fun dispatchMethodRunPerTag(message: DispatchMethodByTag) { + // is not an idempotent action + if (hasMessageAlreadyBeenHandled(message)) return - val workflowIdsPerTag = WorkflowIdsPerTag( - message.clientName, - message.workflowName, - message.workflowTag, - workflowIds = workflowIds - ) - scope.launch { sendToClient(workflowIdsPerTag) } + val ids = storage.getWorkflowIds(message.workflowTag, message.workflowName) + when (ids.isEmpty()) { + true -> { + discardTagWithoutIds(message) + } + false -> ids.forEach { + // parent workflow already applied method to self + if (it != message.parentWorkflowId) { + val dispatchMethod = DispatchMethod( + workflowName = message.workflowName, + workflowId = it, + methodRunId = message.methodRunId, + methodName = message.methodName, + methodParameters = message.methodParameters, + methodParameterTypes = message.methodParameterTypes, + parentWorkflowId = message.parentWorkflowId, + parentWorkflowName = message.parentWorkflowName, + parentMethodRunId = message.parentMethodRunId, + clientWaiting = false, + emitterName = clientName + ) + scope.launch { sendToWorkflowEngine(dispatchMethod) } + } + } + } } - private suspend fun retryWorkflowTaskPerTag(message: RetryWorkflowTaskPerTag) { + private suspend fun retryWorkflowTaskPerTag(message: RetryWorkflowTaskByTag) { // is not an idempotent action if (hasMessageAlreadyBeenHandled(message)) return @@ -103,16 +128,20 @@ class WorkflowTagEngine( discardTagWithoutIds(message) } false -> ids.forEach { - val retryWorkflowTask = RetryWorkflowTask( - workflowId = it, - workflowName = message.workflowName - ) - scope.launch { sendToWorkflowEngine(retryWorkflowTask) } + // parent workflow already applied method to self + if (it != message.emitterWorkflowId) { + val retryWorkflowTask = RetryWorkflowTask( + workflowName = message.workflowName, + workflowId = it, + emitterName = clientName + ) + scope.launch { sendToWorkflowEngine(retryWorkflowTask) } + } } } } - private suspend fun cancelWorkflowPerTag(message: CancelWorkflowPerTag) { + private suspend fun cancelWorkflowPerTag(message: CancelWorkflowByTag) { // is not an idempotent action if (hasMessageAlreadyBeenHandled(message)) return @@ -122,17 +151,22 @@ class WorkflowTagEngine( discardTagWithoutIds(message) } false -> ids.forEach { - val cancelWorkflow = CancelWorkflow( - workflowId = it, - workflowName = message.workflowName, - reason = message.reason - ) - scope.launch { sendToWorkflowEngine(cancelWorkflow) } + // parent workflow already applied method to self + if (it != message.emitterWorkflowId) { + val cancelWorkflow = CancelWorkflow( + workflowName = message.workflowName, + workflowId = it, + methodRunId = MethodRunId.from(it), + reason = message.reason, + emitterName = clientName + ) + scope.launch { sendToWorkflowEngine(cancelWorkflow) } + } } } } - private suspend fun sendToChannelPerTag(message: SendToChannelPerTag) { + private suspend fun sendToChannelPerTag(message: SendSignalByTag) { // sending to channel is not an idempotent action if (hasMessageAlreadyBeenHandled(message)) return @@ -140,28 +174,44 @@ class WorkflowTagEngine( when (ids.isEmpty()) { true -> discardTagWithoutIds(message) false -> ids.forEach { - val sendToChannel = SendToChannel( - clientName = message.clientName, - workflowId = it, - workflowName = message.workflowName, - channelEventId = message.channelEventId, - channelName = message.channelName, - channelEvent = message.channelEvent, - channelEventTypes = message.channelEventTypes - ) - scope.launch { sendToWorkflowEngine(sendToChannel) } + // parent workflow already applied method to self + if (it != message.emitterWorkflowId) { + val sendSignal = SendSignal( + workflowName = message.workflowName, + workflowId = it, + channelName = message.channelName, + channelSignalId = message.channelSignalId, + channelSignal = message.channelSignal, + channelSignalTypes = message.channelSignalTypes, + emitterName = clientName + ) + scope.launch { sendToWorkflowEngine(sendSignal) } + } } } } - private suspend fun addWorkflowTag(message: AddWorkflowTag) { + private suspend fun addWorkflowTag(message: AddTagToWorkflow) { storage.addWorkflowId(message.workflowTag, message.workflowName, message.workflowId) } - private suspend fun removeWorkflowTag(message: RemoveWorkflowTag) { + private suspend fun removeWorkflowTag(message: RemoveTagFromWorkflow) { storage.removeWorkflowId(message.workflowTag, message.workflowName, message.workflowId) } + private suspend fun getWorkflowIds(message: GetWorkflowIdsByTag) { + val workflowIds = storage.getWorkflowIds(message.workflowTag, message.workflowName) + + val workflowIdsByTag = WorkflowIdsByTag( + recipientName = message.emitterName, + message.workflowName, + message.workflowTag, + workflowIds, + emitterName = clientName + ) + scope.launch { sendToClient(workflowIdsByTag) } + } + private suspend fun hasMessageAlreadyBeenHandled(message: WorkflowTagEngineMessage) = when (storage.getLastMessageId(message.workflowTag, message.workflowName)) { message.messageId -> { diff --git a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/storage/BinaryWorkflowTagStorage.kt b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/storage/BinaryWorkflowTagStorage.kt index 400fa4414..3187ed1cd 100644 --- a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/storage/BinaryWorkflowTagStorage.kt +++ b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/storage/BinaryWorkflowTagStorage.kt @@ -27,7 +27,6 @@ package io.infinitic.tags.workflows.storage import io.infinitic.common.data.MessageId import io.infinitic.common.data.UUIDConversion.toByteArray -import io.infinitic.common.data.UUIDConversion.toUUID import io.infinitic.common.storage.Flushable import io.infinitic.common.storage.keySet.KeySetStorage import io.infinitic.common.storage.keyValue.KeyValueStorage @@ -61,18 +60,18 @@ class BinaryWorkflowTagStorage( val key = getTagSetIdsKey(tag, workflowName) return keySetStorage .get(key) - .map { WorkflowId(it.toUUID()) } + .map { WorkflowId(String(it)) } .toSet() } override suspend fun addWorkflowId(tag: WorkflowTag, workflowName: WorkflowName, workflowId: WorkflowId) { val key = getTagSetIdsKey(tag, workflowName) - keySetStorage.add(key, workflowId.id.toByteArray()) + keySetStorage.add(key, workflowId.toString().toByteArray()) } override suspend fun removeWorkflowId(tag: WorkflowTag, workflowName: WorkflowName, workflowId: WorkflowId) { val key = getTagSetIdsKey(tag, workflowName) - keySetStorage.remove(key, workflowId.id.toByteArray()) + keySetStorage.remove(key, workflowId.toString().toByteArray()) } private fun getTagMessageIdKey(tag: WorkflowTag, workflowName: WorkflowName) = "workflow:$workflowName|tag:$tag|messageId" diff --git a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/worker/startWorkflowTagEngine.kt b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/worker/startWorkflowTagEngine.kt index 9d7f47686..1e728abed 100644 --- a/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/worker/startWorkflowTagEngine.kt +++ b/infinitic-tag-engine/src/main/kotlin/io/infinitic/tags/workflows/worker/startWorkflowTagEngine.kt @@ -26,6 +26,7 @@ package io.infinitic.tags.workflows.worker import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.workers.MessageToProcess import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineMessage @@ -49,7 +50,7 @@ private fun logError(messageToProcess: WorkflowTagEngineMessageToProcess, e: Thr } fun CoroutineScope.startWorkflowTagEngine( - coroutineName: String, + name: String, workflowTagStorage: WorkflowTagStorage, eventsInputChannel: ReceiveChannel, eventsOutputChannel: SendChannel, @@ -57,9 +58,10 @@ fun CoroutineScope.startWorkflowTagEngin commandsOutputChannel: SendChannel, sendToWorkflowEngine: SendToWorkflowEngine, sendToClient: SendToClient, -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { val tagEngine = WorkflowTagEngine( + ClientName(name), workflowTagStorage, sendToWorkflowEngine, sendToClient diff --git a/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/tasks/TaskTagEngineTests.kt b/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/tasks/TaskTagEngineTests.kt index bcff0da96..c650efbf2 100644 --- a/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/tasks/TaskTagEngineTests.kt +++ b/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/tasks/TaskTagEngineTests.kt @@ -26,8 +26,9 @@ package io.infinitic.tags.tasks import io.infinitic.common.clients.messages.ClientMessage -import io.infinitic.common.clients.messages.TaskIdsPerTag +import io.infinitic.common.clients.messages.TaskIdsByTag import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.tasks.data.TaskId @@ -37,11 +38,11 @@ import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.engine.messages.CancelTask import io.infinitic.common.tasks.engine.messages.RetryTask import io.infinitic.common.tasks.engine.messages.TaskEngineMessage -import io.infinitic.common.tasks.tags.messages.AddTaskTag -import io.infinitic.common.tasks.tags.messages.CancelTaskPerTag -import io.infinitic.common.tasks.tags.messages.GetTaskIds -import io.infinitic.common.tasks.tags.messages.RemoveTaskTag -import io.infinitic.common.tasks.tags.messages.RetryTaskPerTag +import io.infinitic.common.tasks.tags.messages.AddTagToTask +import io.infinitic.common.tasks.tags.messages.CancelTaskByTag +import io.infinitic.common.tasks.tags.messages.GetTaskIdsByTag +import io.infinitic.common.tasks.tags.messages.RemoveTagFromTask +import io.infinitic.common.tasks.tags.messages.RetryTaskByTag import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage import io.infinitic.tags.tasks.storage.TaskTagStorage import io.kotest.core.spec.style.StringSpec @@ -59,8 +60,10 @@ import io.mockk.slot private fun captured(slot: CapturingSlot) = if (slot.isCaptured) slot.captured else null -private lateinit var stateMessageId: CapturingSlot -private lateinit var stateTaskId: CapturingSlot +private val clientName = ClientName("clientTaskTagEngineTests") + +private lateinit var stateMessageId: CapturingSlot +private lateinit var stateTaskId: CapturingSlot private lateinit var clientMessage: CapturingSlot private lateinit var taskEngineMessage: CapturingSlot @@ -92,7 +95,7 @@ internal class TaskTagEngineTests : StringSpec({ "addTaskTag should complete id" { // given - val msgIn = random() + val msgIn = random() // when getEngine(msgIn.taskTag, msgIn.taskName).handle(msgIn) // then @@ -105,7 +108,7 @@ internal class TaskTagEngineTests : StringSpec({ "removeTaskTag should remove id" { // given - val msgIn = random() + val msgIn = random() // when getEngine(msgIn.taskTag, msgIn.taskName, taskIds = setOf(msgIn.taskId)).handle(msgIn) // then @@ -119,7 +122,7 @@ internal class TaskTagEngineTests : StringSpec({ "retryTaskPerTag should retry task" { // given val taskIds = setOf(TaskId(), TaskId()) - val msgIn = random() + val msgIn = random() // when getEngine(msgIn.taskTag, msgIn.taskName, taskIds = taskIds).handle(msgIn) // then @@ -142,7 +145,7 @@ internal class TaskTagEngineTests : StringSpec({ "cancelTaskPerTag should cancel task" { // given val taskIds = setOf(TaskId(), TaskId()) - val msgIn = random() + val msgIn = random() // when getEngine(msgIn.taskTag, msgIn.taskName, taskIds = taskIds).handle(msgIn) // then @@ -164,7 +167,7 @@ internal class TaskTagEngineTests : StringSpec({ "getTaskIdsPerTag should return set of ids" { // given - val msgIn = random() + val msgIn = random() val taskId1 = TaskId() val taskId2 = TaskId() // when @@ -172,13 +175,13 @@ internal class TaskTagEngineTests : StringSpec({ // then coVerifySequence { tagStateStorage.getTaskIds(msgIn.taskTag, msgIn.taskName) - sendToClient(ofType()) + sendToClient(ofType()) tagStateStorage.setLastMessageId(msgIn.taskTag, msgIn.taskName, msgIn.messageId) } verifyAll() - captured(clientMessage).shouldBeInstanceOf() - (captured(clientMessage) as TaskIdsPerTag).taskIds shouldBe setOf(taskId1, taskId2) + captured(clientMessage).shouldBeInstanceOf() + (captured(clientMessage) as TaskIdsByTag).taskIds shouldBe setOf(taskId1, taskId2) } }) @@ -200,10 +203,10 @@ private fun mockSendToTaskEngine(slots: CapturingSlot): SendT private fun mockTagStateStorage(tag: TaskTag, name: TaskName, messageId: MessageId?, taskIds: Set): TaskTagStorage { val tagStateStorage = mockk() coEvery { tagStateStorage.getLastMessageId(tag, name) } returns messageId - coEvery { tagStateStorage.setLastMessageId(tag, name, capture(stateMessageId)) } just Runs + coEvery { tagStateStorage.setLastMessageId(tag, name, MessageId(capture(stateMessageId))) } just Runs coEvery { tagStateStorage.getTaskIds(tag, name) } returns taskIds - coEvery { tagStateStorage.addTaskId(tag, name, capture(stateTaskId)) } just Runs - coEvery { tagStateStorage.removeTaskId(tag, name, capture(stateTaskId)) } just Runs + coEvery { tagStateStorage.addTaskId(tag, name, TaskId(capture(stateTaskId))) } just Runs + coEvery { tagStateStorage.removeTaskId(tag, name, TaskId(capture(stateTaskId))) } just Runs return tagStateStorage } @@ -223,7 +226,7 @@ private fun getEngine( sendToTaskEngine = mockSendToTaskEngine(taskEngineMessage) sendToClient = mockSendToClient(clientMessage) - return TaskTagEngine(tagStateStorage, sendToTaskEngine, sendToClient) + return TaskTagEngine(clientName, tagStateStorage, sendToTaskEngine, sendToClient) } private fun verifyAll() = confirmVerified( diff --git a/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/workflows/WorkflowTagEngineTests.kt b/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/workflows/WorkflowTagEngineTests.kt index 9ed02cf42..4f3feceb4 100644 --- a/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/workflows/WorkflowTagEngineTests.kt +++ b/infinitic-tag-engine/src/test/kotlin/io/infinitic/tags/workflows/WorkflowTagEngineTests.kt @@ -26,8 +26,9 @@ package io.infinitic.tags.workflows import io.infinitic.common.clients.messages.ClientMessage -import io.infinitic.common.clients.messages.WorkflowIdsPerTag +import io.infinitic.common.clients.messages.WorkflowIdsByTag import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MessageId import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.workflows.data.workflows.WorkflowId @@ -35,11 +36,11 @@ import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.data.workflows.WorkflowTag import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.messages.CancelWorkflow -import io.infinitic.common.workflows.engine.messages.SendToChannel +import io.infinitic.common.workflows.engine.messages.SendSignal import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage -import io.infinitic.common.workflows.tags.messages.CancelWorkflowPerTag -import io.infinitic.common.workflows.tags.messages.GetWorkflowIds -import io.infinitic.common.workflows.tags.messages.SendToChannelPerTag +import io.infinitic.common.workflows.tags.messages.CancelWorkflowByTag +import io.infinitic.common.workflows.tags.messages.GetWorkflowIdsByTag +import io.infinitic.common.workflows.tags.messages.SendSignalByTag import io.infinitic.common.workflows.tags.messages.WorkflowTagEngineMessage import io.infinitic.tags.workflows.storage.WorkflowTagStorage import io.kotest.core.spec.style.StringSpec @@ -57,8 +58,10 @@ import io.mockk.slot private fun captured(slot: CapturingSlot) = if (slot.isCaptured) slot.captured else null -private lateinit var stateMessageId: CapturingSlot -private lateinit var stateWorkflowId: CapturingSlot +private val clientName = ClientName("clientWorkflowTagEngineTests") + +private lateinit var stateMessageId: CapturingSlot +private lateinit var stateWorkflowId: CapturingSlot private lateinit var workflowEngineMessage: CapturingSlot private lateinit var clientMessage: CapturingSlot @@ -91,7 +94,7 @@ internal class WorkflowTagEngineTests : StringSpec({ "cancelWorkflowPerTag should cancel workflow" { // given val workflowIds = setOf(WorkflowId(), WorkflowId()) - val msgIn = random() + val msgIn = random() // when getEngine(msgIn.workflowTag, msgIn.workflowName, workflowIds = workflowIds).handle(msgIn) // then @@ -114,35 +117,35 @@ internal class WorkflowTagEngineTests : StringSpec({ "sendToChannelPerTag should send to channel" { // given val workflowIds = setOf(WorkflowId(), WorkflowId()) - val msgIn = random() + val msgIn = random() // when getEngine(msgIn.workflowTag, msgIn.workflowName, workflowIds = workflowIds).handle(msgIn) // then coVerifySequence { workflowTagStorage.getLastMessageId(msgIn.workflowTag, msgIn.workflowName) workflowTagStorage.getWorkflowIds(msgIn.workflowTag, msgIn.workflowName) - sendToWorkflowEngine(ofType()) - sendToWorkflowEngine(ofType()) + sendToWorkflowEngine(ofType()) + sendToWorkflowEngine(ofType()) workflowTagStorage.setLastMessageId(msgIn.workflowTag, msgIn.workflowName, msgIn.messageId) } verifyAll() // checking last message - val sendToChannel = captured(workflowEngineMessage)!! as SendToChannel + val sendSignal = captured(workflowEngineMessage)!! as SendSignal - with(sendToChannel) { + with(sendSignal) { workflowId shouldBe workflowIds.last() workflowName shouldBe msgIn.workflowName - channelEvent shouldBe msgIn.channelEvent - channelEventId shouldBe msgIn.channelEventId - channelEvent shouldBe msgIn.channelEvent - channelEventTypes shouldBe msgIn.channelEventTypes + channelSignal shouldBe msgIn.channelSignal + channelSignalId shouldBe msgIn.channelSignalId + channelSignal shouldBe msgIn.channelSignal + channelSignalTypes shouldBe msgIn.channelSignalTypes channelName shouldBe msgIn.channelName } } "getWorkflowIdsPerTag should return set of ids" { // given - val msgIn = random() + val msgIn = random() val workflowId1 = WorkflowId() val workflowId2 = WorkflowId() // when @@ -150,13 +153,13 @@ internal class WorkflowTagEngineTests : StringSpec({ // then coVerifySequence { workflowTagStorage.getWorkflowIds(msgIn.workflowTag, msgIn.workflowName) - sendToClient(ofType()) + sendToClient(ofType()) workflowTagStorage.setLastMessageId(msgIn.workflowTag, msgIn.workflowName, msgIn.messageId) } verifyAll() - captured(clientMessage).shouldBeInstanceOf() - (captured(clientMessage) as WorkflowIdsPerTag).workflowIds shouldBe setOf(workflowId1, workflowId2) + captured(clientMessage).shouldBeInstanceOf() + (captured(clientMessage) as WorkflowIdsByTag).workflowIds shouldBe setOf(workflowId1, workflowId2) } }) @@ -183,10 +186,10 @@ private fun mockWorkflowTagStorage( ): WorkflowTagStorage { val tagStateStorage = mockk() coEvery { tagStateStorage.getLastMessageId(workflowTag, workflowName) } returns messageId - coEvery { tagStateStorage.setLastMessageId(workflowTag, workflowName, capture(stateMessageId)) } just Runs + coEvery { tagStateStorage.setLastMessageId(workflowTag, workflowName, MessageId(capture(stateMessageId))) } just Runs coEvery { tagStateStorage.getWorkflowIds(workflowTag, workflowName) } returns workflowIds - coEvery { tagStateStorage.addWorkflowId(workflowTag, workflowName, capture(stateWorkflowId)) } just Runs - coEvery { tagStateStorage.removeWorkflowId(workflowTag, workflowName, capture(stateWorkflowId)) } just Runs + coEvery { tagStateStorage.addWorkflowId(workflowTag, workflowName, WorkflowId(capture(stateWorkflowId))) } just Runs + coEvery { tagStateStorage.removeWorkflowId(workflowTag, workflowName, WorkflowId(capture(stateWorkflowId))) } just Runs return tagStateStorage } @@ -206,7 +209,7 @@ private fun getEngine( sendToWorkflowEngine = mockSendToWorkflowEngine(workflowEngineMessage) sendToClient = mockSendToClient(clientMessage) - return WorkflowTagEngine(workflowTagStorage, sendToWorkflowEngine, sendToClient) + return WorkflowTagEngine(clientName, workflowTagStorage, sendToWorkflowEngine, sendToClient) } private fun verifyAll() = confirmVerified( diff --git a/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/TaskEngine.kt b/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/TaskEngine.kt index bdea9e5e3..5bd7956e7 100644 --- a/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/TaskEngine.kt +++ b/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/TaskEngine.kt @@ -25,13 +25,19 @@ package io.infinitic.tasks.engine -import io.infinitic.common.clients.messages.UnknownTask +import io.infinitic.common.clients.messages.TaskUnknown import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName +import io.infinitic.common.errors.CanceledTaskError +import io.infinitic.common.errors.FailedTaskError +import io.infinitic.common.errors.WorkerError +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.metrics.perName.messages.TaskStatusUpdated import io.infinitic.common.metrics.perName.transport.SendToMetricsPerName import io.infinitic.common.tasks.data.TaskAttemptId import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.data.TaskRetryIndex +import io.infinitic.common.tasks.data.TaskReturnValue import io.infinitic.common.tasks.data.TaskStatus import io.infinitic.common.tasks.data.plus import io.infinitic.common.tasks.engine.SendToTaskEngineAfter @@ -49,15 +55,13 @@ import io.infinitic.common.tasks.engine.state.TaskState import io.infinitic.common.tasks.executors.SendToTaskExecutors import io.infinitic.common.tasks.executors.messages.ExecuteTaskAttempt import io.infinitic.common.tasks.tags.SendToTaskTagEngine -import io.infinitic.common.tasks.tags.messages.RemoveTaskTag +import io.infinitic.common.tasks.tags.messages.RemoveTagFromTask import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.messages.TaskFailed -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.tasks.engine.storage.LoggedTaskStateStorage import io.infinitic.tasks.engine.storage.TaskStateStorage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mu.KotlinLogging import io.infinitic.common.clients.messages.TaskCanceled as TaskCanceledInClient @@ -67,6 +71,7 @@ import io.infinitic.common.workflows.engine.messages.TaskCanceled as TaskCancele import io.infinitic.common.workflows.engine.messages.TaskCompleted as TaskCompletedInWorkflow class TaskEngine( + val clientName: ClientName, storage: TaskStateStorage, val sendToClient: SendToClient, val sendToTaskTagEngine: SendToTaskTagEngine, @@ -103,8 +108,12 @@ class TaskEngine( } if (message is WaitTask) { - val unknownTask = UnknownTask(message.clientName, message.taskId) - launch { sendToClient(unknownTask) } + val taskUnknown = TaskUnknown( + recipientName = message.emitterName, + taskId = message.taskId, + emitterName = clientName + ) + launch { sendToClient(taskUnknown) } } // is should happen only if a previous retry or a cancel command has terminated this task @@ -157,7 +166,7 @@ class TaskEngine( is TaskAttemptCompleted -> taskAttemptCompleted(state, message) is WaitTask -> waitTask(state, message) is CompleteTask -> TODO() - is DispatchTask -> throw thisShouldNotHappen() + is DispatchTask -> thisShouldNotHappen() } // Send TaskStatusUpdated if needed @@ -168,10 +177,11 @@ class TaskEngine( private fun CoroutineScope.taskStatusUpdate(state: TaskState, oldStatus: TaskStatus?) { val taskStatusUpdated = TaskStatusUpdated( - taskId = state.taskId, taskName = TaskName("${state.taskName}::${state.methodName}"), + taskId = state.taskId, oldStatus = oldStatus, - newStatus = state.taskStatus + newStatus = state.taskStatus, + emitterName = clientName ) launch { sendToMetricsPerName(taskStatusUpdated) } @@ -181,7 +191,8 @@ class TaskEngine( when (state.taskStatus) { TaskStatus.TERMINATED_COMPLETED -> { val taskCompleted = TaskCompletedInClient( - clientName = message.clientName, + emitterName = clientName, + recipientName = message.emitterName, taskId = state.taskId, taskReturnValue = state.taskReturnValue!!, taskMeta = state.taskMeta @@ -190,13 +201,24 @@ class TaskEngine( } TaskStatus.TERMINATED_CANCELED -> { val taskCanceled = TaskCanceledInClient( - clientName = message.clientName, + emitterName = clientName, + recipientName = message.emitterName, taskId = state.taskId, - taskMeta = state.taskMeta ) launch { sendToClient(taskCanceled) } } - else -> state.waitingClients.add(message.clientName) + TaskStatus.RUNNING_ERROR -> { + val lastError = state.lastError ?: thisShouldNotHappen() + val taskFailed = TaskFailedInClient( + emitterName = clientName, + recipientName = message.emitterName, + taskId = state.taskId, + error = lastError, + ) + launch { sendToClient(taskFailed) } + } + TaskStatus.RUNNING_OK, TaskStatus.RUNNING_WARNING -> + state.waitingClients.add(message.emitterName) } } @@ -204,7 +226,7 @@ class TaskEngine( // init a state val state = TaskState( waitingClients = when (message.clientWaiting) { - true -> mutableSetOf(message.clientName) + true -> mutableSetOf(message.emitterName) false -> mutableSetOf() }, lastMessageId = message.messageId, @@ -228,6 +250,7 @@ class TaskEngine( val executeTaskAttempt = ExecuteTaskAttempt( taskName = state.taskName, taskId = state.taskId, + taskTags = state.taskTags, workflowId = state.workflowId, workflowName = state.workflowName, taskAttemptId = state.taskAttemptId, @@ -238,8 +261,8 @@ class TaskEngine( methodParameterTypes = state.methodParameterTypes, methodParameters = state.methodParameters, taskOptions = state.taskOptions, - taskTags = state.taskTags, - taskMeta = state.taskMeta + taskMeta = state.taskMeta, + emitterName = clientName ) launch { sendToTaskExecutors(executeTaskAttempt) } @@ -252,11 +275,15 @@ class TaskEngine( // if this task belongs to a workflow, send back the TaskCompleted message state.workflowId?.let { val taskCanceled = TaskCanceledInWorkflow( + emitterName = clientName, workflowId = it, workflowName = state.workflowName!!, methodRunId = state.methodRunId!!, - taskId = state.taskId, - taskName = state.taskName + canceledTaskError = CanceledTaskError( + taskName = state.taskName, + taskId = state.taskId, + methodName = state.methodName, + ) ) launch { sendToWorkflowEngine(taskCanceled) } } @@ -264,12 +291,13 @@ class TaskEngine( // if some clients wait for it, send TaskCompleted output back to them state.waitingClients.map { val taskCanceledInClient = TaskCanceledInClient( - clientName = it, - taskId = state.taskId, - taskMeta = state.taskMeta + emitterName = clientName, + recipientName = it, + taskId = state.taskId ) launch { sendToClient(taskCanceledInClient) } } + state.waitingClients.clear() // delete stored state removeTags(state) @@ -287,6 +315,7 @@ class TaskEngine( val executeTaskAttempt = ExecuteTaskAttempt( taskName = state.taskName, taskId = state.taskId, + taskTags = state.taskTags, workflowId = state.workflowId, workflowName = state.workflowName, taskAttemptId = state.taskAttemptId, @@ -297,8 +326,8 @@ class TaskEngine( methodParameterTypes = state.methodParameterTypes, methodParameters = state.methodParameters, taskOptions = state.taskOptions, - taskTags = state.taskTags, - taskMeta = state.taskMeta + taskMeta = state.taskMeta, + emitterName = clientName ) launch { sendToTaskExecutors(executeTaskAttempt) } } @@ -313,6 +342,7 @@ class TaskEngine( val executeTaskAttempt = ExecuteTaskAttempt( taskName = state.taskName, taskId = state.taskId, + taskTags = state.taskTags, workflowId = state.workflowId, workflowName = state.workflowName, taskAttemptId = state.taskAttemptId, @@ -323,8 +353,8 @@ class TaskEngine( methodParameterTypes = state.methodParameterTypes, methodParameters = state.methodParameters, taskOptions = state.taskOptions, - taskTags = state.taskTags, - taskMeta = state.taskMeta + taskMeta = state.taskMeta, + emitterName = clientName ) launch { sendToTaskExecutors(executeTaskAttempt) } } @@ -339,12 +369,15 @@ class TaskEngine( // if this task belongs to a workflow, send back the adhoc message state.workflowId?.let { val taskCompleted = TaskCompletedInWorkflow( + emitterName = clientName, workflowId = it, workflowName = state.workflowName!!, methodRunId = state.methodRunId!!, - taskId = state.taskId, - taskName = state.taskName, - taskReturnValue = message.taskReturnValue + taskReturnValue = TaskReturnValue( + taskId = state.taskId, + taskName = state.taskName, + returnValue = message.taskReturnValue + ) ) launch { sendToWorkflowEngine(taskCompleted) } } @@ -352,16 +385,15 @@ class TaskEngine( // if client is waiting, send output back to it state.waitingClients.forEach { val taskCompleted = TaskCompletedInClient( - clientName = it, + emitterName = clientName, + recipientName = it, taskId = state.taskId, taskReturnValue = message.taskReturnValue, taskMeta = state.taskMeta ) - launch { - sendToClient(taskCompleted) - state.waitingClients.remove(it) - } + launch { sendToClient(taskCompleted) } } + state.waitingClients.clear() removeTags(state) } @@ -369,18 +401,19 @@ class TaskEngine( private fun CoroutineScope.removeTags(state: TaskState) { // remove tags reference to this instance state.taskTags.map { - val removeTaskTag = RemoveTaskTag( - taskTag = it, + val removeTagFromTask = RemoveTagFromTask( taskName = state.taskName, + taskTag = it, taskId = state.taskId, + emitterName = clientName, ) - launch { sendToTaskTagEngine(removeTaskTag) } + launch { sendToTaskTagEngine(removeTagFromTask) } } } private fun CoroutineScope.taskAttemptFailed(state: TaskState, msg: TaskAttemptFailed) { with(state) { - lastError = msg.taskAttemptError + lastError = msg.workerError taskMeta = msg.taskMeta } @@ -394,27 +427,31 @@ class TaskEngine( // tell parent workflow state.workflowId?.let { val taskFailed = TaskFailed( - workflowId = it, workflowName = state.workflowName!!, + workflowId = it, methodRunId = state.methodRunId!!, - taskId = state.taskId, - taskName = state.taskName, - error = msg.taskAttemptError + emitterName = clientName, + failedTaskError = FailedTaskError( + taskName = msg.taskName, + taskId = msg.taskId, + methodName = state.methodName, + cause = msg.workerError ?: WorkerError.from(ClientName("unsused"), Exception("unused")) + ), + deferredError = msg.deferredError ) launch { sendToWorkflowEngine(taskFailed) } } // tell waiting clients state.waitingClients.forEach { val taskFailed = TaskFailedInClient( - clientName = it, + emitterName = clientName, + recipientName = it, taskId = state.taskId, - error = msg.taskAttemptError, + error = msg.workerError ?: thisShouldNotHappen(), ) - launch { - sendToClient(taskFailed) - state.waitingClients.remove(it) - } + launch { sendToClient(taskFailed) } } + state.waitingClients.clear() } // immediate retry delay.long <= 0 -> retryTaskAttempt(state) @@ -424,11 +461,12 @@ class TaskEngine( // schedule next attempt val retryTaskAttempt = RetryTaskAttempt( - taskId = state.taskId, taskName = state.taskName, + taskId = state.taskId, + taskRetryIndex = state.taskRetryIndex, taskAttemptId = state.taskAttemptId, taskRetrySequence = state.taskRetrySequence, - taskRetryIndex = state.taskRetryIndex + emitterName = clientName ) launch { sendToTaskEngineAfter(retryTaskAttempt, delay) } } diff --git a/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/worker/startTaskEngine.kt b/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/worker/startTaskEngine.kt index 5babf3381..b8e224883 100644 --- a/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/worker/startTaskEngine.kt +++ b/infinitic-task-engine/src/main/kotlin/io/infinitic/tasks/engine/worker/startTaskEngine.kt @@ -26,6 +26,7 @@ package io.infinitic.tasks.engine.worker import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.metrics.perName.transport.SendToMetricsPerName import io.infinitic.common.tasks.engine.SendToTaskEngineAfter import io.infinitic.common.tasks.engine.messages.TaskEngineMessage @@ -53,7 +54,7 @@ private fun logError(messageToProcess: TaskEngineMessageToProcess, e: Throwable) } fun CoroutineScope.startTaskEngine( - coroutineName: String, + name: String, taskStateStorage: TaskStateStorage, eventsInputChannel: ReceiveChannel, eventsOutputChannel: SendChannel, @@ -65,9 +66,10 @@ fun CoroutineScope.startTaskEngine( sendToWorkflowEngine: SendToWorkflowEngine, sendToTaskExecutors: SendToTaskExecutors, sendToMetricsPerName: SendToMetricsPerName -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { val taskEngine = TaskEngine( + ClientName(name), taskStateStorage, sendToClient, sendToTaskTagEngine, diff --git a/infinitic-task-engine/src/test/kotlin/io/infinitic/tasks/engine/TaskEngineTests.kt b/infinitic-task-engine/src/test/kotlin/io/infinitic/tasks/engine/TaskEngineTests.kt index 1eee94a41..4ef2e0a7e 100644 --- a/infinitic-task-engine/src/test/kotlin/io/infinitic/tasks/engine/TaskEngineTests.kt +++ b/infinitic-task-engine/src/test/kotlin/io/infinitic/tasks/engine/TaskEngineTests.kt @@ -25,14 +25,15 @@ package io.infinitic.tasks.engine -import io.infinitic.common.clients.data.ClientName import io.infinitic.common.clients.messages.ClientMessage import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MillisDuration import io.infinitic.common.fixtures.TestFactory import io.infinitic.common.metrics.perName.messages.MetricsPerNameMessage import io.infinitic.common.metrics.perName.messages.TaskStatusUpdated import io.infinitic.common.metrics.perName.transport.SendToMetricsPerName +import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskName import io.infinitic.common.tasks.data.TaskRetryIndex import io.infinitic.common.tasks.data.TaskStatus @@ -52,7 +53,7 @@ import io.infinitic.common.tasks.executors.SendToTaskExecutors import io.infinitic.common.tasks.executors.messages.ExecuteTaskAttempt import io.infinitic.common.tasks.executors.messages.TaskExecutorMessage import io.infinitic.common.tasks.tags.SendToTaskTagEngine -import io.infinitic.common.tasks.tags.messages.RemoveTaskTag +import io.infinitic.common.tasks.tags.messages.RemoveTagFromTask import io.infinitic.common.tasks.tags.messages.TaskTagEngineMessage import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.messages.TaskFailed @@ -78,6 +79,8 @@ import io.infinitic.common.workflows.engine.messages.TaskCompleted as TaskComple private fun captured(slot: CapturingSlot) = if (slot.isCaptured) slot.captured else null +private val clientName = ClientName("clientTaskEngineTests") + private lateinit var taskStateStorage: TaskStateStorage private lateinit var taskState: CapturingSlot @@ -145,10 +148,11 @@ internal class TaskEngineTests : StringSpec({ val stateIn = random( mapOf( "taskStatus" to TaskStatus.RUNNING_OK, - "taskTags" to setOf(TaskTag("foo"), TaskTag("bar")) + "taskTags" to setOf(TaskTag("foo"), TaskTag("bar")), + "waitingClients" to mutableSetOf(ClientName("foo")) ) ) - val msgIn = random(mapOf("taskId" to stateIn.taskId)) + val msgIn = random(mapOf("taskId" to stateIn.taskId.toString())) // when getEngine(stateIn).handle(msgIn) // then @@ -156,8 +160,8 @@ internal class TaskEngineTests : StringSpec({ taskStateStorage.getState(msgIn.taskId) sendToWorkflowEngine(ofType()) sendToClient(ofType()) - sendToTaskTagEngine(ofType()) - sendToTaskTagEngine(ofType()) + sendToTaskTagEngine(ofType()) + sendToTaskTagEngine(ofType()) sendToMetricsPerName(ofType()) taskStateStorage.delState(msgIn.taskId) } @@ -184,7 +188,7 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId, + "taskId" to stateIn.taskId.toString(), "taskName" to stateIn.taskName, "methodName" to null, "methodParameterTypes" to null, @@ -247,7 +251,7 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId + "taskId" to stateIn.taskId.toString() ) ) // when @@ -259,8 +263,8 @@ internal class TaskEngineTests : StringSpec({ sendToWorkflowEngine(ofType()) sendToClient(ofType()) sendToClient(ofType()) - sendToTaskTagEngine(ofType()) - sendToTaskTagEngine(ofType()) + sendToTaskTagEngine(ofType()) + sendToTaskTagEngine(ofType()) sendToMetricsPerName(ofType()) taskStateStorage.delState(msgIn.taskId) } @@ -289,8 +293,8 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId, - "taskAttemptId" to stateIn.taskAttemptId, + "taskId" to stateIn.taskId.toString(), + "taskAttemptId" to stateIn.taskAttemptId.toString(), "taskRetryIndex" to stateIn.taskRetryIndex, "taskAttemptDelayBeforeRetry" to null ) @@ -327,8 +331,8 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId, - "taskAttemptId" to stateIn.taskAttemptId, + "taskId" to stateIn.taskId.toString(), + "taskAttemptId" to stateIn.taskAttemptId.toString(), "taskRetryIndex" to stateIn.taskRetryIndex, "taskAttemptDelayBeforeRetry" to MillisDuration(42000) ) @@ -371,8 +375,8 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId, - "taskAttemptId" to stateIn.taskAttemptId, + "taskId" to stateIn.taskId.toString(), + "taskAttemptId" to stateIn.taskAttemptId.toString(), "taskRetryIndex" to stateIn.taskRetryIndex, "taskAttemptDelayBeforeRetry" to MillisDuration(0) ) @@ -392,8 +396,8 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId, - "taskAttemptId" to stateIn.taskAttemptId, + "taskId" to stateIn.taskId.toString(), + "taskAttemptId" to stateIn.taskAttemptId.toString(), "taskRetryIndex" to stateIn.taskRetryIndex, "taskAttemptDelayBeforeRetry" to MillisDuration(-42000) ) @@ -413,8 +417,8 @@ internal class TaskEngineTests : StringSpec({ ) val msgIn = random( mapOf( - "taskId" to stateIn.taskId, - "taskAttemptId" to stateIn.taskAttemptId, + "taskId" to stateIn.taskId.toString(), + "taskAttemptId" to stateIn.taskAttemptId.toString(), "taskRetryIndex" to stateIn.taskRetryIndex ) ) @@ -506,9 +510,9 @@ private fun mockSendToWorkflowEngine(slot: CapturingSlot) private fun mockTaskStateStorage(state: TaskState?): TaskStateStorage { val taskStateStorage = mockk() - coEvery { taskStateStorage.getState(any()) } returns state?.deepCopy() - coEvery { taskStateStorage.putState(any(), capture(taskState)) } just Runs - coEvery { taskStateStorage.delState(any()) } just Runs + coEvery { taskStateStorage.getState(TaskId(any())) } returns state?.deepCopy() + coEvery { taskStateStorage.putState(TaskId(any()), capture(taskState)) } just Runs + coEvery { taskStateStorage.delState(TaskId(any())) } just Runs return taskStateStorage } @@ -534,6 +538,7 @@ private fun getEngine(state: TaskState?): TaskEngine { sendToMetricsPerName = mockSendToMetricsPerName(metricsPerNameMessage) return TaskEngine( + clientName, taskStateStorage, sendToClient, sendToTaskTagEngine, diff --git a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/TaskContext.kt b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/TaskContext.kt index 022ab0049..7be1c516a 100644 --- a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/TaskContext.kt +++ b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/TaskContext.kt @@ -26,20 +26,19 @@ package io.infinitic.tasks import io.infinitic.client.InfiniticClient -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.WorkerError import io.infinitic.common.tasks.data.TaskOptions -import java.util.UUID interface TaskContext { val register: TaskExecutorRegister val client: InfiniticClient - val id: UUID - val workflowId: UUID? + val id: String + val workflowId: String? val workflowName: String? - val attemptId: UUID + val attemptId: String val retrySequence: Int val retryIndex: Int - val lastError: Error? + val lastError: WorkerError? val tags: Set val meta: MutableMap val options: TaskOptions diff --git a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/TaskExecutor.kt b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/TaskExecutor.kt index fc0388c94..9137b9229 100644 --- a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/TaskExecutor.kt +++ b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/TaskExecutor.kt @@ -26,9 +26,12 @@ package io.infinitic.tasks.executor import io.infinitic.client.InfiniticClient +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MillisDuration -import io.infinitic.common.data.methods.MethodReturnValue -import io.infinitic.common.errors.Error +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.data.methods.MethodParameters +import io.infinitic.common.errors.DeferredError +import io.infinitic.common.errors.WorkerError import io.infinitic.common.parser.getMethodPerNameAndParameters import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.tasks.engine.SendToTaskEngine @@ -36,6 +39,7 @@ import io.infinitic.common.tasks.engine.messages.TaskAttemptCompleted import io.infinitic.common.tasks.engine.messages.TaskAttemptFailed import io.infinitic.common.tasks.executors.messages.ExecuteTaskAttempt import io.infinitic.common.tasks.executors.messages.TaskExecutorMessage +import io.infinitic.exceptions.DeferredException import io.infinitic.exceptions.tasks.ProcessingTimeoutException import io.infinitic.tasks.Task import io.infinitic.tasks.TaskExecutorRegister @@ -52,6 +56,7 @@ import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method class TaskExecutor( + private val clientName: ClientName, private val taskExecutorRegister: TaskExecutorRegister, private val sendToTaskEngine: SendToTaskEngine, private val clientFactory: () -> InfiniticClient @@ -70,10 +75,10 @@ class TaskExecutor( private suspend fun executeTaskAttempt(message: ExecuteTaskAttempt) { val taskContext = TaskContextImpl( register = this, - id = message.taskId.id, - workflowId = message.workflowId?.id, + id = message.taskId.toString(), + workflowId = message.workflowId?.toString(), workflowName = message.workflowName?.name, - attemptId = message.taskAttemptId.id, + attemptId = message.taskAttemptId.toString(), retrySequence = message.taskRetrySequence.int, retryIndex = message.taskRetryIndex.int, lastError = message.lastError, @@ -87,6 +92,7 @@ class TaskExecutor( val (task, method, parameters, options) = try { parse(message) } catch (e: Throwable) { + logger.error(e) {} // returning the exception (no retry) sendTaskAttemptFailed(message, e, null, message.taskMeta) // stop here @@ -107,7 +113,8 @@ class TaskExecutor( sendTaskCompleted(message, output, TaskMeta(task.context.meta)) } catch (e: InvocationTargetException) { val cause = e.cause - if (cause is Exception) { + // do not retry failed workflow task due to failed/canceled task/workflow + if (cause is Exception && cause !is DeferredException) { failTaskWithRetry(task, message, cause) } else { sendTaskAttemptFailed(message, cause ?: e, null, TaskMeta(task.context.meta)) @@ -122,8 +129,9 @@ class TaskExecutor( } } - private suspend fun runTask(method: Method, task: Any, parameters: List) = coroutineScope { - val output = method.invoke(task, *parameters.toTypedArray()) + private suspend fun runTask(method: Method, task: Any, methodParameters: MethodParameters) = coroutineScope { + val parameter = methodParameters.map { it.deserialize() }.toTypedArray() + val output = method.invoke(task, *parameter) ensureActive() output } @@ -167,7 +175,7 @@ class TaskExecutor( msg.methodParameters.size ) - return TaskCommand(task, method, msg.methodParameters.get(), msg.taskOptions) + return TaskCommand(task, method, msg.methodParameters, msg.taskOptions) } private fun getDurationBeforeRetry(task: Task, cause: Exception) = try { @@ -183,21 +191,27 @@ class TaskExecutor( private fun sendTaskAttemptFailed( message: ExecuteTaskAttempt, - cause: Throwable, + throwable: Throwable, delay: MillisDuration?, taskMeta: TaskMeta ) { - logger.error(cause) { "task ${message.taskName} (${message.taskId}) - error: $cause" } - val taskAttemptFailed = TaskAttemptFailed( - taskId = message.taskId, taskName = message.taskName, + taskId = message.taskId, + taskAttemptDelayBeforeRetry = delay, taskAttemptId = message.taskAttemptId, taskRetrySequence = message.taskRetrySequence, taskRetryIndex = message.taskRetryIndex, - taskAttemptDelayBeforeRetry = delay, - taskAttemptError = Error.from(cause), - taskMeta = taskMeta + deferredError = when (throwable is DeferredException) { + true -> DeferredError.from(throwable) + false -> null + }, + workerError = when (throwable is DeferredException) { + true -> null + false -> WorkerError.from(clientName, throwable) + }, + taskMeta = taskMeta, + emitterName = clientName ) sendToTaskEngine(taskAttemptFailed) @@ -209,13 +223,14 @@ class TaskExecutor( taskMeta: TaskMeta ) { val taskAttemptCompleted = TaskAttemptCompleted( - taskId = message.taskId, taskName = message.taskName, + taskId = message.taskId, + taskRetryIndex = message.taskRetryIndex, taskAttemptId = message.taskAttemptId, taskRetrySequence = message.taskRetrySequence, - taskRetryIndex = message.taskRetryIndex, - taskReturnValue = MethodReturnValue.from(returnValue), - taskMeta = taskMeta + taskReturnValue = ReturnValue.from(returnValue), + taskMeta = taskMeta, + emitterName = clientName ) sendToTaskEngine(taskAttemptCompleted) diff --git a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskCommand.kt b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskCommand.kt index a6a59dee2..6f86c860d 100644 --- a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskCommand.kt +++ b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskCommand.kt @@ -25,6 +25,7 @@ package io.infinitic.tasks.executor.task +import io.infinitic.common.data.methods.MethodParameters import io.infinitic.common.tasks.data.TaskOptions import io.infinitic.tasks.Task import java.lang.reflect.Method @@ -32,6 +33,6 @@ import java.lang.reflect.Method internal data class TaskCommand( val task: Task, val method: Method, - val parameters: List, + val parameters: MethodParameters, val options: TaskOptions ) diff --git a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskContextImpl.kt b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskContextImpl.kt index c39fd5e5f..17d539edf 100644 --- a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskContextImpl.kt +++ b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/task/TaskContextImpl.kt @@ -26,21 +26,20 @@ package io.infinitic.tasks.executor.task import io.infinitic.client.InfiniticClient -import io.infinitic.common.errors.Error +import io.infinitic.common.errors.WorkerError import io.infinitic.common.tasks.data.TaskOptions import io.infinitic.tasks.TaskContext import io.infinitic.tasks.executor.TaskExecutor -import java.util.UUID data class TaskContextImpl( override val register: TaskExecutor, - override val id: UUID, - override val workflowId: UUID?, + override val id: String, + override val workflowId: String?, override val workflowName: String?, - override val attemptId: UUID, + override val attemptId: String, override val retrySequence: Int, override val retryIndex: Int, - override val lastError: Error?, + override val lastError: WorkerError?, override val tags: Set, override val meta: MutableMap, override val options: TaskOptions, diff --git a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/worker/startTaskExecutor.kt b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/worker/startTaskExecutor.kt index 4a31fa50e..c0fdc241f 100644 --- a/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/worker/startTaskExecutor.kt +++ b/infinitic-task-executor/src/main/kotlin/io/infinitic/tasks/executor/worker/startTaskExecutor.kt @@ -26,6 +26,7 @@ package io.infinitic.tasks.executor.worker import io.infinitic.client.InfiniticClient +import io.infinitic.common.data.ClientName import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.executors.messages.TaskExecutorMessage import io.infinitic.common.workers.MessageToProcess @@ -47,14 +48,15 @@ private fun logError(messageToProcess: TaskExecutorMessageToProcess, e: Throwabl } fun CoroutineScope.startTaskExecutor( - coroutineName: String, + name: String, register: TaskExecutorRegister, inputChannel: ReceiveChannel, outputChannel: SendChannel, sendToTaskEngine: SendToTaskEngine, clientFactory: () -> InfiniticClient -) = launch(CoroutineName(coroutineName)) { - val taskExecutor = TaskExecutor(register, sendToTaskEngine, clientFactory) +) = launch(CoroutineName(name)) { + + val taskExecutor = TaskExecutor(ClientName(name), register, sendToTaskEngine, clientFactory) for (message in inputChannel) { try { diff --git a/infinitic-task-executor/src/test/kotlin/io/infinitic/tasks/executor/TaskExecutorTests.kt b/infinitic-task-executor/src/test/kotlin/io/infinitic/tasks/executor/TaskExecutorTests.kt index 1136af5dd..6238714c4 100644 --- a/infinitic-task-executor/src/test/kotlin/io/infinitic/tasks/executor/TaskExecutorTests.kt +++ b/infinitic-task-executor/src/test/kotlin/io/infinitic/tasks/executor/TaskExecutorTests.kt @@ -28,11 +28,12 @@ package io.infinitic.tasks.executor import io.infinitic.client.InfiniticClient +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MillisDuration +import io.infinitic.common.data.ReturnValue import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.data.methods.MethodReturnValue import io.infinitic.common.tasks.data.TaskAttemptId import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.data.TaskMeta @@ -65,6 +66,8 @@ import io.mockk.just import io.mockk.mockk import kotlinx.coroutines.coroutineScope +private val clientName = ClientName("clientTaskExecutorTests") + fun mockSendToTaskEngine(slots: MutableList): SendToTaskEngine { val sendToTaskEngine = mockk() coEvery { sendToTaskEngine(capture(slots)) } just Runs @@ -77,7 +80,7 @@ class TaskExecutorTests : StringSpec({ val taskExecutorRegister = TaskExecutorRegisterImpl() val mockClientFactory = mockk<()-> InfiniticClient>() val taskExecutor = - TaskExecutor(taskExecutorRegister, mockSendToTaskEngine(slots), mockClientFactory) + TaskExecutor(clientName, taskExecutorRegister, mockSendToTaskEngine(slots), mockClientFactory) // ensure slots are emptied between each test beforeTest { @@ -100,8 +103,9 @@ class TaskExecutorTests : StringSpec({ taskAttemptId = msg.taskAttemptId, taskRetrySequence = msg.taskRetrySequence, taskRetryIndex = msg.taskRetryIndex, - taskReturnValue = MethodReturnValue.from("9"), - taskMeta = msg.taskMeta + taskReturnValue = ReturnValue.from("9"), + taskMeta = msg.taskMeta, + emitterName = clientName ) } @@ -120,8 +124,9 @@ class TaskExecutorTests : StringSpec({ taskAttemptId = msg.taskAttemptId, taskRetrySequence = msg.taskRetrySequence, taskRetryIndex = msg.taskRetryIndex, - taskReturnValue = MethodReturnValue.from("12"), - taskMeta = msg.taskMeta + taskReturnValue = ReturnValue.from("12"), + taskMeta = msg.taskMeta, + emitterName = clientName ) } @@ -142,7 +147,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe null - fail.taskAttemptError.errorName shouldBe ClassNotFoundException::class.java.name + fail.workerError!!.name shouldBe ClassNotFoundException::class.java.name } "Should throw NoMethodFoundWithParameterTypes when trying to process an unknown method" { @@ -162,7 +167,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe null - fail.taskAttemptError.errorName shouldBe NoMethodFoundWithParameterTypesException::class.java.name + fail.workerError!!.name shouldBe NoMethodFoundWithParameterTypesException::class.java.name } "Should throw NoMethodFoundWithParameterCount when trying to process an unknown method without parameterTypes" { @@ -181,7 +186,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe null - fail.taskAttemptError.errorName shouldBe NoMethodFoundWithParameterCountException::class.java.name + fail.workerError!!.name shouldBe NoMethodFoundWithParameterCountException::class.java.name } "Should throw TooManyMethodsFoundWithParameterCount when trying to process an unknown method without parameterTypes" { @@ -200,7 +205,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe null - fail.taskAttemptError.errorName shouldBe TooManyMethodsFoundWithParameterCountException::class.java.name + fail.workerError!!.name shouldBe TooManyMethodsFoundWithParameterCountException::class.java.name } "Should retry with correct exception" { @@ -219,7 +224,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe MillisDuration(3000) - fail.taskAttemptError.errorName shouldBe IllegalStateException::class.java.name + fail.workerError!!.name shouldBe IllegalStateException::class.java.name } "Should throw when getRetryDelay throw an exception" { @@ -238,7 +243,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe null - fail.taskAttemptError.errorName shouldBe IllegalArgumentException::class.java.name + fail.workerError!!.name shouldBe IllegalArgumentException::class.java.name } "Should be able to access context from task" { @@ -256,8 +261,9 @@ class TaskExecutorTests : StringSpec({ taskAttemptId = msg.taskAttemptId, taskRetrySequence = msg.taskRetrySequence, taskRetryIndex = msg.taskRetryIndex, - taskReturnValue = MethodReturnValue.from("72"), - taskMeta = msg.taskMeta + taskReturnValue = ReturnValue.from("72"), + taskMeta = msg.taskMeta, + emitterName = clientName ) } @@ -278,7 +284,7 @@ class TaskExecutorTests : StringSpec({ fail.taskRetrySequence shouldBe msg.taskRetrySequence fail.taskRetryIndex shouldBe msg.taskRetryIndex fail.taskAttemptDelayBeforeRetry shouldBe null - fail.taskAttemptError.errorName shouldBe ProcessingTimeoutException::class.java.name + fail.workerError!!.name shouldBe ProcessingTimeoutException::class.java.name } }) @@ -296,5 +302,6 @@ private fun getExecuteTaskAttempt(name: String, method: String, input: Array() - val taskTestWithTags = client.newTask(tags = setOf("foo", "bar")) + val taskTest = client.newTask(TaskTest::class.java) + val taskTestWithTags = client.newTask(TaskTest::class.java, tags = setOf("foo", "bar")) beforeTest { worker.storageFlush() } beforeSpec { - thread { worker.start() } + worker.startAsync() } "Asynchronous execution succeeds at first try" { TaskTestImpl.behavior = { _, _ -> Status.SUCCESS } - val deferred = client.async(taskTest) { await(400L) }.join() - - deferred.await() shouldBe 400L + client.dispatch(taskTest::await, 500L).await() shouldBe 500L } "Synchronous execution succeeds at first try" { @@ -100,7 +93,7 @@ internal class TaskTests : StringSpec({ } } - val deferred = client.async(taskTest) { log() }.join() + val deferred = client.dispatch(taskTest::log) deferred.await() shouldBe "00000000001" } @@ -108,9 +101,9 @@ internal class TaskTests : StringSpec({ "Task fails at first try" { TaskTestImpl.behavior = { _, _ -> Status.FAILED_WITHOUT_RETRY } - val e = shouldThrow { taskTest.log() } + val error = shouldThrow { taskTest.log() } - e.causeError?.errorName shouldBe TaskException::class.java.name + error.workerException.name shouldBe ExpectedException::class.java.name } "Task fails after 4 tries " { @@ -122,9 +115,9 @@ internal class TaskTests : StringSpec({ } } - val e = shouldThrow { taskTest.log() } + val error = shouldThrow { taskTest.log() } - e.causeError?.errorName shouldBe TaskException::class.java.name + error.workerException.name shouldBe ExpectedException::class.java.name } "Task succeeds after manual retry" { @@ -136,11 +129,13 @@ internal class TaskTests : StringSpec({ } } - shouldThrow { taskTest.log() } + val deferred = client.dispatch(taskTest::log) + + shouldThrow { deferred.await() } - after { client.retry(taskTest) } + deferred.retry() - client.await(taskTest) shouldBe "01" + deferred.await() shouldBe "01" } "Task succeeds after automatic and manual retry" { @@ -152,11 +147,13 @@ internal class TaskTests : StringSpec({ } } - shouldThrow { taskTest.log() } + val deferred = client.dispatch(taskTest::log) - after { client.retry(taskTest) } + shouldThrow { deferred.await() } - client.await(taskTest) shouldBe "00000001" + deferred.retry() + + deferred.await() shouldBe "00000001" } "Task succeeds after manual retry using tags" { @@ -167,11 +164,14 @@ internal class TaskTests : StringSpec({ else -> Status.SUCCESS } } - val deferred = client.async(taskTestWithTags) { log() }.join() + val deferred = client.dispatch(taskTestWithTags::log) + + shouldThrow { deferred.await() } - shouldThrow { deferred.await() } + val t = client.getTaskByTag(TaskTest::class.java, "foo") + client.retry(t) - client.retryTask("foo").join() + delay(50) deferred.await() shouldBe "00001" } @@ -179,80 +179,90 @@ internal class TaskTests : StringSpec({ "Task canceled during automatic retry" { TaskTestImpl.behavior = { _, _ -> Status.FAILED_WITH_RETRY } - val deferred = client.async(taskTest) { log() }.join() + val deferred = client.dispatch(taskTest::log) - after { client.cancel(taskTest) } + later { deferred.cancel() } - shouldThrow { deferred.await() } + shouldThrow { deferred.await() } } - "Task canceled using A tag" { + "Task canceled using tag" { TaskTestImpl.behavior = { _, _ -> Status.FAILED_WITH_RETRY } - val deferred = client.async(taskTestWithTags) { log() }.join() + val deferred = client.dispatch(taskTestWithTags::log) - after { client.cancelTask("foo") } + later { + val t = client.getTaskByTag(TaskTest::class.java, "foo") + client.cancel(t) + } - shouldThrow { deferred.await() } + shouldThrow { deferred.await() } } - "2 Task canceled using A tag" { + "2 Task canceled using tag" { TaskTestImpl.behavior = { _, _ -> Status.FAILED_WITH_RETRY } - val deferred1 = client.async(taskTestWithTags) { log() }.join() - val deferred2 = client.async(taskTestWithTags) { log() }.join() + val deferred1 = client.dispatch(taskTestWithTags::log) + val deferred2 = client.dispatch(taskTestWithTags::log) - after { client.cancelTask("foo") } + later { + val t = client.getTaskByTag(TaskTest::class.java, "foo") + client.cancel(t) + } - after(0) { - launch { shouldThrow { deferred1.await() } } - launch { shouldThrow { deferred2.await() } } + later(0) { + launch { shouldThrow { deferred1.await() } } + launch { shouldThrow { deferred2.await() } } }.join() } "Tag should be added then deleted after completion" { TaskTestImpl.behavior = { _, _ -> Status.SUCCESS } - val deferred = client.async(taskTestWithTags) { await(200) }.join() + val deferred = client.dispatch(taskTestWithTags::await, 200) + val foo = client.getTaskByTag(TaskTest::class.java, "foo") + val bar = client.getTaskByTag(TaskTest::class.java, "bar") - client.getTaskIds("foo").contains(deferred.id) shouldBe true - client.getTaskIds("bar").contains(deferred.id) shouldBe true + client.getIds(foo).contains(deferred.id) shouldBe true + client.getIds(bar).contains(deferred.id) shouldBe true deferred.await() // wait a bit to ensure tag propagation - delay(200) + delay(500) - client.getTaskIds("foo").contains(deferred.id) shouldBe false - client.getTaskIds("bar").contains(deferred.id) shouldBe false + client.getIds(foo).contains(deferred.id) shouldBe false + client.getIds(bar).contains(deferred.id) shouldBe false } "Tag should be added then deleted after cancellation" { TaskTestImpl.behavior = { _, _ -> Status.FAILED_WITH_RETRY } - val deferred = client.async(taskTestWithTags) { log() }.join() + val deferred = client.dispatch(taskTestWithTags::log) + val foo = client.getTaskByTag(TaskTest::class.java, "foo") + val bar = client.getTaskByTag(TaskTest::class.java, "bar") - client.getTaskIds("foo").contains(deferred.id) shouldBe true - client.getTaskIds("bar").contains(deferred.id) shouldBe true + client.getIds(foo).contains(deferred.id) shouldBe true + client.getIds(bar).contains(deferred.id) shouldBe true - after { client.cancel(taskTestWithTags) } + later { deferred.cancel() } - shouldThrow { deferred.await() } + shouldThrow { deferred.await() } // wait a bit to ensure tag propagation delay(200) - client.getTaskIds("foo").contains(deferred.id) shouldBe false - client.getTaskIds("bar").contains(deferred.id) shouldBe false + client.getIds(foo).contains(deferred.id) shouldBe false + client.getIds(bar).contains(deferred.id) shouldBe false } - "get tags from context" { - val taskWithTags = client.newTask(tags = setOf("foo", "bar")) + "Get tags from context" { + val taskWithTags = client.newTask(TaskA::class.java, tags = setOf("foo", "bar")) taskWithTags.tags() shouldBe setOf("foo", "bar") } - "get meta from context" { - val taskWithMeta = client.newTask(meta = mapOf("foo" to "bar".toByteArray())) + "Get meta from context" { + val taskWithMeta = client.newTask(TaskA::class.java, meta = mapOf("foo" to "bar".toByteArray())) taskWithMeta.meta() shouldBe TaskMeta(mapOf("foo" to "bar".toByteArray())) } diff --git a/infinitic-tests/src/test/kotlin/io/infinitic/tests/WorkflowTests.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/WorkflowTests.kt index 4870d15ba..fec077520 100644 --- a/infinitic-tests/src/test/kotlin/io/infinitic/tests/WorkflowTests.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/WorkflowTests.kt @@ -25,88 +25,78 @@ package io.infinitic.tests -import com.github.valfirst.slf4jtest.TestLoggerFactory -import io.infinitic.client.getWorkflow -import io.infinitic.client.getWorkflowIds -import io.infinitic.client.newWorkflow -import io.infinitic.client.retryTask -import io.infinitic.common.fixtures.after +// import com.github.valfirst.slf4jtest.TestLoggerFactory +import io.infinitic.common.fixtures.later import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.workflows.data.workflows.WorkflowMeta -import io.infinitic.exceptions.clients.CanceledDeferredException -import io.infinitic.exceptions.clients.FailedDeferredException +import io.infinitic.exceptions.CanceledWorkflowException +import io.infinitic.exceptions.FailedTaskException +import io.infinitic.exceptions.FailedWorkflowException +import io.infinitic.exceptions.FailedWorkflowTaskException +import io.infinitic.exceptions.UnknownWorkflowException +import io.infinitic.exceptions.workflows.InvalidInlineException import io.infinitic.factory.InfiniticClientFactory import io.infinitic.factory.InfiniticWorkerFactory -import io.infinitic.pulsar.PulsarInfiniticClient import io.infinitic.tests.tasks.TaskA import io.infinitic.tests.workflows.Obj1 import io.infinitic.tests.workflows.Obj2 import io.infinitic.tests.workflows.WorkflowA import io.infinitic.tests.workflows.WorkflowAnnotated import io.infinitic.tests.workflows.WorkflowB -import io.infinitic.workflows.engine.WorkflowEngine +import io.infinitic.tests.workflows.WorkflowC import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.config.configuration import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.collections.shouldBeIn import io.kotest.matchers.shouldBe import kotlinx.coroutines.delay -import uk.org.lidalia.slf4jext.Level import java.time.Instant -import kotlin.concurrent.thread -import io.infinitic.exceptions.workflows.CanceledDeferredException as CanceledInWorkflowException -import io.infinitic.exceptions.workflows.FailedDeferredException as FailedInWorkflowException internal class WorkflowTests : StringSpec({ // each test should not be longer than 10s configuration.timeout = 10000 - lateinit var workflowA: WorkflowA - lateinit var workflowATagged: WorkflowA - lateinit var workflowAMeta: WorkflowA - lateinit var workflowB: WorkflowB - lateinit var workflowAnnotated: WorkflowAnnotated - val client = autoClose(InfiniticClientFactory.fromConfigResource("/pulsar.yml")) val worker = autoClose(InfiniticWorkerFactory.fromConfigResource("/pulsar.yml")) - val logger = TestLoggerFactory.getTestLogger(WorkflowTests::class.java) + val workflowA = client.newWorkflow(WorkflowA::class.java) + val workflowATagged = client.newWorkflow(WorkflowA::class.java, tags = setOf("foo", "bar")) + val workflowAMeta = client.newWorkflow(WorkflowA::class.java, meta = mapOf("foo" to "bar".toByteArray())) + val workflowB = client.newWorkflow(WorkflowB::class.java) + val workflowAnnotated = client.newWorkflow(WorkflowAnnotated::class.java) + val workflowC = client.newWorkflow(WorkflowC::class.java) + +// val logger = TestLoggerFactory.getTestLogger(WorkflowTests::class.java) beforeSpec { - // print info level log - despite using inMemory TestLoggerFactory implementation - TestLoggerFactory.getInstance().printLevel = Level.WARN + // print info level log - despite using inMemory TestLoggerFactory implementation +// TestLoggerFactory.getInstance().printLevel = Level.DEBUG - thread { worker.start() } + worker.startAsync() } beforeTest { // note that log events of previous test can continue to fill at this stage, due to asynchronous calls - TestLoggerFactory.clearAll() - logger.clear() +// TestLoggerFactory.clearAll() +// logger.clear() worker.storageFlush() - - workflowA = client.newWorkflow() - workflowATagged = client.newWorkflow(setOf("foo", "bar")) - workflowAnnotated = client.newWorkflow() - workflowAMeta = client.newWorkflow(meta = mapOf("foo" to "bar".toByteArray())) - workflowB = client.newWorkflow() } suspend fun expectDiscardingForHavingNullState(expected: Boolean = false) { // When processed by Pulsar, logs are always flowing - this test can not be written that way - if (! PulsarInfiniticClient::class.isInstance(client)) { - // make sure all events are recorded - delay(1000) - // check that workflow state is not deleted too early - TestLoggerFactory.getAllLoggingEvents() - .filter { it.level == Level.INFO } - .map { "${it.timestamp} ${it.message}" } - .joinToString("\n") - // .also { println(it) } - .contains(WorkflowEngine.NO_STATE_DISCARDING_REASON) shouldBe expected - } +// if (! PulsarInfiniticClient::class.isInstance(client)) { +// // make sure all events are recorded +// delay(1000) +// // check that workflow state is not deleted too early +// TestLoggerFactory.getAllLoggingEvents() +// .filter { it.level == Level.INFO } +// .map { "${it.timestamp} ${it.message}" } +// .joinToString("\n") +// // .also { println(it) } +// .contains(WorkflowEngine.NO_STATE_DISCARDING_REASON) shouldBe expected +// } } "empty Workflow" { @@ -122,9 +112,7 @@ internal class WorkflowTests : StringSpec({ } "get id from context" { - val deferred = client.async(workflowA) { context1() }.join() - - deferred.await() shouldBe deferred.id + workflowA.context1() shouldBe client.lastDeferred!!.id } "get tags from context" { @@ -136,9 +124,7 @@ internal class WorkflowTests : StringSpec({ } "get workflow id from task context" { - val deferred = client.async(workflowA) { context4() }.join() - - deferred.await() shouldBe deferred.id + workflowA.context4() shouldBe client.lastDeferred!!.id } "get workflow name from task context" { @@ -158,9 +144,13 @@ internal class WorkflowTests : StringSpec({ } "Wait for a dispatched Workflow" { - val deferred = client.async(workflowA) { seq1() }.join() + val deferred = client.dispatch(workflowA::await, 200L) - deferred.await() shouldBe "123" + deferred.await() shouldBe 200L + } + + "Simple sequential Workflow" { + workflowA.seq1() shouldBe "123" } "Sequential Workflow with an async task" { @@ -223,11 +213,17 @@ internal class WorkflowTests : StringSpec({ } "Inline task with asynchronous task inside" { - workflowA.inline2(21) shouldBe "2 * 21 = 42" + val error = shouldThrow { workflowA.inline2(21) } + + val deferredException = error.deferredException as FailedWorkflowTaskException + deferredException.workerException.name shouldBe InvalidInlineException::class.java.name } "Inline task with synchronous task inside" { - shouldThrow { workflowA.inline3(14) } + val error = shouldThrow { workflowA.inline3(14) } + + val deferredException = error.deferredException as FailedWorkflowTaskException + deferredException.workerException.name shouldBe InvalidInlineException::class.java.name } "Sequential Child Workflow" { @@ -273,70 +269,86 @@ internal class WorkflowTests : StringSpec({ } "Check prop7" { - workflowA.prop7() shouldBe "acbd" + workflowA.prop7() shouldBe "abab" + + expectDiscardingForHavingNullState() + } + + "Check prop8" { + workflowA.prop8() shouldBe "acbd" } "Check multiple sync" { val result1 = workflowA.seq1() - val result2 = client.newWorkflow().prop1() + val result2 = workflowA.prop1() result1 shouldBe "123" result2 shouldBe "ac" } "Waiting for event, sent after dispatched" { - val deferred = client.async(workflowA) { channel1() }.join() + val deferred = client.dispatch(workflowA::channel1) - after { workflowA.channelA.send("test") } + later { client.getWorkflowById(WorkflowA::class.java, deferred.id).channelA.send("test") } deferred.await() shouldBe "test" } "Waiting for event, sent by id" { - val deferred = client.async(workflowA) { channel1() }.join() + val deferred = client.dispatch(workflowA::channel1) - after { client.getWorkflow(deferred.id).channelA.send("test") } + later { client.getWorkflowById(WorkflowA::class.java, deferred.id).channelA.send("test") } deferred.await() shouldBe "test" } "Waiting for event, sent by tag" { - val deferred = client.async(workflowATagged) { channel1() }.join() + val deferred = client.dispatch(workflowATagged::channel1) - after { client.getWorkflow("foo").channelA.send("test") } + later { + client.getWorkflowByTag(WorkflowA::class.java, "foo").channelA.send("test") + } deferred.await() shouldBe "test" } "Waiting for event, sent to the right channel" { - val deferred = client.async(workflowA) { channel2() }.join() + val deferred = client.dispatch(workflowA::channel2) - after { client.getWorkflow(deferred.id).channelA.send("test") } + later { + client.getWorkflowById(WorkflowA::class.java, deferred.id).channelA.send("test") + } deferred.await() shouldBe "test" } "Waiting for event but sent to the wrong channel" { - val deferred = client.async(workflowA) { channel2() }.join() + val deferred = client.dispatch(workflowA::channel2) - after { client.getWorkflow(deferred.id).channelB.send("test") } + later { + client.getWorkflowById(WorkflowA::class.java, deferred.id).channelB.send("test") + } deferred.await()::class.java.name shouldBe Instant::class.java.name } "Sending event before waiting for it prevents catching" { - val deferred = client.async(workflowA) { channel3() }.join() + val deferred = client.dispatch(workflowA::channel3) - after { client.getWorkflow(deferred.id).channelA.send("test") } + later { + client.getWorkflowById(WorkflowA::class.java, deferred.id).channelA.send("test") + } deferred.await()::class.java.name shouldBe Instant::class.java.name } "Waiting for Obj event" { val obj1 = Obj1("foo", 42) - val deferred = client.async(workflowA) { channel4() }.join() + val deferred = client.dispatch(workflowA::channel4) - after { workflowA.channelObj.send(obj1) } + later { + client.getWorkflowById(WorkflowA::class.java, deferred.id).channelObj.send(obj1) + } deferred.await() shouldBe obj1 } @@ -344,11 +356,12 @@ internal class WorkflowTests : StringSpec({ "Waiting for filtered event using jsonPath only" { val obj1a = Obj1("oof", 12) val obj1b = Obj1("foo", 12) - val deferred = client.async(workflowA) { channel4bis() }.join() + val deferred = client.dispatch(workflowA::channel4bis) - after { - workflowA.channelObj.send(obj1a).join() - workflowA.channelObj.send(obj1b) + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + w.channelObj.send(obj1a) + w.channelObj.send(obj1b) } deferred.await() shouldBe obj1b @@ -357,11 +370,12 @@ internal class WorkflowTests : StringSpec({ "Waiting for filtered event using using jsonPath and criteria" { val obj1a = Obj1("oof", 12) val obj1b = Obj1("foo", 12) - val deferred = client.async(workflowA) { channel4ter() }.join() + val deferred = client.dispatch(workflowA::channel4ter) - after { - workflowA.channelObj.send(obj1a).join() - workflowA.channelObj.send(obj1b) + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + w.channelObj.send(obj1a) + w.channelObj.send(obj1b) } deferred.await() shouldBe obj1b @@ -370,11 +384,12 @@ internal class WorkflowTests : StringSpec({ "Waiting for event of specific type" { val obj1 = Obj1("foo", 42) val obj2 = Obj2("foo", 42) - val deferred = client.async(workflowA) { channel5() }.join() + val deferred = client.dispatch(workflowA::channel5) - after { - workflowA.channelObj.send(obj2).join() - workflowA.channelObj.send(obj1) + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + w.channelObj.send(obj2) + w.channelObj.send(obj1) } deferred.await() shouldBe obj1 @@ -384,12 +399,13 @@ internal class WorkflowTests : StringSpec({ val obj1 = Obj1("foo", 42) val obj2 = Obj2("foo", 42) val obj3 = Obj1("oof", 42) - val deferred = client.async(workflowA) { channel5bis() }.join() + val deferred = client.dispatch(workflowA::channel5bis) - after { - workflowA.channelObj.send(obj3).join() - workflowA.channelObj.send(obj2).join() - workflowA.channelObj.send(obj1) + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + w.channelObj.send(obj3) + w.channelObj.send(obj2) + w.channelObj.send(obj1) } deferred.await() shouldBe obj1 @@ -399,12 +415,13 @@ internal class WorkflowTests : StringSpec({ val obj1 = Obj1("foo", 42) val obj2 = Obj2("foo", 42) val obj3 = Obj1("oof", 42) - val deferred = client.async(workflowA) { channel5ter() }.join() + val deferred = client.dispatch(workflowA::channel5ter) - after { - client.getWorkflow(deferred.id).channelObj.send(obj3).join() - client.getWorkflow(deferred.id).channelObj.send(obj2).join() - client.getWorkflow(deferred.id).channelObj.send(obj1) + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + w.channelObj.send(obj3) + w.channelObj.send(obj2) + w.channelObj.send(obj1) } deferred.await() shouldBe obj1 @@ -413,30 +430,26 @@ internal class WorkflowTests : StringSpec({ "Waiting for 2 events of specific types presented in wrong order" { val obj1 = Obj1("foo", 6) val obj2 = Obj2("bar", 7) - val deferred = client.async(workflowA) { channel6() }.join() + val deferred = client.dispatch(workflowA::channel6) - after { - client.getWorkflow(deferred.id).channelObj.send(obj2).join() - workflowA.channelObj.send(obj1) + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + w.channelObj.send(obj2) + w.channelObj.send(obj1) } deferred.await() shouldBe "foobar42" } - "Cancelling async workflow" { - val deferred = client.async(workflowA) { channel1() }.join() - - after { client.cancel(workflowA) } - - shouldThrow { deferred.await() } - } - "Cancelling workflow" { - val deferred = client.async(workflowA) { channel1() }.join() + val deferred = client.dispatch(workflowA::channel1) - after { client.cancel(workflowA) } + later { + val w = client.getWorkflowById(WorkflowA::class.java, deferred.id) + client.cancel(w) + } - shouldThrow { deferred.await() } + shouldThrow { deferred.await() } } "try/catch a failing task" { @@ -444,10 +457,11 @@ internal class WorkflowTests : StringSpec({ } "failing task on main path should throw" { - val e = shouldThrow { workflowA.failing2() } + val error = shouldThrow { workflowA.failing2() } - e.causeError?.errorName shouldBe FailedInWorkflowException::class.java.name - e.causeError?.whereName shouldBe TaskA::class.java.name + val taskException = error.deferredException as FailedTaskException + taskException.taskName shouldBe TaskA::class.java.name + taskException.workerException.name shouldBe Exception::class.java.name } "failing async task on main path should not throw" { @@ -466,86 +480,119 @@ internal class WorkflowTests : StringSpec({ expectDiscardingForHavingNullState() } - "Cancelling task on main path should throw " { - val e = shouldThrow { workflowA.failing4() } +// "Cancelling task on main path should throw " { +// val e = shouldThrow { workflowA.failing4() } +// +// e.causeError?.errorName shouldBe CanceledDeferredException::class.java.name +// e.causeError?.whereName shouldBe TaskA::class.java.name +// } - e.causeError?.errorName shouldBe CanceledInWorkflowException::class.java.name - e.causeError?.whereName shouldBe TaskA::class.java.name - } - - "Cancelling task not on main path should not throw " { - workflowA.failing5() shouldBe 100 - } +// "Cancelling task not on main path should not throw " { +// workflowA.failing5() shouldBe 100 +// } "Cancelling child workflow on main path should throw" { - val e = shouldThrow { workflowB.cancelChild1() } + val error = shouldThrow { workflowB.cancelChild1() } - e.causeError?.errorName shouldBe CanceledInWorkflowException::class.java.name - e.causeError?.whereName shouldBe WorkflowA::class.java.name + val cause = error.deferredException as CanceledWorkflowException + cause.workflowName shouldBe WorkflowA::class.java.name } "Cancelling child workflow not on main path should not throw" { - workflowB.cancelChild2() shouldBe 100 + workflowB.cancelChild2() shouldBe 200L } "Failure in child workflow on main path should throw exception" { - val e = shouldThrow { workflowA.failing6() } + val error = shouldThrow { workflowA.failing6() } - e.causeError?.errorName shouldBe FailedInWorkflowException::class.java.name - e.causeError?.whereName shouldBe WorkflowA::class.java.name + val cause1 = error.deferredException as FailedWorkflowException + cause1.workflowName shouldBe WorkflowA::class.java.name - e.causeError?.errorCause?.errorName shouldBe FailedInWorkflowException::class.java.name - e.causeError?.errorCause?.whereName shouldBe TaskA::class.java.name + val cause2 = cause1.deferredException as FailedTaskException + cause2.taskName shouldBe TaskA::class.java.name } "Failure in child workflow not on main path should not throw" { workflowA.failing7() shouldBe 100 } - "retry a failed task from client should restart a workflow" { - val e = shouldThrow { workflowA.failing8() } + "Failure in child workflow on main path should throw" { + val error = shouldThrow { workflowA.failing7bis() } - e.causeError?.whereName shouldBe TaskA::class.java.name + val cause1 = error.deferredException as FailedWorkflowException + cause1.workflowName shouldBe WorkflowA::class.java.name + cause1.methodName shouldBe "failing2" - after { client.retryTask(e.causeError?.whereId!!) } + val cause2 = cause1.deferredException as FailedTaskException + cause2.taskName shouldBe TaskA::class.java.name + } - client.await(workflowA) shouldBe "ok" + "Failure in child workflow on main path can be caught" { + workflowA.failing7ter() shouldBe Exception::class.java.name } - "retry a caught failed task should not throw and influence workflow" { - workflowA.failing9() shouldBe true +// "Retry a failed task from client should restart a workflow" { +// val e = shouldThrow { workflowA.failing8() } +// +// val deferred = client.lastDeferred!! +// +// e.causeError?.whereName shouldBe TaskA::class.java.name +// +// later { +// val t = client.getTaskById(TaskA::class.java, e.causeError?.whereId!!) +// client.retry(t) +// } +// +// deferred.await() shouldBe "ok" +// } + +// "retry a caught failed task should not throw and influence workflow" { +// workflowA.failing9() shouldBe true +// } +// +// "properties should be correctly set after a deferred cancellation" { +// workflowA.failing10() shouldBe "ok" +// } + + "Synchronous call of unknown workflow should throw" { + val error = shouldThrow { workflowA.failing11() } + + val cause = error.deferredException as UnknownWorkflowException + cause.workflowName shouldBe WorkflowA::class.java.name + cause.workflowId shouldBe "unknown" } - "properties should be correctly set after a deferred cancellation" { - workflowA.failing10() shouldBe "ok" + "Synchronous call of unknown workflow can be caught" { + workflowA.failing12() shouldBe "caught" } "child workflow is canceled when parent workflow is canceled - tag are also added and deleted" { - - client.async(workflowATagged) { cancel1() }.join() + client.dispatch(workflowATagged::cancel1) delay(1000) - val size = client.getWorkflowIds("foo").size + val w = client.getWorkflowByTag(WorkflowA::class.java, "foo") + val size = client.getIds(w).size - client.cancel(workflowATagged).join() + client.cancel(w) delay(1000) - client.getWorkflowIds("foo").size shouldBe size - 2 + client.getIds(w).size shouldBe size - 2 } "Tag should be added then deleted after completion" { - val deferred = client.async(workflowATagged) { channel1() }.join() + val deferred = client.dispatch(workflowATagged::channel1) // delay is necessary to be sure that tag engine has processed delay(500) - client.getWorkflowIds("foo").contains(deferred.id) shouldBe true + val w = client.getWorkflowByTag(WorkflowA::class.java, "foo") + client.getIds(w).contains(deferred.id) shouldBe true // complete workflow - client.getWorkflow("foo").channelA.send("").join() + w.channelA.send("") // delay is necessary to be sure that tag engine has processed delay(500) - client.getWorkflowIds("foo").contains(deferred.id) shouldBe false + client.getIds(w).contains(deferred.id) shouldBe false } "Annotated Workflow" { @@ -553,4 +600,42 @@ internal class WorkflowTests : StringSpec({ result shouldBe "abc" } + + "Check runBranch" { + val deferred = client.dispatch(workflowC::receive, "a") + + val w = client.getWorkflowById(WorkflowC::class.java, deferred.id) + + w.concat("b") shouldBe "ab" + + later { w.channelA.send("c") } + + deferred.await() shouldBe "abc" + } + + "Check multiple runBranch" { + val deferred1 = client.dispatch(workflowC::receive, "a") + val w = client.getWorkflowById(WorkflowC::class.java, deferred1.id) + + client.dispatch(w::add, "b") + client.dispatch(w::add, "c") + client.dispatch(w::add, "d") + + later { w.channelA.send("e") } + + deferred1.await() shouldBe "abcde" + } + + "Check numerous runBranch" { + val deferred1 = client.dispatch(workflowC::receive, "a") + val w = client.getWorkflowById(WorkflowC::class.java, deferred1.id) + + repeat(100) { + client.dispatch(w::add, "b") + } + + later { w.channelA.send("c") } + + deferred1.await() shouldBe "a" + "b".repeat(100) + "c" + } }) diff --git a/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskA.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskA.kt index 6e45cbc44..5bbd07d5c 100644 --- a/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskA.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskA.kt @@ -25,14 +25,10 @@ package io.infinitic.tests.tasks -import io.infinitic.client.cancelTask -import io.infinitic.client.cancelWorkflow -import io.infinitic.client.retryTask import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.tasks.Task import io.infinitic.tests.workflows.WorkflowA import java.time.Duration -import java.util.UUID interface ParentInterface { fun parent(): String @@ -42,20 +38,20 @@ interface TaskA : ParentInterface { fun concat(str1: String, str2: String): String fun reverse(str: String): String fun await(delay: Long): Long - fun workflowId(): UUID? + fun workflowId(): String? fun workflowName(): String? - fun cancelWorkflowA(id: UUID) - fun cancelTaskA(id: UUID) + fun cancelWorkflowA(id: String) +// fun cancelTaskA(id: String) fun failing() fun successAtRetry(): String - fun retryTaskA(id: UUID) +// fun retryTaskA(id: String) fun tags(): Set fun meta(): TaskMeta } class TaskAImpl : Task(), TaskA { - override fun concat(str1: String, str2: String) = str1 + str2 + override fun concat(str1: String, str2: String): String = str1 + str2 override fun reverse(str: String) = str.reversed() @@ -65,15 +61,17 @@ class TaskAImpl : Task(), TaskA { override fun workflowName() = context.workflowName - override fun cancelWorkflowA(id: UUID) { + override fun cancelWorkflowA(id: String) { Thread.sleep(50) - context.client.cancelWorkflow(id) + val t = context.client.getWorkflowById(WorkflowA::class.java, id) + context.client.cancel(t) } - override fun cancelTaskA(id: UUID) { - Thread.sleep(50) - context.client.cancelTask(id) - } +// override fun cancelTaskA(id: String) { +// Thread.sleep(50) +// val t = context.client.getTaskById(TaskA::class.java, id) +// context.client.cancel(t) +// } override fun failing() = throw Exception("sorry") @@ -82,10 +80,11 @@ class TaskAImpl : Task(), TaskA { else -> "ok" } - override fun retryTaskA(id: UUID) { - Thread.sleep(50) - context.client.retryTask(id) - } +// override fun retryTaskA(id: String) { +// Thread.sleep(50) +// val t = context.client.getTaskById(TaskA::class.java, id) +// context.client.retry(t) +// } override fun parent() = "ok" diff --git a/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskTest.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskTest.kt index f2de48561..49d2ed09d 100644 --- a/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskTest.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/tasks/TaskTest.kt @@ -33,7 +33,7 @@ interface TaskTest { fun await(delay: Long): Long } -class TaskException(val log: String) : Exception() +class ExpectedException(val log: String) : Exception() class TaskTestImpl : Task(), TaskTest { companion object { @@ -56,7 +56,7 @@ class TaskTestImpl : Task(), TaskTest { when (status) { Status.TIMEOUT_WITH_RETRY, Status.TIMEOUT_WITHOUT_RETRY -> Thread.sleep(1000) - Status.FAILED_WITH_RETRY, Status.FAILED_WITHOUT_RETRY -> throw TaskException(log) + Status.FAILED_WITH_RETRY, Status.FAILED_WITHOUT_RETRY -> throw ExpectedException(log) else -> Unit } diff --git a/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowA.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowA.kt index b9fd7f2a3..4b6ccb852 100644 --- a/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowA.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowA.kt @@ -26,10 +26,12 @@ package io.infinitic.tests.workflows import com.jayway.jsonpath.Criteria.where +import io.infinitic.annotations.Ignore import io.infinitic.common.tasks.data.TaskMeta import io.infinitic.common.workflows.data.workflows.WorkflowMeta -import io.infinitic.exceptions.workflows.CanceledDeferredException -import io.infinitic.exceptions.workflows.FailedDeferredException +import io.infinitic.exceptions.FailedTaskException +import io.infinitic.exceptions.FailedWorkflowException +import io.infinitic.exceptions.UnknownWorkflowException import io.infinitic.tests.tasks.ParentInterface import io.infinitic.tests.tasks.TaskA import io.infinitic.workflows.Deferred @@ -40,7 +42,6 @@ import io.infinitic.workflows.and import io.infinitic.workflows.or import kotlinx.serialization.Serializable import java.time.Duration -import java.util.UUID sealed class Obj @Serializable @@ -56,20 +57,23 @@ interface WorkflowA : ParentInterface { fun empty(): String fun await(duration: Long): Long fun wparent(): String - fun context1(): UUID + fun context1(): String fun context2(): Set fun context3(): WorkflowMeta - fun context4(): UUID? + fun context4(): String? fun context5(): String? fun context6(): Set fun context7(): TaskMeta fun seq1(): String fun seq2(): String fun seq3(): String + fun seq3bis(): String fun seq4(): String + fun seq4bis(): String fun seq5(): Long fun seq6(): Long fun deferred1(): String + fun deferred1bis(): String fun or1(): String fun or2(): Any fun or3(): String @@ -83,12 +87,22 @@ interface WorkflowA : ParentInterface { fun child1(): String fun child2(): String fun prop1(): String + fun prop1bis() fun prop2(): String + fun prop2bis() fun prop3(): String + fun prop3bis() fun prop4(): String + fun prop4bis() fun prop5(): String + fun prop5bis() + fun prop5ter() fun prop6(): String + fun prop6bis(deferred: Deferred): String fun prop7(): String + fun prop7bis(): String + fun prop8(): String + fun prop8bis() fun channel1(): String fun channel2(): Any fun channel3(): Any @@ -105,23 +119,39 @@ interface WorkflowA : ParentInterface { fun failing2() fun failing2a(): Long fun failing3(): Long + fun failing3bis() + fun failing3bException() fun failing3b(): Long - fun failing4(): Long - fun failing5(): Long +// fun failing4(): Long +// fun failing5(): Long + fun failing5bis(deferred: Deferred): Long fun failing6() fun failing7(): Long + fun failing7bis() + fun failing7ter(): String fun failing8(): String - fun failing9(): Boolean - fun failing10(): String +// fun failing9(): Boolean +// fun failing10(): String + fun failing10bis() + fun failing11() + fun failing12(): String fun cancel1() } +@Suppress("unused") class WorkflowAImpl : Workflow(), WorkflowA { + + @Ignore private val self by lazy { getWorkflowById(WorkflowA::class.java, context.id) } + + lateinit var deferred: Deferred + override val channelObj = channel() override val channelA = channel() override val channelB = channel() - private val taskA = newTask(tags = setOf("foo", "bar"), meta = mapOf("foo" to "bar".toByteArray())) + private val taskA = newTask(TaskA::class.java, tags = setOf("foo", "bar"), meta = mapOf("foo" to "bar".toByteArray())) + private val workflowA = newWorkflow(WorkflowA::class.java) + private var p1 = "" override fun empty() = "void" @@ -130,12 +160,9 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun parent() = taskA.parent() - override fun wparent(): String { - val workflowA = newWorkflow() - return workflowA.parent() - } + override fun wparent(): String = workflowA.parent() - override fun context1(): UUID = context.id + override fun context1(): String = context.id override fun context2(): Set = context.tags @@ -151,10 +178,8 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun seq1(): String { var str = "" - str = taskA.concat(str, "1") str = taskA.concat(str, "2") - str = taskA.concat(str, "3") return str // should be "123" @@ -162,8 +187,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun seq2(): String { var str = "" - - val d = async(taskA) { reverse("ab") } + val d = dispatch(taskA::reverse, "ab") str = taskA.concat(str, "2") str = taskA.concat(str, "3") @@ -172,32 +196,33 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun seq3(): String { var str = "" - - val d = async { taskA.reverse("ab") } + val d = dispatch(self::seq3bis) str = taskA.concat(str, "2") str = taskA.concat(str, "3") return str + d.await() // should be "23ba" } + override fun seq3bis(): String { return taskA.reverse("ab") } + override fun seq4(): String { var str = "" - - val d = async { - val s = taskA.reverse("ab") - taskA.concat(s, "c") - } + val d = dispatch(self::seq4bis) str = taskA.concat(str, "2") str = taskA.concat(str, "3") return str + d.await() // should be "23bac" } + override fun seq4bis(): String { + val s = taskA.reverse("ab") + return taskA.concat(s, "c") + } + override fun seq5(): Long { var l = 0L - val d1 = async(taskA) { await(500) } - val d2 = async(taskA) { await(100) } - + val d1 = dispatch(taskA::await, 500) + val d2 = dispatch(taskA::await, 100) l += d1.await() l += d2.await() @@ -206,9 +231,8 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun seq6(): Long { var l = 0L - val d1 = async(taskA) { await(500) } - val d2 = async(taskA) { await(100) } - + val d1 = dispatch(taskA::await, 500) + val d2 = dispatch(taskA::await, 100) l += d1.await() l += d2.await() @@ -220,10 +244,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun deferred1(): String { var str = "" - - val d = async { - taskA.reverse("X") - } + val d = dispatch(self::deferred1bis) str += d.isOngoing().toString() str += d.isCompleted().toString() d.await() @@ -233,6 +254,8 @@ class WorkflowAImpl : Workflow(), WorkflowA { return str // should be "truefalsefalsetrue" } + override fun deferred1bis(): String { return taskA.reverse("X") } + // override fun deferred1(): String { // var str = "" // @@ -252,26 +275,26 @@ class WorkflowAImpl : Workflow(), WorkflowA { // } override fun or1(): String { - val d1 = async(taskA) { reverse("ab") } - val d2 = async(taskA) { reverse("cd") } - val d3 = async(taskA) { reverse("ef") } + val d1 = dispatch(taskA::reverse, "ab") + val d2 = dispatch(taskA::reverse, "cd") + val d3 = dispatch(taskA::reverse, "ef") return (d1 or d2 or d3).await() // should be "ba" or "dc" or "fe" } override fun or2(): Any { - val d1 = async(taskA) { reverse("ab") } - val d2 = async(taskA) { reverse("cd") } - val d3 = async(taskA) { reverse("ef") } + val d1 = dispatch(taskA::reverse, "ab") + val d2 = dispatch(taskA::reverse, "cd") + val d3 = dispatch(taskA::reverse, "ef") return ((d1 and d2) or d3).await() // should be listOf("ba","dc") or "fe" } override fun or3(): String { val list: MutableList> = mutableListOf() - list.add(async(taskA) { reverse("ab") }) - list.add(async(taskA) { reverse("cd") }) - list.add(async(taskA) { reverse("ef") }) + list.add(dispatch(taskA::reverse, "ab")) + list.add(dispatch(taskA::reverse, "cd")) + list.add(dispatch(taskA::reverse, "ef")) return list.or().await() // should be "ba" or "dc" or "fe" } @@ -279,9 +302,9 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun or4(): String { var s3 = taskA.concat("1", "2") - val d1 = async(taskA) { reverse("ab") } + val d1 = dispatch(taskA::reverse, "ab") val d2 = timer(Duration.ofMillis(2000)) - val d = (d1 or d2) + val d: Deferred = (d1 or d2) if (d.status() != DeferredStatus.COMPLETED) { s3 = taskA.reverse("ab") } @@ -290,9 +313,9 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun and1(): List { - val d1 = async(taskA) { reverse("ab") } - val d2 = async(taskA) { reverse("cd") } - val d3 = async(taskA) { reverse("ef") } + val d1 = dispatch(taskA::reverse, "ab") + val d2 = dispatch(taskA::reverse, "cd") + val d3 = dispatch(taskA::reverse, "ef") return (d1 and d2 and d3).await() // should be listOf("ba","dc","fe") } @@ -300,9 +323,9 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun and2(): List { val list: MutableList> = mutableListOf() - list.add(async(taskA) { reverse("ab") }) - list.add(async(taskA) { reverse("cd") }) - list.add(async(taskA) { reverse("ef") }) + list.add(dispatch(taskA::reverse, "ab")) + list.add(dispatch(taskA::reverse, "cd")) + list.add(dispatch(taskA::reverse, "ef")) return list.and().await() // should be listOf("ba","dc","fe") } @@ -311,7 +334,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { val list: MutableList> = mutableListOf() for (i in 1..1_00) { - list.add(async(taskA) { reverse("ab") }) + list.add(dispatch(taskA::reverse, "ab")) } return list.and().await() // should be listOf("ba","dc","fe") } @@ -323,7 +346,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun inline2(i: Int): String { val result = inline { - async(taskA) { reverse("ab") } + dispatch(taskA::reverse, "ab") 2 * i } @@ -339,7 +362,7 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun child1(): String { - val workflowB = newWorkflow() + val workflowB = newWorkflow(WorkflowB::class.java) var str: String = workflowB.concat("-") str = taskA.concat(str, "-") @@ -347,28 +370,26 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun child2(): String { - val workflowB = newWorkflow() + val workflowB = newWorkflow(WorkflowB::class.java) val str = taskA.reverse("12") - val d = async(workflowB) { concat(str) } + val d = dispatch(workflowB::concat, str) return taskA.concat(d.await(), str) // should be "21abc21" } override fun prop1(): String { p1 = "a" - - async { p1 += "b" } - + dispatch(self::prop1bis) p1 += "c" return p1 // should be "ac" } + override fun prop1bis() { p1 += "b" } + override fun prop2(): String { p1 = "a" - - async { p1 += "b" } - + dispatch(self::prop2bis) p1 += "c" taskA.await(100) p1 += "d" @@ -376,13 +397,11 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acbd" } + override fun prop2bis() { p1 += "b" } + override fun prop3(): String { p1 = "a" - - async { - taskA.await(50) - p1 += "b" - } + dispatch(self::prop3bis) p1 += "c" taskA.await(200) p1 += "d" @@ -390,13 +409,11 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acbd" } + override fun prop3bis() { taskA.await(50); p1 += "b" } + override fun prop4(): String { p1 = "a" - - async { - taskA.await(200) - p1 += "b" - } + dispatch(self::prop4bis) p1 += "c" taskA.await(100) p1 += "d" @@ -404,47 +421,54 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acd" } + override fun prop4bis() { taskA.await(200); p1 += "b" } + override fun prop5(): String { p1 = "a" - - async { - p1 += "b" - } - - async { - p1 += "c" - } + dispatch(self::prop5bis) + dispatch(self::prop5ter) p1 += "d" - taskA.await(100) return p1 // should be "adbc" } - override fun prop6(): String { - val d1 = async(taskA) { reverse("12") } + override fun prop5bis() { p1 += "b" } + override fun prop5ter() { p1 += "c" } - val d2 = async { - d1.await() - p1 += "b" - p1 - } + override fun prop6(): String { + val d1 = dispatch(taskA::reverse, "12") + val d2 = dispatch(self::prop6bis, d1) d1.await() p1 += "a" p1 = d2.await() + p1 // unfortunately p1 = p1 + d2.await() would fail the test // because d2.await() updates p1 value too lately in the expression - // not sure, how to avoid that + // not sure, how to fix that or if it should be fixed return p1 // should be "abab" } + override fun prop6bis(deferred: Deferred): String { deferred.await(); p1 += "b"; return p1 } + override fun prop7(): String { - p1 = taskA.reverse("a") + deferred = dispatch(taskA::reverse, "12") + val d2 = dispatch(self::prop7bis) + deferred.await() + p1 += "a" + p1 = d2.await() + p1 + // unfortunately p1 = p1 + d2.await() would fail the test + // because d2.await() updates p1 value too lately in the expression + // not sure, how to fix that or if it should be fixed - async { - p1 += taskA.reverse("b") - } + return p1 // should be "abab" + } + + override fun prop7bis(): String { deferred.await(); p1 += "b"; return p1 } + + override fun prop8(): String { + p1 = taskA.reverse("a") + dispatch(self::prop8bis) p1 += "c" taskA.await(200) p1 += "d" @@ -452,10 +476,12 @@ class WorkflowAImpl : Workflow(), WorkflowA { return p1 // should be "acbd" } + override fun prop8bis() { p1 += taskA.reverse("b") } + override fun channel1(): String { val deferred: Deferred = channelA.receive() - return deferred.await() + return deferred.await().also { println(it) } } override fun channel2(): Any { @@ -493,26 +519,26 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun channel5(): Obj1 { - val deferred: Deferred = channelObj.receive(Obj1::class) + val deferred: Deferred = channelObj.receive(Obj1::class.java) return deferred.await() } override fun channel5bis(): Obj1 { - val deferred: Deferred = channelObj.receive(Obj1::class, "[?(\$.foo == \"foo\")]") + val deferred: Deferred = channelObj.receive(Obj1::class.java, "[?(\$.foo == \"foo\")]") return deferred.await() } override fun channel5ter(): Obj1 { - val deferred: Deferred = channelObj.receive(Obj1::class, "[?]", where("foo").eq("foo")) + val deferred: Deferred = channelObj.receive(Obj1::class.java, "[?]", where("foo").eq("foo")) return deferred.await() } override fun channel6(): String { - val deferred1: Deferred = channelObj.receive(Obj1::class) - val deferred2: Deferred = channelObj.receive(Obj2::class) + val deferred1: Deferred = channelObj.receive(Obj1::class.java) + val deferred2: Deferred = channelObj.receive(Obj2::class.java) val obj1 = deferred1.await() val obj2 = deferred2.await() @@ -520,8 +546,8 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun channel6bis(): String { - val deferred1: Deferred = channelObj.receive(Obj1::class, "[?(\$.foo == \"foo\")]") - val deferred2: Deferred = channelObj.receive(Obj2::class, "[?(\$.foo == \"foo\")]") + val deferred1: Deferred = channelObj.receive(Obj1::class.java, "[?(\$.foo == \"foo\")]") + val deferred2: Deferred = channelObj.receive(Obj2::class.java, "[?(\$.foo == \"foo\")]") val obj1 = deferred1.await() val obj2 = deferred2.await() @@ -529,8 +555,8 @@ class WorkflowAImpl : Workflow(), WorkflowA { } override fun channel6ter(): String { - val deferred1: Deferred = channelObj.receive(Obj1::class, "[?]", where("foo").eq("foo")) - val deferred2: Deferred = channelObj.receive(Obj2::class, "[?]", where("foo").eq("foo")) + val deferred1: Deferred = channelObj.receive(Obj1::class.java, "[?]", where("foo").eq("foo")) + val deferred2: Deferred = channelObj.receive(Obj2::class.java, "[?]", where("foo").eq("foo")) val obj1 = deferred1.await() val obj2 = deferred2.await() @@ -540,103 +566,123 @@ class WorkflowAImpl : Workflow(), WorkflowA { override fun failing1() = try { taskA.failing() "ok" - } catch (e: FailedDeferredException) { + } catch (e: FailedTaskException) { taskA.reverse("ok") } override fun failing2() = taskA.failing() override fun failing2a(): Long { - async(taskA) { failing() } + dispatch(taskA::failing) return taskA.await(100) } override fun failing3(): Long { - async { taskA.failing() } + dispatch(self::failing3bis) return taskA.await(100) } + override fun failing3bis() { taskA.failing() } + override fun failing3b(): Long { - async { throw Exception() } + dispatch(self::failing3bException) return taskA.await(100) } - override fun failing4(): Long { - val deferred = async(taskA) { await(1000) } - - taskA.cancelTaskA(deferred.id!!) - - return deferred.await() - } + override fun failing3bException() { throw Exception() } - override fun failing5(): Long { - val deferred = async(taskA) { await(1000) } - - taskA.cancelTaskA(deferred.id!!) +// override fun failing4(): Long { +// val deferred = dispatch(taskA::await, 1000) +// +// taskA.cancelTaskA(deferred.id!!) +// +// return deferred.await() +// } - async { deferred.await() } +// override fun failing5(): Long { +// val deferred = dispatch(taskA::await, 1000) +// +// taskA.cancelTaskA(deferred.id!!) +// +// dispatch(self::failing5bis, deferred) +// +// return taskA.await(100) +// } - return taskA.await(100) - } + override fun failing5bis(deferred: Deferred): Long { return deferred.await() } - override fun failing6() { - val workflowABis = newWorkflow() - - return workflowABis.failing2() - } + override fun failing6() = workflowA.failing2() override fun failing7(): Long { - val workflowABis = newWorkflow() + dispatch(self::failing7bis) - async { - workflowABis.failing2() - } return taskA.await(100) } - override fun failing8() = taskA.successAtRetry() - - override fun failing9(): Boolean { - // this method will complete only after retry - val deferred = async(taskA) { successAtRetry() } - - val result = try { - deferred.await() - } catch (e: FailedDeferredException) { - "caught" - } + override fun failing7bis() { workflowA.failing2() } - taskA.retryTaskA(deferred.id!!) - - // we wait here only on avoid to make sure the previous retry is completed + override fun failing7ter(): String = try { + workflowA.failing2() + "ok" + } catch (e: FailedWorkflowException) { + val deferredException = e.deferredException as FailedTaskException taskA.await(100) - - return deferred.await() == "ok" && result == "caught" + deferredException.workerException.name } - override fun failing10(): String { - p1 = "o" + override fun failing8() = taskA.successAtRetry() - val deferred = async(taskA) { await(1000) } +// override fun failing9(): Boolean { +// // this method will complete only after retry +// val deferred = dispatch(taskA::successAtRetry,) +// +// val result = try { +// deferred.await() +// } catch (e: FailedDeferredException) { +// "caught" +// } +// +// taskA.retryTaskA(deferred.id!!) +// +// // we wait here only on avoid to make sure the previous retry is completed +// taskA.await(100) +// +// return deferred.await() == "ok" && result == "caught" +// } +// +// override fun failing10(): String { +// p1 = "o" +// val deferred = dispatch(taskA::await, 1000) +// dispatch(self::failing10bis) +// dispatch(taskA::cancelTaskA, deferred.id!!) +// try { +// deferred.await() +// } catch (e: CanceledDeferredException) { +// // continue +// } +// +// return p1 // should be "ok" +// } - async { p1 += "k" } + override fun failing10bis() { p1 += "k" } - async(taskA) { cancelTaskA(deferred.id!!) } + override fun failing11() { + getWorkflowById(WorkflowA::class.java, "unknown").empty() + } - try { - deferred.await() - } catch (e: CanceledDeferredException) { - // continue + override fun failing12(): String { + return try { + getWorkflowById(WorkflowA::class.java, "unknown").empty() + } catch (e: UnknownWorkflowException) { + taskA.reverse("caught".reversed()) } - - return p1 // should be "ok" } override fun cancel1() { - val taggedChild = newWorkflow(tags = setOf("foo", "bar")) + val taggedChild = newWorkflow(WorkflowA::class.java, tags = setOf("foo", "bar")) taggedChild.channel1() } diff --git a/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowAnnotated.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowAnnotated.kt index 3baa63ea9..144978cbb 100644 --- a/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowAnnotated.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowAnnotated.kt @@ -36,7 +36,7 @@ interface WorkflowAnnotated { } class WorkflowAnnotatedImpl : Workflow(), WorkflowAnnotated { - private val task = newTask() + private val task = newTask(TaskAnnotated::class.java) override fun foo(input: String): String { var str = input diff --git a/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowB.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowB.kt index 4aaeebb5f..314d50d12 100644 --- a/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowB.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowB.kt @@ -25,7 +25,9 @@ package io.infinitic.tests.workflows +import io.infinitic.annotations.Ignore import io.infinitic.tests.tasks.TaskA +import io.infinitic.workflows.Deferred import io.infinitic.workflows.Workflow interface WorkflowB { @@ -33,12 +35,15 @@ interface WorkflowB { fun factorial(n: Long): Long fun cancelChild1(): Long fun cancelChild2(): Long + fun cancelChild2bis(deferred: Deferred): String } class WorkflowBImpl : Workflow(), WorkflowB { - private val task = newTask() - private val workflowB = newWorkflow() - private val workflowA = newWorkflow() + private val task = newTask(TaskA::class.java) + private val workflowB = newWorkflow(WorkflowB::class.java) + private val workflowA = newWorkflow(WorkflowA::class.java) + @Ignore + private val self by lazy { getWorkflowById(WorkflowB::class.java, context.id) } override fun concat(input: String): String { var str = input @@ -51,7 +56,7 @@ class WorkflowBImpl : Workflow(), WorkflowB { } override fun cancelChild1(): Long { - val def = async(workflowA) { channel1() } + val def = dispatch(workflowA::channel1) task.cancelWorkflowA(def.id!!) @@ -61,15 +66,17 @@ class WorkflowBImpl : Workflow(), WorkflowB { } override fun cancelChild2(): Long { - val deferred = async(workflowA) { channel1() } + val deferred = dispatch(workflowA::channel1) task.cancelWorkflowA(deferred.id!!) - async { deferred.await() } + dispatch(self::cancelChild2bis, deferred) - return task.await(100) + return task.await(200) } + override fun cancelChild2bis(deferred: Deferred): String { return deferred.await() } + override fun factorial(n: Long) = when { n > 1 -> n * workflowB.factorial(n - 1) else -> 1 diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskCanceled.kt b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowC.kt similarity index 53% rename from infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskCanceled.kt rename to infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowC.kt index 6722a304e..6bdb3a0e2 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskCanceled.kt +++ b/infinitic-tests/src/test/kotlin/io/infinitic/tests/workflows/WorkflowC.kt @@ -23,30 +23,48 @@ * Licensor: infinitic.io */ -package io.infinitic.workflows.engine.handlers - -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandStatus.Canceled -import io.infinitic.common.workflows.engine.messages.TaskCanceled -import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated -import io.infinitic.workflows.engine.output.WorkflowEngineOutput -import kotlinx.coroutines.CoroutineScope - -internal fun CoroutineScope.taskCanceled( - workflowEngineOutput: WorkflowEngineOutput, - state: WorkflowState, - msg: TaskCanceled -) { - - when (msg.isWorkflowTask()) { - true -> TODO() - false -> commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.taskId), - Canceled(state.workflowTaskIndex) - ) +package io.infinitic.tests.workflows + +import io.infinitic.tests.tasks.TaskA +import io.infinitic.workflows.SendChannel +import io.infinitic.workflows.Workflow + +interface WorkflowC { + val log: String + val channelA: SendChannel + fun receive(str: String): String + fun concat(str: String): String + fun add(str: String): String +} + +class WorkflowCImpl : Workflow(), WorkflowC { + override val channelA = channel() + val taskA = newTask(TaskA::class.java) + + override var log = "" + + override fun receive(str: String): String { + log += str + + val r = channelA.receive().await() + + log += r + + return log + } + + override fun concat(str: String): String { + Thread.sleep(50) + + log = taskA.concat(log, str) + + return log + } + + override fun add(str: String): String { + + log += str + + return log } } diff --git a/infinitic-tests/src/test/resources/pulsar.yml b/infinitic-tests/src/test/resources/pulsar.yml index 16e942f48..128e28346 100644 --- a/infinitic-tests/src/test/resources/pulsar.yml +++ b/infinitic-tests/src/test/resources/pulsar.yml @@ -21,7 +21,7 @@ # # Licensor: infinitic.io -# Comment the line below to perform test on a local Pulsar cluster +# Comment the line below to perform tests on a local Pulsar cluster transport: inMemory stateStorage: inMemory @@ -32,7 +32,7 @@ pulsar: brokerServiceUrl: pulsar://localhost:6650 webServiceUrl: http://localhost:8080 tenant: infinitic - namespace: dev5 + namespace: dev tasks: - name: annotatedTask @@ -52,6 +52,9 @@ workflows: - name: io.infinitic.tests.workflows.WorkflowB class: io.infinitic.tests.workflows.WorkflowBImpl concurrency: 2 + - name: io.infinitic.tests.workflows.WorkflowC + class: io.infinitic.tests.workflows.WorkflowCImpl + concurrency: 2 - name: annotatedWorkflow class: io.infinitic.tests.workflows.WorkflowAnnotatedImpl concurrency: 2 diff --git a/infinitic-tests/src/test/resources/simplelogger.properties b/infinitic-tests/src/test/resources/simplelogger.properties index 83e03ca3a..026f95073 100644 --- a/infinitic-tests/src/test/resources/simplelogger.properties +++ b/infinitic-tests/src/test/resources/simplelogger.properties @@ -8,13 +8,16 @@ # Default logging detail level for all instances of SimpleLogger. # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, defaults to "info". -org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.defaultLogLevel=warn # Logging detail level for a SimpleLogger instance named "xxxxx". # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, the default logging detail level is used. +org.slf4j.simpleLogger.log.io.infinitic.workflows.engine.WorkflowEngine=debug #org.slf4j.simpleLogger.log.io.infinitic.tasks.engine.TaskEngine=debug - +#org.slf4j.simpleLogger.log.io.infinitic.tasks.executor.TaskExecutor=debug +#org.slf4j.simpleLogger.log.io.infinitic.workflows.engine.storage.LoggedWorkflowStateStorage=debug +org.slf4j.simpleLogger.log.io.infinitic.client.dispatcher.ClientDispatcherImpl=debug # Set to true if you want the current date and time to be included in output messages. # Default is false, and will output the number of milliseconds elapsed since startup. org.slf4j.simpleLogger.showDateTime=true diff --git a/infinitic-worker/src/main/kotlin/io/infinitic/worker/InfiniticWorker.kt b/infinitic-worker/src/main/kotlin/io/infinitic/worker/InfiniticWorker.kt index 480688f26..3e29f79e6 100644 --- a/infinitic-worker/src/main/kotlin/io/infinitic/worker/InfiniticWorker.kt +++ b/infinitic-worker/src/main/kotlin/io/infinitic/worker/InfiniticWorker.kt @@ -51,10 +51,11 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.cancel import kotlinx.coroutines.future.future -import kotlinx.coroutines.launch +import kotlinx.coroutines.job import mu.KotlinLogging import org.jetbrains.annotations.TestOnly import java.io.Closeable +import java.util.concurrent.CompletableFuture import java.util.concurrent.Executors @Suppress("MemberVisibilityCanBePrivate", "unused") @@ -76,30 +77,20 @@ abstract class InfiniticWorker(open val workerConfig: WorkerConfig) : Closeable abstract val name: String - protected abstract fun CoroutineScope.startTaskExecutors(name: Name, concurrency: Int) + protected abstract fun startTaskExecutors(name: Name, concurrency: Int) - protected abstract fun CoroutineScope.startWorkflowTagEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowTagStorage) - protected abstract fun CoroutineScope.startTaskEngines(workflowName: WorkflowName, concurrency: Int, storage: TaskStateStorage) - protected abstract fun CoroutineScope.startTaskDelayEngines(workflowName: WorkflowName, concurrency: Int) - protected abstract fun CoroutineScope.startWorkflowEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowStateStorage) - protected abstract fun CoroutineScope.startWorkflowDelayEngines(workflowName: WorkflowName, concurrency: Int) + protected abstract fun startWorkflowTagEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowTagStorage) + protected abstract fun startTaskEngines(workflowName: WorkflowName, concurrency: Int, storage: TaskStateStorage) + protected abstract fun startTaskDelayEngines(workflowName: WorkflowName, concurrency: Int) + protected abstract fun startWorkflowEngines(workflowName: WorkflowName, concurrency: Int, storage: WorkflowStateStorage) + protected abstract fun startWorkflowDelayEngines(workflowName: WorkflowName, concurrency: Int) - protected abstract fun CoroutineScope.startTaskTagEngines(taskName: TaskName, concurrency: Int, storage: TaskTagStorage) - protected abstract fun CoroutineScope.startTaskEngines(taskName: TaskName, concurrency: Int, storage: TaskStateStorage) - protected abstract fun CoroutineScope.startTaskDelayEngines(taskName: TaskName, concurrency: Int) - protected abstract fun CoroutineScope.startMetricsPerNameEngines(taskName: TaskName, storage: MetricsPerNameStateStorage) + protected abstract fun startTaskTagEngines(taskName: TaskName, concurrency: Int, storage: TaskTagStorage) + protected abstract fun startTaskEngines(taskName: TaskName, concurrency: Int, storage: TaskStateStorage) + protected abstract fun startTaskDelayEngines(taskName: TaskName, concurrency: Int) + protected abstract fun startMetricsPerNameEngines(taskName: TaskName, storage: MetricsPerNameStateStorage) - protected abstract fun CoroutineScope.startMetricsGlobalEngine(storage: MetricsGlobalStateStorage) - - /** - * Start worker - */ - open fun start() { - // register WorkflowTasks - taskExecutorRegister.registerTask(WorkflowTask::class.java.name) { WorkflowTaskImpl() } - - runningScope.future { start() }.join() - } + protected abstract fun startMetricsGlobalEngine(storage: MetricsGlobalStateStorage) /** * Close worker @@ -122,7 +113,17 @@ abstract class InfiniticWorker(open val workerConfig: WorkerConfig) : Closeable globalStorages.forEach { it.value.flush() } } - private fun CoroutineScope.start() = launch { + /** + * Start worker synchronously + */ + open fun start(): Unit = startAsync().join() + + /** + * Start worker asynchronously + */ + open fun startAsync(): CompletableFuture { + // register WorkflowTasks + taskExecutorRegister.registerTask(WorkflowTask::class.java.name) { WorkflowTaskImpl() } for (workflow in workerConfig.workflows) { val workflowName = WorkflowName(workflow.name) @@ -221,7 +222,7 @@ abstract class InfiniticWorker(open val workerConfig: WorkerConfig) : Closeable startTaskExecutors(taskName, task.concurrency) } - // starting engines managing tags of tasks + // starting engines managing tags of taskws task.tagEngine?.let { logger.info { "- tag engine".padEnd(25) + ": (" + @@ -295,5 +296,8 @@ abstract class InfiniticWorker(open val workerConfig: WorkerConfig) : Closeable } } logger.info { "Worker \"$name\" ready" } + + // provides a CompletableFuture that waits for completion of all launched coroutines + return runningScope.future { coroutineContext.job.join() } } } diff --git a/infinitic-worker/src/main/kotlin/io/infinitic/worker/config/WorkerConfig.kt b/infinitic-worker/src/main/kotlin/io/infinitic/worker/config/WorkerConfig.kt index 735218c83..3c396393f 100644 --- a/infinitic-worker/src/main/kotlin/io/infinitic/worker/config/WorkerConfig.kt +++ b/infinitic-worker/src/main/kotlin/io/infinitic/worker/config/WorkerConfig.kt @@ -38,48 +38,48 @@ import io.infinitic.transport.TransportConfig import io.infinitic.transport.pulsar.Pulsar data class WorkerConfig( - /* - Worker name + /** + * Worker name */ val name: String? = null, - /* - Transport configuration + /** + * Transport configuration */ override val transport: Transport = Transport.pulsar, - /* - Pulsar configuration + /** + * Pulsar configuration */ override val pulsar: Pulsar?, - /* - Default state storage + /** + * Default state storage */ override var stateStorage: StateStorage? = null, - /* - Redis configuration + /** + * Redis configuration */ override val redis: Redis? = null, - /* - Default state cache + /** + * Default state cache */ override var stateCache: StateCache = StateCache.caffeine, - /* - Caffeine configuration + /** + * Caffeine configuration */ override val caffeine: Caffeine? = null, - /* - Tasks configuration + /** + Tasks configuration */ val tasks: List = listOf(), - /* - Workflows configuration + /** + * Workflows configuration */ val workflows: List = listOf(), diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/WorkflowEngine.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/WorkflowEngine.kt index 2b7be7a37..d41b889a0 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/WorkflowEngine.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/WorkflowEngine.kt @@ -25,42 +25,47 @@ package io.infinitic.workflows.engine -import io.infinitic.common.clients.messages.UnknownWorkflow +import io.infinitic.common.clients.messages.MethodRunUnknown import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.errors.UnknownWorkflowError +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.tags.SendToTaskTagEngine +import io.infinitic.common.workflows.data.commands.CommandId +import io.infinitic.common.workflows.data.commands.CommandStatus import io.infinitic.common.workflows.engine.SendToWorkflowEngine import io.infinitic.common.workflows.engine.SendToWorkflowEngineAfter import io.infinitic.common.workflows.engine.messages.CancelWorkflow -import io.infinitic.common.workflows.engine.messages.ChildWorkflowCanceled -import io.infinitic.common.workflows.engine.messages.ChildWorkflowCompleted -import io.infinitic.common.workflows.engine.messages.ChildWorkflowFailed +import io.infinitic.common.workflows.engine.messages.ChildMethodCanceled +import io.infinitic.common.workflows.engine.messages.ChildMethodCompleted +import io.infinitic.common.workflows.engine.messages.ChildMethodFailed +import io.infinitic.common.workflows.engine.messages.ChildMethodUnknown import io.infinitic.common.workflows.engine.messages.CompleteWorkflow +import io.infinitic.common.workflows.engine.messages.DispatchMethod import io.infinitic.common.workflows.engine.messages.DispatchWorkflow import io.infinitic.common.workflows.engine.messages.RetryWorkflowTask -import io.infinitic.common.workflows.engine.messages.SendToChannel +import io.infinitic.common.workflows.engine.messages.SendSignal import io.infinitic.common.workflows.engine.messages.TaskCanceled import io.infinitic.common.workflows.engine.messages.TaskCompleted import io.infinitic.common.workflows.engine.messages.TaskFailed +import io.infinitic.common.workflows.engine.messages.TaskUnknown import io.infinitic.common.workflows.engine.messages.TimerCompleted import io.infinitic.common.workflows.engine.messages.WaitWorkflow import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage import io.infinitic.common.workflows.engine.messages.interfaces.MethodRunMessage import io.infinitic.common.workflows.engine.state.WorkflowState import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.workflows.engine.handlers.cancelWorkflow -import io.infinitic.workflows.engine.handlers.childWorkflowCanceled -import io.infinitic.workflows.engine.handlers.childWorkflowCompleted -import io.infinitic.workflows.engine.handlers.childWorkflowFailed +import io.infinitic.workflows.engine.handlers.dispatchMethodRun import io.infinitic.workflows.engine.handlers.dispatchWorkflow import io.infinitic.workflows.engine.handlers.retryWorkflowTask -import io.infinitic.workflows.engine.handlers.sendToChannel -import io.infinitic.workflows.engine.handlers.taskCanceled -import io.infinitic.workflows.engine.handlers.taskCompleted -import io.infinitic.workflows.engine.handlers.taskFailed -import io.infinitic.workflows.engine.handlers.timerCompleted +import io.infinitic.workflows.engine.handlers.sendSignal import io.infinitic.workflows.engine.handlers.waitWorkflow +import io.infinitic.workflows.engine.handlers.workflowTaskCompleted +import io.infinitic.workflows.engine.handlers.workflowTaskFailed +import io.infinitic.workflows.engine.helpers.commandTerminated import io.infinitic.workflows.engine.helpers.removeTags import io.infinitic.workflows.engine.output.WorkflowEngineOutput import io.infinitic.workflows.engine.storage.LoggedWorkflowStateStorage @@ -69,8 +74,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import mu.KotlinLogging +import java.time.Instant class WorkflowEngine( + val clientName: ClientName, storage: WorkflowStateStorage, sendEventsToClient: SendToClient, sendToTaskTagEngine: SendToTaskTagEngine, @@ -79,7 +86,6 @@ class WorkflowEngine( sendToWorkflowEngine: SendToWorkflowEngine, sendToWorkflowEngineAfter: SendToWorkflowEngineAfter ) { - companion object { const val NO_STATE_DISCARDING_REASON = "for having null workflow state" } @@ -91,6 +97,7 @@ class WorkflowEngine( private val storage = LoggedWorkflowStateStorage(storage) private val output = WorkflowEngineOutput( + clientName, sendEventsToClient, sendToTaskTagEngine, sendToTaskEngine, @@ -100,7 +107,7 @@ class WorkflowEngine( ) suspend fun handle(message: WorkflowEngineMessage) { - val state = process(message) ?: return + val state = process(message) ?: return // null => discarded message when (state.methodRuns.size) { 0 -> storage.delState(message.workflowId) @@ -113,19 +120,54 @@ class WorkflowEngine( logger.debug { "receiving $message" } - // get associated state + // get current state val state = storage.getState(message.workflowId) - // if no state (newly created workflow or terminated workflow) + // if no state (new or terminated workflow) if (state == null) { - if (message is DispatchWorkflow) { - return@coroutineScope dispatchWorkflow(output, message) + when (message) { + is DispatchWorkflow -> { + return@coroutineScope dispatchWorkflow(output, message) + } + is DispatchMethod -> { + if (message.clientWaiting) { + val methodRunUnknown = MethodRunUnknown( + recipientName = message.emitterName, + message.workflowId, + message.methodRunId, + emitterName = clientName + ) + launch { output.sendEventsToClient(methodRunUnknown) } + } + if (message.parentWorkflowId != null) { + val childMethodFailed = ChildMethodUnknown( + workflowId = message.parentWorkflowId!!, + workflowName = message.parentWorkflowName ?: thisShouldNotHappen(), + methodRunId = message.parentMethodRunId ?: thisShouldNotHappen(), + childUnknownWorkflowError = UnknownWorkflowError( + workflowName = message.workflowName, + workflowId = message.workflowId, + methodRunId = message.methodRunId + ), + emitterName = clientName + ) + if (message.parentWorkflowId != message.workflowId) { + launch { output.sendToWorkflowEngine(childMethodFailed) } + } + } + } + is WaitWorkflow -> { + val methodRunUnknown = MethodRunUnknown( + recipientName = message.emitterName, + message.workflowId, + message.methodRunId, + emitterName = clientName + ) + launch { output.sendEventsToClient(methodRunUnknown) } + } + else -> Unit } - if (message is WaitWorkflow) { - val unknownWorkflow = UnknownWorkflow(message.clientName, message.workflowId) - launch { output.sendEventsToClient(unknownWorkflow) } - } // discard all other messages if workflow is already terminated logDiscardingMessage(message, NO_STATE_DISCARDING_REASON) @@ -151,20 +193,15 @@ class WorkflowEngine( // (a workflowTask can be dispatched twice if the engine is shutdown while processing a workflowTask) if (message.isWorkflowTask() && message is TaskCompleted && - message.taskId != state.runningWorkflowTaskId + message.taskId() != state.runningWorkflowTaskId ) { logDiscardingMessage(message, "as workflowTask is not the current one") return@coroutineScope null } - state.lastMessageId = message.messageId - // if a workflow task is ongoing then buffer this message, except for WorkflowTaskCompleted of course - // except also for WaitWorkflow, as we want to handle it asap to avoid terminating the workflow before it - if (state.runningWorkflowTaskId != null && - ! message.isWorkflowTask() && - message !is WaitWorkflow + if (state.runningWorkflowTaskId != null && ! message.isWorkflowTask() ) { // buffer this message state.messagesBuffer.add(message) @@ -177,7 +214,6 @@ class WorkflowEngine( // process all buffered messages while ( - state.methodRuns.size > 0 && state.runningWorkflowTaskId == null && // if a workflowTask is not ongoing state.messagesBuffer.size > 0 // if there is at least one buffered message ) { @@ -197,6 +233,9 @@ class WorkflowEngine( } private fun CoroutineScope.processMessage(state: WorkflowState, message: WorkflowEngineMessage) { + // record this message as the last processed + state.lastMessageId = message.messageId + // if message is related to a workflowTask, it's not running anymore if (message.isWorkflowTask()) state.runningWorkflowTaskId = null @@ -209,19 +248,121 @@ class WorkflowEngine( @Suppress("UNUSED_VARIABLE") val m = when (message) { - is DispatchWorkflow -> thisShouldNotHappen("DispatchWorkflow should not reach this point") + is DispatchWorkflow -> thisShouldNotHappen() + is DispatchMethod -> dispatchMethodRun(output, state, message) is CancelWorkflow -> cancelWorkflow(output, state, message) - is SendToChannel -> sendToChannel(output, state, message) + is SendSignal -> sendSignal(output, state, message) is WaitWorkflow -> waitWorkflow(output, state, message) is CompleteWorkflow -> TODO() is RetryWorkflowTask -> retryWorkflowTask(output, state) - is ChildWorkflowFailed -> childWorkflowFailed(output, state, message) - is ChildWorkflowCanceled -> childWorkflowCanceled(output, state, message) - is ChildWorkflowCompleted -> childWorkflowCompleted(output, state, message) - is TimerCompleted -> timerCompleted(output, state, message) - is TaskFailed -> taskFailed(output, state, message) - is TaskCanceled -> taskCanceled(output, state, message) - is TaskCompleted -> taskCompleted(output, state, message) + is TimerCompleted -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.timerId), + CommandStatus.Completed(ReturnValue.from(Instant.now()), state.workflowTaskIndex) + ) + is ChildMethodUnknown -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.childUnknownWorkflowError.methodRunId ?: thisShouldNotHappen()), + CommandStatus.Unknown( + message.childUnknownWorkflowError, + state.workflowTaskIndex + ) + ) + is ChildMethodCanceled -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.childCanceledWorkflowError.methodRunId ?: thisShouldNotHappen()), + CommandStatus.Canceled( + message.childCanceledWorkflowError, + state.workflowTaskIndex + ) + ) + is ChildMethodFailed -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.childFailedWorkflowError.methodRunId ?: thisShouldNotHappen()), + CommandStatus.CurrentlyFailed( + message.childFailedWorkflowError, + state.workflowTaskIndex + ) + ) + is ChildMethodCompleted -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.childWorkflowReturnValue.methodRunId), + CommandStatus.Completed( + message.childWorkflowReturnValue.returnValue, + state.workflowTaskIndex + ) + ) + is TaskUnknown -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.unknownTaskError.taskId), + CommandStatus.Unknown( + message.unknownTaskError, + state.workflowTaskIndex + ) + ) + is TaskCanceled -> when (message.isWorkflowTask()) { + true -> { + TODO() + } + false -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.canceledTaskError.taskId), + CommandStatus.Canceled( + message.canceledTaskError, + state.workflowTaskIndex + ) + ) + } + is TaskFailed -> when (message.isWorkflowTask()) { + true -> { + val messages = workflowTaskFailed(output, state, message) + // add fake messages at the top of the messagesBuffer list + state.messagesBuffer.addAll(0, messages); Unit + } + false -> { + commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.failedTaskError.taskId), + CommandStatus.CurrentlyFailed( + message.failedTaskError, + state.workflowTaskIndex + ) + ) + } + } + is TaskCompleted -> when (message.isWorkflowTask()) { + true -> { + val messages = workflowTaskCompleted(output, state, message) + // add fake messages at the top of the messagesBuffer list + state.messagesBuffer.addAll(0, messages); Unit + } + false -> commandTerminated( + output, + state, + message.methodRunId, + CommandId.from(message.taskReturnValue.taskId), + CommandStatus.Completed( + message.taskReturnValue.returnValue, + state.workflowTaskIndex + ) + ) + } } } } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/cancelWorkflow.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/cancelWorkflow.kt index 2b1f43e6d..2e3d40002 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/cancelWorkflow.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/cancelWorkflow.kt @@ -25,14 +25,19 @@ package io.infinitic.workflows.engine.handlers -import io.infinitic.common.clients.messages.WorkflowCanceled -import io.infinitic.common.workflows.data.commands.CommandType +import io.infinitic.common.clients.messages.MethodCanceled +import io.infinitic.common.errors.CanceledWorkflowError +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.workflows.data.commands.DispatchMethodCommand +import io.infinitic.common.workflows.data.commands.DispatchWorkflowCommand +import io.infinitic.common.workflows.data.methodRuns.MethodRun +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflows.WorkflowCancellationReason import io.infinitic.common.workflows.data.workflows.WorkflowId -import io.infinitic.common.workflows.data.workflows.WorkflowName import io.infinitic.common.workflows.engine.messages.CancelWorkflow -import io.infinitic.common.workflows.engine.messages.ChildWorkflowCanceled +import io.infinitic.common.workflows.engine.messages.ChildMethodCanceled import io.infinitic.common.workflows.engine.state.WorkflowState +import io.infinitic.common.workflows.tags.messages.CancelWorkflowByTag import io.infinitic.workflows.engine.output.WorkflowEngineOutput import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -42,41 +47,98 @@ internal fun CoroutineScope.cancelWorkflow( state: WorkflowState, message: CancelWorkflow ) { - state.methodRuns.forEach { methodRun -> - // inform waiting clients of cancellation - methodRun.waitingClients.forEach { - val workflowCanceled = WorkflowCanceled( - clientName = it, - workflowId = state.workflowId, - ) - launch { output.sendEventsToClient(workflowCanceled) } + when (message.methodRunId) { + null -> { + state.methodRuns.forEach { + cancelMethodRun(output, state, it, message.reason) + } + + // clean state + state.removeAllMethodRuns() } + else -> { + state.getMethodRun(message.methodRunId!!)?. let { methodRun -> + cancelMethodRun(output, state, methodRun, message.reason) - // inform parents of cancellation (if parent did not trigger the cancellation!) - if (message.reason != WorkflowCancellationReason.CANCELED_BY_PARENT && methodRun.parentWorkflowId != null) { - val childWorkflowCanceled = ChildWorkflowCanceled( - workflowId = methodRun.parentWorkflowId!!, - workflowName = methodRun.parentWorkflowName!!, - methodRunId = methodRun.parentMethodRunId!!, - childWorkflowId = state.workflowId, - childWorkflowName = state.workflowName - ) - launch { output.sendToWorkflowEngine(childWorkflowCanceled) } + // clean state + state.removeMethodRun(methodRun) + } } + } +} - // cancel children - methodRun.pastCommands - .filter { it.commandType == CommandType.DISPATCH_CHILD_WORKFLOW } - .forEach { +private fun CoroutineScope.cancelMethodRun( + output: WorkflowEngineOutput, + state: WorkflowState, + methodRun: MethodRun, + reason: WorkflowCancellationReason, +) { + // inform waiting clients of cancellation + methodRun.waitingClients.forEach { + val workflowCanceled = MethodCanceled( + recipientName = it, + workflowId = state.workflowId, + methodRunId = methodRun.methodRunId, + emitterName = output.clientName + ) + launch { output.sendEventsToClient(workflowCanceled) } + } + methodRun.waitingClients.clear() + + // inform parents of cancellation (if parent did not trigger the cancellation!) + if (reason != WorkflowCancellationReason.CANCELED_BY_PARENT && methodRun.parentWorkflowId != null) { + val childMethodCanceled = ChildMethodCanceled( + workflowId = methodRun.parentWorkflowId!!, + workflowName = methodRun.parentWorkflowName ?: thisShouldNotHappen(), + methodRunId = methodRun.parentMethodRunId ?: thisShouldNotHappen(), + childCanceledWorkflowError = CanceledWorkflowError( + workflowName = state.workflowName, + workflowId = state.workflowId, + methodRunId = methodRun.methodRunId, + ), + emitterName = output.clientName + ) + launch { output.sendToWorkflowEngine(childMethodCanceled) } + } + + // cancel children + methodRun.pastCommands.forEach { + when (val command = it.command) { + is DispatchMethodCommand -> { + when { + command.workflowId != null -> { + val cancelWorkflow = CancelWorkflow( + workflowId = command.workflowId!!, + workflowName = command.workflowName, + methodRunId = MethodRunId.from(it.commandId), + reason = WorkflowCancellationReason.CANCELED_BY_PARENT, + emitterName = output.clientName + ) + launch { output.sendToWorkflowEngine(cancelWorkflow) } + } + command.workflowTag != null -> { + val cancelWorkflowByTag = CancelWorkflowByTag( + workflowTag = command.workflowTag!!, + workflowName = command.workflowName, + reason = WorkflowCancellationReason.CANCELED_BY_PARENT, + emitterWorkflowId = state.workflowId, + emitterName = output.clientName + ) + launch { output.sendToWorkflowTagEngine(cancelWorkflowByTag) } + } + else -> thisShouldNotHappen() + } + } + is DispatchWorkflowCommand -> { val cancelWorkflow = CancelWorkflow( - workflowId = WorkflowId(it.commandId.id), - workflowName = WorkflowName("${it.commandName}"), - reason = WorkflowCancellationReason.CANCELED_BY_PARENT + workflowId = WorkflowId.from(it.commandId), + workflowName = command.workflowName, + methodRunId = null, + reason = WorkflowCancellationReason.CANCELED_BY_PARENT, + emitterName = output.clientName ) launch { output.sendToWorkflowEngine(cancelWorkflow) } } + } } - - // clean state - state.removeAllMethodRuns() } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowCanceled.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowCanceled.kt deleted file mode 100644 index f3997b4c0..000000000 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowCanceled.kt +++ /dev/null @@ -1,48 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.workflows.engine.handlers - -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandStatus.Canceled -import io.infinitic.common.workflows.engine.messages.ChildWorkflowCanceled -import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated -import io.infinitic.workflows.engine.output.WorkflowEngineOutput -import kotlinx.coroutines.CoroutineScope - -internal fun CoroutineScope.childWorkflowCanceled( - workflowEngineOutput: WorkflowEngineOutput, - state: WorkflowState, - msg: ChildWorkflowCanceled -) { - commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.childWorkflowId), - Canceled(state.workflowTaskIndex) - ) -} diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowCompleted.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowCompleted.kt deleted file mode 100644 index 54eb90e9a..000000000 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowCompleted.kt +++ /dev/null @@ -1,49 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.workflows.engine.handlers - -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandReturnValue -import io.infinitic.common.workflows.data.commands.CommandStatus.Completed -import io.infinitic.common.workflows.engine.messages.ChildWorkflowCompleted -import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated -import io.infinitic.workflows.engine.output.WorkflowEngineOutput -import kotlinx.coroutines.CoroutineScope - -internal fun CoroutineScope.childWorkflowCompleted( - workflowEngineOutput: WorkflowEngineOutput, - state: WorkflowState, - msg: ChildWorkflowCompleted -) { - commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.childWorkflowId), - Completed(CommandReturnValue(msg.childWorkflowReturnValue.serializedData), state.workflowTaskIndex) - ) -} diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowFailed.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowFailed.kt deleted file mode 100644 index a3c08db1b..000000000 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/childWorkflowFailed.kt +++ /dev/null @@ -1,48 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.workflows.engine.handlers - -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandStatus.CurrentlyFailed -import io.infinitic.common.workflows.engine.messages.ChildWorkflowFailed -import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated -import io.infinitic.workflows.engine.output.WorkflowEngineOutput -import kotlinx.coroutines.CoroutineScope - -internal fun CoroutineScope.childWorkflowFailed( - workflowEngineOutput: WorkflowEngineOutput, - state: WorkflowState, - msg: ChildWorkflowFailed -) { - commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.childWorkflowId), - CurrentlyFailed(msg.childWorkflowError, state.workflowTaskIndex) - ) -} diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/timerCompleted.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchMethodRun.kt similarity index 53% rename from infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/timerCompleted.kt rename to infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchMethodRun.kt index 0a4a8f121..b7a0cf01a 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/timerCompleted.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchMethodRun.kt @@ -25,25 +25,36 @@ package io.infinitic.workflows.engine.handlers -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandReturnValue -import io.infinitic.common.workflows.data.commands.CommandStatus.Completed -import io.infinitic.common.workflows.engine.messages.TimerCompleted +import io.infinitic.common.workflows.data.methodRuns.MethodRun +import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition +import io.infinitic.common.workflows.engine.messages.DispatchMethod import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated +import io.infinitic.workflows.engine.helpers.dispatchWorkflowTask import io.infinitic.workflows.engine.output.WorkflowEngineOutput import kotlinx.coroutines.CoroutineScope -internal fun CoroutineScope.timerCompleted( +internal fun CoroutineScope.dispatchMethodRun( workflowEngineOutput: WorkflowEngineOutput, state: WorkflowState, - msg: TimerCompleted + message: DispatchMethod ) { - commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.timerId), - Completed(CommandReturnValue.now(), state.workflowTaskIndex) + val methodRun = MethodRun( + methodRunId = message.methodRunId, + waitingClients = when (message.clientWaiting) { + true -> mutableSetOf(message.emitterName) + false -> mutableSetOf() + }, + parentWorkflowId = message.parentWorkflowId, + parentWorkflowName = message.parentWorkflowName, + parentMethodRunId = message.parentMethodRunId, + methodName = message.methodName, + methodParameterTypes = message.methodParameterTypes, + methodParameters = message.methodParameters, + workflowTaskIndexAtStart = state.workflowTaskIndex, + propertiesNameHashAtStart = state.currentPropertiesNameHash.toMap() ) + + state.methodRuns.add(methodRun) + + dispatchWorkflowTask(workflowEngineOutput, state, methodRun, MethodRunPosition.new()) } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchWorkflow.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchWorkflow.kt index 92d813a91..427a9a5bd 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchWorkflow.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/dispatchWorkflow.kt @@ -28,6 +28,7 @@ package io.infinitic.workflows.engine.handlers import io.infinitic.common.workflows.data.methodRuns.MethodRun import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition +import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskIndex import io.infinitic.common.workflows.engine.messages.DispatchWorkflow import io.infinitic.common.workflows.engine.state.WorkflowState import io.infinitic.workflows.engine.helpers.dispatchWorkflowTask @@ -39,9 +40,9 @@ internal fun CoroutineScope.dispatchWorkflow( message: DispatchWorkflow ): WorkflowState { val methodRun = MethodRun( - methodRunId = MethodRunId(message.workflowId.id), + methodRunId = MethodRunId.from(message.workflowId), waitingClients = when (message.clientWaiting) { - true -> mutableSetOf(message.clientName) + true -> mutableSetOf(message.emitterName) false -> mutableSetOf() }, parentWorkflowId = message.parentWorkflowId, @@ -50,6 +51,7 @@ internal fun CoroutineScope.dispatchWorkflow( methodName = message.methodName, methodParameterTypes = message.methodParameterTypes, methodParameters = message.methodParameters, + workflowTaskIndexAtStart = WorkflowTaskIndex(0), propertiesNameHashAtStart = mapOf() ) @@ -63,7 +65,7 @@ internal fun CoroutineScope.dispatchWorkflow( methodRuns = mutableListOf(methodRun) ) - dispatchWorkflowTask(workflowEngineOutput, state, methodRun, MethodRunPosition("")) + dispatchWorkflowTask(workflowEngineOutput, state, methodRun, MethodRunPosition.new()) return state } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/sendToChannel.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/sendSignal.kt similarity index 77% rename from infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/sendToChannel.kt rename to infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/sendSignal.kt index 5e00a8434..8fe1cd496 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/sendToChannel.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/sendSignal.kt @@ -25,9 +25,9 @@ package io.infinitic.workflows.engine.handlers -import io.infinitic.common.workflows.data.commands.CommandReturnValue +import io.infinitic.common.data.ReturnValue import io.infinitic.common.workflows.data.commands.CommandStatus.Completed -import io.infinitic.common.workflows.engine.messages.SendToChannel +import io.infinitic.common.workflows.engine.messages.SendSignal import io.infinitic.common.workflows.engine.state.WorkflowState import io.infinitic.workflows.engine.helpers.commandTerminated import io.infinitic.workflows.engine.output.WorkflowEngineOutput @@ -36,15 +36,15 @@ import mu.KotlinLogging private val logger = KotlinLogging.logger {} -internal fun CoroutineScope.sendToChannel( +internal fun CoroutineScope.sendSignal( workflowEngineOutput: WorkflowEngineOutput, state: WorkflowState, - msg: SendToChannel + message: SendSignal ) { state.receivingChannels.firstOrNull { - it.channelName == msg.channelName && - (it.channelEventType == null || msg.channelEventTypes.contains(it.channelEventType)) && - (it.channelEventFilter == null || it.channelEventFilter!!.check(msg.channelEvent)) + it.channelName == message.channelName && + (it.channelSignalType == null || message.channelSignalTypes.contains(it.channelSignalType)) && + (it.channelEventFilter == null || it.channelEventFilter!!.check(message.channelSignal)) } ?.also { state.receivingChannels.remove(it) @@ -54,8 +54,8 @@ internal fun CoroutineScope.sendToChannel( state, it.methodRunId, it.commandId, - Completed(CommandReturnValue(msg.channelEvent.serializedData), state.workflowTaskIndex) + Completed(ReturnValue(message.channelSignal.serializedData), state.workflowTaskIndex) ) } - ?: logger.debug { "discarding non-waited event $msg" } + ?: logger.debug { "discarding non-waited event $message" } } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskCompleted.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskCompleted.kt deleted file mode 100644 index ef74f9e19..000000000 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskCompleted.kt +++ /dev/null @@ -1,57 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.workflows.engine.handlers - -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandReturnValue -import io.infinitic.common.workflows.data.commands.CommandStatus.Completed -import io.infinitic.common.workflows.engine.messages.TaskCompleted -import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated -import io.infinitic.workflows.engine.output.WorkflowEngineOutput -import kotlinx.coroutines.CoroutineScope - -internal fun CoroutineScope.taskCompleted( - workflowEngineOutput: WorkflowEngineOutput, - state: WorkflowState, - msg: TaskCompleted -) { - - when (msg.isWorkflowTask()) { - true -> workflowTaskCompleted( - workflowEngineOutput, - state, - msg - ) - false -> commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.taskId), - Completed(CommandReturnValue(msg.taskReturnValue.serializedData), state.workflowTaskIndex) - ) - } -} diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskFailed.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskFailed.kt deleted file mode 100644 index 2a2c12b1e..000000000 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/taskFailed.kt +++ /dev/null @@ -1,56 +0,0 @@ -/** - * "Commons Clause" License Condition v1.0 - * - * The Software is provided to you by the Licensor under the License, as defined - * below, subject to the following condition. - * - * Without limiting other conditions in the License, the grant of rights under the - * License will not include, and the License does not grant to you, the right to - * Sell the Software. - * - * For purposes of the foregoing, “Sell” means practicing any or all of the rights - * granted to you under the License to provide to third parties, for a fee or - * other consideration (including without limitation fees for hosting or - * consulting/ support services related to the Software), a product or service - * whose value derives, entirely or substantially, from the functionality of the - * Software. Any license notice or attribution required by the License must also - * include this Commons Clause License Condition notice. - * - * Software: Infinitic - * - * License: MIT License (https://opensource.org/licenses/MIT) - * - * Licensor: infinitic.io - */ - -package io.infinitic.workflows.engine.handlers - -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandStatus.CurrentlyFailed -import io.infinitic.common.workflows.engine.messages.TaskFailed -import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.workflows.engine.helpers.commandTerminated -import io.infinitic.workflows.engine.output.WorkflowEngineOutput -import kotlinx.coroutines.CoroutineScope - -internal fun CoroutineScope.taskFailed( - workflowEngineOutput: WorkflowEngineOutput, - state: WorkflowState, - msg: TaskFailed -) { - - when (msg.isWorkflowTask()) { - true -> workflowTaskFailed( - workflowEngineOutput, - state, - msg - ) - false -> commandTerminated( - workflowEngineOutput, - state, - msg.methodRunId, - CommandId(msg.taskId), - CurrentlyFailed(msg.error, state.workflowTaskIndex) - ) - } -} diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/waitWorkflow.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/waitWorkflow.kt index 58d815f0e..9c997aa0c 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/waitWorkflow.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/waitWorkflow.kt @@ -25,20 +25,29 @@ package io.infinitic.workflows.engine.handlers -import io.infinitic.common.clients.messages.WorkflowAlreadyCompleted +import io.infinitic.common.clients.messages.MethodRunUnknown import io.infinitic.common.workflows.engine.messages.WaitWorkflow import io.infinitic.common.workflows.engine.state.WorkflowState import io.infinitic.workflows.engine.output.WorkflowEngineOutput import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -internal fun CoroutineScope.waitWorkflow(output: WorkflowEngineOutput, state: WorkflowState, msg: WaitWorkflow) { - when (val main = state.getMainMethodRun()) { +internal fun CoroutineScope.waitWorkflow( + output: WorkflowEngineOutput, + state: WorkflowState, + message: WaitWorkflow +) { + + when (val methodRun = state.getMethodRun(message.methodRunId)) { null -> { - // main branch is already completed (can not be canceled, the state would be deleted) - val workflowAlreadyCompleted = WorkflowAlreadyCompleted(msg.clientName, msg.workflowId) - launch { output.sendEventsToClient(workflowAlreadyCompleted) } + val methodRunUnknown = MethodRunUnknown( + message.emitterName, + message.workflowId, + message.methodRunId, + output.clientName + ) + launch { output.sendEventsToClient(methodRunUnknown) } } - else -> main.waitingClients.add(msg.clientName) + else -> methodRun.waitingClients.add(message.emitterName) } } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskCompleted.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskCompleted.kt index d259998ad..39f7a234b 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskCompleted.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskCompleted.kt @@ -25,55 +25,63 @@ package io.infinitic.workflows.engine.handlers -import io.infinitic.common.clients.data.ClientName +import io.infinitic.common.clients.messages.MethodCompleted +import io.infinitic.common.data.ClientName import io.infinitic.common.data.MillisDuration import io.infinitic.common.data.MillisInstant import io.infinitic.common.data.minus +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.tasks.data.TaskId import io.infinitic.common.tasks.engine.messages.DispatchTask -import io.infinitic.common.tasks.tags.messages.AddTaskTag +import io.infinitic.common.tasks.tags.messages.AddTagToTask +import io.infinitic.common.workflows.data.channels.ChannelSignalId import io.infinitic.common.workflows.data.channels.ReceivingChannel -import io.infinitic.common.workflows.data.commands.CommandStatus -import io.infinitic.common.workflows.data.commands.CommandType -import io.infinitic.common.workflows.data.commands.DispatchChildWorkflow -import io.infinitic.common.workflows.data.commands.EndAsync -import io.infinitic.common.workflows.data.commands.EndInlineTask -import io.infinitic.common.workflows.data.commands.NewCommand -import io.infinitic.common.workflows.data.commands.PastCommand -import io.infinitic.common.workflows.data.commands.ReceiveInChannel -import io.infinitic.common.workflows.data.commands.SendToChannel -import io.infinitic.common.workflows.data.commands.StartAsync -import io.infinitic.common.workflows.data.commands.StartDurationTimer -import io.infinitic.common.workflows.data.commands.StartInlineTask -import io.infinitic.common.workflows.data.commands.StartInstantTimer -import io.infinitic.common.workflows.data.methodRuns.MethodRun +import io.infinitic.common.workflows.data.commands.CommandId +import io.infinitic.common.workflows.data.commands.DispatchMethodCommand +import io.infinitic.common.workflows.data.commands.DispatchMethodPastCommand +import io.infinitic.common.workflows.data.commands.DispatchTaskCommand +import io.infinitic.common.workflows.data.commands.DispatchTaskPastCommand +import io.infinitic.common.workflows.data.commands.DispatchWorkflowCommand +import io.infinitic.common.workflows.data.commands.DispatchWorkflowPastCommand +import io.infinitic.common.workflows.data.commands.InlineTaskPastCommand +import io.infinitic.common.workflows.data.commands.ReceiveSignalCommand +import io.infinitic.common.workflows.data.commands.ReceiveSignalPastCommand +import io.infinitic.common.workflows.data.commands.SendSignalCommand +import io.infinitic.common.workflows.data.commands.SendSignalPastCommand +import io.infinitic.common.workflows.data.commands.StartDurationTimerCommand +import io.infinitic.common.workflows.data.commands.StartDurationTimerPastCommand +import io.infinitic.common.workflows.data.commands.StartInstantTimerCommand +import io.infinitic.common.workflows.data.commands.StartInstantTimerPastCommand +import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.steps.PastStep +import io.infinitic.common.workflows.data.steps.StepStatus.CurrentlyFailed import io.infinitic.common.workflows.data.steps.StepStatus.Failed -import io.infinitic.common.workflows.data.steps.StepStatus.OngoingFailure import io.infinitic.common.workflows.data.timers.TimerId import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskReturnValue -import io.infinitic.common.workflows.data.workflowTasks.plus import io.infinitic.common.workflows.data.workflows.WorkflowId -import io.infinitic.common.workflows.engine.messages.ChildWorkflowCompleted +import io.infinitic.common.workflows.data.workflows.WorkflowReturnValue +import io.infinitic.common.workflows.engine.messages.ChildMethodCompleted +import io.infinitic.common.workflows.engine.messages.DispatchMethod import io.infinitic.common.workflows.engine.messages.DispatchWorkflow +import io.infinitic.common.workflows.engine.messages.SendSignal import io.infinitic.common.workflows.engine.messages.TaskCompleted import io.infinitic.common.workflows.engine.messages.TimerCompleted +import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.common.workflows.tags.messages.AddWorkflowTag -import io.infinitic.workflows.engine.helpers.dispatchWorkflowTask +import io.infinitic.common.workflows.tags.messages.AddTagToWorkflow +import io.infinitic.common.workflows.tags.messages.DispatchMethodByTag +import io.infinitic.common.workflows.tags.messages.SendSignalByTag import io.infinitic.workflows.engine.helpers.stepTerminated import io.infinitic.workflows.engine.output.WorkflowEngineOutput import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import io.infinitic.common.clients.messages.WorkflowCompleted as WorkflowCompletedInClient -import io.infinitic.common.workflows.data.commands.DispatchTask as DispatchTaskInWorkflow internal fun CoroutineScope.workflowTaskCompleted( - workflowEngineOutput: WorkflowEngineOutput, + output: WorkflowEngineOutput, state: WorkflowState, - msg: TaskCompleted -) { - val workflowTaskOutput = msg.taskReturnValue.get() as WorkflowTaskReturnValue + message: TaskCompleted +): MutableList { + val workflowTaskOutput = message.taskReturnValue.returnValue.value() as WorkflowTaskReturnValue // retrieve current methodRun val methodRun = state.getRunningMethodRun() @@ -81,13 +89,14 @@ internal fun CoroutineScope.workflowTaskCompleted( // if current step status was ongoingFailure // convert it to a definitive StepStatusFailed // as the error has been caught by the workflow - methodRun.getStepByPosition(state.runningMethodRunPosition!!) - ?.run { - val status = stepStatus - if (status is OngoingFailure) { - stepStatus = Failed(status.commandId, status.failureWorkflowTaskIndex) - } + methodRun.currentStep?.let { + val oldStatus = it.stepStatus + if (oldStatus is CurrentlyFailed) { + it.stepStatus = Failed(oldStatus.failedDeferredError, oldStatus.failureWorkflowTaskIndex) + methodRun.pastSteps.add(it) + methodRun.currentStep = null } + } // properties updates workflowTaskOutput.properties.map { @@ -101,32 +110,35 @@ internal fun CoroutineScope.workflowTaskCompleted( } } + val bufferedMessages = mutableListOf() + // add new commands to past commands workflowTaskOutput.newCommands.forEach { @Suppress("UNUSED_VARIABLE") - val o = when (it.command) { - is DispatchTaskInWorkflow -> dispatchTask(workflowEngineOutput, methodRun, it, state) - is DispatchChildWorkflow -> dispatchChildWorkflow(workflowEngineOutput, methodRun, it, state) - is StartAsync -> startAsync(methodRun, it, state) - is EndAsync -> endAsync(workflowEngineOutput, methodRun, it, state) - is StartInlineTask -> startInlineTask(methodRun, it) - is EndInlineTask -> endInlineTask(methodRun, it, state) - is StartDurationTimer -> startDurationTimer(workflowEngineOutput, methodRun, it, state) - is StartInstantTimer -> startInstantTimer(workflowEngineOutput, methodRun, it, state) - is ReceiveInChannel -> receiveFromChannel(methodRun, it, state) - is SendToChannel -> TODO() + val o = when (it) { + is DispatchTaskPastCommand -> dispatchTask(output, it, state) + is DispatchWorkflowPastCommand -> dispatchWorkflow(output, it, state) + is DispatchMethodPastCommand -> dispatchMethod(output, it, state, bufferedMessages) + is SendSignalPastCommand -> sendSignal(output, it, state, bufferedMessages) + is InlineTaskPastCommand -> Unit // Nothing to do + is StartDurationTimerPastCommand -> startDurationTimer(output, it, state) + is StartInstantTimerPastCommand -> startInstantTimer(output, it, state) + is ReceiveSignalPastCommand -> receiveFromChannel(it, state) } + methodRun.pastCommands.add(it) } - // add new steps to past steps - workflowTaskOutput.newSteps.map { - methodRun.pastSteps.add( - PastStep( - stepPosition = it.stepPosition, - step = it.step, - stepHash = it.stepHash, - stepStatus = it.step.status() - ) + // add new step to past steps + workflowTaskOutput.newStep?.let { + // checking that currennt step is empty + if (methodRun.currentStep != null) thisShouldNotHappen("non null current step") + // set new step + methodRun.currentStep = PastStep( + stepPosition = it.stepPosition, + step = it.step, + stepHash = it.stepHash, + stepStatus = it.step.status(), + workflowTaskIndexAtStart = state.workflowTaskIndex ) } @@ -136,272 +148,305 @@ internal fun CoroutineScope.workflowTaskCompleted( methodRun.methodReturnValue = workflowTaskOutput.methodReturnValue // send output back to waiting clients - methodRun.waitingClients.map { - val workflowCompleted = WorkflowCompletedInClient( - clientName = it, + methodRun.waitingClients.forEach { + val workflowCompleted = MethodCompleted( + recipientName = it, workflowId = state.workflowId, - workflowReturnValue = methodRun.methodReturnValue!! + methodRunId = methodRun.methodRunId, + methodReturnValue = methodRun.methodReturnValue!!, + emitterName = output.clientName ) - launch { workflowEngineOutput.sendEventsToClient(workflowCompleted) } + launch { output.sendEventsToClient(workflowCompleted) } } + methodRun.waitingClients.clear() // tell parent workflow if any methodRun.parentWorkflowId?.let { - val childWorkflowCompleted = ChildWorkflowCompleted( + val childMethodCompleted = ChildMethodCompleted( + workflowName = methodRun.parentWorkflowName ?: thisShouldNotHappen(), workflowId = it, - workflowName = methodRun.parentWorkflowName!!, - methodRunId = methodRun.parentMethodRunId!!, - childWorkflowId = state.workflowId, - childWorkflowReturnValue = workflowTaskOutput.methodReturnValue!! + methodRunId = methodRun.parentMethodRunId ?: thisShouldNotHappen(), + childWorkflowReturnValue = WorkflowReturnValue( + workflowId = state.workflowId, + methodRunId = methodRun.methodRunId, + returnValue = workflowTaskOutput.methodReturnValue!!, + ), + emitterName = output.clientName ) - launch { workflowEngineOutput.sendToWorkflowEngine(childWorkflowCompleted) } + if (it == state.workflowId) { + // case of method dispatched within same workflow + bufferedMessages.add(childMethodCompleted) + } else { + launch { output.sendToWorkflowEngine(childMethodCompleted) } + } } } // does previous commands trigger another workflowTask? - while (state.runningMethodRunBufferedCommands.isNotEmpty() && state.runningWorkflowTaskId == null) { - val commandId = state.runningMethodRunBufferedCommands.first() - val pastCommand = methodRun.getPastCommand(commandId) - - if (pastCommand.commandType == CommandType.START_ASYNC && ! pastCommand.isTerminated()) { - // update pastCommand with a copy (!) of current properties and anticipated workflowTaskIndex - pastCommand.propertiesNameHashAtStart = state.currentPropertiesNameHash.toMap() - pastCommand.workflowTaskIndexAtStart = state.workflowTaskIndex + 1 - // dispatch a new workflowTask - dispatchWorkflowTask( - workflowEngineOutput, - state, - methodRun, - pastCommand.commandPosition - ) - // removes this command - state.runningMethodRunBufferedCommands.removeFirst() - } else { - if (!stepTerminated( - workflowEngineOutput, - state, - methodRun, - pastCommand - ) - ) { - // no step is completed, we can remove this command - state.runningMethodRunBufferedCommands.removeFirst() - } + while (state.runningTerminatedCommands.isNotEmpty() && state.runningWorkflowTaskId == null) { + val commandId = state.runningTerminatedCommands.first() + val pastCommand = state.getPastCommand(commandId, methodRun) + + if (!stepTerminated(output, state, pastCommand)) { + // if no additional step can be completed, we can remove this command + state.runningTerminatedCommands.removeFirst() } } if (methodRun.isTerminated()) state.removeMethodRun(methodRun) -} - -private fun startAsync(methodRun: MethodRun, newCommand: NewCommand, state: WorkflowState) { - val pastCommand = addPastCommand(methodRun, newCommand) - - state.runningMethodRunBufferedCommands.add(pastCommand.commandId) -} - -private fun CoroutineScope.endAsync( - output: WorkflowEngineOutput, - methodRun: MethodRun, - newCommand: NewCommand, - state: WorkflowState -) { - val command = newCommand.command as EndAsync - - // look for previous Start Async command - val pastCommand = methodRun.pastCommands.first { - it.commandPosition == newCommand.commandPosition && it.commandType == CommandType.START_ASYNC - } - - // do nothing if this command is already terminated (i.e. canceled or completed, failed is transient) - if (pastCommand.isTerminated()) return - - // update command status - pastCommand.commandStatus = CommandStatus.Completed(command.asyncReturnValue, state.workflowTaskIndex) - - if (stepTerminated( - output, - state, - methodRun, - pastCommand - ) - ) { - // keep this command as we could have another pastStep solved by it - state.runningMethodRunBufferedCommands.add(pastCommand.commandId) - } -} -private fun startInlineTask(methodRun: MethodRun, newCommand: NewCommand) { - addPastCommand(methodRun, newCommand) -} - -private fun endInlineTask(methodRun: MethodRun, newCommand: NewCommand, state: WorkflowState) { - val command = newCommand.command as EndInlineTask - // look for previous StartInlineTask command - val pastCommand = methodRun.pastCommands.first { - it.commandPosition == newCommand.commandPosition && it.commandType == CommandType.START_INLINE_TASK - } - // past command completed - pastCommand.commandStatus = CommandStatus.Completed( - returnValue = command.inlineTaskReturnValue, - completionWorkflowTaskIndex = state.workflowTaskIndex - ) + return bufferedMessages } private fun CoroutineScope.startDurationTimer( output: WorkflowEngineOutput, - methodRun: MethodRun, - newCommand: NewCommand, + newCommand: StartDurationTimerPastCommand, state: WorkflowState ) { - val command = newCommand.command as StartDurationTimer + val command: StartDurationTimerCommand = newCommand.command val msg = TimerCompleted( - workflowId = state.workflowId, workflowName = state.workflowName, - methodRunId = methodRun.methodRunId, - timerId = TimerId(newCommand.commandId.id) + workflowId = state.workflowId, + methodRunId = state.runningMethodRunId ?: thisShouldNotHappen(), + timerId = TimerId.from(newCommand.commandId), + emitterName = output.clientName ) val diff: MillisDuration = state.runningWorkflowTaskInstant!! - MillisInstant.now() launch { output.sendToWorkflowEngineAfter(msg, command.duration - diff) } - - addPastCommand(methodRun, newCommand) } private fun CoroutineScope.startInstantTimer( output: WorkflowEngineOutput, - methodRun: MethodRun, - newCommand: NewCommand, + newCommand: StartInstantTimerPastCommand, state: WorkflowState ) { - val command = newCommand.command as StartInstantTimer + val command: StartInstantTimerCommand = newCommand.command val msg = TimerCompleted( - workflowId = state.workflowId, workflowName = state.workflowName, - methodRunId = methodRun.methodRunId, - timerId = TimerId(newCommand.commandId.id) + workflowId = state.workflowId, + methodRunId = state.runningMethodRunId ?: thisShouldNotHappen(), + timerId = TimerId.from(newCommand.commandId), + emitterName = output.clientName ) launch { output.sendToWorkflowEngineAfter(msg, command.instant - MillisInstant.now()) } - - addPastCommand(methodRun, newCommand) } private fun receiveFromChannel( - methodRun: MethodRun, - newCommand: NewCommand, + newCommand: ReceiveSignalPastCommand, state: WorkflowState ) { - val command = newCommand.command as ReceiveInChannel + val command: ReceiveSignalCommand = newCommand.command state.receivingChannels.add( ReceivingChannel( channelName = command.channelName, - channelEventType = command.channelEventType, + channelSignalType = command.channelSignalType, channelEventFilter = command.channelEventFilter, - methodRunId = methodRun.methodRunId, + methodRunId = state.runningMethodRunId!!, commandId = newCommand.commandId ) ) - - addPastCommand(methodRun, newCommand) } private fun CoroutineScope.dispatchTask( output: WorkflowEngineOutput, - methodRun: MethodRun, - newCommand: NewCommand, + newCommand: DispatchTaskPastCommand, state: WorkflowState ) { - val command = newCommand.command as DispatchTaskInWorkflow + val command: DispatchTaskCommand = newCommand.command // send task to task engine val dispatchTask = DispatchTask( - clientName = ClientName("workflow engine"), - clientWaiting = false, - taskId = TaskId(newCommand.commandId.id), taskName = command.taskName, + taskId = TaskId.from(newCommand.commandId), + taskOptions = command.taskOptions, + clientWaiting = false, methodName = command.methodName, methodParameterTypes = command.methodParameterTypes, methodParameters = command.methodParameters, workflowId = state.workflowId, workflowName = state.workflowName, - methodRunId = methodRun.methodRunId, + methodRunId = state.runningMethodRunId, taskTags = command.taskTags, taskMeta = command.taskMeta, - taskOptions = command.taskOptions + emitterName = ClientName("workflow engine") ) launch { output.sendToTaskEngine(dispatchTask) } // add provided tags dispatchTask.taskTags.forEach { - val addTaskTag = AddTaskTag( - taskTag = it, + val addTagToTask = AddTagToTask( taskName = dispatchTask.taskName, - taskId = dispatchTask.taskId + taskTag = it, + taskId = dispatchTask.taskId, + emitterName = output.clientName ) - launch { output.sendToTaskTagEngine(addTaskTag) } + launch { output.sendToTaskTagEngine(addTagToTask) } } - - addPastCommand(methodRun, newCommand) } -private fun CoroutineScope.dispatchChildWorkflow( +private fun CoroutineScope.dispatchWorkflow( output: WorkflowEngineOutput, - methodRun: MethodRun, - newCommand: NewCommand, + newCommand: DispatchWorkflowPastCommand, state: WorkflowState ) { - val command = newCommand.command as DispatchChildWorkflow + val command: DispatchWorkflowCommand = newCommand.command // send task to task engine val dispatchWorkflow = DispatchWorkflow( - clientName = ClientName("workflow engine"), - clientWaiting = false, - workflowId = WorkflowId(newCommand.commandId.id), - workflowName = command.childWorkflowName, - parentWorkflowId = state.workflowId, - parentWorkflowName = state.workflowName, - parentMethodRunId = methodRun.methodRunId, - methodName = command.childMethodName, - methodParameterTypes = command.childMethodParameterTypes, - methodParameters = command.childMethodParameters, + workflowName = command.workflowName, + workflowId = WorkflowId.from(newCommand.commandId), + methodName = command.methodName, + methodParameters = command.methodParameters, + methodParameterTypes = command.methodParameterTypes, + workflowOptions = state.workflowOptions, workflowTags = state.workflowTags, workflowMeta = state.workflowMeta, - workflowOptions = state.workflowOptions + parentWorkflowName = state.workflowName, + parentWorkflowId = state.workflowId, + parentMethodRunId = state.runningMethodRunId, + clientWaiting = false, + emitterName = ClientName("workflow engine") ) launch { output.sendToWorkflowEngine(dispatchWorkflow) } // add provided tags dispatchWorkflow.workflowTags.forEach { - val addWorkflowTag = AddWorkflowTag( - workflowTag = it, + val addTagToWorkflow = AddTagToWorkflow( workflowName = dispatchWorkflow.workflowName, - workflowId = dispatchWorkflow.workflowId + workflowTag = it, + workflowId = dispatchWorkflow.workflowId, + emitterName = output.clientName ) - launch { output.sendToWorkflowTagEngine(addWorkflowTag) } + launch { output.sendToWorkflowTagEngine(addTagToWorkflow) } } - - addPastCommand(methodRun, newCommand) } -private fun addPastCommand( - methodRun: MethodRun, - newCommand: NewCommand -): PastCommand { - val pastCommand = PastCommand( - commandPosition = newCommand.commandPosition, - commandType = newCommand.commandType, - commandId = newCommand.commandId, - commandHash = newCommand.commandHash, - commandName = newCommand.commandName, - commandSimpleName = newCommand.commandSimpleName, - commandStatus = CommandStatus.Running - ) +private fun getDispatchMethod( + emitterName: ClientName, + commandId: CommandId, + command: DispatchMethodCommand, + state: WorkflowState +) = DispatchMethod( + workflowName = command.workflowName, + workflowId = command.workflowId!!, + methodRunId = MethodRunId.from(commandId), + methodName = command.methodName, + methodParameters = command.methodParameters, + methodParameterTypes = command.methodParameterTypes, + parentWorkflowId = state.workflowId, + parentWorkflowName = state.workflowName, + parentMethodRunId = state.runningMethodRunId, + clientWaiting = false, + emitterName = emitterName +) + +private fun CoroutineScope.dispatchMethod( + output: WorkflowEngineOutput, + newCommand: DispatchMethodPastCommand, + state: WorkflowState, + bufferedMessages: MutableList + +) { + val command: DispatchMethodCommand = newCommand.command + + when { + command.workflowId != null -> { + val dispatchMethodRun = getDispatchMethod(output.clientName, newCommand.commandId, command, state) + + when (command.workflowId) { + state.workflowId -> + // dispatch method on this workflow + bufferedMessages.add(dispatchMethodRun) + else -> + // dispatch method on another workflow + launch { output.sendToWorkflowEngine(dispatchMethodRun) } + } + } + command.workflowTag != null -> { + if (state.workflowTags.contains(command.workflowTag!!)) { + // dispatch method on this workflow + bufferedMessages.add(getDispatchMethod(output.clientName, newCommand.commandId, command, state)) + } - methodRun.pastCommands.add(pastCommand) + val dispatchMethodByTag = DispatchMethodByTag( + workflowName = command.workflowName, + workflowTag = command.workflowTag!!, + parentWorkflowId = state.workflowId, + parentWorkflowName = state.workflowName, + parentMethodRunId = state.runningMethodRunId, + methodRunId = MethodRunId.from(newCommand.commandId), + methodName = command.methodName, + methodParameterTypes = command.methodParameterTypes, + methodParameters = command.methodParameters, + clientWaiting = false, + emitterName = output.clientName + ) + // tag engine must ignore this message if parentWorkflowId has the provided tag + launch { output.sendToWorkflowTagEngine(dispatchMethodByTag) } + } + else -> thisShouldNotHappen() + } +} - return pastCommand +private fun getSendSignal( + emitterName: ClientName, + commandId: CommandId, + command: SendSignalCommand +) = SendSignal( + workflowName = command.workflowName, + workflowId = command.workflowId!!, + channelName = command.channelName, + channelSignalId = ChannelSignalId.from(commandId), + channelSignal = command.channelSignal, + channelSignalTypes = command.channelSignalTypes, + emitterName = emitterName +) + +private fun CoroutineScope.sendSignal( + output: WorkflowEngineOutput, + newCommand: SendSignalPastCommand, + state: WorkflowState, + bufferedMessages: MutableList +) { + val command: SendSignalCommand = newCommand.command + + when { + command.workflowId != null -> { + val sendToChannel = getSendSignal(output.clientName, newCommand.commandId, command) + + when (command.workflowId) { + state.workflowId -> + // dispatch signal on current workflow + bufferedMessages.add(sendToChannel) + else -> + // dispatch signal on another workflow + launch { output.sendToWorkflowEngine(sendToChannel) } + } + } + command.workflowTag != null -> { + if (state.workflowTags.contains(command.workflowTag!!)) { + val sendToChannel = getSendSignal(output.clientName, newCommand.commandId, command) + bufferedMessages.add(sendToChannel) + } + // dispatch signal per tag + val sendSignalByTag = SendSignalByTag( + workflowName = command.workflowName, + workflowTag = command.workflowTag!!, + channelName = command.channelName, + channelSignalId = ChannelSignalId(), + channelSignal = command.channelSignal, + channelSignalTypes = command.channelSignalTypes, + emitterWorkflowId = state.workflowId, + emitterName = output.clientName + ) + launch { output.sendToWorkflowTagEngine(sendSignalByTag) } + } + else -> thisShouldNotHappen() + } } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskFailed.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskFailed.kt index fae346677..041e72909 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskFailed.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/handlers/workflowTaskFailed.kt @@ -25,16 +25,14 @@ package io.infinitic.workflows.engine.handlers -import io.infinitic.common.clients.messages.WorkflowFailed -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandStatus.Canceled -import io.infinitic.common.workflows.data.commands.CommandStatus.Completed -import io.infinitic.common.workflows.data.commands.CommandStatus.CurrentlyFailed -import io.infinitic.common.workflows.data.commands.CommandStatus.Running -import io.infinitic.common.workflows.engine.messages.ChildWorkflowFailed +import io.infinitic.common.clients.messages.MethodFailed +import io.infinitic.common.errors.FailedWorkflowError +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.workflows.data.methodRuns.MethodRun +import io.infinitic.common.workflows.engine.messages.ChildMethodFailed import io.infinitic.common.workflows.engine.messages.TaskFailed +import io.infinitic.common.workflows.engine.messages.WorkflowEngineMessage import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.workflows.engine.output.WorkflowEngineOutput import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -42,41 +40,52 @@ import kotlinx.coroutines.launch internal fun CoroutineScope.workflowTaskFailed( output: WorkflowEngineOutput, state: WorkflowState, - msg: TaskFailed -) { - // if on main path, forward the error - if (state.isRunningWorkflowTaskOnMainPath()) { - val methodRun = state.getRunningMethodRun() + message: TaskFailed +): MutableList { + val methodRun: MethodRun = state.getRunningMethodRun() - // if the error is due to a a command failure, we enrich the error - val error = when (msg.error.errorCause == null && msg.error.whereId != null) { - true -> msg.error.copy( - errorCause = when (val commandStatus = methodRun.getPastCommand(CommandId(msg.error.whereId!!)).commandStatus) { - is Completed -> thisShouldNotHappen() - Running -> thisShouldNotHappen() - is CurrentlyFailed -> commandStatus.error - is Canceled -> null - } - ) - false -> msg.error - } + val deferredError = when (val deferredError = message.deferredError) { + null -> message.failedTaskError + else -> deferredError + } - // send to waiting clients - methodRun.waitingClients.forEach { - val workflowFailed = WorkflowFailed(it, state.workflowId, error) - launch { output.sendEventsToClient(workflowFailed) } - } + // send to waiting clients + methodRun.waitingClients.forEach { + val methodFailed = MethodFailed( + recipientName = it, + workflowId = state.workflowId, + methodRunId = methodRun.methodRunId, + cause = deferredError, + emitterName = output.clientName + ) + launch { output.sendEventsToClient(methodFailed) } + } + methodRun.waitingClients.clear() + + val bufferedMessages = mutableListOf() - // send to parent workflow - methodRun.parentWorkflowId?. run { - val childWorkflowFailed = ChildWorkflowFailed( - workflowId = methodRun.parentWorkflowId!!, - workflowName = methodRun.parentWorkflowName!!, - methodRunId = methodRun.parentMethodRunId!!, - childWorkflowId = state.workflowId, - childWorkflowError = error - ) - launch { output.sendToWorkflowEngine(childWorkflowFailed) } + // send to parent workflow + methodRun.parentWorkflowId?.let { + val childMethodFailed = ChildMethodFailed( + workflowId = it, + workflowName = methodRun.parentWorkflowName ?: thisShouldNotHappen(), + methodRunId = methodRun.parentMethodRunId ?: thisShouldNotHappen(), + childFailedWorkflowError = FailedWorkflowError( + workflowName = state.workflowName, + workflowId = state.workflowId, + methodName = methodRun.methodName, + methodRunId = methodRun.methodRunId, + deferredError = deferredError + ), + emitterName = output.clientName + ) + if (it == state.workflowId) { + // case of method dispatched within same workflow + bufferedMessages.add(childMethodFailed) + } else { + launch { output.sendToWorkflowEngine(childMethodFailed) } } } + + return bufferedMessages } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/commandTerminated.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/commandTerminated.kt index b11ea1377..e73ec609a 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/commandTerminated.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/commandTerminated.kt @@ -25,10 +25,10 @@ package io.infinitic.workflows.engine.helpers +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.workflows.data.commands.CommandId import io.infinitic.common.workflows.data.commands.CommandStatus import io.infinitic.common.workflows.data.commands.PastCommand -import io.infinitic.common.workflows.data.methodRuns.MethodRun import io.infinitic.common.workflows.data.methodRuns.MethodRunId import io.infinitic.common.workflows.data.workflowTasks.plus import io.infinitic.common.workflows.engine.state.WorkflowState @@ -42,14 +42,14 @@ import kotlinx.coroutines.CoroutineScope // trigger a new workflow task for the *first* step solved by this command // note: pastSteps is ordered per workflowTaskIndex (time) => the first completed step is the earliest internal fun CoroutineScope.commandTerminated( - workflowEngineOutput: WorkflowEngineOutput, + output: WorkflowEngineOutput, state: WorkflowState, methodRunId: MethodRunId, commandId: CommandId, commandStatus: CommandStatus ) { - val methodRun = state.getMethodRun(methodRunId)!! - val pastCommand = methodRun.getPastCommand(commandId) + val methodRun = state.getMethodRun(methodRunId) ?: thisShouldNotHappen() + val pastCommand = state.getPastCommand(commandId, methodRun) // do nothing if this command is already terminated // (i.e. canceled or completed, not failed as it's a transient status) @@ -58,39 +58,48 @@ internal fun CoroutineScope.commandTerminated( // update command status pastCommand.commandStatus = commandStatus - if (stepTerminated( - workflowEngineOutput, - state, - methodRun, - pastCommand - ) - ) { + if (stepTerminated(output, state, pastCommand)) { // keep this command as we could have another pastStep solved by it - state.runningMethodRunBufferedCommands.add(commandId) + state.runningTerminatedCommands.add(0, commandId) } } -// trigger a new workflow task for the *first* step solved by this command -// note: pastSteps is ordered per workflowTaskIndex (time) => the first completed step is the earliest +// search the first step completed by this command internal fun CoroutineScope.stepTerminated( - workflowEngineOutput: WorkflowEngineOutput, + output: WorkflowEngineOutput, state: WorkflowState, - methodRun: MethodRun, pastCommand: PastCommand -): Boolean = when (val pastStep = methodRun.pastSteps.find { it.isTerminatedBy(pastCommand) }) { - null -> false - else -> { +): Boolean { + // get all methodRuns terminated by this command + val methodRuns = state.methodRuns + .filter { it.currentStep?.isTerminatedBy(pastCommand) == true } + + // get step with lowest workflowTaskIndexAtStart + methodRuns.minByOrNull { it.currentStep!!.workflowTaskIndexAtStart }?.let { + val pastStep = it.currentStep!! + // terminate step + pastStep.updateWith(pastCommand) // update pastStep with a copy (!) of current properties and anticipated workflowTaskIndex pastStep.propertiesNameHashAtTermination = state.currentPropertiesNameHash.toMap() pastStep.workflowTaskIndexAtTermination = state.workflowTaskIndex + 1 + // we need to add this check to handle the case of ongoing task failure + if (pastStep.isTerminated()) { + it.pastSteps.add(pastStep) + it.currentStep = null + } + // dispatch a new workflowTask dispatchWorkflowTask( - workflowEngineOutput, + output, state, - methodRun, + it, pastStep.stepPosition ) - true + + // keep this command if more than 1 methodRun is completed by this command + return methodRuns.size > 1 } + + return false } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/dispatchWorkflowTask.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/dispatchWorkflowTask.kt index bc110c738..928367ddb 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/dispatchWorkflowTask.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/dispatchWorkflowTask.kt @@ -25,7 +25,6 @@ package io.infinitic.workflows.engine.helpers -import io.infinitic.common.clients.data.ClientName import io.infinitic.common.data.MillisInstant import io.infinitic.common.data.methods.MethodName import io.infinitic.common.data.methods.MethodParameterTypes @@ -46,7 +45,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch internal fun CoroutineScope.dispatchWorkflowTask( - workflowEngineOutput: WorkflowEngineOutput, + output: WorkflowEngineOutput, state: WorkflowState, methodRun: MethodRun, methodRunPosition: MethodRunPosition @@ -62,16 +61,15 @@ internal fun CoroutineScope.dispatchWorkflowTask( workflowMeta = state.workflowMeta, workflowPropertiesHashValue = state.propertiesHashValue, // TODO filterStore(state.propertyStore, listOf(methodRun)) workflowTaskIndex = state.workflowTaskIndex, - methodRun = methodRun, - targetPosition = methodRunPosition + methodRun = methodRun ) // defines workflow task val workflowTask = DispatchTask( - clientName = ClientName("workflow engine"), - clientWaiting = false, - taskId = TaskId(), taskName = TaskName(WorkflowTask::class.java.name), + taskId = TaskId(), + taskOptions = TaskOptions(), + clientWaiting = false, methodName = MethodName(WorkflowTask::handle.name), methodParameterTypes = MethodParameterTypes(listOf(WorkflowTaskParameters::class.java.name)), methodParameters = MethodParameters.from(workflowTaskParameters), @@ -79,12 +77,12 @@ internal fun CoroutineScope.dispatchWorkflowTask( workflowName = state.workflowName, methodRunId = methodRun.methodRunId, taskTags = setOf(), - taskOptions = TaskOptions(), - taskMeta = TaskMeta() + taskMeta = TaskMeta(), + emitterName = output.clientName ) // dispatch workflow task - launch { workflowEngineOutput.sendToTaskEngine(workflowTask) } + launch { output.sendToTaskEngine(workflowTask) } with(state) { runningWorkflowTaskId = workflowTask.taskId diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/removeTags.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/removeTags.kt index 3b62706b6..b84ce0eeb 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/removeTags.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/helpers/removeTags.kt @@ -25,20 +25,20 @@ package io.infinitic.workflows.engine.helpers -import io.infinitic.common.workflows.data.workflowTasks.plus import io.infinitic.common.workflows.engine.state.WorkflowState -import io.infinitic.common.workflows.tags.messages.RemoveWorkflowTag +import io.infinitic.common.workflows.tags.messages.RemoveTagFromWorkflow import io.infinitic.workflows.engine.output.WorkflowEngineOutput import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -internal fun CoroutineScope.removeTags(workflowEngineOutput: WorkflowEngineOutput, state: WorkflowState) { +internal fun CoroutineScope.removeTags(output: WorkflowEngineOutput, state: WorkflowState) { state.workflowTags.map { - val removeWorkflowTag = RemoveWorkflowTag( - workflowTag = it, + val removeTagFromWorkflow = RemoveTagFromWorkflow( workflowName = state.workflowName, + workflowTag = it, workflowId = state.workflowId, + emitterName = output.clientName, ) - launch { workflowEngineOutput.sendToWorkflowTagEngine(removeWorkflowTag) } + launch { output.sendToWorkflowTagEngine(removeTagFromWorkflow) } } } diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/output/WorkflowEngineOutput.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/output/WorkflowEngineOutput.kt index 7aac96cdc..912252bb6 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/output/WorkflowEngineOutput.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/output/WorkflowEngineOutput.kt @@ -26,6 +26,7 @@ package io.infinitic.workflows.engine.output import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.tags.SendToTaskTagEngine import io.infinitic.common.workflows.engine.SendToWorkflowEngine @@ -33,6 +34,7 @@ import io.infinitic.common.workflows.engine.SendToWorkflowEngineAfter import io.infinitic.common.workflows.tags.SendToWorkflowTagEngine internal data class WorkflowEngineOutput( + val clientName: ClientName, val sendEventsToClient: SendToClient, val sendToTaskTagEngine: SendToTaskTagEngine, val sendToTaskEngine: SendToTaskEngine, diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowDelayEngine.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowDelayEngine.kt index 299d331fb..670c675da 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowDelayEngine.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowDelayEngine.kt @@ -41,11 +41,11 @@ private fun logError(messageToProcess: WorkflowEngineMessageToProcess, e: Throwa } fun CoroutineScope.startWorkflowDelayEngine( - coroutineName: String, + name: String, inputChannel: ReceiveChannel, outputChannel: SendChannel, sendToWorkflowEngine: SendToWorkflowEngine, -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { for (messageToProcess in inputChannel) { try { diff --git a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowEngine.kt b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowEngine.kt index 50beebfa1..94a9d9ee5 100644 --- a/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowEngine.kt +++ b/infinitic-workflow-engine/src/main/kotlin/io/infinitic/workflows/engine/worker/StartWorkflowEngine.kt @@ -26,6 +26,7 @@ package io.infinitic.workflows.engine.worker import io.infinitic.common.clients.transport.SendToClient +import io.infinitic.common.data.ClientName import io.infinitic.common.tasks.engine.SendToTaskEngine import io.infinitic.common.tasks.tags.SendToTaskTagEngine import io.infinitic.common.workers.MessageToProcess @@ -53,7 +54,7 @@ private fun logError(messageToProcess: WorkflowEngineMessageToProcess, e: Throwa } fun CoroutineScope.startWorkflowEngine( - coroutineName: String, + name: String, workflowStateStorage: WorkflowStateStorage, eventsInputChannel: ReceiveChannel, eventsOutputChannel: SendChannel, @@ -65,9 +66,10 @@ fun CoroutineScope.startWorkflowEngine( sendToWorkflowTagEngine: SendToWorkflowTagEngine, sendToWorkflowEngine: SendToWorkflowEngine, sendToWorkflowEngineAfter: SendToWorkflowEngineAfter -) = launch(CoroutineName(coroutineName)) { +) = launch(CoroutineName(name)) { val workflowEngine = WorkflowEngine( + ClientName(name), workflowStateStorage, sendEventsToClient, sendToTaskTagEngine, diff --git a/infinitic-workflow-task/build.gradle.kts b/infinitic-workflow-task/build.gradle.kts index 731211163..c1ef40e1b 100644 --- a/infinitic-workflow-task/build.gradle.kts +++ b/infinitic-workflow-task/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(Libs.JsonPath.jayway) implementation(project(":infinitic-common")) + implementation(project(":infinitic-client")) implementation(project(":infinitic-task-executor")) } diff --git a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowContextImpl.kt b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowContextImpl.kt index fdba97203..0703b6fb9 100644 --- a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowContextImpl.kt +++ b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowContextImpl.kt @@ -27,14 +27,15 @@ package io.infinitic.workflows.workflowTask import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskParameters import io.infinitic.workflows.WorkflowContext -import java.util.UUID internal data class WorkflowContextImpl( private val workflowTaskParameters: WorkflowTaskParameters ) : WorkflowContext { + // workflow name + override val name: String = workflowTaskParameters.workflowName.toString() - // internal workflow id - override val id: UUID = workflowTaskParameters.workflowId.id + // workflow id + override val id: String = workflowTaskParameters.workflowId.toString() // workflow tags provided at launch override val tags: Set = workflowTaskParameters.workflowTags.map { it.tag }.toSet() diff --git a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowDispatcherImpl.kt b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowDispatcherImpl.kt index 7d38a47d0..d757499a5 100644 --- a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowDispatcherImpl.kt +++ b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowDispatcherImpl.kt @@ -28,492 +28,355 @@ package io.infinitic.workflows.workflowTask import com.jayway.jsonpath.Criteria import io.infinitic.common.data.MillisDuration import io.infinitic.common.data.MillisInstant -import io.infinitic.common.data.Name -import io.infinitic.common.data.methods.MethodName -import io.infinitic.common.data.methods.MethodParameterTypes -import io.infinitic.common.data.methods.MethodParameters -import io.infinitic.common.proxies.SendChannelProxyHandler -import io.infinitic.common.proxies.TaskProxyHandler -import io.infinitic.common.proxies.WorkflowProxyHandler -import io.infinitic.common.workflows.data.channels.ChannelEvent +import io.infinitic.common.data.ReturnValue +import io.infinitic.common.exceptions.thisShouldNotHappen +import io.infinitic.common.proxies.ChannelProxyHandler +import io.infinitic.common.proxies.GetTaskProxyHandler +import io.infinitic.common.proxies.GetWorkflowProxyHandler +import io.infinitic.common.proxies.NewTaskProxyHandler +import io.infinitic.common.proxies.NewWorkflowProxyHandler +import io.infinitic.common.proxies.ProxyHandler import io.infinitic.common.workflows.data.channels.ChannelEventFilter -import io.infinitic.common.workflows.data.channels.ChannelEventType -import io.infinitic.common.workflows.data.channels.ChannelImpl import io.infinitic.common.workflows.data.channels.ChannelName +import io.infinitic.common.workflows.data.channels.ChannelSignal +import io.infinitic.common.workflows.data.channels.ChannelSignalType import io.infinitic.common.workflows.data.commands.Command -import io.infinitic.common.workflows.data.commands.CommandId -import io.infinitic.common.workflows.data.commands.CommandReturnValue import io.infinitic.common.workflows.data.commands.CommandSimpleName import io.infinitic.common.workflows.data.commands.CommandStatus -import io.infinitic.common.workflows.data.commands.CommandType -import io.infinitic.common.workflows.data.commands.DispatchChildWorkflow -import io.infinitic.common.workflows.data.commands.DispatchTask -import io.infinitic.common.workflows.data.commands.EndAsync -import io.infinitic.common.workflows.data.commands.EndInlineTask -import io.infinitic.common.workflows.data.commands.NewCommand +import io.infinitic.common.workflows.data.commands.DispatchMethodCommand +import io.infinitic.common.workflows.data.commands.DispatchTaskCommand +import io.infinitic.common.workflows.data.commands.DispatchWorkflowCommand +import io.infinitic.common.workflows.data.commands.InlineTaskCommand import io.infinitic.common.workflows.data.commands.PastCommand -import io.infinitic.common.workflows.data.commands.ReceiveInChannel -import io.infinitic.common.workflows.data.commands.SendToChannel -import io.infinitic.common.workflows.data.commands.StartAsync -import io.infinitic.common.workflows.data.commands.StartDurationTimer -import io.infinitic.common.workflows.data.commands.StartInlineTask -import io.infinitic.common.workflows.data.commands.StartInstantTimer +import io.infinitic.common.workflows.data.commands.ReceiveSignalCommand +import io.infinitic.common.workflows.data.commands.SendSignalCommand +import io.infinitic.common.workflows.data.commands.StartDurationTimerCommand +import io.infinitic.common.workflows.data.commands.StartInstantTimerCommand +import io.infinitic.common.workflows.data.methodRuns.MethodRunPosition import io.infinitic.common.workflows.data.properties.PropertyHash import io.infinitic.common.workflows.data.properties.PropertyName -import io.infinitic.common.workflows.data.properties.PropertyValue import io.infinitic.common.workflows.data.steps.NewStep import io.infinitic.common.workflows.data.steps.PastStep import io.infinitic.common.workflows.data.steps.Step +import io.infinitic.common.workflows.data.steps.StepStatus import io.infinitic.common.workflows.data.steps.StepStatus.Canceled import io.infinitic.common.workflows.data.steps.StepStatus.Completed +import io.infinitic.common.workflows.data.steps.StepStatus.CurrentlyFailed import io.infinitic.common.workflows.data.steps.StepStatus.Failed -import io.infinitic.common.workflows.data.steps.StepStatus.OngoingFailure +import io.infinitic.common.workflows.data.steps.StepStatus.Unknown import io.infinitic.common.workflows.data.steps.StepStatus.Waiting import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskParameters -import io.infinitic.exceptions.thisShouldNotHappen -import io.infinitic.exceptions.workflows.CanceledDeferredException -import io.infinitic.exceptions.workflows.FailedDeferredException -import io.infinitic.exceptions.workflows.ShouldNotUseAsyncFunctionInsideInlinedTaskException -import io.infinitic.exceptions.workflows.ShouldNotWaitInsideInlinedTaskException -import io.infinitic.exceptions.workflows.WorkflowUpdatedWhileRunningException +import io.infinitic.exceptions.CanceledDeferredException +import io.infinitic.exceptions.FailedDeferredException +import io.infinitic.exceptions.UnknownDeferredException +import io.infinitic.exceptions.clients.InvalidChannelUsageException +import io.infinitic.exceptions.clients.InvalidRunningTaskException +import io.infinitic.exceptions.workflows.WorkflowUpdatedException +import io.infinitic.workflows.Channel import io.infinitic.workflows.Deferred import io.infinitic.workflows.DeferredStatus +import io.infinitic.workflows.SendChannel import io.infinitic.workflows.WorkflowDispatcher import mu.KotlinLogging -import java.lang.reflect.Proxy -import java.util.concurrent.CompletableFuture import java.time.Duration as JavaDuration import java.time.Instant as JavaInstant internal class WorkflowDispatcherImpl( private val workflowTaskParameters: WorkflowTaskParameters, - private val setProperties: (Map, Map) -> Unit ) : WorkflowDispatcher { private val logger = KotlinLogging.logger {} - // position in the current method processing - private var methodRunIndex = MethodRunIndex() - - // current workflowTaskIndex (useful to retrieve status of Deferred) - private var workflowTaskIndex = workflowTaskParameters.methodRun.workflowTaskIndexAtStart + // function used to set properties on workflow + lateinit var setProperties: (Map) -> Unit // new commands discovered during execution of the method - var newCommands: MutableList = mutableListOf() - - // new steps discovered during execution the method (possibly more than one due to `async` function) - var newSteps: MutableList = mutableListOf() - - /* - * Async Task dispatching - */ - override fun dispatch( - proxy: T, - method: T.() -> S - ): Deferred = when (val handler = Proxy.getInvocationHandler(proxy)) { - is TaskProxyHandler<*> -> { - handler.isSync = false - proxy.method() - dispatchTask(handler) - } - is WorkflowProxyHandler<*> -> { - handler.isSync = false - proxy.method() - dispatchWorkflow(handler) - } - else -> throw RuntimeException("Not Yet Implemented") - } - - /* - * Async Branch dispatching - */ - override fun async(branch: () -> S): Deferred { - // increment position - positionNext() - - // create instruction that will be sent to engine only if new - val newCommand = NewCommand( - command = StartAsync, - commandName = null, - commandSimpleName = CommandSimpleName("${CommandType.START_ASYNC}"), - commandPosition = methodRunIndex.methodPosition - ) + var newCommands: MutableList = mutableListOf() - val pastCommand = getSimilarPastCommand(newCommand) + // new step discovered during execution the method + var newStep: NewStep? = null - if (pastCommand == null) { - // if this is a new command, we add it to the newCommands list - newCommands.add(newCommand) - - // returns a Deferred with an ongoing step - return Deferred(Step.Id.from(newCommand), this) - } - - // async branch is processed only if on path of targetPosition - if (methodRunIndex.leadsTo(workflowTaskParameters.targetPosition)) { - // update workflowTaskIndex to value linked to first processing of this branch - workflowTaskIndex = pastCommand.workflowTaskIndexAtStart!! - - // update workflow instance properties - setProperties( - workflowTaskParameters.workflowPropertiesHashValue, - pastCommand.propertiesNameHashAtStart!! - ) - - // go down - positionDown() - - // exceptions caught by runMethod - val commandOutput = CommandReturnValue.from(branch()) - - // go up - positionUp() + // position in the current method processing + private var methodRunPosition = MethodRunPosition.new() - newCommands.add( - NewCommand( - command = EndAsync(commandOutput), - commandName = null, - commandSimpleName = CommandSimpleName("${CommandType.END_ASYNC}"), - commandPosition = methodRunIndex.methodPosition - ) - ) + // current workflowTaskIndex (useful to retrieve status of Deferred) + private var workflowTaskIndex = workflowTaskParameters.methodRun.workflowTaskIndexAtStart - // async is completed - throw AsyncCompletedException + // synchronous call: stub.method(*args) + @Suppress("UNCHECKED_CAST") + override fun dispatchAndWait(handler: ProxyHandler<*>): R = + when (handler is GetWorkflowProxyHandler<*> && handler.isChannelGetter()) { + true -> ChannelProxyHandler>(handler).stub() as R + false -> dispatch(handler, true).await() } - // returns a Deferred linked to pastCommand - return Deferred(Step.Id.from(pastCommand), this) + // asynchronous call: dispatch(stub::method)(*args) + override fun dispatch(handler: ProxyHandler<*>, clientWaiting: Boolean): Deferred = when (handler) { + is NewTaskProxyHandler -> dispatchTask(handler) + is NewWorkflowProxyHandler -> dispatchWorkflow(handler) + is GetTaskProxyHandler -> throw InvalidRunningTaskException("${handler.stub()}") + is GetWorkflowProxyHandler -> dispatchMethod(handler) + is ChannelProxyHandler -> dispatchSignal(handler) } - /* + /** * Inlined task */ override fun inline(task: () -> S): S { // increment position - positionNext() - - // create instruction that will be sent to engine only if new - val startCommand = NewCommand( - command = StartInlineTask, - commandName = null, - commandSimpleName = CommandSimpleName("${CommandType.START_INLINE_TASK}"), - commandPosition = methodRunIndex.methodPosition - ) - - val pastCommand = getSimilarPastCommand(startCommand) - - if (pastCommand == null) { - // if this is a new command, we add it to the newCommands list - newCommands.add(startCommand) - // go down (in case this inline task asynchronously dispatches some tasks) - positionDown() - // run inline task - val commandOutput = try { CommandReturnValue.from(task()) } catch (e: Exception) { - when (e) { - is NewStepException, is KnownStepException -> throw ShouldNotWaitInsideInlinedTaskException(workflowTaskParameters.getFullMethodName()) - is AsyncCompletedException -> throw ShouldNotUseAsyncFunctionInsideInlinedTaskException(workflowTaskParameters.getFullMethodName()) - else -> throw e - } + nextPosition() + + @Suppress("UNCHECKED_CAST") + return when (val pastCommand = getPastCommandAtCurrentPosition()) { + null -> { + // run inline task, checking that no proxy are used inside + val value = ProxyHandler.inline(task) + // record result + val command = InlineTaskCommand() + val endCommand = PastCommand.from( + commandPosition = methodRunPosition, + commandSimpleName = InlineTaskCommand.simpleName(), + commandStatus = CommandStatus.Completed(ReturnValue.from(value), workflowTaskIndex), + command = command, + ) + newCommands.add(endCommand) + // returns value + value } - // go up - positionUp() - // record result - val endCommand = NewCommand( - command = EndInlineTask(commandOutput), - commandName = null, - commandSimpleName = CommandSimpleName("${CommandType.END_INLINE_TASK}"), - commandPosition = methodRunIndex.methodPosition - ) - newCommands.add(endCommand) - // returns a Deferred with an ongoing step - @Suppress("UNCHECKED_CAST") - return commandOutput.get() as S - } else { - @Suppress("UNCHECKED_CAST") - return when (val status = pastCommand.commandStatus) { - is CommandStatus.Completed -> status.returnValue.get() as S - else -> thisShouldNotHappen("inline task with status $status") + else -> when (pastCommand.commandSimpleName == InlineTaskCommand.simpleName()) { + true -> when (val status = pastCommand.commandStatus) { + is CommandStatus.Completed -> status.returnValue.value() as S + else -> thisShouldNotHappen() + } + else -> throwWorkflowUpdatedException(pastCommand, null) } } } - /* + /** * Deferred await() */ @Suppress("UNCHECKED_CAST") override fun await(deferred: Deferred): T { // increment position - positionNext() + nextPosition() // create a new step - val newStep = NewStep( + val step = NewStep( step = deferred.step, - stepPosition = methodRunIndex.methodPosition + stepPosition = methodRunPosition ) - val pastStep = getSimilarPastStep(newStep) - - // new step (e.g. a logical combination of previous deferred) - if (pastStep == null) { - - // determine status - deferred.stepStatus = newStep.step.statusAt(workflowTaskIndex) - - return when (val stepStatus = deferred.stepStatus) { - is Completed -> stepStatus.returnValue.get() as T - is Waiting -> { - // add this step - newSteps.add(newStep) - throw NewStepException + return when (val pastStep = getSimilarPastStep(step)) { + // new step + null -> { + // determine status + when (val stepStatus = step.step.statusAt(workflowTaskIndex)) { + is Waiting -> { + // found a new step + newStep = step + + throw NewStepException + } + is Canceled, is Failed, is CurrentlyFailed, is Unknown -> + throw getDeferredException(stepStatus) + is Completed -> + stepStatus.returnValue.value() as T } - is Canceled -> throw CanceledDeferredException( - getCommandName(stepStatus.commandId)?.toString(), - stepStatus.commandId.id - ) - is Failed -> throw FailedDeferredException( - getCommandName(stepStatus.commandId)?.toString(), - stepStatus.commandId.id - ) - is OngoingFailure -> throw FailedDeferredException( - getCommandName(stepStatus.commandId)?.toString(), - stepStatus.commandId.id - ) } - } - - // known step - val stepStatus = pastStep.stepStatus - - // update deferred status - deferred.stepStatus = stepStatus - - // if still ongoing, we stop here - if (stepStatus == Waiting) throw KnownStepException + // known step + else -> { + val stepStatus = pastStep.stepStatus - // instance properties are now as when this deferred was terminated - setProperties( - workflowTaskParameters.workflowPropertiesHashValue, - pastStep.propertiesNameHashAtTermination!! - ) - - // return deferred value - return when (stepStatus) { - is Waiting -> thisShouldNotHappen() - is Completed -> { - // workflowTaskIndex is now the one where this deferred was completed - workflowTaskIndex = stepStatus.completionWorkflowTaskIndex - - stepStatus.returnValue.get() as T - } - is Canceled -> { - // workflowTaskIndex is now the one where this deferred was canceled - workflowTaskIndex = stepStatus.cancellationWorkflowTaskIndex + // if still ongoing, we stop here + if (stepStatus == Waiting) throw KnownStepException - throw CanceledDeferredException( - getCommandName(stepStatus.commandId)?.toString(), - stepStatus.commandId.id + // instance properties are now as when this deferred was terminated + setProperties( + pastStep.propertiesNameHashAtTermination ?: thisShouldNotHappen() ) - } - is Failed -> { - // workflowTaskIndex is now the one where this deferred was failed - workflowTaskIndex = stepStatus.failureWorkflowTaskIndex - - throw FailedDeferredException( - getCommandName(stepStatus.commandId)?.toString(), - stepStatus.commandId.id - ) - } - is OngoingFailure -> { - // workflowTaskIndex is now the one where this deferred was failed - workflowTaskIndex = stepStatus.failureWorkflowTaskIndex - throw FailedDeferredException( - getCommandName(stepStatus.commandId)?.toString(), - stepStatus.commandId.id - ) + // return deferred value + when (stepStatus) { + is Waiting -> { + thisShouldNotHappen() + } + is Unknown -> { + // workflowTaskIndex is now the one where this deferred was unknowing + workflowTaskIndex = stepStatus.unknowingWorkflowTaskIndex + + throw getDeferredException(stepStatus) + } + is Canceled -> { + // workflowTaskIndex is now the one where this deferred was canceled + workflowTaskIndex = stepStatus.cancellationWorkflowTaskIndex + + throw getDeferredException(stepStatus) + } + is CurrentlyFailed -> { + // workflowTaskIndex is now the one where this deferred was failed + workflowTaskIndex = stepStatus.failureWorkflowTaskIndex + + throw getDeferredException(stepStatus) + } + is Failed -> { + // workflowTaskIndex is now the one where this deferred was failed + workflowTaskIndex = stepStatus.failureWorkflowTaskIndex + + throw getDeferredException(stepStatus) + } + is Completed -> { + // workflowTaskIndex is now the one where this deferred was completed + workflowTaskIndex = stepStatus.completionWorkflowTaskIndex + + stepStatus.returnValue.value() as T + } + } } } } - /* - * Deferred status() + /** + * Deferred status */ override fun status(deferred: Deferred): DeferredStatus = when (deferred.step.statusAt(workflowTaskIndex)) { is Waiting -> DeferredStatus.ONGOING + is Unknown -> DeferredStatus.UNKNOWN is Completed -> DeferredStatus.COMPLETED is Canceled -> DeferredStatus.CANCELED is Failed -> DeferredStatus.FAILED - is OngoingFailure -> DeferredStatus.FAILED + is CurrentlyFailed -> DeferredStatus.FAILED } - /* - * Task dispatching - */ - override fun dispatchTask(handler: TaskProxyHandler<*>): Deferred { - val method = handler.method - val methodArgs = handler.methodArgs - - checkMethodIsNotSuspend(method) - - val deferred = dispatchCommand( - DispatchTask( - taskName = handler.taskName, - methodParameters = MethodParameters.from(method, methodArgs), - methodParameterTypes = MethodParameterTypes.from(method), - methodName = MethodName(handler.methodName), - taskTags = handler.taskTags!!, - taskMeta = handler.taskMeta!!, - taskOptions = handler.taskOptions!! - ), - CommandSimpleName(handler.simpleName) - ) - handler.reset() - return deferred - } - - override fun dispatchAndWait(handler: TaskProxyHandler<*>): S = - dispatchTask(handler).await() - - /* - * Workflow dispatching - */ - override fun dispatchWorkflow(handler: WorkflowProxyHandler<*>): Deferred { - val method = handler.method - val args = handler.methodArgs - val name = handler.methodName - - checkMethodIsNotSuspend(method) - - val deferred = dispatchCommand( - DispatchChildWorkflow( - childWorkflowName = handler.workflowName, - childMethodName = MethodName(name), - childMethodParameterTypes = MethodParameterTypes.from(method), - childMethodParameters = MethodParameters.from(method, args), - workflowTags = handler.workflowTags!!, - workflowMeta = handler.workflowMeta!!, - workflowOptions = handler.workflowOptions!! - ), - CommandSimpleName(handler.simpleName) - ) - handler.reset() - return deferred - } - - /* - * Start another running child workflow - */ - override fun dispatchAndWait(handler: WorkflowProxyHandler<*>): S = - dispatchWorkflow(handler).await() - - /* - * Send event to another workflow's channel - */ - override fun dispatchAndWait(handler: SendChannelProxyHandler<*>): CompletableFuture { - TODO("Not yet implemented") - } - - /* - * Start_Duration_Timer command dispatching - */ override fun timer(duration: JavaDuration): Deferred = dispatchCommand( - StartDurationTimer(MillisDuration(duration.toMillis())), - CommandSimpleName("${CommandType.START_DURATION_TIMER}") + StartDurationTimerCommand(MillisDuration(duration.toMillis())), + StartDurationTimerCommand.simpleName() ) - /* - * Start_Instant_Timer command dispatching - */ override fun timer(instant: JavaInstant): Deferred = dispatchCommand( - StartInstantTimer(MillisInstant(instant.toEpochMilli())), - CommandSimpleName("${CommandType.START_INSTANT_TIMER}") + StartInstantTimerCommand(MillisInstant(instant.toEpochMilli())), + StartInstantTimerCommand.simpleName() ) - /* - * Receive_From_Channel command - */ - override fun receiveFromChannel( - channel: ChannelImpl, - jsonPath: String?, - criteria: Criteria? - ): Deferred = dispatchCommand( - ReceiveInChannel( - ChannelName(channel.getNameOrThrow()), - null, - ChannelEventFilter.from(jsonPath, criteria) - - ), - CommandSimpleName("${CommandType.RECEIVE_IN_CHANNEL}") - ) - - /* - * Receive_From_Channel command with channelEventType - */ - override fun receiveFromChannel( - channel: ChannelImpl, - klass: Class, + override fun receiveSignal( + channel: Channel, + klass: Class?, jsonPath: String?, criteria: Criteria? ): Deferred = dispatchCommand( - ReceiveInChannel( - ChannelName(channel.getNameOrThrow()), - ChannelEventType.from(klass), + ReceiveSignalCommand( + ChannelName(channel.name), + klass?.let { ChannelSignalType.from(it) }, ChannelEventFilter.from(jsonPath, criteria) ), - CommandSimpleName("${CommandType.RECEIVE_IN_CHANNEL}") + ReceiveSignalCommand.simpleName() ) - /* - * Sent_to_Channel command dispatching - */ - override fun sendToChannel(channel: ChannelImpl, event: T) { + override fun sendSignal(channel: Channel, signal: T) { dispatchCommand( - SendToChannel( - ChannelName(channel.getNameOrThrow()), - ChannelEvent.from(event), - ChannelEventType.allFrom(event::class.java) + SendSignalCommand( + workflowName = workflowTaskParameters.workflowName, + workflowId = workflowTaskParameters.workflowId, + workflowTag = null, + channelName = ChannelName(channel.name), + channelSignalTypes = ChannelSignalType.allFrom(signal::class.java), + channelSignal = ChannelSignal.from(signal) ), - CommandSimpleName("${CommandType.SENT_TO_CHANNEL}") + SendSignalCommand.simpleName() ) } - /* + /** * Go to next position within the same branch */ - private fun positionNext() { - methodRunIndex = methodRunIndex.next() + private fun nextPosition() { + methodRunPosition = methodRunPosition.next() } - /* - * End of a async { ... } function + /** + * Task dispatching + */ + private fun dispatchTask(handler: NewTaskProxyHandler<*>): Deferred = dispatchCommand( + DispatchTaskCommand( + taskName = handler.taskName, + methodParameters = handler.methodParameters, + methodParameterTypes = handler.methodParameterTypes, + methodName = handler.methodName, + taskTags = handler.taskTags, + taskOptions = handler.taskOptions, + taskMeta = handler.taskMeta + ), + CommandSimpleName(handler.simpleName) + ) + + /** + * Workflow dispatching */ - private fun positionUp() { - methodRunIndex.up()?.let { methodRunIndex = it } + private fun dispatchWorkflow(handler: NewWorkflowProxyHandler<*>): Deferred = when (handler.isChannelGetter()) { + true -> throw InvalidChannelUsageException() + false -> dispatchCommand( + DispatchWorkflowCommand( + workflowName = handler.workflowName, + methodName = handler.methodName, + methodParameterTypes = handler.methodParameterTypes, + methodParameters = handler.methodParameters, + workflowTags = handler.workflowTags, + workflowOptions = handler.workflowOptions, + workflowMeta = handler.workflowMeta, + ), + CommandSimpleName(handler.simpleName) + ) + } + + /** + * Method dispatching + */ + private fun dispatchMethod(handler: GetWorkflowProxyHandler<*>): Deferred = when (handler.isChannelGetter()) { + true -> throw InvalidChannelUsageException() + false -> dispatchCommand( + DispatchMethodCommand( + workflowName = handler.workflowName, + workflowId = handler.workflowId, + workflowTag = handler.workflowTag, + methodName = handler.methodName, + methodParameterTypes = handler.methodParameterTypes, + methodParameters = handler.methodParameters + ), + CommandSimpleName(handler.simpleName) + ) } - /* - * Start of a async { ... } function + /** + * Signal dispatching */ - private fun positionDown() { - methodRunIndex = methodRunIndex.down() + private fun dispatchSignal(handler: ChannelProxyHandler<*>): Deferred { + if (handler.methodName.toString() != SendChannel<*>::send.name) thisShouldNotHappen() + + return dispatchCommand( + SendSignalCommand( + workflowName = handler.workflowName, + workflowId = handler.workflowId, + workflowTag = handler.workflowTag, + channelName = handler.channelName, + channelSignalTypes = handler.channelSignalTypes, + channelSignal = handler.channelSignal + ), + SendSignalCommand.simpleName() + ) } private fun dispatchCommand(command: Command, commandSimpleName: CommandSimpleName): Deferred { // increment position - positionNext() + nextPosition() - // create instruction that may be sent to engine - val newCommand = NewCommand( + // create instruction that will be sent to engine + // if it does not already exist in the history + val newCommand = PastCommand.from( command = command, - commandName = when (command) { - is DispatchTask -> command.taskName - is DispatchChildWorkflow -> command.childWorkflowName - else -> null - }, commandSimpleName = commandSimpleName, - commandPosition = methodRunIndex.methodPosition + commandPosition = methodRunPosition, + commandStatus = CommandStatus.Running, ) val pastCommand = getSimilarPastCommand(newCommand) @@ -522,53 +385,65 @@ internal class WorkflowDispatcherImpl( // if this is a new command, we add it to the newCommands list newCommands.add(newCommand) // and returns a Deferred with an ongoing step - Deferred(Step.Id.from(newCommand), this) + Deferred(Step.Id.from(newCommand)).apply { this.workflowDispatcher = this@WorkflowDispatcherImpl } } else { // else returns a Deferred linked to pastCommand - Deferred(Step.Id.from(pastCommand), this) + Deferred(Step.Id.from(pastCommand)).apply { this.workflowDispatcher = this@WorkflowDispatcherImpl } } } - private fun getSimilarPastCommand(newCommand: NewCommand): PastCommand? { - // find pastCommand in current position - val pastCommand = workflowTaskParameters.methodRun.pastCommands - .find { it.commandPosition == methodRunIndex.methodPosition } + private fun getPastCommandAtCurrentPosition(): PastCommand? = workflowTaskParameters.methodRun.pastCommands + .find { it.commandPosition == methodRunPosition } + + private fun throwWorkflowUpdatedException(pastCommand: PastCommand?, newCommand: PastCommand?): Nothing { + logger.error { "pastCommand = ${pastCommand?.command}" } + logger.error { "newCommand = ${newCommand?.command}" } + logger.error { "workflowChangeCheckMode = ${workflowTaskParameters.workflowOptions.workflowChangeCheckMode}" } + throw WorkflowUpdatedException( + workflowTaskParameters.workflowName.name, + "${workflowTaskParameters.methodRun.methodName}", + "$methodRunPosition" + ) + } + private fun getSimilarPastCommand(newCommand: PastCommand): PastCommand? { + // find pastCommand in current position + val pastCommand = getPastCommandAtCurrentPosition() // if it exists, check it has not changed - if (pastCommand != null && !pastCommand.isSameThan(newCommand, workflowTaskParameters.workflowOptions.workflowChangeCheckMode)) { - logger.error { "pastCommand = $pastCommand" } - logger.error { "newCommand = $newCommand" } - logger.error { "workflowChangeCheckMode = ${workflowTaskParameters.workflowOptions.workflowChangeCheckMode}" } - throw WorkflowUpdatedWhileRunningException( - workflowTaskParameters.workflowName.name, - "${workflowTaskParameters.methodRun.methodName}", - "${methodRunIndex.methodPosition}" - ) - } + if (pastCommand != null && !pastCommand.isSameThan(newCommand, workflowTaskParameters.workflowOptions.workflowChangeCheckMode)) + throwWorkflowUpdatedException(pastCommand, newCommand) return pastCommand } - private fun getSimilarPastStep(newStep: NewStep): PastStep? { - // find pastCommand in current position - val pastStep = workflowTaskParameters.methodRun.pastSteps - .find { it.stepPosition == methodRunIndex.methodPosition } + private fun getSimilarPastStep(step: NewStep): PastStep? { + // Do we already know a step in this position ? + val currentStep = workflowTaskParameters.methodRun.currentStep + val pastStep = if (currentStep?.stepPosition == methodRunPosition) { + currentStep + } else { + workflowTaskParameters.methodRun.pastSteps.find { it.stepPosition == methodRunPosition } + } // if it exists, check it has not changed - if (pastStep != null && !pastStep.isSimilarTo(newStep)) { + if (pastStep != null && !pastStep.isSameThan(step)) { logger.error { "pastStep = $pastStep" } - logger.error { "newStep = $newStep" } - throw WorkflowUpdatedWhileRunningException( + logger.error { "newStep = $step" } + throw WorkflowUpdatedException( workflowTaskParameters.workflowName.name, "${workflowTaskParameters.methodRun.methodName}", - "${methodRunIndex.methodPosition}" + "$methodRunPosition" ) } return pastStep } - private fun getCommandName(commandId: CommandId): Name? = workflowTaskParameters.methodRun - .pastCommands.firstOrNull { it.commandId == commandId }?.commandName - ?: newCommands.firstOrNull { it.commandId == commandId }?.commandName + private fun getDeferredException(stepStatus: StepStatus) = when (stepStatus) { + is Unknown -> UnknownDeferredException.from(stepStatus.unknownDeferredError) + is Canceled -> CanceledDeferredException.from(stepStatus.canceledDeferredError) + is Failed -> FailedDeferredException.from(stepStatus.failedDeferredError) + is CurrentlyFailed -> FailedDeferredException.from(stepStatus.failedDeferredError) + is Completed, Waiting -> thisShouldNotHappen() + } } diff --git a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskException.kt b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskException.kt index 62e1d7c17..396ff8ddf 100644 --- a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskException.kt +++ b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskException.kt @@ -30,5 +30,3 @@ internal sealed class WorkflowTaskException : RuntimeException() internal object NewStepException : WorkflowTaskException() internal object KnownStepException : WorkflowTaskException() - -internal object AsyncCompletedException : WorkflowTaskException() diff --git a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskImpl.kt b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskImpl.kt index 9e088cb43..b6b383089 100644 --- a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskImpl.kt +++ b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/WorkflowTaskImpl.kt @@ -25,21 +25,20 @@ package io.infinitic.workflows.workflowTask -import io.infinitic.common.data.methods.MethodReturnValue +import io.infinitic.common.data.ClientName +import io.infinitic.common.data.ReturnValue import io.infinitic.common.parser.getMethodPerNameAndParameters -import io.infinitic.common.workflows.data.channels.ChannelImpl import io.infinitic.common.workflows.data.properties.PropertyHash import io.infinitic.common.workflows.data.properties.PropertyName -import io.infinitic.common.workflows.data.properties.PropertyValue import io.infinitic.common.workflows.data.workflowTasks.WorkflowTask import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskParameters import io.infinitic.common.workflows.data.workflowTasks.WorkflowTaskReturnValue -import io.infinitic.exceptions.workflows.MultipleNamesForChannelException -import io.infinitic.exceptions.workflows.NonUniqueChannelFromChannelMethodException -import io.infinitic.exceptions.workflows.ParametersInChannelMethodException +import io.infinitic.exceptions.DeferredException +import io.infinitic.exceptions.FailedWorkflowTaskException +import io.infinitic.exceptions.WorkerException import io.infinitic.tasks.Task -import io.infinitic.workflows.Channel -import io.infinitic.workflows.Workflow +import io.infinitic.workflows.Deferred +import io.infinitic.workflows.setChannelNames import java.lang.reflect.InvocationTargetException import java.time.Duration @@ -51,80 +50,73 @@ class WorkflowTaskImpl : Task(), WorkflowTask { // get instance workflow by name val workflow = context.register.getWorkflowInstance("${workflowTaskParameters.workflowName}") - // setProperties function + // get method + val methodRun = workflowTaskParameters.methodRun + val method = getMethodPerNameAndParameters( + workflow::class.java, + "${methodRun.methodName}", + methodRun.methodParameterTypes?.types, + methodRun.methodParameters.size + ) + + // set context + val dispatcher = WorkflowDispatcherImpl(workflowTaskParameters) + workflow.context = WorkflowContextImpl(workflowTaskParameters) + workflow.dispatcher = dispatcher + + // define setProperties function val setProperties = { - hashValues: Map, nameHashes: Map -> - workflow.setProperties(hashValues, nameHashes) + // in case properties contain some Deferred + Deferred.setWorkflowDispatcher(dispatcher) + workflow.setProperties(workflowTaskParameters.workflowPropertiesHashValue, nameHashes) + Deferred.delWorkflowDispatcher() } - // set workflow's initial properties - setProperties( - workflowTaskParameters.workflowPropertiesHashValue, - workflowTaskParameters.methodRun.propertiesNameHashAtStart - ) + // give it to dispatcher + dispatcher.setProperties = setProperties - // set context - workflow.context = WorkflowContextImpl(workflowTaskParameters) - workflow.dispatcher = WorkflowDispatcherImpl(workflowTaskParameters, setProperties) + // set workflow's initial properties + setProperties(methodRun.propertiesNameHashAtStart) // initialize name of channels for this workflow, based on the methods that provide them - setChannelNames(workflow) + workflow.setChannelNames() - // get method - val methodRun = workflowTaskParameters.methodRun - val method = getMethodPerNameAndParameters( - workflow::class.java, - "${methodRun.methodName}", - methodRun.methodParameterTypes?.types, - methodRun.methodParameters.size - ) + // get method parameters + // in case parameters contain some Deferred + Deferred.setWorkflowDispatcher(dispatcher) + val parameters = methodRun.methodParameters.map { it.deserialize() }.toTypedArray() + Deferred.delWorkflowDispatcher() // run method and get return value (null if end not reached) - val parameters = methodRun.methodParameters.get().toTypedArray() - val methodReturnValue = try { - MethodReturnValue.from(method.invoke(workflow, *parameters)) + ReturnValue.from(method.invoke(workflow, *parameters)) } catch (e: InvocationTargetException) { - when (e.cause) { + when (val cause = e.cause) { is WorkflowTaskException -> null - else -> throw e.cause ?: e // this error will be caught by the task executor + // the errors below will be caught by the task executor + is DeferredException -> throw cause + else -> { + val throwable = cause ?: e + + throw FailedWorkflowTaskException( + workflowName = workflowTaskParameters.workflowName.toString(), + workflowId = workflowTaskParameters.workflowId.toString(), + workflowTaskId = context.id, + workerException = WorkerException.from(ClientName(context.client.name), throwable) + ) + } } } val properties = workflow.getProperties() return WorkflowTaskReturnValue( - (workflow.dispatcher as WorkflowDispatcherImpl).newCommands, - (workflow.dispatcher as WorkflowDispatcherImpl).newSteps, + dispatcher.newCommands, + dispatcher.newStep, properties, methodReturnValue ) } - - private fun setChannelNames(workflow: Workflow) { - workflow::class.java.declaredMethods - .filter { it.returnType.name == Channel::class.java.name } - .map { - // channel must not have parameters - if (it.parameterCount > 0) { - throw ParametersInChannelMethodException(workflow::class.java.name, it.name) - } - // channel must be created only once per method - it.isAccessible = true - val channel = it.invoke(workflow) - val channelBis = it.invoke(workflow) - if (channel !== channelBis) { - throw NonUniqueChannelFromChannelMethodException(workflow::class.java.name, it.name) - } - // this channel must not have a name already - channel as ChannelImpl<*> - if (channel.isNameInitialized()) { - throw MultipleNamesForChannelException(workflow::class.java.name, it.name, channel.name) - } - // set channel name - channel.name = it.name - } - } } diff --git a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/workflowProperties.kt b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/workflowProperties.kt index 1fbed2b73..8771ecdc9 100644 --- a/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/workflowProperties.kt +++ b/infinitic-workflow-task/src/main/kotlin/io/infinitic/workflows/workflowTask/workflowProperties.kt @@ -26,12 +26,12 @@ package io.infinitic.workflows.workflowTask import io.infinitic.annotations.Ignore +import io.infinitic.common.exceptions.thisShouldNotHappen import io.infinitic.common.workflows.data.properties.PropertyHash import io.infinitic.common.workflows.data.properties.PropertyName import io.infinitic.common.workflows.data.properties.PropertyValue import io.infinitic.common.workflows.executors.parser.getPropertiesFromObject import io.infinitic.common.workflows.executors.parser.setPropertiesToObject -import io.infinitic.exceptions.thisShouldNotHappen import io.infinitic.workflows.Channel import io.infinitic.workflows.Workflow import io.infinitic.workflows.WorkflowContext @@ -62,8 +62,8 @@ internal fun Workflow.getProperties() = getPropertiesFromObject(this) { it.first.returnType.javaType.typeName != WorkflowDispatcher::class.java.name && // excludes Channels !it.first.returnType.isSubtypeOf(Channel::class.starProjectedType) && - // excludes Proxies (tasks and workflows) - !Proxy.isProxyClass(it.second!!::class.java) && + // excludes Proxies (tasks and workflows) and null + !(it.second?. let { Proxy.isProxyClass(it::class.java) } ?: true) && // exclude SLF4J loggers !it.first.returnType.isSubtypeOf(org.slf4j.Logger::class.createType()) && // exclude Ignore annotation diff --git a/infinitic-workflow-task/src/test/kotlin/io/infinitic/workflows/workflowTask/workflows/WorkflowA.kt b/infinitic-workflow-task/src/test/kotlin/io/infinitic/workflows/workflowTask/workflows/WorkflowA.kt index e49ac1f2e..82785f55a 100644 --- a/infinitic-workflow-task/src/test/kotlin/io/infinitic/workflows/workflowTask/workflows/WorkflowA.kt +++ b/infinitic-workflow-task/src/test/kotlin/io/infinitic/workflows/workflowTask/workflows/WorkflowA.kt @@ -32,6 +32,7 @@ import io.infinitic.workflows.workflowTask.tasks.TaskA import kotlinx.serialization.Serializable import mu.KotlinLogging import org.slf4j.LoggerFactory +import java.util.UUID sealed class Obj @Serializable @@ -44,14 +45,21 @@ interface WorkflowA { } class WorkflowAImpl : Workflow(), WorkflowA { + // a channel override val channelObj = channel() - // a task - private val taskA = newTask() + // a new task + private val newTaskA = newTask(TaskA::class.java) + + // an existing task +// private val getTaskA = getTaskById(TaskA::class.java, UUID.randomUUID().toString()) + + // a new workflow + private val newWorkflowA = newWorkflow(WorkflowA::class.java) - // a workflow - private val workflowA = newWorkflow() + // an existing workflow + private val getWorkflowA = getWorkflowById(WorkflowA::class.java, UUID.randomUUID().toString()) // a logger private var logger1 = LoggerFactory.getLogger(WorkflowAImpl::class.qualifiedName)