Skip to content

Instantly share code, notes, and snippets.

@mike-neck
Created December 19, 2019 18:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-neck/de52315c2abf66662adc8bb5e2deba36 to your computer and use it in GitHub Desktop.
Save mike-neck/de52315c2abf66662adc8bb5e2deba36 to your computer and use it in GitHub Desktop.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.2.RELEASE)
2019-12-20 00:10:03.221 INFO 7504 --- [ main] com.example.MainKt : Starting MainKt on app.local with PID 7504 (/Users/mike/spring-scheduled-task-example)
2019-12-20 00:10:03.224 INFO 7504 --- [ main] com.example.MainKt : No active profile set, falling back to default profiles: default
2019-12-20 00:10:03.906 INFO 7504 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2019-12-20 00:10:04.157 INFO 7504 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2019-12-20 00:10:04.447 INFO 7504 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2019-12-20 00:10:04.451 INFO 7504 --- [ main] com.example.MainKt : Started MainKt in 1.411 seconds (JVM running for 2.111)
2019-12-20 00:10:14.458 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:10:14.460 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: FIVE_MIN
2019-12-20 00:11:04.479 INFO 7504 --- [ parallel-1] com.example.DurableTask : finish task: FIVE_MIN
2019-12-20 00:11:04.482 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:11:04.480Z in 50022ms
2019-12-20 00:11:04.489 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:11:24.480Z
2019-12-20 00:11:24.486 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:11:24.486 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: THREE_MIN
2019-12-20 00:11:54.491 INFO 7504 --- [ parallel-2] com.example.DurableTask : finish task: THREE_MIN
2019-12-20 00:11:54.491 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:11:54.491Z in 30005ms
2019-12-20 00:11:54.493 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:12:24.491Z
2019-12-20 00:12:24.493 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:12:24.493 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: ONE_MIN
2019-12-20 00:12:34.496 INFO 7504 --- [ parallel-3] com.example.DurableTask : finish task: ONE_MIN
2019-12-20 00:12:34.497 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:12:34.497Z in 10004ms
2019-12-20 00:12:34.497 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:13:34.497Z
2019-12-20 00:13:34.501 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:13:34.502 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: FIVE_MIN
2019-12-20 00:14:24.508 INFO 7504 --- [ parallel-4] com.example.DurableTask : finish task: FIVE_MIN
2019-12-20 00:14:24.508 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:14:24.508Z in 50007ms
2019-12-20 00:14:24.508 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:14:44.508Z
2019-12-20 00:14:44.509 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:14:44.509 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: THREE_MIN
2019-12-20 00:15:14.514 INFO 7504 --- [ parallel-5] com.example.DurableTask : finish task: THREE_MIN
2019-12-20 00:15:14.514 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:15:14.514Z in 30005ms
2019-12-20 00:15:14.515 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:15:44.514Z
2019-12-20 00:15:44.515 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:15:44.516 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: ONE_MIN
2019-12-20 00:15:54.521 INFO 7504 --- [ parallel-6] com.example.DurableTask : finish task: ONE_MIN
2019-12-20 00:15:54.521 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:15:54.521Z in 10006ms
2019-12-20 00:15:54.522 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:16:54.521Z
2019-12-20 00:16:54.526 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:16:54.526 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: THREE_MIN
2019-12-20 00:17:24.530 INFO 7504 --- [ parallel-7] com.example.DurableTask : finish task: THREE_MIN
2019-12-20 00:17:24.531 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:17:24.531Z in 30005ms
2019-12-20 00:17:24.531 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:17:54.531Z
2019-12-20 00:17:54.531 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:17:54.532 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: FIVE_MIN
2019-12-20 00:18:44.537 INFO 7504 --- [ parallel-8] com.example.DurableTask : finish task: FIVE_MIN
2019-12-20 00:18:44.537 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:18:44.537Z in 50006ms
2019-12-20 00:18:44.537 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:19:04.537Z
2019-12-20 00:19:04.538 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:19:04.538 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: ONE_MIN
2019-12-20 00:19:14.539 INFO 7504 --- [ parallel-9] com.example.DurableTask : finish task: ONE_MIN
2019-12-20 00:19:14.540 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:19:14.540Z in 10002ms
2019-12-20 00:19:14.540 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:20:14.540Z
2019-12-20 00:20:14.546 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:20:14.546 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: TWO_MIN
2019-12-20 00:20:34.552 INFO 7504 --- [ parallel-10] com.example.DurableTask : finish task: TWO_MIN
2019-12-20 00:20:34.553 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:20:34.553Z in 20007ms
2019-12-20 00:20:34.553 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:21:04.553Z
2019-12-20 00:21:04.556 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:21:04.556 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: ONE_MIN
2019-12-20 00:21:14.562 INFO 7504 --- [ parallel-11] com.example.DurableTask : finish task: ONE_MIN
2019-12-20 00:21:14.563 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:21:14.563Z in 10007ms
2019-12-20 00:21:14.563 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:22:14.563Z
2019-12-20 00:22:14.567 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task start
2019-12-20 00:22:14.567 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task: TWO_MIN
2019-12-20 00:22:34.569 INFO 7504 --- [ parallel-12] com.example.DurableTask : finish task: TWO_MIN
2019-12-20 00:22:34.569 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : task finished: 2019-12-19T15:22:34.569Z in 20002ms
2019-12-20 00:22:34.569 INFO 7504 --- [ scheduling-1] com.example.TaskConfig : next task will start at 2019-12-19T15:23:04.569Z
2019-12-20 00:22:35.203 INFO 7504 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskScheduler : Shutting down ExecutorService 'taskScheduler'
package com.example
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling
@EnableScheduling
@SpringBootApplication
class Main
fun main(args: Array<String>) {
runApplication<Main>(*args)
}
package com.example
import java.util.function.Predicate
sealed class Step<T, R> {
fun on(p: (T) -> Boolean): TransformStep<T, R> = on(Predicate { p(it) })
abstract fun on(predicate: Predicate<T>): TransformStep<T, R>
abstract fun others(f: (T) -> R): R
}
interface TransformStep<T, R> {
fun then(f: (T) -> R): Step<T, R>
}
data class NotMatched<T, R>(val original: T): Step<T, R>() {
override fun on(predicate: Predicate<T>): TransformStep<T, R> =
if (predicate.test(original)) object : TransformStep<T, R> {
override fun then(f: (T) -> R): Step<T, R> = Matched(f(original))
}
else object: TransformStep<T, R> {
override fun then(f: (T) -> R): Step<T, R> = NotMatched(original)
}
override fun others(f: (T) -> R): R = f(original)
}
data class Matched<T, R>(val transformed: R): Step<T, R>() {
override fun on(predicate: Predicate<T>): TransformStep<T, R> = object: TransformStep<T, R> {
override fun then(f: (T) -> R): Step<T, R> = this@Matched
}
override fun others(f: (T) -> R): R = transformed
}
package com.example
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.Trigger
import org.springframework.scheduling.TriggerContext
import reactor.core.Disposable
import reactor.core.publisher.Mono
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ThreadLocalRandom
enum class DurableTask(private val size: Int) {
ONE_MIN(1),
TWO_MIN(2),
THREE_MIN(3),
FIVE_MIN(5),
;
fun run(): Mono<Unit> =
Mono.defer { Mono.delay(Duration.ofSeconds(size * lengthUnit)) }
.thenReturn(Unit)
.doOnTerminate { logger.info("finish task: {}", this) }
companion object {
val logger: Logger = LoggerFactory.getLogger(DurableTask::class.java)
const val lengthUnit: Long = 10L
private val length: Int = values().size
fun random(): DurableTask = values()[ThreadLocalRandom.current().nextInt(length)]
}
}
val Instant.date: Date get() = Date.from(this)
fun duration(from: Date, to: Date): Duration = Duration.between(from.toInstant(), to.toInstant())
val Disposable.asCloseable: AutoCloseable get() = AutoCloseable { this.dispose() }
inline fun <A, B, C> Pair<A, B>.map(f: (A, B) -> C): C = f(this.first, this.second)
inline fun <A : Any, B : Any> A?.with(fb: () -> B?): Pair<A, B>? =
if (this == null) null
else fb()
.let { b ->
when (b) {
null -> null
else -> this to b
}
}
fun <A : Any, B : Any> A.determineConditionally(): Step<A, B> = NotMatched(this)
inline fun <A: Any, B: Any, C: Any> A.determineConditionally(f: (A) -> B): Step<B, C> = f(this).determineConditionally()
@Configuration
class TaskConfig {
@Bean
fun commandLineRunner(taskScheduler: TaskScheduler): CommandLineRunner =
CommandLineRunner {
taskScheduler.schedule(Runnable {
logger.info("task start")
val latch = CountDownLatch(1)
DurableTask
.random()
.also { logger.info("next task: {}", it) }
.run()
.doOnTerminate { latch.countDown() }
.subscribe().asCloseable
.use { latch.await() }
}, Companion)
}
companion object : Trigger {
private val logger: Logger = LoggerFactory.getLogger(TaskConfig::class.java)
override fun nextExecutionTime(triggerContext: TriggerContext): Date =
triggerContext.lastActualExecutionTime().with { triggerContext.lastCompletionTime() }
?.map { start, end -> end.toInstant() to duration(start, end) }
?.also { logger.info("task finished: {} in {}ms", it.first, it.second.toMillis()) }
?.determineConditionally<Pair<Instant, Duration>, PreviousTask, Instant> { PreviousTask(it.first, it.second) }
?.on { FIVE_MIN < it.timeElapsed }?.then { it.finishedAt + ONE_MIN + ONE_MIN }
?.on { TWO_MIN < it.timeElapsed }?.then { it.finishedAt + ONE_MIN + TWO_MIN }
?.on { ONE_MIN < it.timeElapsed }?.then { it.finishedAt + ONE_MIN + FIVE_MIN }
?.others { it.finishedAt + ONE_MIN + TWO_MIN + FIVE_MIN }
?.also { logger.info("next task will start at {}", it) }
?.date ?: (Instant.now() + ONE_MIN).date
private val FIVE_MIN: Duration = Duration.ofSeconds(5 * DurableTask.lengthUnit)
private val TWO_MIN: Duration = Duration.ofSeconds(2 * DurableTask.lengthUnit)
private val ONE_MIN: Duration = Duration.ofSeconds(1 * DurableTask.lengthUnit)
}
}
data class PreviousTask(val finishedAt: Instant, val timeElapsed: Duration)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment