diff --git a/.VERSION_PREFIX b/.VERSION_PREFIX new file mode 100644 index 0000000..5625e59 --- /dev/null +++ b/.VERSION_PREFIX @@ -0,0 +1 @@ +1.2 diff --git a/.gitignore b/.gitignore index eee71cb..902f2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .cake -pom.xml *.jar *.war lib @@ -13,3 +12,4 @@ pom.xml.asc /.nrepl-port .hgignore .hg/ +.cpcache diff --git a/CHANGELOG.md b/CHANGELOG.md index d67ff47..a0532d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,10 @@ -# at-at Changelog +# Unreleased -## 1.1.0 -_14th Jan 2013_ - -* Added new fn `interspaced` which will call fun repeatedly with a - specified interspacing. -* Added missing trailing quotes when printing schedule. +- Add exception handling through `uncaught-exception-handler` +- Make our thread pool threads recognizable by adding `at-at` to the thread name +- Add pprint handlers for records +- Add type hints to avoid reflection, and to be Babashka/GraalVM compatible +- Make `shutdown-pool!` public ## 1.2.0 _28th May 2013_ @@ -13,3 +12,11 @@ _28th May 2013_ * BREAKING CHANGE - Remove support for specifying stop-delayed? and stop-periodic? scheduler strategies. * Jobs now correctly report as no longer being scheduled when pool is shutdown. + +## 1.1.0 +_14th Jan 2013_ + +* Added new fn `interspaced` which will call fun repeatedly with a + specified interspacing. +* Added missing trailing quotes when printing schedule. + diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md index 8aee496..bd2782f 100644 --- a/README.md +++ b/README.md @@ -29,22 +29,42 @@ ### at-at + +[![cljdoc badge](https://cljdoc.org/badge/overtone/at-at)](https://cljdoc.org/d/overtone/at-at) [![Clojars Project](https://img.shields.io/clojars/v/overtone/at-at.svg)](https://clojars.org/overtone/at-at) + + Simple ahead-of-time function scheduler. Allows you to schedule the execution of an anonymous function for a point in the future. > This is a graalvm compatible fork of the original [`overtone/at-at`](https://github.com/overtone/at-at) repo. + +## Installation + +To use the latest release, add the following to your `deps.edn` ([Clojure CLI](https://clojure.org/guides/deps_and_cli)) + +``` +overtone/at-at {:mvn/version "0.0.0"} +``` + +or add the following to your `project.clj` ([Leiningen](https://leiningen.org/)) + +``` +[overtone/at-at "0.0.0"] +``` + + ### Basic Usage First pull in the lib: ```clj -(use 'overtone.at-at) +(require '[overtone.at-at :as at]) ``` `at-at` uses `ScheduledThreadPoolExecutor`s behind the scenes which use a thread pool to run the scheduled tasks. You therefore need create a pool before you can get going: ```clj -(def my-pool (mk-pool)) +(def my-pool (at/mk-pool)) ``` It is possible to pass in extra options `:cpu-count`, `:stop-delayed?` and `:stop-periodic?` to further configure your pool. See `mk-pool`'s docstring for further info. @@ -52,7 +72,7 @@ It is possible to pass in extra options `:cpu-count`, `:stop-delayed?` and `:sto Next, schedule the function of your dreams. Here we schedule the function to execute in 1000 ms from now (i.e. 1 second): ```clj -(at (+ 1000 (now)) #(println "hello from the past!") my-pool) +(at/at (+ 1000 (at/now)) #(println "hello from the past!") my-pool) ``` You may also specify a description for the scheduled task with the optional `:desc` key. @@ -60,19 +80,19 @@ You may also specify a description for the scheduled task with the optional `:de Another way of achieving the same result is to use `after` which takes a delaty time in ms from now: ```clj -(after 1000 #(println "hello from the past!") my-pool) +(at/after 1000 #(println "hello from the past!") my-pool) ``` You can also schedule functions to occur periodically. Here we schedule the function to execute every second: ```clj -(every 1000 #(println "I am cool!") my-pool) +(at/every 1000 #(println "I am cool!") my-pool) ``` This returns a scheduled-fn which may easily be stopped `stop`: ```clj -(stop *1) +(at/stop *1) ``` Or more forcefully killed with `kill`. @@ -80,13 +100,13 @@ Or more forcefully killed with `kill`. It's also possible to start a periodic repeating fn with an initial delay: ```clj -(every 1000 #(println "I am cool!") my-pool :initial-delay 2000) +(at/every 1000 #(println "I am cool!") my-pool :initial-delay 2000) ``` Finally, you can also schedule tasks for a fixed delay (vs a rate): ```clj -(interspaced 1000 #(println "I am cool!") my-pool) +(at/interspaced 1000 #(println "I am cool!") my-pool) ``` This means that it will wait 1000 ms after the task is completed before @@ -97,13 +117,13 @@ starting the next one. When necessary it's possible to stop and reset a given pool: ```clj -(stop-and-reset-pool! my-pool) +(at/stop-and-reset-pool! my-pool) ``` You may forcefully reset the pool using the `:kill` strategy: ```clj -(stop-and-reset-pool! my-pool :strategy :kill) +(at/stop-and-reset-pool! my-pool :strategy :kill) ``` ### Viewing running scheduled tasks. @@ -111,28 +131,34 @@ You may forcefully reset the pool using the `:kill` strategy: `at-at` keeps an eye on all the tasks you've scheduled. You can get a set of the current jobs (both scheduled and recurring) using `scheduled-jobs` and you can pretty-print a list of these job using `show-schedule`. The ids shown in the output of `show-schedule` are also accepted in `kill` and `stop`, provided you also specify the associated pool. See the `kill` and `stop` docstrings for more information. ```clj -(def tp (mk-pool)) -(after 10000 #(println "hello") tp :desc "Hello printer") -(every 5000 #(println "I am still alive!") tp :desc "Alive task") -(show-schedule tp) -;; [6][RECUR] created: Thu 12:03:35s, period: 5000ms, desc: "Alive task -;; [5][SCHED] created: Thu 12:03:32s, starts at: Thu 12:03:42s, desc: "Hello printer +(def tp (at/mk-pool)) +(at/after 10000 #(println "hello") tp :desc "Hello printer") +(at/every 5000 #(println "I am still alive!") tp :desc "Alive task") +(at/show-schedule tp) +;; [6][RECUR] created: Thu 12:03:35s, period: 5000ms, desc: "Alive task" +;; [5][SCHED] created: Thu 12:03:32s, starts at: Thu 12:03:42s, desc: "Hello printer" ``` -### Install - -Fetch at-at from github: https://github.com/overtone/at-at or pull from clojars: `[overtone/at-at "X.Y.Z"]` - ### History at-at was extracted from the awesome music making wonder that is Overtone (http://github.com/overtone/overtone) - ### Authors * Sam Aaron * Jeff Rose * Michael Neale * Alexander Oloo +* Arne Brasseur +* Daniel MacDougall +* Josh Comer (Ascii art borrowed from http://www.sanitarium.net/jokes/getjoke.cgi?132) + + +## License + +Copyright © 2011-2023 Sam Aaron, Jeff Rose, and contributors + +Available under the terms of the Eclipse Public License 1.0, see LICENSE.txt + diff --git a/bb.edn b/bb.edn new file mode 100644 index 0000000..fa0460c --- /dev/null +++ b/bb.edn @@ -0,0 +1,4 @@ +{:deps + {lambdaisland/open-source {:git/url "https://github.com/lambdaisland/open-source" + :sha "b46bd6273c5c554f8374406a7482f6e0a6f1dd25" + #_#_:local/root "../open-source"}}} diff --git a/bin/proj b/bin/proj new file mode 100755 index 0000000..81722f7 --- /dev/null +++ b/bin/proj @@ -0,0 +1,17 @@ +#!/usr/bin/env bb + +(ns proj (:require [lioss.main :as lioss])) + +(lioss/main + {:license :epl + :group-id "overtone" + :gh-project "overtone/at-at" + :org-name "Overtone" + :org-url "https://overtone.github.io/" + :inception-year 2011 + :description "Ahead-of-time function scheduler."}) + + +;; Local Variables: +;; mode:clojure +;; End: diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/deps.edn @@ -0,0 +1 @@ +{} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e957031 --- /dev/null +++ b/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + overtone + at-at + 0.0.51 + at-at + Ahead-of-time function scheduler. + https://github.com/lambdaisland/at-at + 2011 + + Lambda Island + https://lambdaisland.com + + + UTF-8 + + + + Eclipse Public License 1.0 + https://www.eclipse.org/legal/epl-v10.html + + + + https://github.com/lambdaisland/at-at + scm:git:git://github.com/lambdaisland/at-at.git + scm:git:ssh://git@github.com/lambdaisland/at-at.git + 797011be61c92d3e6578e6fcff49fd39eef11b64 + + + + src + + + src + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + 797011be61c92d3e6578e6fcff49fd39eef11b64 + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + clojars + https://repo.clojars.org/ + + + + + clojars + Clojars repository + https://clojars.org/repo + + + \ No newline at end of file diff --git a/project.clj b/project.clj deleted file mode 100644 index 119799e..0000000 --- a/project.clj +++ /dev/null @@ -1,3 +0,0 @@ -(defproject org.alekcz/at-at "1.2.0-SNAPSHOT" - :description "Ahead-of-time function scheduler" - :dependencies [[org.clojure/clojure "1.3.0" :scope "provided"]]) diff --git a/repl_sessions/walkthrough.clj b/repl_sessions/walkthrough.clj new file mode 100644 index 0000000..acc0ece --- /dev/null +++ b/repl_sessions/walkthrough.clj @@ -0,0 +1,24 @@ +(ns at-at.walkthrough + (:require + [overtone.at-at :as at])) + +(def my-pool (at-at/mk-pool)) + +(at/at (+ 1000 (at/now)) #(println "hello from the past!") my-pool) +(at/after 1000 #(println "hello from the past!") my-pool) + +(at/every 1000 #(println "I am cool!") my-pool) +(at/every 1000 #(println "I am cool!") my-pool :initial-delay 2000) +(at/show-schedule my-pool) +(at/stop (first (at/scheduled-jobs my-pool))) + +(at/interspaced 1000 #(println "I am cool!") my-pool) +(at/stop-and-reset-pool! my-pool) +(at/stop-and-reset-pool! my-pool :strategy :kill) + +(def tp (at/mk-pool)) +(at/after 10000 #(println "hello") tp :desc "Hello printer") +(at/every 5000 #(println "I am still alive!") tp :desc "Alive task") +(at/show-schedule tp) + +(run! at/stop (at/scheduled-jobs tp)) diff --git a/src/overtone/at_at.clj b/src/overtone/at_at.clj index 3dd0f1b..e1e7572 100644 --- a/src/overtone/at_at.clj +++ b/src/overtone/at_at.clj @@ -3,9 +3,17 @@ [clojure.pprint :as pprint]) (:import (java.io Writer) - (java.util.concurrent ScheduledThreadPoolExecutor - TimeUnit - ThreadPoolExecutor))) + (java.util.concurrent Executors Future ScheduledThreadPoolExecutor ThreadFactory ThreadPoolExecutor TimeUnit))) + +(declare job-string) + +(defn uncaught-exception-handler + "Called when a scheduled function throws. Use `alter-var-root` to customize + this." + [throwable job] + (println (str throwable " thrown by at-at task: " (job-string job))) + (.printStackTrace throwable) + (throw throwable)) (defrecord PoolInfo [thread-pool jobs-ref id-count-ref]) (defrecord MutablePool [pool-atom]) @@ -84,6 +92,14 @@ [] (.availableProcessors (Runtime/getRuntime))) +(defn- wrap-fun-with-exception-handler + [fun job-info-prom] + (fn [& args] + (try + (apply fun args) + (catch Throwable t + (uncaught-exception-handler t @job-info-prom))))) + (defn- schedule-job "Schedule the fun to execute periodically in pool-info's pool with the specified initial-delay and ms-period. Returns a RecurringJob record." @@ -91,6 +107,8 @@ (let [initial-delay (long initial-delay) ms-period (long ms-period) ^ScheduledThreadPoolExecutor t-pool (:thread-pool pool-info) + job-info-prom (promise) + ^Callable fun (wrap-fun-with-exception-handler fun job-info-prom) job (if interspaced? (.scheduleWithFixedDelay t-pool fun @@ -104,19 +122,21 @@ TimeUnit/MILLISECONDS)) start-time (System/currentTimeMillis) jobs-ref (:jobs-ref pool-info) - id-count-ref (:id-count-ref pool-info)] - (dosync - (let [id (commute id-count-ref inc) - job-info (RecurringJob. id - start-time - ms-period - initial-delay - job - pool-info - desc - (atom true))] - (commute jobs-ref assoc id job-info) - job-info)))) + id-count-ref (:id-count-ref pool-info) + job-info (dosync + (let [id (commute id-count-ref inc) + job-info (RecurringJob. id + start-time + ms-period + initial-delay + job + pool-info + desc + (atom true))] + (commute jobs-ref assoc id job-info) + job-info))] + (deliver job-info-prom job-info) + job-info)) (defn- wrap-fun-to-remove-itself [fun jobs-ref job-info-prom] @@ -136,8 +156,10 @@ (let [initial-delay (long initial-delay) ^ScheduledThreadPoolExecutor t-pool (:thread-pool pool-info) jobs-ref (:jobs-ref pool-info) - id-prom (promise) - ^Callable fun (wrap-fun-to-remove-itself fun jobs-ref id-prom) + job-info-prom (promise) + ^Callable fun (-> fun + (wrap-fun-with-exception-handler job-info-prom) + (wrap-fun-to-remove-itself jobs-ref job-info-prom)) job (.schedule t-pool fun initial-delay TimeUnit/MILLISECONDS) start-time (System/currentTimeMillis) id-count-ref (:id-count-ref pool-info) @@ -152,7 +174,7 @@ (atom true))] (commute jobs-ref assoc id job-info) job-info))] - (deliver id-prom job-info) + (deliver job-info-prom job-info) job-info)) (defn- shutdown-pool-now! @@ -185,7 +207,14 @@ (defn- mk-sched-thread-pool "Create a new scheduled thread pool containing num-threads threads." [num-threads] - (let [t-pool (ScheduledThreadPoolExecutor. num-threads)] + (let [thread-factory (Executors/defaultThreadFactory) + t-pool (ScheduledThreadPoolExecutor. + num-threads + (reify ThreadFactory + (newThread [this runnable] + (let [thread (.newThread thread-factory runnable)] + (.setName thread (str "at-at-" (.getName thread))) + thread))))] t-pool)) (defn- mk-pool-info @@ -303,7 +332,7 @@ pool-info (:pool-info job-info) pool (:thread-pool pool-info) jobs-ref (:jobs-ref pool-info)] - (.cancel ^Future job cancel-immediately?) + (.cancel ^Future job cancel-immediately?) (reset! (:scheduled? job-info) false) (dosync (let [job (get @jobs-ref id)] @@ -353,14 +382,16 @@ "[RECUR] created: " (format-date (:created-at job)) (format-start-time (+ (:created-at job) (:initial-delay job))) ", period: " (:ms-period job) "ms" - ", desc: \""(:desc job) "\"")) + (when (not= "" (:desc job)) + (str ", desc: \"" (:desc job) "\"")))) (defn- scheduled-job-string [job] (str "[" (:id job) "]" "[SCHED] created: " (format-date (:created-at job)) (format-start-time (+ (:created-at job) (:initial-delay job))) - ", desc: \"" (:desc job) "\"")) + (when (not= "" (:desc job)) + (str ", desc: \"" (:desc job) "\"")))) (defn- job-string [job]