Skip to content

Commit d9737c3

Browse files
feat: make nextRuntime optional & add optional jobId parameter in shortcut methods (#23)
Signed-off-by: starry-shivam <[email protected]>
1 parent ae53f53 commit d9737c3

File tree

9 files changed

+150
-78
lines changed

9 files changed

+150
-78
lines changed

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ keeping things simple and easy to use.
2222

2323
### Highlights ✨
2424

25-
- Lightweight with no external dependencies other than `kotlinx:kotlinx-coroutines-core` from the Kotlin standard library.
25+
- Lightweight with no external dependencies other than `kotlinx:kotlinx-coroutines-core` from the Kotlin standard
26+
library.
2627
- Designed to respect time zones, allowing you to set the time zone yourself or use the system's time zone by default.
27-
- Provides four different types of triggers to execute jobs daily, at certain intervals, once at a given time, or with a cron-like schedule.
28-
- Can run multiple instances of a job concurrently while giving you the option to run only one instance if the job is already executing.
28+
- Provides four different types of triggers to execute jobs daily, at certain intervals, once at a given time, or with a
29+
cron-like schedule.
30+
- Can run multiple instances of a job concurrently while giving you the option to run only one instance if the job is
31+
already executing.
2932
- Can be easily extended to suit your specific use case by allowing you to write custom triggers and job stores.
3033
- Easy to use and straightforward API with full KDoc/Javadoc documentation coverage.
3134
- 100% unit test coverage to ensure reliability across different scenarios.
@@ -50,6 +53,7 @@ dependencies {
5053
implementation("com.github.Pool-Of-Tears:KtScheduler:version")
5154
}
5255
```
56+
5357
------
5458

5559
### Documentation 📑

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ tasks.register("printLineCoverage") {
9090
group = "verification"
9191
dependsOn("koverXmlReport")
9292
doLast {
93-
val report = file("$buildDir/reports/kover/report.xml")
93+
val report = file("${layout.buildDirectory}/reports/kover/report.xml")
9494
val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(report)
9595
val rootNode = doc.firstChild
9696
var childNode = rootNode.firstChild

src/main/kotlin/dev/starry/ktscheduler/event/JobEvent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package dev.starry.ktscheduler.event
1919

20+
import dev.starry.ktscheduler.event.JobStatus.ERROR
21+
import dev.starry.ktscheduler.event.JobStatus.SUCCESS
2022
import java.time.ZonedDateTime
2123

2224
/**

src/main/kotlin/dev/starry/ktscheduler/job/Job.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ data class Job(
4646

4747
/**
4848
* The next time the job should run.
49+
*
50+
* When adding a new job, it is used as the initial run time.
51+
* If not provided, it will be calculated automatically based
52+
* on the [Trigger.getNextRunTime] method when the job is added.
4953
*/
50-
val nextRunTime: ZonedDateTime,
54+
val nextRunTime: ZonedDateTime? = null,
5155

5256
/**
5357
* Whether to run multiple instances of this job concurrently.
@@ -67,3 +71,5 @@ data class Job(
6771
val callback: suspend () -> Unit
6872
)
6973

74+
75+

src/main/kotlin/dev/starry/ktscheduler/jobstore/InMemoryJobStore.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class InMemoryJobStore : JobStore {
5757
*/
5858
override fun getDueJobs(currentTime: ZonedDateTime, maxGraceTime: Duration?): List<Job> {
5959
return jobs.values.filter { job ->
60-
val jobNextRunTime = job.nextRunTime
60+
val jobNextRunTime = job.nextRunTime!!
6161
maxGraceTime?.let { graceTime ->
6262
jobNextRunTime <= currentTime && currentTime <= jobNextRunTime.plus(graceTime)
6363
} ?: (jobNextRunTime <= currentTime)

src/main/kotlin/dev/starry/ktscheduler/scheduler/KtScheduler.kt

Lines changed: 51 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,9 @@ import dev.starry.ktscheduler.triggers.CronTrigger
2727
import dev.starry.ktscheduler.triggers.DailyTrigger
2828
import dev.starry.ktscheduler.triggers.IntervalTrigger
2929
import dev.starry.ktscheduler.triggers.OneTimeTrigger
30-
import kotlinx.coroutines.CoroutineDispatcher
31-
import kotlinx.coroutines.CoroutineScope
32-
import kotlinx.coroutines.Dispatchers
33-
import kotlinx.coroutines.SupervisorJob
34-
import kotlinx.coroutines.cancel
35-
import kotlinx.coroutines.delay
36-
import kotlinx.coroutines.isActive
37-
import kotlinx.coroutines.launch
38-
import java.time.DayOfWeek
39-
import java.time.Duration
40-
import java.time.LocalTime
41-
import java.time.ZoneId
42-
import java.time.ZonedDateTime
43-
import java.util.UUID
30+
import kotlinx.coroutines.*
31+
import java.time.*
32+
import java.util.*
4433
import java.util.logging.Logger
4534

4635
/**
@@ -163,7 +152,14 @@ class KtScheduler(
163152
*/
164153
override fun addJob(job: Job) {
165154
logger.info("Adding job ${job.jobId}")
166-
jobStore.addJob(job)
155+
val jobToAdd = job.nextRunTime?.let {
156+
job
157+
} ?: job.copy(
158+
nextRunTime = job.trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone)
159+
).apply {
160+
logger.info("Calculated next run time for job ${job.jobId}: $nextRunTime")
161+
}
162+
jobStore.addJob(jobToAdd)
167163
}
168164

169165
/**
@@ -282,17 +278,18 @@ class KtScheduler(
282278
* ```
283279
* val trigger = CronTrigger(daysOfWeek, time)
284280
* val job = Job(
285-
* jobId = "runCron-${UUID.randomUUID()}",
286-
* trigger = trigger,
287-
* nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
288-
* runConcurrently = runConcurrently,
289-
* dispatcher = dispatcher,
290-
* callback = callback
281+
* jobId = "runCron-${UUID.randomUUID()}",
282+
* trigger = trigger,
283+
* nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
284+
* runConcurrently = runConcurrently,
285+
* dispatcher = dispatcher,
286+
* callback = callback
291287
* )
292288
*
293289
* scheduler.addJob(job)
294290
* ```
295291
*
292+
* @param jobId The ID of the job. Default is a random UUID.
296293
* @param daysOfWeek The set of days of the week on which the job should run.
297294
* @param time The time at which the job should run.
298295
* @param dispatcher The coroutine dispatcher to use. Default is [Dispatchers.Default].
@@ -301,6 +298,7 @@ class KtScheduler(
301298
* @return The ID of the scheduled job.
302299
*/
303300
fun runCron(
301+
jobId: String = "runCron-${UUID.randomUUID()}",
304302
daysOfWeek: Set<DayOfWeek>,
305303
time: LocalTime,
306304
dispatcher: CoroutineDispatcher = Dispatchers.Default,
@@ -309,14 +307,14 @@ class KtScheduler(
309307
): String {
310308
val trigger = CronTrigger(daysOfWeek, time)
311309
val job = Job(
312-
jobId = "runCron-${UUID.randomUUID()}",
310+
jobId = jobId,
313311
trigger = trigger,
314312
nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
315313
runConcurrently = runConcurrently,
316314
dispatcher = dispatcher,
317315
callback = block
318316
)
319-
job.let { addJob(it) }.also { return job.jobId }
317+
addJob(job).also { return job.jobId }
320318
}
321319

322320
/**
@@ -328,39 +326,41 @@ class KtScheduler(
328326
* ```
329327
* val trigger = DailyTrigger(dailyTime)
330328
* val job = Job(
331-
* jobId = "runDaily-${UUID.randomUUID()}",
332-
* trigger = trigger,
333-
* nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
334-
* runConcurrently = runConcurrently,
335-
* dispatcher = dispatcher,
336-
* callback = callback
329+
* jobId = "runDaily-${UUID.randomUUID()}",
330+
* trigger = trigger,
331+
* nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
332+
* runConcurrently = runConcurrently,
333+
* dispatcher = dispatcher,
334+
* callback = callback
337335
* )
338336
*
339337
* scheduler.addJob(job)
340338
* ```
341339
*
340+
* @param jobId The ID of the job. Default is a random UUID.
342341
* @param dailyTime The time at which the job should run daily.
343342
* @param dispatcher The coroutine dispatcher to use. Default is [Dispatchers.Default].
344343
* @param runConcurrently Whether the job should run concurrently. Default is `true`.
345344
* @param block The block of code to execute.
346345
* @return The ID of the scheduled job.
347346
*/
348347
fun runDaily(
348+
jobId: String = "runDaily-${UUID.randomUUID()}",
349349
dailyTime: LocalTime,
350350
dispatcher: CoroutineDispatcher = Dispatchers.Default,
351351
runConcurrently: Boolean = true,
352352
block: suspend () -> Unit
353353
): String {
354354
val trigger = DailyTrigger(dailyTime)
355355
val job = Job(
356-
jobId = "runDaily-${UUID.randomUUID()}",
356+
jobId = jobId,
357357
trigger = trigger,
358358
nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
359359
runConcurrently = runConcurrently,
360360
dispatcher = dispatcher,
361361
callback = block
362362
)
363-
job.let { addJob(it) }.also { return job.jobId }
363+
addJob(job).also { return job.jobId }
364364
}
365365

366366
/**
@@ -372,39 +372,41 @@ class KtScheduler(
372372
* ```
373373
* val trigger = IntervalTrigger(intervalSeconds)
374374
* val job = Job(
375-
* jobId = "runRepeating-${UUID.randomUUID()}",
376-
* trigger = trigger,
377-
* nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
378-
* runConcurrently = runConcurrently,
379-
* dispatcher = dispatcher,
380-
* callback = block
375+
* jobId = "runRepeating-${UUID.randomUUID()}",
376+
* trigger = trigger,
377+
* nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
378+
* runConcurrently = runConcurrently,
379+
* dispatcher = dispatcher,
380+
* callback = block
381381
* )
382382
*
383383
* scheduler.addJob(job)
384384
* ```
385385
*
386+
* @param jobId The ID of the job. Default is a random UUID.
386387
* @param intervalSeconds The interval in seconds at which the job should run.
387388
* @param dispatcher The coroutine dispatcher to use. Default is [Dispatchers.Default].
388389
* @param runConcurrently Whether the job should run concurrently. Default is `true`.
389390
* @param block The block of code to execute.
390391
* @return The ID of the scheduled job.
391392
*/
392393
fun runRepeating(
394+
jobId: String = "runRepeating-${UUID.randomUUID()}",
393395
intervalSeconds: Long,
394396
dispatcher: CoroutineDispatcher = Dispatchers.Default,
395397
runConcurrently: Boolean = true,
396398
block: suspend () -> Unit
397399
): String {
398400
val trigger = IntervalTrigger(intervalSeconds)
399401
val job = Job(
400-
jobId = "runRepeating-${UUID.randomUUID()}",
402+
jobId = jobId,
401403
trigger = trigger,
402404
nextRunTime = trigger.getNextRunTime(ZonedDateTime.now(timeZone), timeZone),
403405
runConcurrently = runConcurrently,
404406
dispatcher = dispatcher,
405407
callback = block
406408
)
407-
job.let { addJob(it) }.also { return job.jobId }
409+
addJob(job).also { return job.jobId }
408410
}
409411

410412
/**
@@ -415,38 +417,40 @@ class KtScheduler(
415417
*
416418
* ```
417419
* val job = Job(
418-
* jobId = "runOnce-${UUID.randomUUID()}",
419-
* trigger = OneTimeTrigger(runAt),
420-
* nextRunTime = runAt,
421-
* runConcurrently = runConcurrently,
422-
* dispatcher = dispatcher,
423-
* callback = callback
420+
* jobId = "runOnce-${UUID.randomUUID()}",
421+
* trigger = OneTimeTrigger(runAt),
422+
* nextRunTime = runAt,
423+
* runConcurrently = runConcurrently,
424+
* dispatcher = dispatcher,
425+
* callback = callback
424426
* )
425427
*
426428
* scheduler.addJob(job)
427429
* ```
428430
*
431+
* @param jobId The ID of the job. Default is a random UUID.
429432
* @param runAt The time at which the job should run.
430433
* @param dispatcher The coroutine dispatcher to use. Default is [Dispatchers.Default].
431434
* @param runConcurrently Whether the job should run concurrently. Default is `true`.
432435
* @param block The block of code to execute.
433436
* @return The ID of the scheduled job.
434437
*/
435438
fun runOnce(
439+
jobId: String = "runOnce-${UUID.randomUUID()}",
436440
runAt: ZonedDateTime,
437441
dispatcher: CoroutineDispatcher = Dispatchers.Default,
438442
runConcurrently: Boolean = true,
439443
block: suspend () -> Unit
440444
): String {
441445
val job = Job(
442-
jobId = "runOnce-${UUID.randomUUID()}",
446+
jobId = jobId,
443447
trigger = OneTimeTrigger(runAt),
444448
nextRunTime = runAt,
445449
runConcurrently = runConcurrently,
446450
dispatcher = dispatcher,
447451
callback = block
448452
)
449-
job.let { addJob(it) }.also { return job.jobId }
453+
addJob(job).also { return job.jobId }
450454
}
451455

452456
// ============================================================================================

src/test/kotlin/dev/starry/ktscheduler/CoroutineExecutorTest.kt

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,14 @@ import kotlin.test.assertNotNull
4040
@OptIn(ExperimentalCoroutinesApi::class)
4141
class CoroutineExecutorTest {
4242

43-
private lateinit var executor: CoroutineExecutor
44-
private lateinit var trigger: OneTimeTrigger
45-
46-
@Before
47-
fun setUp() {
48-
executor = CoroutineExecutor()
49-
trigger = OneTimeTrigger(ZonedDateTime.now(ZoneId.of("UTC")).plusSeconds(1))
50-
}
51-
5243
@After
5344
fun tearDown() {
5445
Dispatchers.resetMain()
5546
}
5647

5748
@Test
5849
fun testExecuteSuccess(): Unit = runTest {
50+
val executor = CoroutineExecutor()
5951
val job = createTestJob(scheduler = testScheduler) { }
6052
var onSuccessCalled = false
6153
val onSuccess: () -> Unit = { onSuccessCalled = true }
@@ -68,6 +60,7 @@ class CoroutineExecutorTest {
6860

6961
@Test
7062
fun testExecuteError(): Unit = runTest {
63+
val executor = CoroutineExecutor()
7164
val job = createTestJob(scheduler = testScheduler) { throw IllegalArgumentException("Error") }
7265

7366
val onSuccess: () -> Unit = { fail("onSuccess should not be called") }
@@ -83,6 +76,7 @@ class CoroutineExecutorTest {
8376

8477
@Test
8578
fun testConcurrentExecution(): Unit = runTest {
79+
val executor = CoroutineExecutor()
8680
// Create a job that takes 100ms to execute.
8781
val job = createTestJob(
8882
scheduler = testScheduler, runConcurrently = true
@@ -102,11 +96,14 @@ class CoroutineExecutorTest {
10296

10397
@Test
10498
fun testNonConcurrentExecution(): Unit = runTest {
99+
val executor = CoroutineExecutor()
105100
// Create a job that takes 100ms to execute.
106-
val job = createTestJob(scheduler = testScheduler, runConcurrently = false) { delay(100) }
101+
val job = createTestJob(
102+
scheduler = testScheduler, runConcurrently = false
103+
) { delay(100) }
107104

108105
var onSuccessCalled = 0
109-
val onSuccess: () -> Unit = { onSuccessCalled++ }
106+
val onSuccess: () -> Unit = { onSuccessCalled += 1 }
110107
val onError: (Throwable) -> Unit = { fail("onError should not be called") }
111108
// Execute the job 3 times concurrently.
112109
executor.execute(job, onSuccess, onError)
@@ -124,7 +121,7 @@ class CoroutineExecutorTest {
124121
callback: suspend () -> Unit,
125122
): Job = Job(
126123
jobId = jobId,
127-
trigger = trigger,
124+
trigger = OneTimeTrigger(ZonedDateTime.now(ZoneId.of("UTC")).plusSeconds(1)),
128125
nextRunTime = ZonedDateTime.now(),
129126
dispatcher = UnconfinedTestDispatcher(scheduler),
130127
runConcurrently = runConcurrently,

0 commit comments

Comments
 (0)