From 05723ba6c1be76a9d8ce48ceeb70807c566b2180 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 14:31:41 +1000 Subject: [PATCH 01/23] update deps --- cronut-integrant/project.clj | 6 +++--- cronut-javax/project.clj | 4 ++-- cronut/project.clj | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cronut-integrant/project.clj b/cronut-integrant/project.clj index 6333d81..35dbcd9 100644 --- a/cronut-integrant/project.clj +++ b/cronut-integrant/project.clj @@ -9,15 +9,15 @@ :plugins [[dev.weavejester/lein-cljfmt "0.13.1"]] - :dependencies [[org.clojure/clojure "1.12.1"] + :dependencies [[org.clojure/clojure "1.12.3"] [org.clojure/tools.logging "1.3.0"]] :profiles {:dev {:resource-paths ["dev-resources"] :dependencies [[integrant "0.13.1"] - [ch.qos.logback/logback-classic "1.5.18"] + [ch.qos.logback/logback-classic "1.5.19"] [org.slf4j/slf4j-api "2.0.17"] [org.clojure/core.async "1.8.741"] - [clj-kondo "2025.06.05"]]} + [clj-kondo "2025.09.22"]]} :jakarta {:dependencies [[io.factorhouse/cronut "1.0.0"]]} :javax {:dependencies [[io.factorhouse/cronut-javax "1.0.0"]]} :smoke {:pedantic? :abort}} diff --git a/cronut-javax/project.clj b/cronut-javax/project.clj index efad34d..09c1320 100644 --- a/cronut-javax/project.clj +++ b/cronut-javax/project.clj @@ -9,7 +9,7 @@ :plugins [[dev.weavejester/lein-cljfmt "0.13.1"]] - :dependencies [[org.clojure/clojure "1.12.1"] + :dependencies [[org.clojure/clojure "1.12.3"] [org.clojure/tools.logging "1.3.0"] [org.quartz-scheduler/quartz "2.4.0" :exclusions [org.slf4j/slf4j-api]]] @@ -17,7 +17,7 @@ :dependencies [[ch.qos.logback/logback-classic "1.3.15"] [org.slf4j/slf4j-api "2.0.17"] [org.clojure/core.async "1.8.741"] - [clj-kondo "2025.06.05"]]} + [clj-kondo "2025.09.22"]]} :smoke {:pedantic? :abort}} :aliases {"check" ["with-profile" "+smoke" "check"] diff --git a/cronut/project.clj b/cronut/project.clj index 5a589a0..d3def32 100644 --- a/cronut/project.clj +++ b/cronut/project.clj @@ -9,15 +9,15 @@ :plugins [[dev.weavejester/lein-cljfmt "0.13.1"]] - :dependencies [[org.clojure/clojure "1.12.1"] + :dependencies [[org.clojure/clojure "1.12.3"] [org.clojure/tools.logging "1.3.0"] [org.quartz-scheduler/quartz "2.5.0" :exclusions [org.slf4j/slf4j-api]]] :profiles {:dev {:resource-paths ["dev-resources"] - :dependencies [[ch.qos.logback/logback-classic "1.5.18"] + :dependencies [[ch.qos.logback/logback-classic "1.5.19"] [org.slf4j/slf4j-api "2.0.17"] [org.clojure/core.async "1.8.741"] - [clj-kondo "2025.06.05"]]} + [clj-kondo "2025.09.22"]]} :smoke {:pedantic? :abort}} :aliases {"check" ["with-profile" "+smoke" "check"] From bdb8920a26981522befbf1522510f877e0e900d8 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 14:39:52 +1000 Subject: [PATCH 02/23] fix pedantic and update cljfmt --- cronut-integrant/project.clj | 4 ++-- cronut-javax/project.clj | 4 ++-- cronut/project.clj | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cronut-integrant/project.clj b/cronut-integrant/project.clj index 35dbcd9..e2c7d6e 100644 --- a/cronut-integrant/project.clj +++ b/cronut-integrant/project.clj @@ -7,7 +7,7 @@ :license {:name "Apache 2.0 License" :url "https://github.com/factorhosue/slipway/blob/main/LICENSE"} - :plugins [[dev.weavejester/lein-cljfmt "0.13.1"]] + :plugins [[dev.weavejester/lein-cljfmt "0.13.4"]] :dependencies [[org.clojure/clojure "1.12.3"] [org.clojure/tools.logging "1.3.0"]] @@ -17,7 +17,7 @@ [ch.qos.logback/logback-classic "1.5.19"] [org.slf4j/slf4j-api "2.0.17"] [org.clojure/core.async "1.8.741"] - [clj-kondo "2025.09.22"]]} + [clj-kondo "2025.09.22" :exclusions [org.clojure/tools.reader]]]} :jakarta {:dependencies [[io.factorhouse/cronut "1.0.0"]]} :javax {:dependencies [[io.factorhouse/cronut-javax "1.0.0"]]} :smoke {:pedantic? :abort}} diff --git a/cronut-javax/project.clj b/cronut-javax/project.clj index 09c1320..f157f88 100644 --- a/cronut-javax/project.clj +++ b/cronut-javax/project.clj @@ -7,7 +7,7 @@ :license {:name "Apache 2.0 License" :url "https://github.com/factorhosue/slipway/blob/main/LICENSE"} - :plugins [[dev.weavejester/lein-cljfmt "0.13.1"]] + :plugins [[dev.weavejester/lein-cljfmt "0.13.4"]] :dependencies [[org.clojure/clojure "1.12.3"] [org.clojure/tools.logging "1.3.0"] @@ -17,7 +17,7 @@ :dependencies [[ch.qos.logback/logback-classic "1.3.15"] [org.slf4j/slf4j-api "2.0.17"] [org.clojure/core.async "1.8.741"] - [clj-kondo "2025.09.22"]]} + [clj-kondo "2025.09.22" :exclusions [org.clojure/tools.reader]]]} :smoke {:pedantic? :abort}} :aliases {"check" ["with-profile" "+smoke" "check"] diff --git a/cronut/project.clj b/cronut/project.clj index d3def32..e030304 100644 --- a/cronut/project.clj +++ b/cronut/project.clj @@ -7,7 +7,7 @@ :license {:name "Apache 2.0 License" :url "https://github.com/factorhosue/slipway/blob/main/LICENSE"} - :plugins [[dev.weavejester/lein-cljfmt "0.13.1"]] + :plugins [[dev.weavejester/lein-cljfmt "0.13.4"]] :dependencies [[org.clojure/clojure "1.12.3"] [org.clojure/tools.logging "1.3.0"] @@ -17,7 +17,7 @@ :dependencies [[ch.qos.logback/logback-classic "1.5.19"] [org.slf4j/slf4j-api "2.0.17"] [org.clojure/core.async "1.8.741"] - [clj-kondo "2025.09.22"]]} + [clj-kondo "2025.09.22" :exclusions [org.clojure/tools.reader]]]} :smoke {:pedantic? :abort}} :aliases {"check" ["with-profile" "+smoke" "check"] From 418640ac7018745344f9bae049d1573059d57c6d Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 15:15:41 +1000 Subject: [PATCH 03/23] minor comments --- cronut-javax/test/cronut/job_test.clj | 2 +- cronut-javax/test/cronut_test.clj | 2 +- cronut/test/cronut/job_test.clj | 2 +- cronut/test/cronut_test.clj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cronut-javax/test/cronut/job_test.clj b/cronut-javax/test/cronut/job_test.clj index 56ac105..6ac8b78 100644 --- a/cronut-javax/test/cronut/job_test.clj +++ b/cronut-javax/test/cronut/job_test.clj @@ -49,7 +49,7 @@ (select-keys job-keys) (dissoc :fullName)))) - ;; global concurrentExecutionDisallowed? = false + ;; global concurrentExecutionDisallowed? = true (is (= {:jobClass SerialProxyJob :description nil :durable false diff --git a/cronut-javax/test/cronut_test.clj b/cronut-javax/test/cronut_test.clj index d039e37..1b73ab0 100644 --- a/cronut-javax/test/cronut_test.clj +++ b/cronut-javax/test/cronut_test.clj @@ -5,7 +5,7 @@ [cronut.trigger :as trigger]) (:import (org.quartz Job Trigger))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallow-concurrent-execution?] Job (execute [this _job-context] (log/info "Defrecord Impl:" this))) diff --git a/cronut/test/cronut/job_test.clj b/cronut/test/cronut/job_test.clj index 5abedf1..e04aaab 100644 --- a/cronut/test/cronut/job_test.clj +++ b/cronut/test/cronut/job_test.clj @@ -47,7 +47,7 @@ (select-keys job-keys) (dissoc :fullName)))) - ;; global concurrentExecutionDisallowed? = false + ;; global concurrentExecutionDisallowed? = true (is (= {:jobClass SerialProxyJob :description nil :durable false diff --git a/cronut/test/cronut_test.clj b/cronut/test/cronut_test.clj index d039e37..1b73ab0 100644 --- a/cronut/test/cronut_test.clj +++ b/cronut/test/cronut_test.clj @@ -5,7 +5,7 @@ [cronut.trigger :as trigger]) (:import (org.quartz Job Trigger))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallow-concurrent-execution?] Job (execute [this _job-context] (log/info "Defrecord Impl:" this))) From 255c9dc5915be664654c09a3ed9b02ce794213e5 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 15:33:44 +1000 Subject: [PATCH 04/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 271 +++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 cronut-integrant/README.md diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md new file mode 100644 index 0000000..c8f546f --- /dev/null +++ b/cronut-integrant/README.md @@ -0,0 +1,271 @@ +# Cronut-Integrant: Integrant bindings for Cronut + +[![Cronut Test](https://github.com/factorhouse/cronut/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/factorhouse/cronut/actions/workflows/ci.yml) +[![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) + +# Summary + +Cronut provides a data-first Clojure wrapper for the [Quartz Job Scheduler](https://github.com/quartz-scheduler). + +Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI framework. + +# Usage + +## :cronut/scheduler + +Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant / `:cronut/scheduler` + +The scheduler supports the following fields: + +1. (required) :schedule - a sequence of 'items' to schedule, each being a map containing a :job and :trigger +2. (optional, default false) :disallowConcurrentExecution? - run all jobs with @DisableConcurrentExecution +2. (optional, default false) :update-check? check for Quartz updates on system startup. + +e.g. + +````clojure +:cronut/scheduler {:schedule [{:job #ig/ref :test.job/two + :trigger #cronut/interval 3500} + {:job #ig/ref :test.job/two + :trigger #cronut/cron "*/8 * * * * ?" + :misfire :do-nothing}] + :disallowConcurrentExecution? true} +```` + +### Controlling Concurrent Execution + +Cronut 0.2.6+ supports the option to disable concurrent execution of jobs (see configuration, above). + +This flag is set at a global level and affects all scheduled jobs. Raise a PR if you want to intermingle concurrency. + +If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set `org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more information. + +See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples. + +### The :job + +The `:job` in every scheduled item must implement the org.quartz.Job interface + +The expectation being that every 'job' in your Integrant system will reify that interface, either directly via `reify` +or by returning a defrecord that implements the interface. e.g. + +````clojure +(defmethod ig/init-key :test.job/one + [_ config] + (reify Job + (execute [this job-context] + (log/info "Reified Impl:" config)))) + +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep] + Job + (execute [this job-context] + (log/info "Defrecord Impl:" this))) + +(defmethod ig/init-key :test.job/two + [_ config] + (map->TestDefrecordJobImpl config)) +```` + +Cronut supports further Quartz configuration of jobs (identity, description, recovery, and priority) by expecting those +values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore them), +however if you do want that control you will likely use the defrecord approach as opposed to the simpler reify option +and pass that configuration through edn, e.g. + +````clojure +:test.job/two {:identity ["job-two" "test"] + :description "test job" + :recover? true + :durable? false + :dep-one #ig/ref :dep/one + :dep-two #ig/ref :test.job/one} +```` + +### The :trigger + +The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that +resolution Cronut provides the following tagged literals: + +### Tagged Literals + +#### #cronut/cron: Simple Cron Scheduling + +A job is scheduled to run on a cron by using the `#cronut/cron` tagged literal followed by a valid cron expression + +The job will start immediately when the system is initialized, and runs in the default system time-zone + +````clojure +:trigger #cronut/cron "*/8 * * * * ?" +```` + +#### #cronut/interval: Simple Interval Scheduling + +A job is scheduled to run periodically by using the `#cronut/interval` tagged literal followed by a milliseconds value + +````clojure +:trigger #cronut/interval 3500 +```` + +#### #cronut/trigger: Full (and extensible) Trigger Definition + +Both #cronut/cron and #cronut/interval are effectively shortcuts to full trigger definition with sensible defaults. + +The #cronut/trigger tagged literal supports the full set of Quartz configuration for Simple and Cron triggers: + +````clojure +;; interval +:trigger #cronut/trigger {:type :simple + :interval 3000 + :repeat :forever + :identity ["trigger-two" "test"] + :description "sample simple trigger" + :start #inst "2019-01-01T00:00:00.000-00:00" + :end #inst "2019-02-01T00:00:00.000-00:00" + :misfire :ignore + :priority 5} + +;;cron +:trigger #cronut/trigger {:type :cron + :cron "*/6 * * * * ?" + :identity ["trigger-five" "test"] + :description "sample cron trigger" + :start #inst "2018-01-01T00:00:00.000-00:00" + :end #inst "2029-02-01T00:00:00.000-00:00" + :time-zone "Australia/Melbourne" + :misfire :fire-and-proceed + :priority 4} +```` + +This tagged literal calls a Clojure multi-method that is open for extension. + +You should implement the remaining two Quartz Triggers (CalendarInterval and DailyTimeInterval), create the Tagged +Literal for each, and raise a PR. Go on it will be fun, open issues exist for both triggers. + +## Integrant + +When initializing an Integrant system you will need to provide the Cronut data readers. + +See: `cronut/data-readers` for convenience. + +````clojure +(def data-readers + {'cronut/trigger cronut/trigger-builder + 'cronut/cron cronut/shortcut-cron + 'cronut/interval cronut/shortcut-interval}) +```` + +e.g. +````clojure +(defn init-system + "Convenience for starting integrant systems with cronut data-readers" + ([config] + (init-system config nil)) + ([config readers] + (ig/init (ig/read-string {:readers (merge cronut/data-readers readers)} config)))) +```` + +## Quartz Specifics and Remaining Todo's + +Cronut supports a single Quartz Scheduler per JVM (optionally configured with [quartz.properties](http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigMain.html)). + +The default StdScheduler is reset and re-used on each instantiation of a :cronut/scheduler. + +Cron triggers default to using the system time-zone if no trigger time-zone specifically set. + +Tickets are open for the following extensions (contributions warmly welcomed): + +* Implement DailyTimeInterval Trigger +* Implement CalendarInverval Trigger +* Pluggable SchedulerFactory (support more than one scheduler per JVM) + +## Example System + +Given a simple Integrant configuration of two jobs and four triggers. + +Job Two executes on multiple schedules as defined by the latter three triggers. + +````clojure +{:test.job/one {} + + :test.job/two {:identity ["job-two" "test"] + :description "test job" + :recover? true + :durable? false + :dep-two #ig/ref :test.job/one} + + :cronut/scheduler {:schedule [;; basic interval + {:job #ig/ref :test.job/one + :trigger #cronut/trigger {:type :simple + :interval 2 + :time-unit :seconds + :repeat :forever}} + + ;; shortcut interval via cronut/interval data-reader + {:job #ig/ref :test.job/two + :trigger #cronut/interval 3500} + + ;; basic cron + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :cron + :cron "*/4 * * * * ?"}} + + ;; shortcut cron via cronut/cron data-reader + {:job #ig/ref :test.job/two + :trigger #cronut/cron "*/8 * * * * ?"}]}} + +```` + +And the associated Integrant lifecycle impl, note: + +- `test.job/one` reifies the org.quartz.Job interface +- `test.job/two` instantiates a defrecord (that allows some further quartz job configuration) + +````clojure +(defmethod ig/init-key :test.job/one + [_ config] + (reify Job + (execute [this job-context] + (log/info "Reified Impl:" config)))) + +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep] + Job + (execute [this job-context] + (log/info "Defrecord Impl:" this))) + +(defmethod ig/init-key :test.job/two + [_ config] + (map->TestDefrecordJobImpl config)) +```` + +We can realise that system and run those jobs (See `cronut.integration-fixture` for full example): + +````clojure +(require '[cronut.integration-fixture :as itf]) +=> nil + +(itf/init-system) +=> +{:dep/one {:a 1}, + :test.job/one #object[ronut.integration_fixture$eval2343$fn$reify__2345 + 0x2e906b8a + "cronut.integration_fixture$eval2343$fn$reify__2345@2e906b8a"], + :test.job/two #cronut.integration_fixture.TestDefrecordJobImpl{:identity ["job-two" "test"], + :description "test job", + :recover? true, + :durable? false, + :test-dep nil, + :dep-one {:a 1}, + :dep-two #object[cronut.integration_fixture$eval2343$fn$reify__2345 + 0x2e906b8a + "cronut.integration_fixture$eval2343$fn$reify__2345@2e906b8a"]}, + :cronut/scheduler #object[org.quartz.impl.StdScheduler 0x7565dd8e "org.quartz.impl.StdScheduler@7565dd8e"]} + +(require '[integrant.core :as ig]) +(ig/halt! *1) +=> nil +```` + +## License + +Distributed under the Apache 2.0 License. + +Copyright (c) [Factor House](https://factorhouse.io) From 55834707226e857233ab8509632e807b32d3cd42 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 15:49:55 +1000 Subject: [PATCH 05/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 32 +++++++++++++++++++------------- cronut-javax/src/cronut/job.clj | 2 +- cronut/src/cronut/job.clj | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index c8f546f..ce94faf 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -13,12 +13,12 @@ Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/ ## :cronut/scheduler -Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant / `:cronut/scheduler` +Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` The scheduler supports the following fields: 1. (required) :schedule - a sequence of 'items' to schedule, each being a map containing a :job and :trigger -2. (optional, default false) :disallowConcurrentExecution? - run all jobs with @DisableConcurrentExecution +2. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution 2. (optional, default false) :update-check? check for Quartz updates on system startup. e.g. @@ -29,19 +29,9 @@ e.g. {:job #ig/ref :test.job/two :trigger #cronut/cron "*/8 * * * * ?" :misfire :do-nothing}] - :disallowConcurrentExecution? true} + :concurrent-execution-disallowed? true} ```` -### Controlling Concurrent Execution - -Cronut 0.2.6+ supports the option to disable concurrent execution of jobs (see configuration, above). - -This flag is set at a global level and affects all scheduled jobs. Raise a PR if you want to intermingle concurrency. - -If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set `org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more information. - -See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples. - ### The :job The `:job` in every scheduled item must implement the org.quartz.Job interface @@ -85,6 +75,22 @@ and pass that configuration through edn, e.g. The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that resolution Cronut provides the following tagged literals: +### Controlling concurrent execution + +#### Global scheduler `:concurrent-execution-disallowed?` + +Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. + +#### Local job `:disallow-concurrent-execution?` + +Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. + +#### Misfire configuration + +If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set `org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more information. + +See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and behaviour configuration. + ### Tagged Literals #### #cronut/cron: Simple Cron Scheduling diff --git a/cronut-javax/src/cronut/job.clj b/cronut-javax/src/cronut/job.clj index 8faa9b3..ac97338 100644 --- a/cronut-javax/src/cronut/job.clj +++ b/cronut-javax/src/cronut/job.clj @@ -38,7 +38,7 @@ [job concurrent-execution-disallowed?] (let [{:keys [identity description recover? durable? disallow-concurrent-execution?]} job] (.build (cond-> (-> (JobBuilder/newJob (if (or concurrent-execution-disallowed? ;; global concurrency disallowed flag - disallow-concurrent-execution?) ;; job specific concurrency dissalowed flag + disallow-concurrent-execution?) ;; job specific concurrency disallowed flag SerialProxyJob ProxyJob)) (.setJobData (JobDataMap. {"job-impl" job}))) (seq identity) (.withIdentity (first identity) (second identity)) diff --git a/cronut/src/cronut/job.clj b/cronut/src/cronut/job.clj index 8faa9b3..ac97338 100644 --- a/cronut/src/cronut/job.clj +++ b/cronut/src/cronut/job.clj @@ -38,7 +38,7 @@ [job concurrent-execution-disallowed?] (let [{:keys [identity description recover? durable? disallow-concurrent-execution?]} job] (.build (cond-> (-> (JobBuilder/newJob (if (or concurrent-execution-disallowed? ;; global concurrency disallowed flag - disallow-concurrent-execution?) ;; job specific concurrency dissalowed flag + disallow-concurrent-execution?) ;; job specific concurrency disallowed flag SerialProxyJob ProxyJob)) (.setJobData (JobDataMap. {"job-impl" job}))) (seq identity) (.withIdentity (first identity) (second identity)) From c22fd54acdbd8d8b4dcf7592e0a103784ee9cef0 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 16:31:40 +1000 Subject: [PATCH 06/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 364 ++++++++++++------ .../test/cronut/integration_test.clj | 11 +- 2 files changed, 254 insertions(+), 121 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index ce94faf..c7a7f70 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -7,11 +7,12 @@ Cronut provides a data-first Clojure wrapper for the [Quartz Job Scheduler](https://github.com/quartz-scheduler). -Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI framework. +Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI +framework. # Usage -## :cronut/scheduler +## `:cronut/scheduler` Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` @@ -24,15 +25,15 @@ The scheduler supports the following fields: e.g. ````clojure -:cronut/scheduler {:schedule [{:job #ig/ref :test.job/two - :trigger #cronut/interval 3500} - {:job #ig/ref :test.job/two - :trigger #cronut/cron "*/8 * * * * ?" - :misfire :do-nothing}] +:cronut/scheduler {:schedule [{:job #ig/ref :test.job/two + :trigger #cronut/interval 3500} + {:job #ig/ref :test.job/two + :trigger #cronut/cron "*/8 * * * * ?" + :misfire :do-nothing}] :concurrent-execution-disallowed? true} ```` -### The :job +### The `:job` The `:job` in every scheduled item must implement the org.quartz.Job interface @@ -56,44 +57,32 @@ or by returning a defrecord that implements the interface. e.g. (map->TestDefrecordJobImpl config)) ```` -Cronut supports further Quartz configuration of jobs (identity, description, recovery, and priority) by expecting those -values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore them), -however if you do want that control you will likely use the defrecord approach as opposed to the simpler reify option -and pass that configuration through edn, e.g. +Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by expecting +those values to be assoc'd onto your job. + +You do not have to set them (in fact in most cases you can likely ignore them), however if you do want that control you +will likely use the defrecord approach as opposed to the simpler reify option. + +Concurrent execution can be controlled on a per-job bases with the `disallow-concurrent-execution?` flag. ````clojure -:test.job/two {:identity ["job-two" "test"] - :description "test job" - :recover? true - :durable? false - :dep-one #ig/ref :dep/one - :dep-two #ig/ref :test.job/one} +:test.job/two {:identity ["job-two" "test"] + :description "test job" + :recover? true + :durable? false + :disallow-concurrent-execution? true + :dep-one #ig/ref :dep/one + :dep-two #ig/ref :test.job/one} ```` -### The :trigger +### The `:trigger` -The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that +The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that resolution Cronut provides the following tagged literals: -### Controlling concurrent execution - -#### Global scheduler `:concurrent-execution-disallowed?` - -Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. - -#### Local job `:disallow-concurrent-execution?` - -Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. - -#### Misfire configuration - -If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set `org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more information. - -See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and behaviour configuration. - -### Tagged Literals +### Trigger tagged literals -#### #cronut/cron: Simple Cron Scheduling +#### `#cronut/cron`: Simple Cron Scheduling A job is scheduled to run on a cron by using the `#cronut/cron` tagged literal followed by a valid cron expression @@ -103,19 +92,19 @@ The job will start immediately when the system is initialized, and runs in the d :trigger #cronut/cron "*/8 * * * * ?" ```` -#### #cronut/interval: Simple Interval Scheduling +#### `#cronut/interval`: Simple Interval Scheduling -A job is scheduled to run periodically by using the `#cronut/interval` tagged literal followed by a milliseconds value +A job is scheduled to run periodically by using the `#cronut/interval` tagged literal followed by a milliseconds value ````clojure :trigger #cronut/interval 3500 ```` -#### #cronut/trigger: Full (and extensible) Trigger Definition +#### `#cronut/trigger`: Full trigger definition -Both #cronut/cron and #cronut/interval are effectively shortcuts to full trigger definition with sensible defaults. +Both `#cronut/cron` and `#cronut/interval` are effectively shortcuts to full trigger definition with sensible defaults. -The #cronut/trigger tagged literal supports the full set of Quartz configuration for Simple and Cron triggers: +The `#cronut/trigger` tagged literal supports the full set of Quartz configuration triggers: ````clojure ;; interval @@ -128,7 +117,7 @@ The #cronut/trigger tagged literal supports the full set of Quartz configuration :end #inst "2019-02-01T00:00:00.000-00:00" :misfire :ignore :priority 5} - + ;;cron :trigger #cronut/trigger {:type :cron :cron "*/6 * * * * ?" @@ -141,12 +130,26 @@ The #cronut/trigger tagged literal supports the full set of Quartz configuration :priority 4} ```` -This tagged literal calls a Clojure multi-method that is open for extension. +### Controlling concurrent execution + +#### `:concurrent-execution-disallowed?` on the global scheduler + +Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. + +#### `:disallow-concurrent-execution?` on a specific job + +Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. + +#### Misfire configuration + +If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set +`org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more +information. -You should implement the remaining two Quartz Triggers (CalendarInterval and DailyTimeInterval), create the Tagged -Literal for each, and raise a PR. Go on it will be fun, open issues exist for both triggers. +See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and +behaviour configuration. -## Integrant +## Integrant system initialization When initializing an Integrant system you will need to provide the Cronut data readers. @@ -160,6 +163,7 @@ See: `cronut/data-readers` for convenience. ```` e.g. + ````clojure (defn init-system "Convenience for starting integrant systems with cronut data-readers" @@ -169,106 +173,230 @@ e.g. (ig/init (ig/read-string {:readers (merge cronut/data-readers readers)} config)))) ```` -## Quartz Specifics and Remaining Todo's - -Cronut supports a single Quartz Scheduler per JVM (optionally configured with [quartz.properties](http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigMain.html)). - -The default StdScheduler is reset and re-used on each instantiation of a :cronut/scheduler. +## Example system -Cron triggers default to using the system time-zone if no trigger time-zone specifically set. +This repository contains an example system composed of of integratant configuration, job definitions, and helper +functions. -Tickets are open for the following extensions (contributions warmly welcomed): +### Integrant configuration -* Implement DailyTimeInterval Trigger -* Implement CalendarInverval Trigger -* Pluggable SchedulerFactory (support more than one scheduler per JVM) - -## Example System - -Given a simple Integrant configuration of two jobs and four triggers. - -Job Two executes on multiple schedules as defined by the latter three triggers. +Integrant configuration source: [dev-resources/config.edn](dev-resources/config.edn). ````clojure -{:test.job/one {} +{:dep/one {:a 1} + + :test.job/one {:dep-one #ig/ref :dep/one} - :test.job/two {:identity ["job-two" "test"] + :test.job/two {:identity ["test-group" "test-name"] :description "test job" :recover? true :durable? false + :dep-one #ig/ref :dep/one :dep-two #ig/ref :test.job/one} - :cronut/scheduler {:schedule [;; basic interval - {:job #ig/ref :test.job/one - :trigger #cronut/trigger {:type :simple - :interval 2 - :time-unit :seconds - :repeat :forever}} - - ;; shortcut interval via cronut/interval data-reader - {:job #ig/ref :test.job/two - :trigger #cronut/interval 3500} - - ;; basic cron - {:job #ig/ref :test.job/two - :trigger #cronut/trigger {:type :cron - :cron "*/4 * * * * ?"}} - - ;; shortcut cron via cronut/cron data-reader - {:job #ig/ref :test.job/two - :trigger #cronut/cron "*/8 * * * * ?"}]}} + :test.job/three {} + + :cronut/scheduler {:update-check? false + :concurrent-execution-disallowed? true + :schedule [;; basic interval + {:job #ig/ref :test.job/one + :trigger #cronut/trigger {:type :simple + :interval 2 + :time-unit :seconds + :repeat :forever}} + + ;; full interval + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :simple + :interval 3000 + :repeat :forever + :identity ["trigger-two" "test"] + :description "test trigger" + :start #inst "2019-01-01T00:00:00.000-00:00" + :end #inst "2019-02-01T00:00:00.000-00:00" + :priority 5}} + + ;; shortcut interval + {:job #ig/ref :test.job/two + :trigger #cronut/interval 3500} + + ;; basic cron + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :cron + :cron "*/4 * * * * ?"}} + + ;; full cron + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :cron + :cron "*/6 * * * * ?" + :identity ["trigger-five" "test"] + :description "another-test trigger" + :start #inst "2018-01-01T00:00:00.000-00:00" + :end #inst "2029-02-01T00:00:00.000-00:00" + :time-zone "Australia/Melbourne" + :priority 4}} + + ;; shortcut cron + {:job #ig/ref :test.job/two + :trigger #cronut/cron "*/8 * * * * ?"} + + ;; Note: This job misfires because it takes 7 seconds to run, but runs every 5 seconds, and isn't allowed to run concurrently with {:disallowConcurrentExecution? true} + ;; So every second job fails to run, and is just ignored with the :do-nothing :misfire rule + {:job #ig/ref :test.job/three + :trigger #cronut/trigger {:type :cron + :cron "*/5 * * * * ?" + :misfire :do-nothing}}]}} ```` -And the associated Integrant lifecycle impl, note: +### Job definitions -- `test.job/one` reifies the org.quartz.Job interface -- `test.job/two` instantiates a defrecord (that allows some further quartz job configuration) +Job definitions source: [test/cronut/integration-test.clj](test/cronut/integration_test.clj) + +```clojure +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallow-concurrent-execution?] + Job + (execute [this _job-context] + (log/info "Defrecord Impl:" this))) + +(defmethod ig/init-key :dep/one + [_ config] + config) -````clojure (defmethod ig/init-key :test.job/one [_ config] (reify Job - (execute [this job-context] + (execute [_this _job-context] (log/info "Reified Impl:" config)))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep] - Job - (execute [this job-context] - (log/info "Defrecord Impl:" this))) - (defmethod ig/init-key :test.job/two [_ config] (map->TestDefrecordJobImpl config)) -```` -We can realise that system and run those jobs (See `cronut.integration-fixture` for full example): +(defmethod ig/init-key :test.job/three + [_ config] + (reify Job + (execute [_this _job-context] + (let [rand-id (str (UUID/randomUUID))] + (log/info rand-id "Reified Impl (Job Delay 7s):" config) + (async/ nil +(defn init-system + "Example of starting integrant cronut systems with data-readers" + ([] + (init-system (slurp (io/resource "config.edn")))) + ([config] + (init-system config nil)) + ([config readers] + (ig/init (ig/read-string {:readers (merge cig/data-readers readers)} config)))) -(itf/init-system) +(defn halt-system + "Example of stopping integrant cronut systems" + [system] + (ig/halt! system)) +```` + +### Putting it together + +#### Starting the system + +```clojure +(do + (require '[cronut.integration-test :as test]) + (test/init-system)) +``` + +#### Logs of the running system + +```bash +16:29:37.378 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – initializing scheduler +16:29:37.378 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – with quartz update check disabled +16:29:37.387 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.impl.StdSchedulerFactory – Using default implementation for ThreadExecutor +16:29:37.392 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] o.quartz.core.SchedulerSignalerImpl – Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl +16:29:37.392 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.core.QuartzScheduler – Quartz Scheduler v2.5.0 created. +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.simpl.RAMJobStore – RAMJobStore initialized. +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.core.QuartzScheduler – Scheduler meta-data: Quartz Scheduler (v2.5.0) 'CronutScheduler' with instanceId 'NON_CLUSTERED' + Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. + NOT STARTED. + Currently in standby mode. + Number of jobs executed: 0 + Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 6 threads. + Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. + +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.impl.StdSchedulerFactory – Quartz scheduler 'CronutScheduler' initialized from default resource file in Quartz package: 'quartz.properties' +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.impl.StdSchedulerFactory – Quartz scheduler version: 2.5.0 +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – with global concurrent execution disallowed +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.core.QuartzScheduler – JobFactory set to: cronut.job$factory$reify__12146@101e15ee +16:29:37.393 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling [7] jobs +16:29:37.401 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new job #object[org.quartz.impl.triggers.SimpleTriggerImpl 0x467fb22c Trigger 'DEFAULT.6da64b5bd2ee-880e78bc-4873-42b3-9b5a-64bcfce6f5a1': triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0x28117587 JobDetail 'DEFAULT.6da64b5bd2ee-b8c8c92a-b907-4859-b28a-6be8f4f41fea': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: false] +16:29:37.402 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new job #object[org.quartz.impl.triggers.SimpleTriggerImpl 0x3cd7003b Trigger 'test.trigger-two': triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0x737df17e JobDetail 'test-name.test-group': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: true] +16:29:37.402 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new trigger for existing job #object[org.quartz.impl.triggers.SimpleTriggerImpl 0x41c1388d Trigger 'DEFAULT.6da64b5bd2ee-c94286ad-ab6a-4539-92db-4bcf467f77fd': triggerClass: 'org.quartz.impl.triggers.SimpleTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0x1e1c028d JobDetail 'test-name.test-group': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: true] +16:29:37.403 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new trigger for existing job #object[org.quartz.impl.triggers.CronTriggerImpl 0x78817b4e Trigger 'DEFAULT.6da64b5bd2ee-f706f964-b8c2-4d55-b7a5-a9a4c5795f4f': triggerClass: 'org.quartz.impl.triggers.CronTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0x36fdd3e3 JobDetail 'test-name.test-group': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: true] +16:29:37.404 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new trigger for existing job #object[org.quartz.impl.triggers.CronTriggerImpl 0x44d4758e Trigger 'test.trigger-five': triggerClass: 'org.quartz.impl.triggers.CronTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0x6ee1851e JobDetail 'test-name.test-group': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: true] +16:29:37.404 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new trigger for existing job #object[org.quartz.impl.triggers.CronTriggerImpl 0x4900a5b0 Trigger 'DEFAULT.6da64b5bd2ee-31aa5433-c5d3-4ddd-b21e-04a2d4920548': triggerClass: 'org.quartz.impl.triggers.CronTriggerImpl calendar: 'null' misfireInstruction: 0 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0xc979851 JobDetail 'test-name.test-group': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: true] +16:29:37.404 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – scheduling new job #object[org.quartz.impl.triggers.CronTriggerImpl 0x6b88f448 Trigger 'DEFAULT.6da64b5bd2ee-6fd7a0a0-bf32-4256-8c27-a2b29f11d5ef': triggerClass: 'org.quartz.impl.triggers.CronTriggerImpl calendar: 'null' misfireInstruction: 2 nextFireTime: null] #object[org.quartz.impl.JobDetailImpl 0x9a35552 JobDetail 'DEFAULT.6da64b5bd2ee-c6c26ace-ce1a-44ed-b922-096a3f5233f4': jobClass: 'cronut.job.SerialProxyJob concurrentExecutionDisallowed: true persistJobDataAfterExecution: false isDurable: false requestsRecovers: false] +16:29:37.405 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] org.quartz.core.QuartzScheduler – Scheduler CronutScheduler_$_NON_CLUSTERED started. +16:29:37.406 INFO [CronutScheduler_Worker-1] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:37.408 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:37.409 INFO [CronutScheduler_Worker-3] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} => {:dep/one {:a 1}, - :test.job/one #object[ronut.integration_fixture$eval2343$fn$reify__2345 - 0x2e906b8a - "cronut.integration_fixture$eval2343$fn$reify__2345@2e906b8a"], - :test.job/two #cronut.integration_fixture.TestDefrecordJobImpl{:identity ["job-two" "test"], - :description "test job", - :recover? true, - :durable? false, - :test-dep nil, - :dep-one {:a 1}, - :dep-two #object[cronut.integration_fixture$eval2343$fn$reify__2345 - 0x2e906b8a - "cronut.integration_fixture$eval2343$fn$reify__2345@2e906b8a"]}, - :cronut/scheduler #object[org.quartz.impl.StdScheduler 0x7565dd8e "org.quartz.impl.StdScheduler@7565dd8e"]} - -(require '[integrant.core :as ig]) -(ig/halt! *1) -=> nil -```` + :test.job/one #object[cronut.integration_test$eval13104$fn$reify__13106 + 0x45425cf3 + "cronut.integration_test$eval13104$fn$reify__13106@45425cf3"], + :test.job/three #object[cronut.integration_test$eval13115$fn$reify__13117 + 0x7527011a + "cronut.integration_test$eval13115$fn$reify__13117@7527011a"], + :test.job/two #cronut.integration_test.TestDefrecordJobImpl{:identity ["test-group" "test-name"], + :description "test job", + :recover? true, + :durable? false, + :test-dep nil, + :disallow-concurrent-execution? nil, + :dep-one {:a 1}, + :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 + 0x45425cf3 + "cronut.integration_test$eval13104$fn$reify__13106@45425cf3"]}, + :cronut/scheduler #object[org.quartz.impl.StdScheduler 0x59a18142 "org.quartz.impl.StdScheduler@59a18142"]} +16:29:39.368 INFO [CronutScheduler_Worker-4] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:40.005 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:40.005 INFO [CronutScheduler_Worker-6] cronut.integration-test – 3979b197-5683-47a9-a267-dcaded343697 Reified Impl (Job Delay 7s): {} +16:29:40.006 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:40.876 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:41.368 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:42.004 INFO [CronutScheduler_Worker-4] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:43.364 INFO [CronutScheduler_Worker-5] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:44.007 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:44.375 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:45.368 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:47.011 INFO [CronutScheduler_Worker-6] cronut.integration-test – 3979b197-5683-47a9-a267-dcaded343697 Finished +16:29:47.368 INFO [CronutScheduler_Worker-4] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:47.875 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:48.008 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:48.010 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:48.011 INFO [CronutScheduler_Worker-3] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:49.370 INFO [CronutScheduler_Worker-6] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:50.004 INFO [CronutScheduler_Worker-4] cronut.integration-test – 299b73c8-97ad-4d85-848f-35960ced6362 Reified Impl (Job Delay 7s): {} +16:29:51.368 INFO [CronutScheduler_Worker-5] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:51.368 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:52.004 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:53.366 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:54.007 INFO [CronutScheduler_Worker-6] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:54.874 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +``` + +#### Stopping the system + +```clojure +(test/halt-system *1) +``` ## License diff --git a/cronut-integrant/test/cronut/integration_test.clj b/cronut-integrant/test/cronut/integration_test.clj index fc1cadd..4d0787b 100644 --- a/cronut-integrant/test/cronut/integration_test.clj +++ b/cronut-integrant/test/cronut/integration_test.clj @@ -7,7 +7,7 @@ (:import (java.util UUID) (org.quartz Job))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallow-concurrent-execution?] Job (execute [this _job-context] (log/info "Defrecord Impl:" this))) @@ -36,10 +36,15 @@ (log/info rand-id "Finished"))))) (defn init-system - "Convenience for starting integrant systems with cronut data-readers" + "Example of starting integrant cronut systems with data-readers" ([] (init-system (slurp (io/resource "config.edn")))) ([config] (init-system config nil)) ([config readers] - (ig/init (ig/read-string {:readers (merge cig/data-readers readers)} config)))) \ No newline at end of file + (ig/init (ig/read-string {:readers (merge cig/data-readers readers)} config)))) + +(defn halt-system + "Example of stopping integrant cronut systems" + [system] + (ig/halt! system)) \ No newline at end of file From 4f9ae5e9b482faca84cf1cc5d57e9fac30a36d37 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 16:32:25 +1000 Subject: [PATCH 07/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index c7a7f70..21c8802 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -5,7 +5,7 @@ # Summary -Cronut provides a data-first Clojure wrapper for the [Quartz Job Scheduler](https://github.com/quartz-scheduler). +[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for the [Quartz Job Scheduler](https://github.com/quartz-scheduler). Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI framework. From 86b71b86086336e8040088bfa21401ff54aefde6 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 16:37:16 +1000 Subject: [PATCH 08/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 40 ++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index 21c8802..7568b96 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -3,9 +3,37 @@ [![Cronut Test](https://github.com/factorhouse/cronut/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/factorhouse/cronut/actions/workflows/ci.yml) [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) +# Contents + +- [Summary](#summary) +- [Usage](#usage) + * [`:cronut/scheduler`](#cronutscheduler) + + [The `:job`](#the-job) + - [Example job](#example-job) + + [The `:trigger`](#the-trigger) + + [Trigger tagged literals](#trigger-tagged-literals) + - [`#cronut/cron`: Simple Cron Scheduling](#cronutcron-simple-cron-scheduling) + - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) + - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) + + [Controlling concurrent execution](#controlling-concurrent-execution) + - [`:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) + - [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) + - [Misfire configuration](#misfire-configuration) + * [Integrant system initialization](#integrant-system-initialization) + * [Example system](#example-system) + + [Integrant configuration](#integrant-configuration) + + [Job definitions](#job-definitions) + + [Helper functions](#helper-functions) + + [Putting it together](#putting-it-together) + - [Starting the system](#starting-the-system) + - [Logs of the running system](#logs-of-the-running-system) + - [Stopping the system](#stopping-the-system) + * [License](#license) + # Summary -[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for the [Quartz Job Scheduler](https://github.com/quartz-scheduler). +[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for +the [Quartz Job Scheduler](https://github.com/quartz-scheduler). Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI framework. @@ -38,7 +66,7 @@ e.g. The `:job` in every scheduled item must implement the org.quartz.Job interface The expectation being that every 'job' in your Integrant system will reify that interface, either directly via `reify` -or by returning a defrecord that implements the interface. e.g. +or by returning a `defrecord` that implements the interface. e.g. ````clojure (defmethod ig/init-key :test.job/one @@ -58,13 +86,13 @@ or by returning a defrecord that implements the interface. e.g. ```` Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by expecting -those values to be assoc'd onto your job. - -You do not have to set them (in fact in most cases you can likely ignore them), however if you do want that control you -will likely use the defrecord approach as opposed to the simpler reify option. +those values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore +them), however if you do want that control you will likely use the `defrecord` approach as opposed to `reify`. Concurrent execution can be controlled on a per-job bases with the `disallow-concurrent-execution?` flag. +#### Example job + ````clojure :test.job/two {:identity ["job-two" "test"] :description "test job" From 4f6c9b86145f604eda9e17dcbff383c04122f9a2 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 16:43:18 +1000 Subject: [PATCH 09/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 83 +++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index 7568b96..0583b45 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -6,29 +6,30 @@ # Contents - [Summary](#summary) -- [Usage](#usage) - * [`:cronut/scheduler`](#cronutscheduler) - + [The `:job`](#the-job) - - [Example job](#example-job) - + [The `:trigger`](#the-trigger) - + [Trigger tagged literals](#trigger-tagged-literals) +- [Configuration](#configuration) + * [`:cronut/scheduler` definition](#cronutscheduler-definition) + + [Scheduler example](#scheduler-example) + * [`:job` definition](#job-definition) + + [Job example](#job-example) + * [`:trigger` definition](#trigger-definition) + + [`:trigger` tagged literals](#trigger-tagged-literals) - [`#cronut/cron`: Simple Cron Scheduling](#cronutcron-simple-cron-scheduling) - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) - + [Controlling concurrent execution](#controlling-concurrent-execution) - - [`:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) - - [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) - - [Misfire configuration](#misfire-configuration) - * [Integrant system initialization](#integrant-system-initialization) - * [Example system](#example-system) - + [Integrant configuration](#integrant-configuration) - + [Job definitions](#job-definitions) - + [Helper functions](#helper-functions) - + [Putting it together](#putting-it-together) - - [Starting the system](#starting-the-system) - - [Logs of the running system](#logs-of-the-running-system) - - [Stopping the system](#stopping-the-system) - * [License](#license) + * [Concurrent execution](#concurrent-execution) + + [`:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) + + [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) + + [Misfire configuration](#misfire-configuration) +- [System initialization](#system-initialization) +- [Example system](#example-system) + * [Configuration](#configuration-1) + * [Job definitions](#job-definitions) + * [Helper functions](#helper-functions) + * [Putting it together](#putting-it-together) + + [Starting the system](#starting-the-system) + + [Logs of the running system](#logs-of-the-running-system) + + [Stopping the system](#stopping-the-system) +- [License](#license) # Summary @@ -38,9 +39,9 @@ the [Quartz Job Scheduler](https://github.com/quartz-scheduler). Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI framework. -# Usage +# Configuration -## `:cronut/scheduler` +## `:cronut/scheduler` definition Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` @@ -50,7 +51,7 @@ The scheduler supports the following fields: 2. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution 2. (optional, default false) :update-check? check for Quartz updates on system startup. -e.g. +### Scheduler example ````clojure :cronut/scheduler {:schedule [{:job #ig/ref :test.job/two @@ -61,7 +62,7 @@ e.g. :concurrent-execution-disallowed? true} ```` -### The `:job` +## `:job` definition The `:job` in every scheduled item must implement the org.quartz.Job interface @@ -91,7 +92,7 @@ them), however if you do want that control you will likely use the `defrecord` a Concurrent execution can be controlled on a per-job bases with the `disallow-concurrent-execution?` flag. -#### Example job +### Job example ````clojure :test.job/two {:identity ["job-two" "test"] @@ -103,12 +104,12 @@ Concurrent execution can be controlled on a per-job bases with the `disallow-con :dep-two #ig/ref :test.job/one} ```` -### The `:trigger` +## `:trigger` definition The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that resolution Cronut provides the following tagged literals: -### Trigger tagged literals +### `:trigger` tagged literals #### `#cronut/cron`: Simple Cron Scheduling @@ -158,17 +159,17 @@ The `#cronut/trigger` tagged literal supports the full set of Quartz configurati :priority 4} ```` -### Controlling concurrent execution +## Concurrent execution -#### `:concurrent-execution-disallowed?` on the global scheduler +### `:concurrent-execution-disallowed?` on the global scheduler Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. -#### `:disallow-concurrent-execution?` on a specific job +### `:disallow-concurrent-execution?` on a specific job Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. -#### Misfire configuration +### Misfire configuration If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set `org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more @@ -177,7 +178,7 @@ information. See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and behaviour configuration. -## Integrant system initialization +# System initialization When initializing an Integrant system you will need to provide the Cronut data readers. @@ -201,12 +202,12 @@ e.g. (ig/init (ig/read-string {:readers (merge cronut/data-readers readers)} config)))) ```` -## Example system +# Example system This repository contains an example system composed of of integratant configuration, job definitions, and helper functions. -### Integrant configuration +## Configuration Integrant configuration source: [dev-resources/config.edn](dev-resources/config.edn). @@ -277,7 +278,7 @@ Integrant configuration source: [dev-resources/config.edn](dev-resources/config. ```` -### Job definitions +## Job definitions Job definitions source: [test/cronut/integration-test.clj](test/cronut/integration_test.clj) @@ -311,7 +312,7 @@ Job definitions source: [test/cronut/integration-test.clj](test/cronut/integrati (log/info rand-id "Finished"))))) ``` -### Helper functions +## Helper functions Helper functions source: [test/cronut/integration-test.clj](test/cronut/integration_test.clj) @@ -331,9 +332,9 @@ Helper functions source: [test/cronut/integration-test.clj](test/cronut/integrat (ig/halt! system)) ```` -### Putting it together +## Putting it together -#### Starting the system +### Starting the system ```clojure (do @@ -341,7 +342,7 @@ Helper functions source: [test/cronut/integration-test.clj](test/cronut/integrat (test/init-system)) ``` -#### Logs of the running system +### Logs of the running system ```bash 16:29:37.378 INFO [nREPL-session-03644f18-045b-47e8-b0be-5c9b069c6ee0] cronut – initializing scheduler @@ -420,13 +421,13 @@ Helper functions source: [test/cronut/integration-test.clj](test/cronut/integrat 16:29:54.874 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} ``` -#### Stopping the system +### Stopping the system ```clojure (test/halt-system *1) ``` -## License +# License Distributed under the Apache 2.0 License. From 69ac912460b9c9cd324bb3bf1148a3c84d086eac Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 16:54:15 +1000 Subject: [PATCH 10/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index 0583b45..cfd9936 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -3,9 +3,16 @@ [![Cronut Test](https://github.com/factorhouse/cronut/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/factorhouse/cronut/actions/workflows/ci.yml) [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) +# Summary + +[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for +the [Quartz Job Scheduler](https://github.com/quartz-scheduler). + +Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI +framework. + # Contents -- [Summary](#summary) - [Configuration](#configuration) * [`:cronut/scheduler` definition](#cronutscheduler-definition) + [Scheduler example](#scheduler-example) @@ -31,14 +38,6 @@ + [Stopping the system](#stopping-the-system) - [License](#license) -# Summary - -[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for -the [Quartz Job Scheduler](https://github.com/quartz-scheduler). - -Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI -framework. - # Configuration ## `:cronut/scheduler` definition From 3c0d3c983f469aa3d79c605653a45308d0bc4606 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:01:46 +1000 Subject: [PATCH 11/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index cfd9936..3b32e4b 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -40,6 +40,10 @@ framework. # Configuration +A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. + +These are the key items that we configure with Cronut. + ## `:cronut/scheduler` definition Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` From 427682c3391715a09e999fa1643983740a9ae679 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:03:31 +1000 Subject: [PATCH 12/23] cronut-integrant README.md wip --- cronut-integrant/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index 3b32e4b..b0586e8 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -42,8 +42,6 @@ framework. A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. -These are the key items that we configure with Cronut. - ## `:cronut/scheduler` definition Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` From 2b6ce03d558a910ef629898b096c28259d91b6af Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:14:41 +1000 Subject: [PATCH 13/23] cronut README.md wip --- cronut/README.md | 442 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 cronut/README.md diff --git a/cronut/README.md b/cronut/README.md new file mode 100644 index 0000000..050cab8 --- /dev/null +++ b/cronut/README.md @@ -0,0 +1,442 @@ +# Cronut: A Clojure Companion to Quartz + +[![Cronut Test](https://github.com/factorhouse/cronut/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/factorhouse/cronut/actions/workflows/ci.yml) +[![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut.svg)](https://clojars.org/io.factorhouse/cronut) + +### Related Projects + +| Project | Quartz Dependency | Desription | Clojars Project | +|-------------------------------------------------------------------------|-------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [cronut](https://clojars.org/io.factorhouse/cronut) | 2.5.0 | Primary (Jakarta) project | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut.svg)](https://clojars.org/io.factorhouse/cronut) | +| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | 2.4.0 | Legacy (Javax) project | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | - | Integrant bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | + +# Summary + +[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for +the [Quartz Job Scheduler](https://github.com/quartz-scheduler). + +Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. + +# Contents + +- [Configuration](#configuration) + * [`:cronut/scheduler` definition](#cronutscheduler-definition) + + [Scheduler example](#scheduler-example) + * [`:job` definition](#job-definition) + + [Job example](#job-example) + * [`:trigger` definition](#trigger-definition) + + [`:trigger` tagged literals](#trigger-tagged-literals) + - [`#cronut/cron`: Simple Cron Scheduling](#cronutcron-simple-cron-scheduling) + - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) + - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) + * [Concurrent execution](#concurrent-execution) + + [`:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) + + [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) + + [Misfire configuration](#misfire-configuration) +- [System initialization](#system-initialization) +- [Example system](#example-system) + * [Configuration](#configuration-1) + * [Job definitions](#job-definitions) + * [Helper functions](#helper-functions) + * [Putting it together](#putting-it-together) + + [Starting the system](#starting-the-system) + + [Logs of the running system](#logs-of-the-running-system) + + [Stopping the system](#stopping-the-system) +- [License](#license) + +# Configuration + +A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. + +## `:cronut/scheduler` definition + +Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` + +The scheduler supports the following fields: + +1. (required) :schedule - a sequence of 'items' to schedule, each being a map containing a :job and :trigger +2. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution +2. (optional, default false) :update-check? check for Quartz updates on system startup. + +### Scheduler example + +````clojure +:cronut/scheduler {:schedule [{:job #ig/ref :test.job/two + :trigger #cronut/interval 3500} + {:job #ig/ref :test.job/two + :trigger #cronut/cron "*/8 * * * * ?" + :misfire :do-nothing}] + :concurrent-execution-disallowed? true} +```` + +## `:job` definition + +The `:job` in every scheduled item must implement the org.quartz.Job interface + +The expectation being that every 'job' in your Integrant system will reify that interface, either directly via `reify` +or by returning a `defrecord` that implements the interface. e.g. + +````clojure +(defmethod ig/init-key :test.job/one + [_ config] + (reify Job + (execute [this job-context] + (log/info "Reified Impl:" config)))) + +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep] + Job + (execute [this job-context] + (log/info "Defrecord Impl:" this))) + +(defmethod ig/init-key :test.job/two + [_ config] + (map->TestDefrecordJobImpl config)) +```` + +Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by expecting +those values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore +them), however if you do want that control you will likely use the `defrecord` approach as opposed to `reify`. + +Concurrent execution can be controlled on a per-job bases with the `disallow-concurrent-execution?` flag. + +### Job example + +````clojure +:test.job/two {:identity ["job-two" "test"] + :description "test job" + :recover? true + :durable? false + :disallow-concurrent-execution? true + :dep-one #ig/ref :dep/one + :dep-two #ig/ref :test.job/one} +```` + +## `:trigger` definition + +The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that +resolution Cronut provides the following tagged literals: + +### `:trigger` tagged literals + +#### `#cronut/cron`: Simple Cron Scheduling + +A job is scheduled to run on a cron by using the `#cronut/cron` tagged literal followed by a valid cron expression + +The job will start immediately when the system is initialized, and runs in the default system time-zone + +````clojure +:trigger #cronut/cron "*/8 * * * * ?" +```` + +#### `#cronut/interval`: Simple Interval Scheduling + +A job is scheduled to run periodically by using the `#cronut/interval` tagged literal followed by a milliseconds value + +````clojure +:trigger #cronut/interval 3500 +```` + +#### `#cronut/trigger`: Full trigger definition + +Both `#cronut/cron` and `#cronut/interval` are effectively shortcuts to full trigger definition with sensible defaults. + +The `#cronut/trigger` tagged literal supports the full set of Quartz configuration triggers: + +````clojure +;; interval +:trigger #cronut/trigger {:type :simple + :interval 3000 + :repeat :forever + :identity ["trigger-two" "test"] + :description "sample simple trigger" + :start #inst "2019-01-01T00:00:00.000-00:00" + :end #inst "2019-02-01T00:00:00.000-00:00" + :misfire :ignore + :priority 5} + +;;cron +:trigger #cronut/trigger {:type :cron + :cron "*/6 * * * * ?" + :identity ["trigger-five" "test"] + :description "sample cron trigger" + :start #inst "2018-01-01T00:00:00.000-00:00" + :end #inst "2029-02-01T00:00:00.000-00:00" + :time-zone "Australia/Melbourne" + :misfire :fire-and-proceed + :priority 4} +```` + +## Concurrent execution + +### `:concurrent-execution-disallowed?` on the global scheduler + +Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. + +### `:disallow-concurrent-execution?` on a specific job + +Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. + +### Misfire configuration + +If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set +`org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more +information. + +See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and +behaviour configuration. + +# System initialization + +When initializing an Integrant system you will need to provide the Cronut data readers. + +See: `cronut/data-readers` for convenience. + +````clojure +(def data-readers + {'cronut/trigger cronut/trigger-builder + 'cronut/cron cronut/shortcut-cron + 'cronut/interval cronut/shortcut-interval}) +```` + +e.g. + +````clojure +(defn init-system + "Convenience for starting integrant systems with cronut data-readers" + ([config] + (init-system config nil)) + ([config readers] + (ig/init (ig/read-string {:readers (merge cronut/data-readers readers)} config)))) +```` + +# Example system + +This repository contains an example system composed of of integratant configuration, job definitions, and helper +functions. + +## Configuration + +Integrant configuration source: [dev-resources/config.edn](dev-resources/config.edn). + +````clojure +{:dep/one {:a 1} + + :test.job/one {:dep-one #ig/ref :dep/one} + + :test.job/two {:identity ["test-group" "test-name"] + :description "test job" + :recover? true + :durable? false + :dep-one #ig/ref :dep/one + :dep-two #ig/ref :test.job/one} + + :test.job/three {} + + :cronut/scheduler {:update-check? false + :concurrent-execution-disallowed? true + :schedule [;; basic interval + {:job #ig/ref :test.job/one + :trigger #cronut/trigger {:type :simple + :interval 2 + :time-unit :seconds + :repeat :forever}} + + ;; full interval + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :simple + :interval 3000 + :repeat :forever + :identity ["trigger-two" "test"] + :description "test trigger" + :start #inst "2019-01-01T00:00:00.000-00:00" + :end #inst "2019-02-01T00:00:00.000-00:00" + :priority 5}} + + ;; shortcut interval + {:job #ig/ref :test.job/two + :trigger #cronut/interval 3500} + + ;; basic cron + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :cron + :cron "*/4 * * * * ?"}} + + ;; full cron + {:job #ig/ref :test.job/two + :trigger #cronut/trigger {:type :cron + :cron "*/6 * * * * ?" + :identity ["trigger-five" "test"] + :description "another-test trigger" + :start #inst "2018-01-01T00:00:00.000-00:00" + :end #inst "2029-02-01T00:00:00.000-00:00" + :time-zone "Australia/Melbourne" + :priority 4}} + + ;; shortcut cron + {:job #ig/ref :test.job/two + :trigger #cronut/cron "*/8 * * * * ?"} + + ;; Note: This job misfires because it takes 7 seconds to run, but runs every 5 seconds, and isn't allowed to run concurrently with {:disallowConcurrentExecution? true} + ;; So every second job fails to run, and is just ignored with the :do-nothing :misfire rule + {:job #ig/ref :test.job/three + :trigger #cronut/trigger {:type :cron + :cron "*/5 * * * * ?" + :misfire :do-nothing}}]}} + +```` + +## Job definitions + +Job definitions source: [test/cronut/integration-test.clj](test/cronut/integration_test.clj) + +```clojure +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallow-concurrent-execution?] + Job + (execute [this _job-context] + (log/info "Defrecord Impl:" this))) + +(defmethod ig/init-key :dep/one + [_ config] + config) + +(defmethod ig/init-key :test.job/one + [_ config] + (reify Job + (execute [_this _job-context] + (log/info "Reified Impl:" config)))) + +(defmethod ig/init-key :test.job/two + [_ config] + (map->TestDefrecordJobImpl config)) + +(defmethod ig/init-key :test.job/three + [_ config] + (reify Job + (execute [_this _job-context] + (let [rand-id (str (UUID/randomUUID))] + (log/info rand-id "Reified Impl (Job Delay 7s):" config) + (async/ +{:dep/one {:a 1}, + :test.job/one #object[cronut.integration_test$eval13104$fn$reify__13106 + 0x45425cf3 + "cronut.integration_test$eval13104$fn$reify__13106@45425cf3"], + :test.job/three #object[cronut.integration_test$eval13115$fn$reify__13117 + 0x7527011a + "cronut.integration_test$eval13115$fn$reify__13117@7527011a"], + :test.job/two #cronut.integration_test.TestDefrecordJobImpl{:identity ["test-group" "test-name"], + :description "test job", + :recover? true, + :durable? false, + :test-dep nil, + :disallow-concurrent-execution? nil, + :dep-one {:a 1}, + :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 + 0x45425cf3 + "cronut.integration_test$eval13104$fn$reify__13106@45425cf3"]}, + :cronut/scheduler #object[org.quartz.impl.StdScheduler 0x59a18142 "org.quartz.impl.StdScheduler@59a18142"]} +16:29:39.368 INFO [CronutScheduler_Worker-4] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:40.005 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:40.005 INFO [CronutScheduler_Worker-6] cronut.integration-test – 3979b197-5683-47a9-a267-dcaded343697 Reified Impl (Job Delay 7s): {} +16:29:40.006 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:40.876 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:41.368 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:42.004 INFO [CronutScheduler_Worker-4] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:43.364 INFO [CronutScheduler_Worker-5] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:44.007 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:44.375 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:45.368 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:47.011 INFO [CronutScheduler_Worker-6] cronut.integration-test – 3979b197-5683-47a9-a267-dcaded343697 Finished +16:29:47.368 INFO [CronutScheduler_Worker-4] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:47.875 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:48.008 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:48.010 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:48.011 INFO [CronutScheduler_Worker-3] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:49.370 INFO [CronutScheduler_Worker-6] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:50.004 INFO [CronutScheduler_Worker-4] cronut.integration-test – 299b73c8-97ad-4d85-848f-35960ced6362 Reified Impl (Job Delay 7s): {} +16:29:51.368 INFO [CronutScheduler_Worker-5] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:51.368 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:52.004 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:53.366 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} +16:29:54.007 INFO [CronutScheduler_Worker-6] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +16:29:54.874 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} +``` + +### Stopping the system + +```clojure +(test/halt-system *1) +``` + +# License + +Distributed under the Apache 2.0 License. + +Copyright (c) [Factor House](https://factorhouse.io) From e616bf3c5d6dfc589693a63a40a99211ffde1543 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:18:41 +1000 Subject: [PATCH 14/23] cronut README.md wip --- cronut/README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cronut/README.md b/cronut/README.md index 050cab8..e28d722 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -3,14 +3,6 @@ [![Cronut Test](https://github.com/factorhouse/cronut/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/factorhouse/cronut/actions/workflows/ci.yml) [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut.svg)](https://clojars.org/io.factorhouse/cronut) -### Related Projects - -| Project | Quartz Dependency | Desription | Clojars Project | -|-------------------------------------------------------------------------|-------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| [cronut](https://clojars.org/io.factorhouse/cronut) | 2.5.0 | Primary (Jakarta) project | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut.svg)](https://clojars.org/io.factorhouse/cronut) | -| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | 2.4.0 | Legacy (Javax) project | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | -| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | - | Integrant bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | - # Summary [Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for @@ -18,6 +10,17 @@ the [Quartz Job Scheduler](https://github.com/quartz-scheduler). Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. +## Related Projects + +This project is compatible with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE), the following projects +provide [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) compatibility and support for +[Integrant](https://github.com/weavejester/integrant) configuration. + +| Project | Quartz Dependency | Desription | Clojars Project | +|-------------------------------------------------------------------------|-------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | 2.4.0 | Legacy (Javax) project | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | - | Integrant bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | + # Contents - [Configuration](#configuration) @@ -31,7 +34,8 @@ Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and d - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) * [Concurrent execution](#concurrent-execution) - + [`:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) + + [ + `:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) + [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) + [Misfire configuration](#misfire-configuration) - [System initialization](#system-initialization) From fe4bc1fedc6e590ee0acfcf4863c5eac3449124b Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:27:30 +1000 Subject: [PATCH 15/23] cronut README.md wip --- cronut/README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cronut/README.md b/cronut/README.md index e28d722..bc2107a 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -6,20 +6,18 @@ # Summary [Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for -the [Quartz Job Scheduler](https://github.com/quartz-scheduler). +the [Quartz Job Scheduler](https://github.com/quartz-scheduler) version `2.5.0`. Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. -## Related Projects +Cronut is compatible with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE). -This project is compatible with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE), the following projects -provide [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) compatibility and support for -[Integrant](https://github.com/weavejester/integrant) configuration. +## Related Projects -| Project | Quartz Dependency | Desription | Clojars Project | -|-------------------------------------------------------------------------|-------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | 2.4.0 | Legacy (Javax) project | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | -| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | - | Integrant bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | +| Project | Desription | Clojars Project | +|-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | [Integrant](https://github.com/weavejester/integrant) bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | | # Contents From 571e4a0aeb81e36ca77ee5ffdb9849489145d2b0 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:28:38 +1000 Subject: [PATCH 16/23] cronut README.md wip --- cronut/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cronut/README.md b/cronut/README.md index bc2107a..6f1f61b 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -5,13 +5,12 @@ # Summary -[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for -the [Quartz Job Scheduler](https://github.com/quartz-scheduler) version `2.5.0`. +[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper +for [Quartz](https://github.com/quartz-scheduler) version `2.5.0`, compatible +with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE). Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. -Cronut is compatible with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE). - ## Related Projects | Project | Desription | Clojars Project | From 1ba84be20a0d2a9101f350c7a77e3d6d899b2093 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:29:54 +1000 Subject: [PATCH 17/23] cronut README.md wip --- cronut/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cronut/README.md b/cronut/README.md index 6f1f61b..2f806cc 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -5,8 +5,8 @@ # Summary -[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper -for [Quartz](https://github.com/quartz-scheduler) version `2.5.0`, compatible +[Cronut](https://github.com/factorhouse/cronut) provides a data-first [Clojure](https://clojure.org/) wrapper +for [Quartz Scheduler](https://github.com/quartz-scheduler) version `2.5.0`, compatible with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE). Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. From 43e436066f4cfce4fdbac52289c2d55baa245c13 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:35:14 +1000 Subject: [PATCH 18/23] cronut README.md wip --- cronut-integrant/README.md | 14 +++++++++----- cronut/README.md | 8 ++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index b0586e8..c104189 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -5,12 +5,16 @@ # Summary -[Cronut](https://github.com/factorhouse/cronut) provides a data-first Clojure wrapper for -the [Quartz Job Scheduler](https://github.com/quartz-scheduler). - Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI framework. +## Related Projects + +| Project | Desription | Clojars Project | +|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| [cronut](https://clojars.org/io.factorhouse/cronut) | Cronut with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE) support (Primary) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut) | +| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | Cronut with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | + # Contents - [Configuration](#configuration) @@ -24,7 +28,7 @@ framework. - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) * [Concurrent execution](#concurrent-execution) - + [`:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) + + [`:concurrent-execution-disallowed?` on the scheduler](#concurrent-execution-disallowed-on-the-scheduler) + [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) + [Misfire configuration](#misfire-configuration) - [System initialization](#system-initialization) @@ -162,7 +166,7 @@ The `#cronut/trigger` tagged literal supports the full set of Quartz configurati ## Concurrent execution -### `:concurrent-execution-disallowed?` on the global scheduler +### `:concurrent-execution-disallowed?` on the scheduler Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. diff --git a/cronut/README.md b/cronut/README.md index 2f806cc..d6b7919 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -13,10 +13,10 @@ Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and d ## Related Projects -| Project | Desription | Clojars Project | -|-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | -| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | [Integrant](https://github.com/weavejester/integrant) bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | | +| Project | Desription | Clojars Project | +|-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | Cronut with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | [Integrant](https://github.com/weavejester/integrant) bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | # Contents From c05a1fa632e23bfebe780b7ee8f6274495b74859 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:35:56 +1000 Subject: [PATCH 19/23] cronut README.md wip --- cronut-integrant/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index c104189..40c16a4 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -5,7 +5,7 @@ # Summary -Cronut-Integrant provides bindings for Cronut to [Integrant](https://github.com/weavejester/integrant), the DI +Cronut-Integrant provides bindings for [Cronut](https://github.com/factorhouse/cronut) to [Integrant](https://github.com/weavejester/integrant), the DI framework. ## Related Projects From 19178362f08a5c227d45f5371ee5e4e08500cb4c Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 17:45:53 +1000 Subject: [PATCH 20/23] cronut README.md wip --- cronut-integrant/README.md | 4 ++-- cronut/README.md | 43 ++++++-------------------------------- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index 40c16a4..69ccadd 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -48,13 +48,13 @@ A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. ## `:cronut/scheduler` definition -Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` +Cronut provides access to the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` The scheduler supports the following fields: 1. (required) :schedule - a sequence of 'items' to schedule, each being a map containing a :job and :trigger 2. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution -2. (optional, default false) :update-check? check for Quartz updates on system startup. +3. (optional, default false) :update-check? check for Quartz updates on system startup. ### Scheduler example diff --git a/cronut/README.md b/cronut/README.md index d6b7919..6cd74d9 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -20,55 +20,24 @@ Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and d # Contents -- [Configuration](#configuration) - * [`:cronut/scheduler` definition](#cronutscheduler-definition) - + [Scheduler example](#scheduler-example) - * [`:job` definition](#job-definition) - + [Job example](#job-example) - * [`:trigger` definition](#trigger-definition) - + [`:trigger` tagged literals](#trigger-tagged-literals) - - [`#cronut/cron`: Simple Cron Scheduling](#cronutcron-simple-cron-scheduling) - - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) - - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) - * [Concurrent execution](#concurrent-execution) - + [ - `:concurrent-execution-disallowed?` on the global scheduler](#concurrent-execution-disallowed-on-the-global-scheduler) - + [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) - + [Misfire configuration](#misfire-configuration) -- [System initialization](#system-initialization) -- [Example system](#example-system) - * [Configuration](#configuration-1) - * [Job definitions](#job-definitions) - * [Helper functions](#helper-functions) - * [Putting it together](#putting-it-together) - + [Starting the system](#starting-the-system) - + [Logs of the running system](#logs-of-the-running-system) - + [Stopping the system](#stopping-the-system) -- [License](#license) - # Configuration A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. -## `:cronut/scheduler` definition +## `cronut/scheduler` definition -Cronut provides lifecycle implementation for the Quartz Scheduler, exposed via Integrant with `:cronut/scheduler` +Cronut provides access to the Quartz Scheduler, exposed via the `cronut/scheduler` function. -The scheduler supports the following fields: +Create a scheduler with the following configuration: -1. (required) :schedule - a sequence of 'items' to schedule, each being a map containing a :job and :trigger -2. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution +1. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution 2. (optional, default false) :update-check? check for Quartz updates on system startup. ### Scheduler example ````clojure -:cronut/scheduler {:schedule [{:job #ig/ref :test.job/two - :trigger #cronut/interval 3500} - {:job #ig/ref :test.job/two - :trigger #cronut/cron "*/8 * * * * ?" - :misfire :do-nothing}] - :concurrent-execution-disallowed? true} +(cronut/scheduler {:concurrent-execution-disallowed? true + :update-check? false}) ```` ## `:job` definition From a7e767cf0f1a89a452130e317ed75d5065c04030 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 23:34:07 +1000 Subject: [PATCH 21/23] update readme, minor adjusts to group/name ordering in scheduler functions --- cronut-integrant/README.md | 28 +- cronut-integrant/dev-resources/config.edn | 2 +- cronut-javax/README.md | 250 ++++++++++ cronut-javax/dev-resources/config.edn | 2 +- cronut-javax/src/cronut.clj | 92 ++-- cronut-javax/src/cronut/trigger.clj | 9 +- cronut-javax/test/cronut/integration_test.clj | 6 +- cronut-javax/test/cronut_test.clj | 2 +- cronut/README.md | 440 ++++++------------ cronut/dev-resources/config.edn | 2 +- cronut/src/cronut.clj | 92 ++-- cronut/src/cronut/trigger.clj | 9 +- cronut/test/cronut/integration_test.clj | 6 +- cronut/test/cronut_test.clj | 2 +- 14 files changed, 532 insertions(+), 410 deletions(-) create mode 100644 cronut-javax/README.md diff --git a/cronut-integrant/README.md b/cronut-integrant/README.md index 69ccadd..0724aaf 100644 --- a/cronut-integrant/README.md +++ b/cronut-integrant/README.md @@ -5,15 +5,15 @@ # Summary -Cronut-Integrant provides bindings for [Cronut](https://github.com/factorhouse/cronut) to [Integrant](https://github.com/weavejester/integrant), the DI -framework. +Cronut-Integrant provides bindings for [Cronut](https://github.com/factorhouse/cronut) +to [Integrant](https://github.com/weavejester/integrant), the DI micro-framework. ## Related Projects -| Project | Desription | Clojars Project | -|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| -| [cronut](https://clojars.org/io.factorhouse/cronut) | Cronut with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE) support (Primary) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut) | -| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | Cronut with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| Project | Desription | Clojars Project | +|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| [cronut](https://github.com/factorhouse/cronut) | Cronut with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE) support (Primary) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut) | +| [cronut-javax](https://github.com/factorhouse/cronut-javax) | Cronut with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | # Contents @@ -28,8 +28,8 @@ framework. - [`#cronut/interval`: Simple Interval Scheduling](#cronutinterval-simple-interval-scheduling) - [`#cronut/trigger`: Full trigger definition](#cronuttrigger-full-trigger-definition) * [Concurrent execution](#concurrent-execution) - + [`:concurrent-execution-disallowed?` on the scheduler](#concurrent-execution-disallowed-on-the-scheduler) - + [`:disallow-concurrent-execution?` on a specific job](#disallow-concurrent-execution-on-a-specific-job) + + [Global concurrent execution](#global-concurrent-execution) + + [Job-specific concurrent execution](#job-specific-concurrent-execution) + [Misfire configuration](#misfire-configuration) - [System initialization](#system-initialization) - [Example system](#example-system) @@ -52,9 +52,9 @@ Cronut provides access to the Quartz Scheduler, exposed via Integrant with `:cro The scheduler supports the following fields: -1. (required) :schedule - a sequence of 'items' to schedule, each being a map containing a :job and :trigger -2. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution -3. (optional, default false) :update-check? check for Quartz updates on system startup. +1. `:schedule`: (required) - a sequence of 'items' to schedule, each being a map containing a :job and :trigger +2. `:concurrent-execution-disallowed?`: (optional, default false) - run all jobs with @DisableConcurrentExecution +3. `:update-check?`: (optional, default false) - check for Quartz updates on system startup ### Scheduler example @@ -166,11 +166,11 @@ The `#cronut/trigger` tagged literal supports the full set of Quartz configurati ## Concurrent execution -### `:concurrent-execution-disallowed?` on the scheduler +### Global concurrent execution Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. -### `:disallow-concurrent-execution?` on a specific job +### Job-specific concurrent execution Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. @@ -221,7 +221,7 @@ Integrant configuration source: [dev-resources/config.edn](dev-resources/config. :test.job/one {:dep-one #ig/ref :dep/one} - :test.job/two {:identity ["test-group" "test-name"] + :test.job/two {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false diff --git a/cronut-integrant/dev-resources/config.edn b/cronut-integrant/dev-resources/config.edn index 7e60da1..446127f 100644 --- a/cronut-integrant/dev-resources/config.edn +++ b/cronut-integrant/dev-resources/config.edn @@ -2,7 +2,7 @@ :test.job/one {:dep-one #ig/ref :dep/one} - :test.job/two {:identity ["test-group" "test-name"] + :test.job/two {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false diff --git a/cronut-javax/README.md b/cronut-javax/README.md new file mode 100644 index 0000000..75f6856 --- /dev/null +++ b/cronut-javax/README.md @@ -0,0 +1,250 @@ +# Cronut Javax: A Clojure Companion to Quartz (Legacy Javax Support) + +[![Cronut Test](https://github.com/factorhouse/cronut/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/factorhouse/cronut/actions/workflows/ci.yml) +[![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut.svg)](https://clojars.org/io.factorhouse/cronut) + +# Summary + +[Cronut](https://github.com/factorhouse/cronut) provides a data-first [Clojure](https://clojure.org/) wrapper +for [Quartz Scheduler](https://github.com/quartz-scheduler) version `2.4.0`, compatible +with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/). + +Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. + +## Related Projects + +| Project | Desription | Clojars Project | +|---------------------------------------------------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [cronut](https://github.com/factorhouse/cronut) | Cronut with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE) support | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| [cronut-integrant](https://github.com/factorhouse/cronut-integrant) | [Integrant](https://github.com/weavejester/integrant) bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | + +# Contents + +# Usage + +A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. + +## Scheduler + +Cronut provides access to the Quartz Scheduler, exposed via the `cronut/scheduler` function. + +Create a scheduler with the following configuration: + +1. `:concurrent-execution-disallowed?`: (optional, default false) - run all jobs with @DisableConcurrentExecution +2. `:update-check?`: (optional, default false) - check for Quartz updates on system startup. + +````clojure +(cronut/scheduler {:concurrent-execution-disallowed? true + :update-check? false}) +```` + +### Scheduler lifecycle + +Once created, you can: + +* `cronut/start`: start the scheduler +* `cronut/start-delayed`: start the scheduler with a delay +* `cronut/standy`: temporarily halt the firing of triggers by the scheduler +* `cronut/shutdown`: stop the scheduler +* `cronut/pause-all`: pause all triggers +* `cronut/resume-all`: resume all triggers +* `cronut/clear`: clear all scheduling data of jobs and triggers + +### Scheduling jobs + +To schedule jobs, you can + +* `cronut/schedule-job`: schedule a single job +* `cronut/schedule-jobs`: schedule multiple jobs at once +* `cronut/pause-job`: pause a job +* `cronut/resume-job`: resume a paused job +* `cronut/unschedule-job`: remove a trigger from the scheduler +* `cronut/delete-job`: remove a job and all associated triggers from the scheduler +* `cronut/pause-trigger`: pause a trigger +* `cronut/resume-trigger`: resume a paused trigger + +## Jobs + +Each cronut job must implement the `org.quartz.Job` interface. + +The expectation being that every job will reify that interface either directly via `reify` or by returning a `defrecord` +that implements the interface. + +Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by expecting +those values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore +them), however if you do want that control you will likely use the `defrecord` approach as opposed to `reify`. + +Concurrent execution can be controlled on a per-job bases with the `disallow-concurrent-execution?` flag. + +### Job example + +````clojure +(defrecord TestDefrecordJobImpl [identity description recover? durable?] + Job + (execute [this _job-context] + (log/info "Defrecord Impl:" this))) + + +(let [scheduler (cronut/scheduler {:concurrent-execution-disallowed? true + :update-check? false}) + defrecord-job (map->TestDefrecordJobImpl {:identity ["name1" "group2"] + :description "test job" + :recover? true + :durable? false}) + reify-job (reify Job + (execute [_this _job-context] + (let [rand-id (str (UUID/randomUUID))] + (log/info rand-id "Reified Impl"))))] + + (cronut/schedule-job scheduler (trigger/interval 1000) defrecord-job) + + (cronut/schedule-job scheduler + (trigger/builder {:type :cron + :cron "*/5 * * * * ?" + :misfire :do-nothing}) + reify-job)) +```` + +## Triggers + +Each cronut trigger must be of tyee `org.quartz.Trigger` of some variety or another, the following functions are +provided to simplify trigger creation: + +#### `cronut.trigger/cron`: Simple Cron Scheduling + +A job is scheduled to run on a cron by using the `cronut.trigger/cron` function with a valid cron expression. + +The job will start immediately when the system is initialized, and runs in the default system time-zone + +````clojure +(cronut.trigger/cron "*/8 * * * * ?") +```` + +#### `cronut.trigger/interval`: Simple Interval Scheduling + +A job is scheduled to run periodically by using the `cronut.trigger/interval` function with a milliseconds value + +````clojure +(cronut.trigger/interval 3500) +```` + +#### `cronut.trigger/builder`: Full trigger definition + +Both `cronut.trigger/cron` and `cronut.trigger/interval` are effectively shortcuts to full trigger definition with +sensible defaults. + +The `cronut.trigger/builder` function supports the full set of Quartz configuration triggers: + +````clojure +;; interval +(cronut.trigger/builder {:type :simple + :interval 3000 + :repeat :forever + :identity ["trigger-two" "test"] + :description "sample simple trigger" + :start #inst "2019-01-01T00:00:00.000-00:00" + :end #inst "2019-02-01T00:00:00.000-00:00" + :misfire :ignore + :priority 5}) + +;;cron +(cronut.trigger/builder {:type :cron + :cron "*/6 * * * * ?" + :identity ["trigger-five" "test"] + :description "sample cron trigger" + :start #inst "2018-01-01T00:00:00.000-00:00" + :end #inst "2029-02-01T00:00:00.000-00:00" + :time-zone "Australia/Melbourne" + :misfire :fire-and-proceed + :priority 4}) +```` + +## Concurrent execution + +### Global concurrent execution + +Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. + +### Job-specific concurrent execution + +Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. + +### Misfire configuration + +If you disable concurrent job execution ensure you understand Quartz Misfire options and remember to set +`org.quartz.jobStore.misfireThreshold=[some ms value]` in your quartz.properties file. See Quartz documentation for more +information. + +See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and +behaviour configuration. + +# Example system + +See: integration test source: [test/cronut/integration-test.clj](test/cronut/integration_test.clj). + +````clojure +(ns cronut.integration-test + (:require [clojure.core.async :as async] + [clojure.tools.logging :as log] + [cronut :as cronut] + [cronut.trigger :as trigger]) + (:import (java.util UUID) + (org.quartz Job))) + +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] + Job + (execute [this _job-context] + (log/info "Defrecord Impl:" this))) + +(def reify-job (reify Job + (execute [_this _job-context] + (let [rand-id (str (UUID/randomUUID))] + (log/info rand-id "Reified Impl (Job Delay 7s)") + (async/TestDefrecordJobImpl {:identity ["name1" "group2"] + :description "test job" + :recover? true + :durable? false})) + + ;; demonstrate scheduler can start with jobs, and jobs can start after scheduler + (cronut/start scheduler) + + (async/TestDefrecordJobImpl {:identity ["test-group" "test-name"] + (map->TestDefrecordJobImpl {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false})) @@ -50,8 +50,8 @@ (async/TestDefrecordJobImpl {:identity ["test-group" "test-name"] + (map->TestDefrecordJobImpl {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false}))] diff --git a/cronut/README.md b/cronut/README.md index 6cd74d9..833c5ff 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -13,56 +13,62 @@ Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and d ## Related Projects -| Project | Desription | Clojars Project | -|-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| [cronut-javax](https://clojars.org/io.factorhouse/cronut-javax) | Cronut with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | -| [cronut-integrant](https://clojars.org/io.factorhouse/cronut-integrant) | [Integrant](https://github.com/weavejester/integrant) bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | +| Project | Desription | Clojars Project | +|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [cronut-javax](https://github.com/factorhouse/cronut-javax) | Cronut with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/) support (Legacy) | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-javax.svg)](https://clojars.org/io.factorhouse/cronut-javax) | +| [cronut-integrant](https://github.com/factorhouse/cronut-integrant) | [Integrant](https://github.com/weavejester/integrant) bindings for Cronut | [![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/cronut-integrant.svg)](https://clojars.org/io.factorhouse/cronut-integrant) | # Contents -# Configuration +# Usage A quartz `scheduler` runs a `job` on a schedule defined by a `trigger`. -## `cronut/scheduler` definition +## Scheduler Cronut provides access to the Quartz Scheduler, exposed via the `cronut/scheduler` function. Create a scheduler with the following configuration: -1. (optional, default false) :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution -2. (optional, default false) :update-check? check for Quartz updates on system startup. - -### Scheduler example +1. `:concurrent-execution-disallowed?`: (optional, default false) - run all jobs with @DisableConcurrentExecution +2. `:update-check?`: (optional, default false) - check for Quartz updates on system startup. ````clojure (cronut/scheduler {:concurrent-execution-disallowed? true :update-check? false}) ```` -## `:job` definition +### Scheduler lifecycle -The `:job` in every scheduled item must implement the org.quartz.Job interface +Once created, you can: -The expectation being that every 'job' in your Integrant system will reify that interface, either directly via `reify` -or by returning a `defrecord` that implements the interface. e.g. +* `cronut/start`: start the scheduler +* `cronut/start-delayed`: start the scheduler with a delay +* `cronut/standy`: temporarily halt the firing of triggers by the scheduler +* `cronut/shutdown`: stop the scheduler +* `cronut/pause-all`: pause all triggers +* `cronut/resume-all`: resume all triggers +* `cronut/clear`: clear all scheduling data of jobs and triggers -````clojure -(defmethod ig/init-key :test.job/one - [_ config] - (reify Job - (execute [this job-context] - (log/info "Reified Impl:" config)))) +### Scheduling jobs -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep] - Job - (execute [this job-context] - (log/info "Defrecord Impl:" this))) +To schedule jobs, you can -(defmethod ig/init-key :test.job/two - [_ config] - (map->TestDefrecordJobImpl config)) -```` +* `cronut/schedule-job`: schedule a single job +* `cronut/schedule-jobs`: schedule multiple jobs at once +* `cronut/pause-job`: pause a job +* `cronut/resume-job`: resume a paused job +* `cronut/unschedule-job`: remove a trigger from the scheduler +* `cronut/delete-job`: remove a job and all associated triggers from the scheduler +* `cronut/pause-trigger`: pause a trigger +* `cronut/resume-trigger`: resume a paused trigger + +## Jobs + +Each cronut job must implement the `org.quartz.Job` interface. + +The expectation being that every job will reify that interface either directly via `reify` or by returning a `defrecord` +that implements the interface. Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by expecting those values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore @@ -73,77 +79,93 @@ Concurrent execution can be controlled on a per-job bases with the `disallow-con ### Job example ````clojure -:test.job/two {:identity ["job-two" "test"] - :description "test job" - :recover? true - :durable? false - :disallow-concurrent-execution? true - :dep-one #ig/ref :dep/one - :dep-two #ig/ref :test.job/one} -```` +(defrecord TestDefrecordJobImpl [identity description recover? durable?] + Job + (execute [this _job-context] + (log/info "Defrecord Impl:" this))) + + +(let [scheduler (cronut/scheduler {:concurrent-execution-disallowed? true + :update-check? false}) + defrecord-job (map->TestDefrecordJobImpl {:identity ["name1" "group2"] + :description "test job" + :recover? true + :durable? false}) + reify-job (reify Job + (execute [_this _job-context] + (let [rand-id (str (UUID/randomUUID))] + (log/info rand-id "Reified Impl"))))] + + (cronut/schedule-job scheduler (trigger/interval 1000) defrecord-job) -## `:trigger` definition + (cronut/schedule-job scheduler + (trigger/builder {:type :cron + :cron "*/5 * * * * ?" + :misfire :do-nothing}) + reify-job)) +```` -The `:trigger` in every scheduled item must resolve to an org.quartz.Trigger of some variety or another, to ease that -resolution Cronut provides the following tagged literals: +## Triggers -### `:trigger` tagged literals +Each cronut trigger must be of tyee `org.quartz.Trigger` of some variety or another, the following functions are +provided to simplify trigger creation: -#### `#cronut/cron`: Simple Cron Scheduling +#### `cronut.trigger/cron`: Simple Cron Scheduling -A job is scheduled to run on a cron by using the `#cronut/cron` tagged literal followed by a valid cron expression +A job is scheduled to run on a cron by using the `cronut.trigger/cron` function with a valid cron expression. The job will start immediately when the system is initialized, and runs in the default system time-zone ````clojure -:trigger #cronut/cron "*/8 * * * * ?" +(cronut.trigger/cron "*/8 * * * * ?") ```` -#### `#cronut/interval`: Simple Interval Scheduling +#### `cronut.trigger/interval`: Simple Interval Scheduling -A job is scheduled to run periodically by using the `#cronut/interval` tagged literal followed by a milliseconds value +A job is scheduled to run periodically by using the `cronut.trigger/interval` function with a milliseconds value ````clojure -:trigger #cronut/interval 3500 +(cronut.trigger/interval 3500) ```` -#### `#cronut/trigger`: Full trigger definition +#### `cronut.trigger/builder`: Full trigger definition -Both `#cronut/cron` and `#cronut/interval` are effectively shortcuts to full trigger definition with sensible defaults. +Both `cronut.trigger/cron` and `cronut.trigger/interval` are effectively shortcuts to full trigger definition with +sensible defaults. -The `#cronut/trigger` tagged literal supports the full set of Quartz configuration triggers: +The `cronut.trigger/builder` function supports the full set of Quartz configuration triggers: ````clojure ;; interval -:trigger #cronut/trigger {:type :simple - :interval 3000 - :repeat :forever - :identity ["trigger-two" "test"] - :description "sample simple trigger" - :start #inst "2019-01-01T00:00:00.000-00:00" - :end #inst "2019-02-01T00:00:00.000-00:00" - :misfire :ignore - :priority 5} +(cronut.trigger/builder {:type :simple + :interval 3000 + :repeat :forever + :identity ["trigger-two" "test"] + :description "sample simple trigger" + :start #inst "2019-01-01T00:00:00.000-00:00" + :end #inst "2019-02-01T00:00:00.000-00:00" + :misfire :ignore + :priority 5}) ;;cron -:trigger #cronut/trigger {:type :cron - :cron "*/6 * * * * ?" - :identity ["trigger-five" "test"] - :description "sample cron trigger" - :start #inst "2018-01-01T00:00:00.000-00:00" - :end #inst "2029-02-01T00:00:00.000-00:00" - :time-zone "Australia/Melbourne" - :misfire :fire-and-proceed - :priority 4} +(cronut.trigger/builder {:type :cron + :cron "*/6 * * * * ?" + :identity ["trigger-five" "test"] + :description "sample cron trigger" + :start #inst "2018-01-01T00:00:00.000-00:00" + :end #inst "2029-02-01T00:00:00.000-00:00" + :time-zone "Australia/Melbourne" + :misfire :fire-and-proceed + :priority 4}) ```` ## Concurrent execution -### `:concurrent-execution-disallowed?` on the global scheduler +### Global concurrent execution Set `:concurrent-execution-disallowed?` on the scheduler to disable concurrent execution of all jobs. -### `:disallow-concurrent-execution?` on a specific job +### Job-specific concurrent execution Set `:disallow-concurrent-execution?` on a specific job to disable concurrent execution of that job only. @@ -156,255 +178,71 @@ information. See our test-resources/config.edn and test-resources/org/quartz/quartz.properties for examples of misfire threshold and behaviour configuration. -# System initialization - -When initializing an Integrant system you will need to provide the Cronut data readers. - -See: `cronut/data-readers` for convenience. - -````clojure -(def data-readers - {'cronut/trigger cronut/trigger-builder - 'cronut/cron cronut/shortcut-cron - 'cronut/interval cronut/shortcut-interval}) -```` - -e.g. - -````clojure -(defn init-system - "Convenience for starting integrant systems with cronut data-readers" - ([config] - (init-system config nil)) - ([config readers] - (ig/init (ig/read-string {:readers (merge cronut/data-readers readers)} config)))) -```` - # Example system -This repository contains an example system composed of of integratant configuration, job definitions, and helper -functions. - -## Configuration - -Integrant configuration source: [dev-resources/config.edn](dev-resources/config.edn). +See: integration test source: [test/cronut/integration-test.clj](test/cronut/integration_test.clj). ````clojure -{:dep/one {:a 1} - - :test.job/one {:dep-one #ig/ref :dep/one} - - :test.job/two {:identity ["test-group" "test-name"] - :description "test job" - :recover? true - :durable? false - :dep-one #ig/ref :dep/one - :dep-two #ig/ref :test.job/one} - - :test.job/three {} - - :cronut/scheduler {:update-check? false - :concurrent-execution-disallowed? true - :schedule [;; basic interval - {:job #ig/ref :test.job/one - :trigger #cronut/trigger {:type :simple - :interval 2 - :time-unit :seconds - :repeat :forever}} - - ;; full interval - {:job #ig/ref :test.job/two - :trigger #cronut/trigger {:type :simple - :interval 3000 - :repeat :forever - :identity ["trigger-two" "test"] - :description "test trigger" - :start #inst "2019-01-01T00:00:00.000-00:00" - :end #inst "2019-02-01T00:00:00.000-00:00" - :priority 5}} - - ;; shortcut interval - {:job #ig/ref :test.job/two - :trigger #cronut/interval 3500} - - ;; basic cron - {:job #ig/ref :test.job/two - :trigger #cronut/trigger {:type :cron - :cron "*/4 * * * * ?"}} - - ;; full cron - {:job #ig/ref :test.job/two - :trigger #cronut/trigger {:type :cron - :cron "*/6 * * * * ?" - :identity ["trigger-five" "test"] - :description "another-test trigger" - :start #inst "2018-01-01T00:00:00.000-00:00" - :end #inst "2029-02-01T00:00:00.000-00:00" - :time-zone "Australia/Melbourne" - :priority 4}} - - ;; shortcut cron - {:job #ig/ref :test.job/two - :trigger #cronut/cron "*/8 * * * * ?"} - - ;; Note: This job misfires because it takes 7 seconds to run, but runs every 5 seconds, and isn't allowed to run concurrently with {:disallowConcurrentExecution? true} - ;; So every second job fails to run, and is just ignored with the :do-nothing :misfire rule - {:job #ig/ref :test.job/three - :trigger #cronut/trigger {:type :cron - :cron "*/5 * * * * ?" - :misfire :do-nothing}}]}} +(ns cronut.integration-test + (:require [clojure.core.async :as async] + [clojure.tools.logging :as log] + [cronut :as cronut] + [cronut.trigger :as trigger]) + (:import (java.util UUID) + (org.quartz Job))) + +(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] + Job + (execute [this _job-context] + (log/info "Defrecord Impl:" this))) -```` +(def reify-job (reify Job + (execute [_this _job-context] + (let [rand-id (str (UUID/randomUUID))] + (log/info rand-id "Reified Impl (Job Delay 7s)") + (async/TestDefrecordJobImpl {:identity ["name1" "group2"] + :description "test job" + :recover? true + :durable? false})) -(defmethod ig/init-key :dep/one - [_ config] - config) + ;; demonstrate scheduler can start with jobs, and jobs can start after scheduler + (cronut/start scheduler) -(defmethod ig/init-key :test.job/one - [_ config] - (reify Job - (execute [_this _job-context] - (log/info "Reified Impl:" config)))) + (async/TestDefrecordJobImpl config)) + ;; demonstrates concurrency disallowed (every second job runs, 10s interval between jobs that should run every 5s) + (log/info "scheduling reify/7s/no-misfire job on 5s interval") + (cronut/schedule-job scheduler + (trigger/builder {:type :cron + :cron "*/5 * * * * ?" + :misfire :do-nothing}) + reify-job) -(defmethod ig/init-key :test.job/three - [_ config] - (reify Job - (execute [_this _job-context] - (let [rand-id (str (UUID/randomUUID))] - (log/info rand-id "Reified Impl (Job Delay 7s):" config) - (async/ -{:dep/one {:a 1}, - :test.job/one #object[cronut.integration_test$eval13104$fn$reify__13106 - 0x45425cf3 - "cronut.integration_test$eval13104$fn$reify__13106@45425cf3"], - :test.job/three #object[cronut.integration_test$eval13115$fn$reify__13117 - 0x7527011a - "cronut.integration_test$eval13115$fn$reify__13117@7527011a"], - :test.job/two #cronut.integration_test.TestDefrecordJobImpl{:identity ["test-group" "test-name"], - :description "test job", - :recover? true, - :durable? false, - :test-dep nil, - :disallow-concurrent-execution? nil, - :dep-one {:a 1}, - :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 - 0x45425cf3 - "cronut.integration_test$eval13104$fn$reify__13106@45425cf3"]}, - :cronut/scheduler #object[org.quartz.impl.StdScheduler 0x59a18142 "org.quartz.impl.StdScheduler@59a18142"]} -16:29:39.368 INFO [CronutScheduler_Worker-4] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:40.005 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:40.005 INFO [CronutScheduler_Worker-6] cronut.integration-test – 3979b197-5683-47a9-a267-dcaded343697 Reified Impl (Job Delay 7s): {} -16:29:40.006 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:40.876 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:41.368 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:42.004 INFO [CronutScheduler_Worker-4] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:43.364 INFO [CronutScheduler_Worker-5] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:44.007 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:44.375 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:45.368 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:47.011 INFO [CronutScheduler_Worker-6] cronut.integration-test – 3979b197-5683-47a9-a267-dcaded343697 Finished -16:29:47.368 INFO [CronutScheduler_Worker-4] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:47.875 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:48.008 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:48.010 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:48.011 INFO [CronutScheduler_Worker-3] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:49.370 INFO [CronutScheduler_Worker-6] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:50.004 INFO [CronutScheduler_Worker-4] cronut.integration-test – 299b73c8-97ad-4d85-848f-35960ced6362 Reified Impl (Job Delay 7s): {} -16:29:51.368 INFO [CronutScheduler_Worker-5] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:51.368 INFO [CronutScheduler_Worker-1] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:52.004 INFO [CronutScheduler_Worker-2] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:53.366 INFO [CronutScheduler_Worker-3] cronut.integration-test – Reified Impl: {:dep-one {:a 1}} -16:29:54.007 INFO [CronutScheduler_Worker-6] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -16:29:54.874 INFO [CronutScheduler_Worker-5] cronut.integration-test – Defrecord Impl: #cronut.integration_test.TestDefrecordJobImpl{:identity [test-group test-name], :description test job, :recover? true, :durable? false, :test-dep nil, :disallow-concurrent-execution? nil, :dep-one {:a 1}, :dep-two #object[cronut.integration_test$eval13104$fn$reify__13106 0x45425cf3 cronut.integration_test$eval13104$fn$reify__13106@45425cf3]} -``` - -### Stopping the system - -```clojure -(test/halt-system *1) -``` - # License Distributed under the Apache 2.0 License. diff --git a/cronut/dev-resources/config.edn b/cronut/dev-resources/config.edn index 7e60da1..446127f 100644 --- a/cronut/dev-resources/config.edn +++ b/cronut/dev-resources/config.edn @@ -2,7 +2,7 @@ :test.job/one {:dep-one #ig/ref :dep/one} - :test.job/two {:identity ["test-group" "test-name"] + :test.job/two {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false diff --git a/cronut/src/cronut.clj b/cronut/src/cronut.clj index 9e6edec..7f6037a 100644 --- a/cronut/src/cronut.clj +++ b/cronut/src/cronut.clj @@ -5,11 +5,31 @@ (:import (org.quartz JobDetail JobKey Scheduler Trigger TriggerBuilder TriggerKey) (org.quartz.impl StdSchedulerFactory))) +(defn scheduler + "Create a new Quartz scheduler: + :concurrent-execution-disallowed? - run all jobs with @DisableConcurrentExecution (default false) + :update-check? - check for Quartz updates on system startup (default: false)" + [{:keys [update-check? concurrent-execution-disallowed?]}] + (log/infof "initializing scheduler") + (when-not update-check? + (System/setProperty "org.terracotta.quartz.skipUpdateCheck" "true") + (log/infof "with quartz update check disabled")) + (let [scheduler (StdSchedulerFactory/getDefaultScheduler)] + (if concurrent-execution-disallowed? + (do + (log/infof "with global concurrent execution disallowed") + (.put (.getContext scheduler) "concurrentExecutionDisallowed?" "true")) + (.put (.getContext scheduler) "concurrentExecutionDisallowed?" "false")) + (.setJobFactory scheduler (job/factory)) + scheduler)) + (defn concurrent-execution-disallowed? + "Determine if a scheduler has concurrent job execution disallowed" [^Scheduler scheduler] (= "true" (get (.getContext scheduler) "concurrentExecutionDisallowed?"))) (defn get-detail + "Get the job detail for a key" [^Scheduler scheduler ^JobKey key] (.getJobDetail scheduler key)) @@ -35,56 +55,56 @@ (recur (rest schedule) (conj triggers (schedule-job scheduler trigger job))) triggers))) -(defn scheduler - [{:keys [update-check? concurrent-execution-disallowed?]}] - (log/infof "initializing scheduler") - (when-not update-check? - (System/setProperty "org.terracotta.quartz.skipUpdateCheck" "true") - (log/infof "with quartz update check disabled")) - (let [scheduler (StdSchedulerFactory/getDefaultScheduler)] - (if concurrent-execution-disallowed? - (do - (log/infof "with global concurrent execution disallowed") - (.put (.getContext scheduler) "concurrentExecutionDisallowed?" "true")) - (.put (.getContext scheduler) "concurrentExecutionDisallowed?" "false")) - (.setJobFactory scheduler (job/factory)) - scheduler)) - (defn pause-job - ([^Scheduler scheduler group name] + ([^Scheduler scheduler name group] (.pauseJob scheduler (JobKey. name group))) ([^Scheduler scheduler ^Trigger trigger] (.pauseJob scheduler (.getJobKey trigger)))) (defn resume-job - ([^Scheduler scheduler group name] + ([^Scheduler scheduler name group] (.resumeJob scheduler (JobKey. name group))) ([^Scheduler scheduler ^Trigger trigger] (.resumeJob scheduler (.getJobKey trigger)))) +(defn unschedule-job + ([^Scheduler scheduler name group] + (.unscheduleJob scheduler (TriggerKey. name group))) + ([^Scheduler scheduler ^Trigger trigger] + (.unscheduleJob scheduler (.getKey trigger)))) + +(defn delete-job + ([^Scheduler scheduler name group] + (.deleteJob scheduler (JobKey. name group))) + ([^Scheduler scheduler ^Trigger trigger] + (.deleteJob scheduler (.getJobKey trigger)))) + (defn pause-trigger - ([^Scheduler scheduler group name] + ([^Scheduler scheduler name group] (.pauseTrigger scheduler (TriggerKey. name group))) ([^Scheduler scheduler ^Trigger trigger] (.pauseTrigger scheduler (.getKey trigger)))) (defn resume-trigger - ([^Scheduler scheduler group name] + ([^Scheduler scheduler name group] (.resumeTrigger scheduler (TriggerKey. name group))) ([^Scheduler scheduler ^Trigger trigger] (.resumeTrigger scheduler (.getKey trigger)))) -(defn delete-job - ([^Scheduler scheduler group name] - (.deleteJob scheduler (JobKey. name group))) - ([^Scheduler scheduler ^Trigger trigger] - (.deleteJob scheduler (.getJobKey trigger)))) +(defn start + [^Scheduler scheduler] + (.start scheduler) + scheduler) -(defn unschedule-job - ([^Scheduler scheduler group name] - (.unscheduleJob scheduler (TriggerKey. name group))) - ([^Scheduler scheduler ^Trigger trigger] - (.unscheduleJob scheduler (.getKey trigger)))) +(defn start-delayed + [^Scheduler scheduler delay-s] + (.startDelayed scheduler delay-s) + scheduler) + +(defn standby + [^Scheduler scheduler] + (.standby scheduler) + scheduler) (defn pause-all [^Scheduler scheduler] @@ -99,12 +119,10 @@ (.clear scheduler) scheduler) -(defn start - [^Scheduler scheduler] - (.start scheduler) - scheduler) - (defn shutdown - [scheduler] - (.shutdown ^Scheduler scheduler) - scheduler) \ No newline at end of file + ([scheduler] + (.shutdown ^Scheduler scheduler) + scheduler) + ([scheduler wait?] + (.shutdown ^Scheduler scheduler wait?) + scheduler)) \ No newline at end of file diff --git a/cronut/src/cronut/trigger.clj b/cronut/src/cronut/trigger.clj index bab3887..e52963e 100644 --- a/cronut/src/cronut/trigger.clj +++ b/cronut/src/cronut/trigger.clj @@ -53,13 +53,11 @@ (defmethod builder :simple [config] - (.withSchedule ^TriggerBuilder (base-builder config) - (simple-schedule config))) + (.withSchedule ^TriggerBuilder (base-builder config) (simple-schedule config))) (defmethod builder :cron [config] - (.withSchedule ^TriggerBuilder (base-builder config) - (cron-schedule config))) + (.withSchedule ^TriggerBuilder (base-builder config) (cron-schedule config))) (defn interval "Trigger immediately, at an interval-ms, run forever (well that's optimistic but you get the idea)" @@ -70,6 +68,7 @@ :repeat :forever})) (defn cron + "Trigger on a schedule defined by the cron expression" [cron] (builder {:type :cron - :cron cron})) + :cron cron})) \ No newline at end of file diff --git a/cronut/test/cronut/integration_test.clj b/cronut/test/cronut/integration_test.clj index 2756a10..56adce9 100644 --- a/cronut/test/cronut/integration_test.clj +++ b/cronut/test/cronut/integration_test.clj @@ -30,7 +30,7 @@ (log/info "scheduling defrecord job on 1s interval") (cronut/schedule-job scheduler (trigger/interval 1000) - (map->TestDefrecordJobImpl {:identity ["test-group" "test-name"] + (map->TestDefrecordJobImpl {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false})) @@ -50,8 +50,8 @@ (async/TestDefrecordJobImpl {:identity ["test-group" "test-name"] + (map->TestDefrecordJobImpl {:identity ["name1" "group2"] :description "test job" :recover? true :durable? false}))] From c96d9206ee567b7318be1800d94c25f784edd409 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 23:41:09 +1000 Subject: [PATCH 22/23] update README.md --- cronut-javax/README.md | 3 +-- cronut/README.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cronut-javax/README.md b/cronut-javax/README.md index 75f6856..03898a7 100644 --- a/cronut-javax/README.md +++ b/cronut-javax/README.md @@ -107,8 +107,7 @@ Concurrent execution can be controlled on a per-job bases with the `disallow-con ## Triggers -Each cronut trigger must be of tyee `org.quartz.Trigger` of some variety or another, the following functions are -provided to simplify trigger creation: +Cronut triggers are of type `org.quartz.Trigger`, the following functions are provided to simplify trigger creation: #### `cronut.trigger/cron`: Simple Cron Scheduling diff --git a/cronut/README.md b/cronut/README.md index 833c5ff..0ad7aef 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -107,8 +107,7 @@ Concurrent execution can be controlled on a per-job bases with the `disallow-con ## Triggers -Each cronut trigger must be of tyee `org.quartz.Trigger` of some variety or another, the following functions are -provided to simplify trigger creation: +Cronut triggers are of type `org.quartz.Trigger`, the following functions are provided to simplify trigger creation: #### `cronut.trigger/cron`: Simple Cron Scheduling From 0e9e09d7bb2740975f15bb20da8add01b0eddeb6 Mon Sep 17 00:00:00 2001 From: Derek Troy-West Date: Wed, 1 Oct 2025 23:54:07 +1000 Subject: [PATCH 23/23] update README.md --- cronut-javax/README.md | 2 +- cronut/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cronut-javax/README.md b/cronut-javax/README.md index 03898a7..326b34c 100644 --- a/cronut-javax/README.md +++ b/cronut-javax/README.md @@ -6,7 +6,7 @@ # Summary [Cronut](https://github.com/factorhouse/cronut) provides a data-first [Clojure](https://clojure.org/) wrapper -for [Quartz Scheduler](https://github.com/quartz-scheduler) version `2.4.0`, compatible +for [Quartz Scheduler](https://github.com/quartz-scheduler/quartz) version `2.4.0`, compatible with [Javax](https://jakarta.ee/blogs/javax-jakartaee-namespace-ecosystem-progress/). Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported. diff --git a/cronut/README.md b/cronut/README.md index 0ad7aef..d664390 100644 --- a/cronut/README.md +++ b/cronut/README.md @@ -6,7 +6,7 @@ # Summary [Cronut](https://github.com/factorhouse/cronut) provides a data-first [Clojure](https://clojure.org/) wrapper -for [Quartz Scheduler](https://github.com/quartz-scheduler) version `2.5.0`, compatible +for [Quartz Scheduler](https://github.com/quartz-scheduler/quartz) version `2.5.0`, compatible with [Jakarta](https://en.wikipedia.org/wiki/Jakarta_EE). Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and distributed jobstore are not supported.