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

org.jitsi.utils.concurrent.FakeScheduledExecutorService.kt Maven / Gradle / Ivy

There is a newer version: 1.0-133-g6af1020
Show newest version
/*
 * Copyright @ 2018 - present 8x8, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jitsi.utils.concurrent

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.jitsi.utils.time.FakeClock
import java.time.Duration
import java.time.Instant
import java.util.concurrent.Callable
import java.util.concurrent.Delayed
import java.util.concurrent.Future
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit

/**
 * A fake [ScheduledExecutorService] which gives control over when scheduled tasks are run without requiring a
 * separate thread
 */
class FakeScheduledExecutorService(
    val clock: FakeClock = FakeClock()
) : ScheduledExecutorService {
    private var jobs = JobsTimeline()

    override fun scheduleAtFixedRate(
        command: Runnable,
        initialDelay: Long,
        period: Long,
        unit: TimeUnit
    ): ScheduledFuture<*> {
        val nextRunTime = clock.instant().plus(Duration.ofMillis(unit.toMillis(initialDelay)))
        val job = FixedRateJob(command, nextRunTime, Duration.ofMillis(unit.toMillis(period)))
        jobs.add(job)

        return EmptyFuture { job.cancelled = true }
    }

    override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> {
//        println("scheduling job with a delay of $delay $unit from time ${clock.instant()}")
        val nextRunTime = clock.instant().plus(Duration.ofNanos(unit.toNanos(delay)))
        val job = Job(command, nextRunTime)
        jobs.add(job)

        return EmptyFuture { job.cancelled = true }
    }

    override fun scheduleWithFixedDelay(
        command: Runnable,
        initialDelay: Long,
        delay: Long,
        unit: TimeUnit
    ): ScheduledFuture<*> {
        val nextRunTime = clock.instant().plus(Duration.ofMillis(unit.toMillis(initialDelay)))
        val job = FixedDelayJob(command, nextRunTime, Duration.ofMillis(unit.toMillis(delay)))
        jobs.add(job)

        return EmptyFuture { job.cancelled = true }
    }

    // Note that, when a job is cancelled, we don't remove it from the timeline, we just mark it
    // as cancelled.  We only want to reflect non-cancelled jobs in the count here, so we filter
    // them
    fun numPendingJobs(): Int = jobs.filter { !it.cancelled }.size

    /**
     * Run the next pending task and advance the clock to that time
     */
    fun runOne() {
        while (jobs.isNotEmpty() && jobs[0].cancelled) {
            jobs.removeAt(0)
        }
        if (jobs.isNotEmpty()) {
            val nextJob = jobs.removeAt(0)
            if (clock.instant() < nextJob.nextRunTime) {
                clock.setTime(nextJob.nextRunTime)
            }
            nextJob.run()
            if (nextJob is RecurringJob) {
                nextJob.updateNextRuntime(clock.instant())
                jobs.add(nextJob)
            }
        }
    }

    /**
     * Run pending tasks until [endTime], or until there are no pending tasks
     * in the queue, advancing the clock with each task.
     */
    fun runUntil(endTime: Instant) {
        while (jobs.isNotEmpty() && clock.instant() <= endTime && jobs.first().nextRunTime <= endTime) {
            runOne()
        }
    }

    /**
     * Runs all jobs that are due to run by the current time.  This may include a single job multiple times if the
     * time since the last run is longer than 2 times the job's period
     */
    fun run() {
        val now = clock.instant()
        if (jobs.isNotEmpty()) {
            val job = jobs.removeAt(0)
            if (!job.cancelled) {
                if (job.ready(now)) {
                    job.run()
                    if (job is RecurringJob) {
                        job.updateNextRuntime(now)
                        jobs.add(job)
                    }
                    run()
                } else {
                    // The job wasn't ready yet, re-add it to the front of the queue
                    jobs.add(0, job)
                }
            }
        }
    }

    override fun execute(command: Runnable) { schedule(command, 0, TimeUnit.MILLISECONDS) }

    /* Methods required by ScheduledExecutorService, but not needed by our unit tests. */
    override fun shutdown() = TODO("Not yet implemented")
    override fun shutdownNow(): MutableList = TODO("Not yet implemented")
    override fun isShutdown(): Boolean = TODO("Not yet implemented")
    override fun isTerminated(): Boolean = TODO("Not yet implemented")
    override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean = TODO("Not yet implemented")
    override fun  submit(task: Callable): Future = TODO("Not yet implemented")
    override fun  submit(task: Runnable, result: T): Future = TODO("Not yet implemented")
    override fun submit(task: Runnable): Future<*> = TODO("Not yet implemented")
    override fun  invokeAll(tasks: MutableCollection>): MutableList> =
        TODO("Not yet implemented")
    override fun  invokeAll(
        tasks: MutableCollection>,
        timeout: Long,
        unit: TimeUnit
    ): MutableList> = TODO("Not yet implemented")
    override fun  invokeAny(tasks: MutableCollection>): T = TODO("Not yet implemented")
    override fun  invokeAny(tasks: MutableCollection>, timeout: Long, unit: TimeUnit): T =
        TODO("Not yet implemented")
    override fun  schedule(callable: Callable, delay: Long, unit: TimeUnit): ScheduledFuture =
        TODO("Not yet implemented")
}

/**
 * Model a Job that is track in the [FakeScheduledExecutorService]
 */
internal open class Job(val command: Runnable, var nextRunTime: Instant) {
    var cancelled = false

    fun run() = command.run()

    fun ready(now: Instant): Boolean = nextRunTime <= now

    override fun toString(): String = with(StringBuffer()) {
        append("next run time: $nextRunTime")

        toString()
    }
}

internal abstract class RecurringJob(command: Runnable, nextRunTime: Instant) : Job(command, nextRunTime) {
    abstract fun updateNextRuntime(now: Instant)
}

internal class FixedRateJob(
    command: Runnable,
    nextRunTime: Instant,
    val period: Duration
) : RecurringJob(command, nextRunTime) {
    override fun updateNextRuntime(now: Instant) {
        nextRunTime += period
    }
}

internal class FixedDelayJob(
    command: Runnable,
    nextRunTime: Instant,
    val period: Duration
) : RecurringJob(command, nextRunTime) {
    override fun updateNextRuntime(now: Instant) {
        nextRunTime = now + period
    }
}

/**
 * An [ArrayList] which keeps a list of [Job]s sorted according to their next run tim
 */
internal class JobsTimeline : ArrayList() {
    override fun add(element: Job): Boolean {
        val result = super.add(element)
        sortBy(Job::nextRunTime)
        return result
    }
}

/**
 * A simple implementation of [ScheduledFuture] which allows passing a handler
 * to be invoked on cancellation.
 */
@SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS")
private class EmptyFuture(
    private val cancelHandler: () -> Unit
) : ScheduledFuture {
    private var cancelled = false
    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
        cancelHandler()
        cancelled = true
        return true
    }

    override fun get() {}
    override fun get(timeout: Long, unit: TimeUnit) {}
    override fun getDelay(unit: TimeUnit): Long = TODO("Not yet implemented")
    override fun isCancelled(): Boolean = cancelled
    override fun isDone(): Boolean = TODO("Not yet implemented")
    override fun compareTo(other: Delayed?): Int = TODO("Not yet implemented")
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy