Is there a way to stop a micronaut scheduled job between tests?

Tags: , , ,



I have an application that dynamically schedules jobs based on properties. It listens for the ServiceReadyEvent and then schedules the job. Below is a small example that would get added to the context via a factory.

class JobsLoader(
    @param:Named(TaskExecutors.SCHEDULED) val taskScheduler: TaskScheduler,
    val myJobName: String
) : ApplicationEventListener<ServiceReadyEvent> {

    override fun onApplicationEvent(event: ServiceReadyEvent?) {
        taskScheduler.schedule("* * * * *", ()  -> println("running job $jobName"))
    }
}

To test this code I have a Micronaut test, using kotest.

@MicronautTest
class Job1Spec(
        @param:Named(TaskExecutors.SCHEDULED) val taskScheduler: TaskScheduler
) {

    init {

        "start job loader" {
            val jobsLoader = JobsLoader("job1", taskScheduler)
            jobsLoader.onApplicationEvent(ServiceReadyEvent(ServiceInstance.of("test", URL("http://localhost:8080"))))
        }

        "verify the job runs" {
            //code here to verify the job ran and performed the correct calls
        }

    }   
}

I also have a second test for another job

@MicronautTest
class Job2Spec(
        @param:Named(TaskExecutors.SCHEDULED) val taskScheduler: TaskScheduler
) {

    init {

        "start job loader" {
            val jobsLoader = JobsLoader("job2", taskScheduler)
            jobsLoader.onApplicationEvent(ServiceReadyEvent(ServiceInstance.of("test", URL("http://localhost:8080"))))
        }

        "verify the job runs" {
            //code here to verify the job ran and performed the correct calls
        }

    }   
}

My problem lies in that during the second test I see that the first job is still scheduled during the second tests run:

[INFO] 2020-09-10 12:57:00,005 pool-2-thread-1 running job job1
[INFO] 2020-09-10 12:57:00,009 pool-4-thread-1 running job job2 

Everything else about the first run of tests has stopped except for the running of the job. I found it was still running because it throws an exception due to the HttpClient being closed during the job run.

I’ve tried

  • Rebuilding the test context between tests with rebuildContext = true
  • Tracking the scheduled futures in my JobsLoader class and then cancelling them in an afterSpec function. The list of futures is always empty by the time I go to cancel them.
  • Refreshing the TaskScheduler and ExecutorService beans manually

The tests all pass fine, but they are littered with exceptions – especially as I get to testing more and more jobs and all but the one I’m testing continuously fails. I’m not sure if this is an issue with kotest, micronaut test, the schedulers, some combination, or something else entirely (I see vertx logs as well, but I don’t think that’s the issue). I really just need to figure out a way to kill these jobs/threads before the next test runs.

Any help or ideas would be HUGELY appreciated!

Answer

So after typing all that out I figured out what my issue was almost immediately. I’m manually creating a job in my test, which isn’t a part of the micronaut context, so that’s why my future list is always empty when I go and try to cancel the jobs.

I changed my test to load the job and add it to the context:

var jobsLoader: JobsLoader? = null

    override fun beforeSpec(spec: Spec) {
        jobsLoader = JobsLoader(taskScheduler, getJobsConfiguration(), jobResolver, eventPublisher)
        jobsLoader?.onApplicationEvent(ServiceReadyEvent(ServiceInstance.of("test", URL("http://localhost:8080"))))
        super.beforeSpec(spec)
    }

    override fun afterSpec(spec: Spec) {
        jobsLoader?.stopJobs()
        super.afterSpec(spec)
    }

Then provided a function in my job loader to cancel any jobs.

class JobsLoader(
    @param:Named(TaskExecutors.SCHEDULED) val taskScheduler: TaskScheduler,
    val myJobName: String
) : ApplicationEventListener<ServiceReadyEvent> {

    private val scheduledJobs = mutableListOf<ScheduledFuture<*>>()

    override fun onApplicationEvent(event: ServiceReadyEvent?) {
        val scheduledFuture = taskScheduler.schedule("* * * * *", ()  -> println("running job $jobName"))
        scheduledJobs.add(scheduledFuture)
    }

    fun stopJobs() {
        scheduledJobs.forEach { it.cancel(false) }
    }
}


Source: stackoverflow