Flexible (date/time-based) scheduling facilities for Clojure.
A fork of jarohen/chime, taking the core idea into a slightly different direction. I remain utterly grateful to the original maintainer, not only for the code, but also for our discussions. If you're thinking of using this library, please read the original README first (90% of the things apply here too).
The underlying idea behind jarohen/chime
is very powerful. Its power stems from the fact that producing the times you want scheduled,
is completely orthogonal to actually scheduling them. In other words, the right separation of concerns gave birth to opportunity/leverage.
The implementation is equally clever with a lazy approach whereby each task is scheduled as soon as the previous one finishes.
Finally, its maintainer is a cool, responsive and easy to talk to guy.
The thing is that, while jarohen/chime
invented/discovered the absolute perfect separation of concerns wrt when VS what to schedule,
in my view it missed the mark regarding the right separation between the enduring/macroscopic notion of the 'entire-schedule',
and the ephemeral/microscopic notion of the 'upcoming-task'. In addition, it has made the (unfortunate in my eyes) decision to associate
closing the entire schedule with pool.shutdownNow()
, leaving little room for concerns such as greceful shutdown
and
cancelling the upcoming (potentially already started) task, while keeping the overall schedule alive
.
Lastly its default error-handler, in order to be correct (given the shutdownNow
semantics), handles InterruptedException
differently from any other exception. This is something that should be mimicked by user-provided error-handlers,
but firstly it is not communicated anywhere (quite the contrary), and secondly it is kind of unreasonable.
This has all been discussed quite extensively between me and the jarohen/chime
maintainer. If you enjoy conversations like these
and are short of reading material, check this out.
Given the breaking nature of my vision/suggestions, it was mutually decided to instead fork and re-release as jimpil/chime
.
In order to avoid namespace collisions, this project has a slightly different project structure. The mapping is as follows:
chime.core
has becomechime.schedule
chime.core-async
has becomechime.channel
Furthermore, chime.joda-time
and chime.utils
don't exist here. chime.joda-time
and the clj-time
dependency are gone for good,
whereas the single function merge-schedules
in chime.utils
has been migrated to a brand new chime.times
namespace, which contains
anything related to generating times (i.e. periodic-seq
, without-past-times
etc).
It will hopefully become apparent later on, but the single most important difference is how the two projects handle 'closing the schedule'.
jarohen/chime
takes it upon itself to also cancel the (potentially already started) next-task (via shutdownNow
), whereas jimpil/chime
lets you decide on those two things separately. This also affects how errors are handled in the two projects
(i.e. the default error-handler in jimpil/chime
doesn't need to assign any particular importance to InterruptedException
- this is up to the end-user to decide).
Finally, this project aims to provide a higher-level (than chime-at
), stateful scheduler construct which can deal which multiple jobs.
Use chime.times
to build your seq of scheduling times.periodic-seq
is particularly useful for building fixed interval periods
(in case the built-in ones don't suffice). Also, see jarohen/chime for some nice examples.
This is the core scheduling primitive of chime
. It simply takes the dates/times, and the (1-arg) function you want scheduled.
It returns an implementation of:
AutoCloseable
- reflecting the closeable nature of the schedule as a whole (NOT concerned with already scheduled tasks)IPending
- reflecting the potentially finite nature of the scheduleIDeref
/IBlockingDeref
- reflecting the optionally blocking nature of the scheduleScheduledFuture
(partially) - representing the currently scheduled task
If you're confident you understand that the first 3 are about the entire schedule, whereas the last one is about a single task, you can use the returned object as is. That said, there is a somewhat higher-level api to assist you (see below).
Cancels the next upcoming chime, potentially abruptly, as it may have already started. The rest of the schedule
will remain unaffected, unless the interruption is handled by the error-handler (i.e. InterruptedException
), and it
returns falsey (or throws). The cancel-next?!
variant does the same thing, unless the next task has already started.
Returns the remaining time (in millis by default) until the next chime.
Gracefully closes the entire schedule (per pool.shutdown()
). If the next task hasn't started yet, it will be cancelled,
otherwise it will be allowed to finish.
Attempts a graceful shutdown (per shutdown!
), but if the latest task is already happening attempts to interrupt it.
Semantically equivalent to pool.shutdownNow()
.
From a user's perspective I find the above separation of concerns rather empowering, as I can ask questions/make decisions about
the next-task VS the entire-schedule separately - and this is essentially the crux of the divergence from jarohen/chime
.
Returns a readable core.async
channel that 'chimes' at every time in the times
list.
Unlike jarohen/chime
, you're not allowed to pass in a :ch
param, but you can pass a :buffer
instead.
Uses chime-at
internally, so you can also pass :error-handler
, and/or :on-finished
callbacks.
Closing the returned read-channel (gracefully) stops the schedule, and closes the underlying write-channel (where the chime-fn writes).
This means that no more writes will succeed (even if there is something already scheduled).
It is often desirable to add times to a schedule which is already running.
jimpil/chime
supports this via the schedule/mutable-times
fn. It basically wraps a (finite)
sequence of times into an atom (which chime-at
can work with).
;; the mutable times
(def times
(->> (times/every-n-seconds 2)
(take 10)))
;; the mutable schedule
(def sched (chime-at times println {:mutable? true}))
;; cancelling the upcoming chime works
(cancel-current?! sched)
;; append to the schedule (if not finished already)
(append-relative-to-last! sched #(.plusSeconds ^Instant % 2))
It is on YOU to add to the schedule BEFORE it finishes!
The project was forked in its v0.3.2
state (actually a little later but not super important).
- Java 8 or above (for
java.time
)
Copyright © 2020 Dimitrios Piliouras
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.