0

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.:
  1. I make a call to the repository
  2. 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?

Adamo Branz
  • 164
  • 2
  • 7
  • 1
    "Is there a cleaner or more elegant way to achieve this?" is an off-topic sort of question for Stack Overflow because it's too open-ended and opinion-based. It might be a good fit for the [Code Review](https://codereview.stackexchange.com/) site if your code is already functioning correctly, but be sure to check their site rules before posting there. – Tenfour04 Mar 17 '23 at 12:54

1 Answers1

1

I didn't want to rewrite your code but I have some tips for you.

Use withTimout and measureTimeMillis for the concise and better readability of your code.

Don't use global scope instead use MainScope() or CoroutineScope().but for the sake of structure concurrency I recommend using coroutineScope{} and supervisorScope{} as much as possible.

handle exceptions in your jobs with coroutinExceptionHandler and make sure to use supervisorJob if you didn't want your parent job to be canceled.

Shift Delete
  • 1,015
  • 6
  • 13
  • Do you like this version better? (I didn't understand how to use "measureTimeMillis" in your advice from earlier) – Adamo Branz Apr 03 '23 at 13:40
  • But with "withTimeout" a "TimeoutCancellationException" is thrown which is very bad, it was better that in the while loop there was the control, you say no? – Adamo Branz Apr 04 '23 at 09:08
  • 1
    your code is far better than the previous and you can improve it by preventing indentation. for this one, I think you can use a function for creating jobs and just use your try-catch block in that function. I think `withTimoutOrNull` is better than `withTimout` because it doesn't throw exceptions. good luck – Shift Delete Apr 05 '23 at 21:10