From 52d35cd82905612f0ef9f7b7d88611300fb48ebe Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 18 Feb 2022 14:08:18 +0100 Subject: refactor(utils): Rename utils module to common module This change adds a new module, opendc-common, that contains functionality that is shared across OpenDC's modules. We move the existing utils module into this new module. --- opendc-common/build.gradle.kts | 37 ++++ .../main/kotlin/org/opendc/common/util/Pacer.kt | 92 ++++++++ .../org/opendc/common/util/TimerScheduler.kt | 239 +++++++++++++++++++++ .../kotlin/org/opendc/common/util/PacerTest.kt | 127 +++++++++++ .../org/opendc/common/util/TimerSchedulerTest.kt | 139 ++++++++++++ .../opendc-compute-service/build.gradle.kts | 2 +- .../compute/service/internal/ComputeServiceImpl.kt | 2 +- .../opendc-compute-simulator/build.gradle.kts | 2 +- .../opendc-experiments-tf20/build.gradle.kts | 4 +- opendc-faas/opendc-faas-service/build.gradle.kts | 2 +- .../autoscaler/FunctionTerminationPolicyFixed.kt | 2 +- .../faas/service/internal/FaaSServiceImpl.kt | 2 +- opendc-utils/build.gradle.kts | 37 ---- .../src/main/kotlin/org/opendc/utils/Pacer.kt | 92 -------- .../main/kotlin/org/opendc/utils/TimerScheduler.kt | 239 --------------------- .../src/test/kotlin/org/opendc/utils/PacerTest.kt | 127 ----------- .../kotlin/org/opendc/utils/TimerSchedulerTest.kt | 139 ------------ .../opendc-workflow-service/build.gradle.kts | 2 +- .../service/internal/WorkflowServiceImpl.kt | 2 +- settings.gradle.kts | 2 +- 20 files changed, 645 insertions(+), 645 deletions(-) create mode 100644 opendc-common/build.gradle.kts create mode 100644 opendc-common/src/main/kotlin/org/opendc/common/util/Pacer.kt create mode 100644 opendc-common/src/main/kotlin/org/opendc/common/util/TimerScheduler.kt create mode 100644 opendc-common/src/test/kotlin/org/opendc/common/util/PacerTest.kt create mode 100644 opendc-common/src/test/kotlin/org/opendc/common/util/TimerSchedulerTest.kt delete mode 100644 opendc-utils/build.gradle.kts delete mode 100644 opendc-utils/src/main/kotlin/org/opendc/utils/Pacer.kt delete mode 100644 opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt delete mode 100644 opendc-utils/src/test/kotlin/org/opendc/utils/PacerTest.kt delete mode 100644 opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt diff --git a/opendc-common/build.gradle.kts b/opendc-common/build.gradle.kts new file mode 100644 index 00000000..67441e93 --- /dev/null +++ b/opendc-common/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Common functionality used across OpenDC modules" + +/* Build configuration */ +plugins { + `kotlin-library-conventions` + `testing-conventions` + `jacoco-conventions` +} + +dependencies { + api(platform(projects.opendcPlatform)) + api(libs.kotlinx.coroutines) + + testImplementation(projects.opendcSimulator.opendcSimulatorCore) +} diff --git a/opendc-common/src/main/kotlin/org/opendc/common/util/Pacer.kt b/opendc-common/src/main/kotlin/org/opendc/common/util/Pacer.kt new file mode 100644 index 00000000..8ccff6c3 --- /dev/null +++ b/opendc-common/src/main/kotlin/org/opendc/common/util/Pacer.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.common.util + +import kotlinx.coroutines.* +import java.lang.Runnable +import java.time.Clock +import kotlin.coroutines.ContinuationInterceptor +import kotlin.coroutines.CoroutineContext + +/** + * Helper class to pace the incoming scheduling requests. + * + * @param context The [CoroutineContext] in which the pacer runs. + * @param clock The virtual simulation clock. + * @param quantum The scheduling quantum. + * @param process The process to invoke for the incoming requests. + */ +public class Pacer( + private val context: CoroutineContext, + private val clock: Clock, + private val quantum: Long, + private val process: (Long) -> Unit +) { + /** + * The [Delay] instance that provides scheduled execution of [Runnable]s. + */ + @OptIn(InternalCoroutinesApi::class) + private val delay = + requireNotNull(context[ContinuationInterceptor] as? Delay) { "Invalid CoroutineDispatcher: no delay implementation" } + + /** + * The current [DisposableHandle] representing the pending scheduling cycle. + */ + private var handle: DisposableHandle? = null + + /** + * Determine whether a scheduling cycle is pending. + */ + public val isPending: Boolean get() = handle != null + + /** + * Enqueue a new scheduling cycle. + */ + public fun enqueue() { + if (handle != null) { + return + } + + val quantum = quantum + val now = clock.millis() + + // We assume that the scheduler runs at a fixed slot every time quantum (e.g t=0, t=60, t=120). + // We calculate here the delay until the next scheduling slot. + val timeUntilNextSlot = quantum - (now % quantum) + + @OptIn(InternalCoroutinesApi::class) + handle = delay.invokeOnTimeout(timeUntilNextSlot, { + process(now + timeUntilNextSlot) + handle = null + }, context) + } + + /** + * Cancel the currently pending scheduling cycle. + */ + public fun cancel() { + val handle = handle ?: return + this.handle = null + handle.dispose() + } +} diff --git a/opendc-common/src/main/kotlin/org/opendc/common/util/TimerScheduler.kt b/opendc-common/src/main/kotlin/org/opendc/common/util/TimerScheduler.kt new file mode 100644 index 00000000..86314411 --- /dev/null +++ b/opendc-common/src/main/kotlin/org/opendc/common/util/TimerScheduler.kt @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.common.util + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.selects.select +import java.time.Clock +import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.math.max + +/** + * A TimerScheduler facilitates scheduled execution of future tasks. + * + * @param context The [CoroutineContext] to run the tasks with. + * @param clock The clock to keep track of the time. + */ +@OptIn(ExperimentalCoroutinesApi::class) +public class TimerScheduler(context: CoroutineContext, private val clock: Clock) : AutoCloseable { + /** + * The scope in which the scheduler runs. + */ + private val scope = CoroutineScope(context + Job()) + + /** + * A priority queue containing the tasks to be scheduled in the future. + */ + private val queue = PriorityQueue() + + /** + * A map that keeps track of the timers. + */ + private val timers = mutableMapOf() + + /** + * The channel to communicate with the scheduling job. + */ + private val channel = Channel(Channel.CONFLATED) + + /** + * A flag to indicate that the scheduler is active. + */ + private var isActive = true + + init { + scope.launch { + val timers = timers + val queue = queue + val clock = clock + val job = requireNotNull(coroutineContext[Job]) + val exceptionHandler = coroutineContext[CoroutineExceptionHandler] + var next: Long? = channel.receive() + + while (true) { + next = select { + channel.onReceive { it } + + val delay = next?.let { max(0L, it - clock.millis()) } ?: return@select + + onTimeout(delay) { + while (queue.isNotEmpty() && job.isActive) { + val timer = queue.peek() + val timestamp = clock.millis() + + assert(timer.timestamp >= timestamp) { "Found task in the past" } + + if (timer.timestamp > timestamp && !timer.isCancelled) { + // Schedule a task for the next event to occur. + return@onTimeout timer.timestamp + } + + queue.poll() + + if (!timer.isCancelled) { + timers.remove(timer.key) + try { + timer() + } catch (e: Throwable) { + exceptionHandler?.handleException(coroutineContext, e) + } + } + } + + null + } + } + } + } + } + + /** + * Stop the scheduler. + */ + override fun close() { + isActive = false + cancelAll() + scope.cancel() + } + + /** + * Cancel a timer with a given key. + * + * If canceling a timer that was already canceled, or key never was used to start + * a timer this operation will do nothing. + * + * @param key The key of the timer to cancel. + */ + public fun cancel(key: T) { + if (!isActive) { + return + } + + val timer = timers.remove(key) + + // Mark the timer as cancelled + timer?.isCancelled = true + + // Optimization: check whether we are the head of the queue + val queue = queue + val channel = channel + val peek = queue.peek() + if (peek == timer) { + queue.poll() + + if (queue.isNotEmpty()) { + channel.trySend(peek.timestamp) + } else { + channel.trySend(null) + } + } + } + + /** + * Cancel all timers. + */ + public fun cancelAll() { + queue.clear() + timers.clear() + } + + /** + * Check if a timer with a given key is active. + * + * @param key The key to check if active. + * @return `true` if the timer with the specified [key] is active, `false` otherwise. + */ + public fun isTimerActive(key: T): Boolean = key in timers + + /** + * Start a timer that will invoke the specified [block] after [delay]. + * + * Each timer has a key and if a new timer with same key is started the previous is cancelled. + * + * @param key The key of the timer to start. + * @param delay The delay before invoking the block. + * @param block The block to invoke. + */ + public fun startSingleTimer(key: T, delay: Long, block: () -> Unit) { + startSingleTimerTo(key, clock.millis() + delay, block) + } + + /** + * Start a timer that will invoke the specified [block] at [timestamp]. + * + * Each timer has a key and if a new timer with same key is started the previous is cancelled. + * + * @param key The key of the timer to start. + * @param timestamp The timestamp at which to invoke the block. + * @param block The block to invoke. + */ + public fun startSingleTimerTo(key: T, timestamp: Long, block: () -> Unit) { + val now = clock.millis() + val queue = queue + val channel = channel + + require(timestamp >= now) { "Timestamp must be in the future" } + check(isActive) { "Timer is stopped" } + + timers.compute(key) { _, old -> + if (old != null && old.timestamp == timestamp) { + // Fast-path: timer for the same timestamp already exists + old + } else { + // Slow-path: cancel old timer and replace it with new timer + val timer = Timer(key, timestamp, block) + + old?.isCancelled = true + queue.add(timer) + + // Check if we need to push the interruption forward + // Note that we check by timer reference + if (queue.peek() === timer) { + channel.trySend(timer.timestamp) + } + + timer + } + } + } + + /** + * A task that is scheduled to run in the future. + */ + private inner class Timer(val key: T, val timestamp: Long, val block: () -> Unit) : Comparable { + /** + * A flag to indicate that the task has been cancelled. + */ + @JvmField + var isCancelled: Boolean = false + + /** + * Run the task. + */ + operator fun invoke(): Unit = block() + + override fun compareTo(other: Timer): Int = timestamp.compareTo(other.timestamp) + } +} diff --git a/opendc-common/src/test/kotlin/org/opendc/common/util/PacerTest.kt b/opendc-common/src/test/kotlin/org/opendc/common/util/PacerTest.kt new file mode 100644 index 00000000..1cd435f6 --- /dev/null +++ b/opendc-common/src/test/kotlin/org/opendc/common/util/PacerTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.common.util + +import kotlinx.coroutines.delay +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import java.time.Clock +import kotlin.coroutines.EmptyCoroutineContext + +/** + * Test suite for the [Pacer] class. + */ +class PacerTest { + @Test + fun testEmptyContext() { + assertThrows { Pacer(EmptyCoroutineContext, Clock.systemUTC(), 100) {} } + } + + @Test + fun testSingleEnqueue() { + var count = 0 + + runBlockingSimulation { + val pacer = Pacer(coroutineContext, clock, quantum = 100) { + count++ + } + + pacer.enqueue() + } + + assertEquals(1, count) { "Process should execute once" } + } + + @Test + fun testCascade() { + var count = 0 + + runBlockingSimulation { + val pacer = Pacer(coroutineContext, clock, quantum = 100) { + count++ + } + + pacer.enqueue() + pacer.enqueue() + + assertTrue(pacer.isPending) + } + + assertEquals(1, count) { "Process should execute once" } + } + + @Test + fun testCancel() { + var count = 0 + + runBlockingSimulation { + val pacer = Pacer(coroutineContext, clock, quantum = 100) { + count++ + } + + pacer.enqueue() + pacer.cancel() + + assertFalse(pacer.isPending) + } + + assertEquals(0, count) { "Process should never execute " } + } + + @Test + fun testCancelWithoutPending() { + var count = 0 + + runBlockingSimulation { + val pacer = Pacer(coroutineContext, clock, quantum = 100) { + count++ + } + + assertFalse(pacer.isPending) + assertDoesNotThrow { pacer.cancel() } + + pacer.enqueue() + } + + assertEquals(1, count) { "Process should execute once" } + } + + @Test + fun testSubsequent() { + var count = 0 + + runBlockingSimulation { + val pacer = Pacer(coroutineContext, clock, quantum = 100) { + count++ + } + + pacer.enqueue() + delay(100) + pacer.enqueue() + } + + assertEquals(2, count) { "Process should execute twice" } + } +} diff --git a/opendc-common/src/test/kotlin/org/opendc/common/util/TimerSchedulerTest.kt b/opendc-common/src/test/kotlin/org/opendc/common/util/TimerSchedulerTest.kt new file mode 100644 index 00000000..89b0cbe9 --- /dev/null +++ b/opendc-common/src/test/kotlin/org/opendc/common/util/TimerSchedulerTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.common.util + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation + +/** + * A test suite for the [TimerScheduler] class. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class TimerSchedulerTest { + @Test + fun testBasicTimer() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + scheduler.startSingleTimer(0, 1000) { + scheduler.close() + assertEquals(1000, clock.millis()) + } + } + } + + @Test + fun testCancelNonExisting() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + scheduler.cancel(1) + scheduler.close() + } + } + + @Test + fun testCancelExisting() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + scheduler.startSingleTimer(0, 1000) { + assertFalse(false) + } + + scheduler.startSingleTimer(1, 100) { + scheduler.cancel(0) + scheduler.close() + + assertEquals(100, clock.millis()) + } + } + } + + @Test + fun testCancelAll() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + scheduler.startSingleTimer(0, 1000) { + assertFalse(false) + } + + scheduler.startSingleTimer(1, 100) { + assertFalse(false) + } + + scheduler.close() + } + } + + @Test + fun testOverride() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + scheduler.startSingleTimer(0, 1000) { + assertFalse(false) + } + + scheduler.startSingleTimer(0, 200) { + scheduler.close() + + assertEquals(200, clock.millis()) + } + } + } + + @Test + fun testStopped() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + scheduler.close() + + assertThrows { + scheduler.startSingleTimer(1, 100) { + assertFalse(false) + } + } + } + } + + @Test + fun testNegativeDelay() { + runBlockingSimulation { + val scheduler = TimerScheduler(coroutineContext, clock) + + assertThrows { + scheduler.startSingleTimer(1, -1) { + assertFalse(false) + } + } + + scheduler.close() + } + } +} diff --git a/opendc-compute/opendc-compute-service/build.gradle.kts b/opendc-compute/opendc-compute-service/build.gradle.kts index 33cafc45..b609b147 100644 --- a/opendc-compute/opendc-compute-service/build.gradle.kts +++ b/opendc-compute/opendc-compute-service/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { api(platform(projects.opendcPlatform)) api(projects.opendcCompute.opendcComputeApi) api(projects.opendcTelemetry.opendcTelemetryApi) - implementation(projects.opendcUtils) + implementation(projects.opendcCommon) implementation(libs.kotlin.logging) implementation(libs.opentelemetry.semconv) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 6df3c4f9..144b6573 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -29,13 +29,13 @@ import io.opentelemetry.api.metrics.MeterProvider import io.opentelemetry.api.metrics.ObservableLongMeasurement import kotlinx.coroutines.* import mu.KotlinLogging +import org.opendc.common.util.Pacer import org.opendc.compute.api.* import org.opendc.compute.service.ComputeService import org.opendc.compute.service.driver.Host import org.opendc.compute.service.driver.HostListener import org.opendc.compute.service.driver.HostState import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.utils.Pacer import java.time.Clock import java.time.Duration import java.util.* diff --git a/opendc-compute/opendc-compute-simulator/build.gradle.kts b/opendc-compute/opendc-compute-simulator/build.gradle.kts index aaf69f78..686d9ca1 100644 --- a/opendc-compute/opendc-compute-simulator/build.gradle.kts +++ b/opendc-compute/opendc-compute-simulator/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { api(projects.opendcCompute.opendcComputeService) api(projects.opendcSimulator.opendcSimulatorCompute) api(libs.commons.math3) - implementation(projects.opendcUtils) + implementation(projects.opendcCommon) implementation(libs.opentelemetry.semconv) implementation(libs.kotlin.logging) diff --git a/opendc-experiments/opendc-experiments-tf20/build.gradle.kts b/opendc-experiments/opendc-experiments-tf20/build.gradle.kts index 882c4894..75381e1f 100644 --- a/opendc-experiments/opendc-experiments-tf20/build.gradle.kts +++ b/opendc-experiments/opendc-experiments-tf20/build.gradle.kts @@ -34,11 +34,11 @@ dependencies { implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcSimulator.opendcSimulatorCompute) implementation(projects.opendcTelemetry.opendcTelemetrySdk) - implementation(projects.opendcUtils) + implementation(projects.opendcCommon) implementation(libs.kotlin.logging) implementation(libs.jackson.module.kotlin) { exclude(group = "org.jetbrains.kotlin", module = "kotlin-reflect") } - implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.30") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10") } diff --git a/opendc-faas/opendc-faas-service/build.gradle.kts b/opendc-faas/opendc-faas-service/build.gradle.kts index 6f4fcc9b..95b171b9 100644 --- a/opendc-faas/opendc-faas-service/build.gradle.kts +++ b/opendc-faas/opendc-faas-service/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { api(platform(projects.opendcPlatform)) api(projects.opendcFaas.opendcFaasApi) api(projects.opendcTelemetry.opendcTelemetryApi) - implementation(projects.opendcUtils) + implementation(projects.opendcCommon) implementation(libs.kotlin.logging) implementation(libs.opentelemetry.semconv) diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt index 63dbadc7..d579ad0c 100644 --- a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/autoscaler/FunctionTerminationPolicyFixed.kt @@ -22,9 +22,9 @@ package org.opendc.faas.service.autoscaler +import org.opendc.common.util.TimerScheduler import org.opendc.faas.service.deployer.FunctionInstance import org.opendc.faas.service.deployer.FunctionInstanceState -import org.opendc.utils.TimerScheduler import java.time.Clock import java.time.Duration import kotlin.coroutines.CoroutineContext diff --git a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt index 82032718..1526be9d 100644 --- a/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt +++ b/opendc-faas/opendc-faas-service/src/main/kotlin/org/opendc/faas/service/internal/FaaSServiceImpl.kt @@ -27,6 +27,7 @@ import io.opentelemetry.api.metrics.MeterProvider import kotlinx.coroutines.* import kotlinx.coroutines.intrinsics.startCoroutineCancellable import mu.KotlinLogging +import org.opendc.common.util.Pacer import org.opendc.faas.api.FaaSClient import org.opendc.faas.api.FaaSFunction import org.opendc.faas.service.FaaSService @@ -37,7 +38,6 @@ import org.opendc.faas.service.deployer.FunctionInstance import org.opendc.faas.service.deployer.FunctionInstanceListener import org.opendc.faas.service.deployer.FunctionInstanceState import org.opendc.faas.service.router.RoutingPolicy -import org.opendc.utils.Pacer import java.lang.IllegalStateException import java.time.Clock import java.util.* diff --git a/opendc-utils/build.gradle.kts b/opendc-utils/build.gradle.kts deleted file mode 100644 index 858aa64c..00000000 --- a/opendc-utils/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Utilities used across OpenDC modules" - -/* Build configuration */ -plugins { - `kotlin-library-conventions` - `testing-conventions` - `jacoco-conventions` -} - -dependencies { - api(platform(projects.opendcPlatform)) - api(libs.kotlinx.coroutines) - - testImplementation(projects.opendcSimulator.opendcSimulatorCore) -} diff --git a/opendc-utils/src/main/kotlin/org/opendc/utils/Pacer.kt b/opendc-utils/src/main/kotlin/org/opendc/utils/Pacer.kt deleted file mode 100644 index 07d3cb87..00000000 --- a/opendc-utils/src/main/kotlin/org/opendc/utils/Pacer.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.utils - -import kotlinx.coroutines.* -import java.lang.Runnable -import java.time.Clock -import kotlin.coroutines.ContinuationInterceptor -import kotlin.coroutines.CoroutineContext - -/** - * Helper class to pace the incoming scheduling requests. - * - * @param context The [CoroutineContext] in which the pacer runs. - * @param clock The virtual simulation clock. - * @param quantum The scheduling quantum. - * @param process The process to invoke for the incoming requests. - */ -public class Pacer( - private val context: CoroutineContext, - private val clock: Clock, - private val quantum: Long, - private val process: (Long) -> Unit -) { - /** - * The [Delay] instance that provides scheduled execution of [Runnable]s. - */ - @OptIn(InternalCoroutinesApi::class) - private val delay = - requireNotNull(context[ContinuationInterceptor] as? Delay) { "Invalid CoroutineDispatcher: no delay implementation" } - - /** - * The current [DisposableHandle] representing the pending scheduling cycle. - */ - private var handle: DisposableHandle? = null - - /** - * Determine whether a scheduling cycle is pending. - */ - public val isPending: Boolean get() = handle != null - - /** - * Enqueue a new scheduling cycle. - */ - public fun enqueue() { - if (handle != null) { - return - } - - val quantum = quantum - val now = clock.millis() - - // We assume that the scheduler runs at a fixed slot every time quantum (e.g t=0, t=60, t=120). - // We calculate here the delay until the next scheduling slot. - val timeUntilNextSlot = quantum - (now % quantum) - - @OptIn(InternalCoroutinesApi::class) - handle = delay.invokeOnTimeout(timeUntilNextSlot, { - process(now + timeUntilNextSlot) - handle = null - }, context) - } - - /** - * Cancel the currently pending scheduling cycle. - */ - public fun cancel() { - val handle = handle ?: return - this.handle = null - handle.dispose() - } -} diff --git a/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt b/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt deleted file mode 100644 index d7da7f99..00000000 --- a/opendc-utils/src/main/kotlin/org/opendc/utils/TimerScheduler.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.utils - -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.selects.select -import java.time.Clock -import java.util.* -import kotlin.coroutines.CoroutineContext -import kotlin.math.max - -/** - * A TimerScheduler facilitates scheduled execution of future tasks. - * - * @param context The [CoroutineContext] to run the tasks with. - * @param clock The clock to keep track of the time. - */ -@OptIn(ExperimentalCoroutinesApi::class) -public class TimerScheduler(context: CoroutineContext, private val clock: Clock) : AutoCloseable { - /** - * The scope in which the scheduler runs. - */ - private val scope = CoroutineScope(context + Job()) - - /** - * A priority queue containing the tasks to be scheduled in the future. - */ - private val queue = PriorityQueue() - - /** - * A map that keeps track of the timers. - */ - private val timers = mutableMapOf() - - /** - * The channel to communicate with the scheduling job. - */ - private val channel = Channel(Channel.CONFLATED) - - /** - * A flag to indicate that the scheduler is active. - */ - private var isActive = true - - init { - scope.launch { - val timers = timers - val queue = queue - val clock = clock - val job = requireNotNull(coroutineContext[Job]) - val exceptionHandler = coroutineContext[CoroutineExceptionHandler] - var next: Long? = channel.receive() - - while (true) { - next = select { - channel.onReceive { it } - - val delay = next?.let { max(0L, it - clock.millis()) } ?: return@select - - onTimeout(delay) { - while (queue.isNotEmpty() && job.isActive) { - val timer = queue.peek() - val timestamp = clock.millis() - - assert(timer.timestamp >= timestamp) { "Found task in the past" } - - if (timer.timestamp > timestamp && !timer.isCancelled) { - // Schedule a task for the next event to occur. - return@onTimeout timer.timestamp - } - - queue.poll() - - if (!timer.isCancelled) { - timers.remove(timer.key) - try { - timer() - } catch (e: Throwable) { - exceptionHandler?.handleException(coroutineContext, e) - } - } - } - - null - } - } - } - } - } - - /** - * Stop the scheduler. - */ - override fun close() { - isActive = false - cancelAll() - scope.cancel() - } - - /** - * Cancel a timer with a given key. - * - * If canceling a timer that was already canceled, or key never was used to start - * a timer this operation will do nothing. - * - * @param key The key of the timer to cancel. - */ - public fun cancel(key: T) { - if (!isActive) { - return - } - - val timer = timers.remove(key) - - // Mark the timer as cancelled - timer?.isCancelled = true - - // Optimization: check whether we are the head of the queue - val queue = queue - val channel = channel - val peek = queue.peek() - if (peek == timer) { - queue.poll() - - if (queue.isNotEmpty()) { - channel.trySend(peek.timestamp) - } else { - channel.trySend(null) - } - } - } - - /** - * Cancel all timers. - */ - public fun cancelAll() { - queue.clear() - timers.clear() - } - - /** - * Check if a timer with a given key is active. - * - * @param key The key to check if active. - * @return `true` if the timer with the specified [key] is active, `false` otherwise. - */ - public fun isTimerActive(key: T): Boolean = key in timers - - /** - * Start a timer that will invoke the specified [block] after [delay]. - * - * Each timer has a key and if a new timer with same key is started the previous is cancelled. - * - * @param key The key of the timer to start. - * @param delay The delay before invoking the block. - * @param block The block to invoke. - */ - public fun startSingleTimer(key: T, delay: Long, block: () -> Unit) { - startSingleTimerTo(key, clock.millis() + delay, block) - } - - /** - * Start a timer that will invoke the specified [block] at [timestamp]. - * - * Each timer has a key and if a new timer with same key is started the previous is cancelled. - * - * @param key The key of the timer to start. - * @param timestamp The timestamp at which to invoke the block. - * @param block The block to invoke. - */ - public fun startSingleTimerTo(key: T, timestamp: Long, block: () -> Unit) { - val now = clock.millis() - val queue = queue - val channel = channel - - require(timestamp >= now) { "Timestamp must be in the future" } - check(isActive) { "Timer is stopped" } - - timers.compute(key) { _, old -> - if (old != null && old.timestamp == timestamp) { - // Fast-path: timer for the same timestamp already exists - old - } else { - // Slow-path: cancel old timer and replace it with new timer - val timer = Timer(key, timestamp, block) - - old?.isCancelled = true - queue.add(timer) - - // Check if we need to push the interruption forward - // Note that we check by timer reference - if (queue.peek() === timer) { - channel.trySend(timer.timestamp) - } - - timer - } - } - } - - /** - * A task that is scheduled to run in the future. - */ - private inner class Timer(val key: T, val timestamp: Long, val block: () -> Unit) : Comparable { - /** - * A flag to indicate that the task has been cancelled. - */ - @JvmField - var isCancelled: Boolean = false - - /** - * Run the task. - */ - operator fun invoke(): Unit = block() - - override fun compareTo(other: Timer): Int = timestamp.compareTo(other.timestamp) - } -} diff --git a/opendc-utils/src/test/kotlin/org/opendc/utils/PacerTest.kt b/opendc-utils/src/test/kotlin/org/opendc/utils/PacerTest.kt deleted file mode 100644 index b8419e80..00000000 --- a/opendc-utils/src/test/kotlin/org/opendc/utils/PacerTest.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.utils - -import kotlinx.coroutines.delay -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.core.runBlockingSimulation -import java.time.Clock -import kotlin.coroutines.EmptyCoroutineContext - -/** - * Test suite for the [Pacer] class. - */ -class PacerTest { - @Test - fun testEmptyContext() { - assertThrows { Pacer(EmptyCoroutineContext, Clock.systemUTC(), 100) {} } - } - - @Test - fun testSingleEnqueue() { - var count = 0 - - runBlockingSimulation { - val pacer = Pacer(coroutineContext, clock, quantum = 100) { - count++ - } - - pacer.enqueue() - } - - assertEquals(1, count) { "Process should execute once" } - } - - @Test - fun testCascade() { - var count = 0 - - runBlockingSimulation { - val pacer = Pacer(coroutineContext, clock, quantum = 100) { - count++ - } - - pacer.enqueue() - pacer.enqueue() - - assertTrue(pacer.isPending) - } - - assertEquals(1, count) { "Process should execute once" } - } - - @Test - fun testCancel() { - var count = 0 - - runBlockingSimulation { - val pacer = Pacer(coroutineContext, clock, quantum = 100) { - count++ - } - - pacer.enqueue() - pacer.cancel() - - assertFalse(pacer.isPending) - } - - assertEquals(0, count) { "Process should never execute " } - } - - @Test - fun testCancelWithoutPending() { - var count = 0 - - runBlockingSimulation { - val pacer = Pacer(coroutineContext, clock, quantum = 100) { - count++ - } - - assertFalse(pacer.isPending) - assertDoesNotThrow { pacer.cancel() } - - pacer.enqueue() - } - - assertEquals(1, count) { "Process should execute once" } - } - - @Test - fun testSubsequent() { - var count = 0 - - runBlockingSimulation { - val pacer = Pacer(coroutineContext, clock, quantum = 100) { - count++ - } - - pacer.enqueue() - delay(100) - pacer.enqueue() - } - - assertEquals(2, count) { "Process should execute twice" } - } -} diff --git a/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt b/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt deleted file mode 100644 index 101a6546..00000000 --- a/opendc-utils/src/test/kotlin/org/opendc/utils/TimerSchedulerTest.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.utils - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.simulator.core.runBlockingSimulation - -/** - * A test suite for the [TimerScheduler] class. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal class TimerSchedulerTest { - @Test - fun testBasicTimer() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - scheduler.startSingleTimer(0, 1000) { - scheduler.close() - assertEquals(1000, clock.millis()) - } - } - } - - @Test - fun testCancelNonExisting() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - scheduler.cancel(1) - scheduler.close() - } - } - - @Test - fun testCancelExisting() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - scheduler.startSingleTimer(0, 1000) { - assertFalse(false) - } - - scheduler.startSingleTimer(1, 100) { - scheduler.cancel(0) - scheduler.close() - - assertEquals(100, clock.millis()) - } - } - } - - @Test - fun testCancelAll() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - scheduler.startSingleTimer(0, 1000) { - assertFalse(false) - } - - scheduler.startSingleTimer(1, 100) { - assertFalse(false) - } - - scheduler.close() - } - } - - @Test - fun testOverride() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - scheduler.startSingleTimer(0, 1000) { - assertFalse(false) - } - - scheduler.startSingleTimer(0, 200) { - scheduler.close() - - assertEquals(200, clock.millis()) - } - } - } - - @Test - fun testStopped() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - scheduler.close() - - assertThrows { - scheduler.startSingleTimer(1, 100) { - assertFalse(false) - } - } - } - } - - @Test - fun testNegativeDelay() { - runBlockingSimulation { - val scheduler = TimerScheduler(coroutineContext, clock) - - assertThrows { - scheduler.startSingleTimer(1, -1) { - assertFalse(false) - } - } - - scheduler.close() - } - } -} diff --git a/opendc-workflow/opendc-workflow-service/build.gradle.kts b/opendc-workflow/opendc-workflow-service/build.gradle.kts index 4d8b7d7f..8f082e2f 100644 --- a/opendc-workflow/opendc-workflow-service/build.gradle.kts +++ b/opendc-workflow/opendc-workflow-service/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { api(projects.opendcWorkflow.opendcWorkflowApi) api(projects.opendcCompute.opendcComputeApi) api(projects.opendcTelemetry.opendcTelemetryApi) - implementation(projects.opendcUtils) + implementation(projects.opendcCommon) implementation(libs.kotlin.logging) testImplementation(projects.opendcWorkflow.opendcWorkflowWorkload) diff --git a/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt b/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt index cb4bfd45..cdaec021 100644 --- a/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt +++ b/opendc-workflow/opendc-workflow-service/src/main/kotlin/org/opendc/workflow/service/internal/WorkflowServiceImpl.kt @@ -25,8 +25,8 @@ package org.opendc.workflow.service.internal import io.opentelemetry.api.metrics.Meter import io.opentelemetry.api.metrics.MeterProvider import kotlinx.coroutines.* +import org.opendc.common.util.Pacer import org.opendc.compute.api.* -import org.opendc.utils.Pacer import org.opendc.workflow.api.Job import org.opendc.workflow.api.WORKFLOW_TASK_CORES import org.opendc.workflow.service.* diff --git a/settings.gradle.kts b/settings.gradle.kts index 170a687d..f388a385 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ rootProject.name = "opendc" include(":opendc-platform") +include(":opendc-common") include(":opendc-compute:opendc-compute-api") include(":opendc-compute:opendc-compute-service") include(":opendc-compute:opendc-compute-simulator") @@ -60,7 +61,6 @@ include(":opendc-harness:opendc-harness-api") include(":opendc-harness:opendc-harness-engine") include(":opendc-harness:opendc-harness-cli") include(":opendc-harness:opendc-harness-junit5") -include(":opendc-utils") enableFeaturePreview("VERSION_CATALOGS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -- cgit v1.2.3