My Goal:
Have a class with two methods start and stop the start method has 3 parameters:
- startHour which indicates an hour of the day in which to restart the execution of the algorithm
- isImmediatly which indicates whether, the first time and only the first time, start the algorithm immediately or wait for the next startHour
(which can be from the same day if the current time is less, or from the following day if it is greater)
- numOfJobs which indicates how many jobs to launch in parallel. Inside start I have to:
- calculate the waiting time before starting to launch the jobs.
- calculate the maximum time that jobs have to do their job before being stopped and restarted, this time is the difference between when I launch them and the next startHour.
- a cycle that launches the jobs that I create dynamically
- each job performs the same thing, i.e.:
- I make a call to the repository
- I run an internal loop where I make a call to the repository every 5 seconds until either the time runs out or I get my result which depends on the calls to the repository regardless of whether every single job has finished its work or has run out of time, at the next start hour I have to re-launch them. the start method can be stopped by the stop method and can be restarted by calling start() again
First implementation:
import kotlinx.coroutines.*
import java.time.Duration
import java.time.LocalDateTime
import java.time.LocalTime
class JobScheduler(private val repository: Repository) {
private val supervisorJob = SupervisorJob()
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
println("Caught exception: $throwable")
}
private var isRunning = false
private val jobList = mutableListOf<Job>()
suspend fun start(startHour: Int, isImmediately: Boolean, numOfJobs: Int) {
isRunning = true
var waitTime = if (isImmediately) Duration.ZERO else startHour.amountTimeFromNow()
while (isRunning) {
try {
// Wait the necessary time before launching the jobs
delay(waitTime.toMillis())
// Clears all previous jobs that were still running or were scheduled but not yet executed
jobList.forEach { it.cancel() }
jobList.clear()
// Calculate the maximum duration of jobs and create jobs to launch
val maxJobDuration = startHour.amountTimeFromNow().toMillis()
supervisorScope {
val newJobs = (1..numOfJobs).map {
launch(supervisorJob + coroutineExceptionHandler) {
withTimeout(maxJobDuration) {
while (true) {
// Make the call to the repository
val success: Boolean = repository.call()
// Check if the result is what you want
if (success) {
// The result has been achieved, get out of the loop
break
}
// Wait 5 seconds before making the next call
delay(Duration.ofSeconds(5).toMillis())
}
}
}
}
// Add new jobs to the list
jobList.addAll(newJobs)
}
// Wait for the next start time
waitTime = startHour.amountTimeFromNow()
} catch (e: CancellationException) {
// Gracefully handle cancellation
println("JobScheduler has been cancelled")
break
} catch (e: Exception) {
// Handle any other exceptions
println("Caught exception: $e")
}
}
}
fun stop() {
isRunning = false
supervisorJob.cancelChildren()
jobList.clear()
}
fun Int.amountTimeFromNow(): Duration {
val now = LocalDateTime.now()
val startHour = LocalDateTime.of(now.toLocalDate(), LocalTime.of(this, 0))
val nextStart = if(now >= startHour){
startHour.plusDays(1)
}else{
startHour
}
return Duration.between(now, nextStart)
}
}
it's correct?