Coder Social home page Coder Social logo

chime's People

Contributors

bartadv avatar cassiel avatar cloojure avatar dexterminator avatar holyjak avatar jarohen avatar jimpil avatar krastins avatar rockolo avatar vemv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chime's Issues

Extension idea - sequence of time + function + some controls

Hi

I've just checked chime and it does things slightly different than what I thought.

If chime-ch takes a seq of map, which may be look like:
(chime-ch [{:at <time1> :fn my-fn1} {:at <time2> :fn my-fn2} ...])
then fn can be executed when time comes.

Also it may allow extra key to control fn execution when the time's already passed:
{:at <time1> :fn fn1 :run-fn-for-passed-time? true}

Breaks on newer core.async version

When my project depends on the latest version of core.async (currently 0.2.385) calling chime-at will result in the following exception:

Exception in thread "async-dispatch-4" java.lang.IllegalArgumentException: No implementation of method: :take! of protocol: #'clojure.core.async.impl.protocols/ReadPort found for class: chime$chime_ch$reify__19318
    at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
    at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
    at clojure.core.async.impl.protocols$eval59$fn__60$G__50__67.invoke(protocols.clj:15)
    at clojure.core.async.impl.ioc_macros$take_BANG_.invokeStatic(ioc_macros.clj:1022)
    at clojure.core.async.impl.ioc_macros$take_BANG_.invoke(ioc_macros.clj:1021)
    at chime$chime_at$fn__19347$state_machine__5082__auto____19348$fn__19350.invoke(chime.clj:66)
    at chime$chime_at$fn__19347$state_machine__5082__auto____19348.invoke(chime.clj:65)
    at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:1012)
    at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:1011)
    at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:1016)
    at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:1014)
    at chime$chime_at$fn__19347.invoke(chime.clj:65)
    at clojure.lang.AFn.run(AFn.java:22)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Quick workaround: depend on 0.2.374

Support for times already past in `chime-at`

#7 doesn't seem to be resolved.

Sometimes the following code produces an error.

(let [start-time (t/now)]
  (chime/chime-at [(-> 1 t/millis t/from-now)] println {:start-time start-time}))
=>
#object[chime$chime_at$cancel_BANG___15477
        0x40b6d48d
        "chime$chime_at$cancel_BANG___15477@40b6d48d"]
Exception in thread "async-dispatch-2" java.lang.IllegalArgumentException: No implementation of method: :before? of protocol: #'clj-time.core/DateTimeProtocol found for class: nil
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
	at clj_time.core$eval6044$fn__6089$G__6021__6096.invoke(core.clj:102)
	at chime$ms_between.invokeStatic(chime.clj:8)
	at chime$ms_between.invoke(chime.clj:7)
	at chime$chime_ch$fn__15317$state_machine__12479__auto____15332$fn__15334.invoke(chime.clj:38)
	at chime$chime_ch$fn__15317$state_machine__12479__auto____15332.invoke(chime.clj:38)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:973)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:972)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:977)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:975)
	at chime$chime_ch$fn__15317.invoke(chime.clj:38)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Long running tasks

Suppose we have such code:

(chime/chime-at (chime/periodic-seq (Instant/now) (Duration/ofSeconds 20))
                            (fn [_]
                              (when (has-new-task)
                                 (long-running-task))))

We need to check if there is a new task every 20 secs, and if so, we need run long-time calculation (which can last for an hour). Then after long running task is completed, there will be a lot of (has-new-task) calls, which would be better to avoid.

Is it possible to add a parameter to avoid such behaviour and instead run only 1 function call after long-running-task is completed? Or is it better to use another mechanism for such situation?

New feature: request upcoming chimes

It would be useful to be able to ask chime for upcoming scheduled jobs. While my code could cache this information itself, I figure chime is already storing this information somewhere, so it would be better to just ask the source, rather than duplicating it in my own code.

java.lang.InterruptedException race in chiming functions.

There seems to be a race between closing a chime and the chiming function invocation that can lead to a java.lang.InterruptedException being thrown when functions are awaiting something coming from, for example, a future, or have consumed a function that does this somewhere along the call stack.

It'd be nice if either the chiming function was never called, or any inflight functions were allowed to complete before closing.

Example:

(let [now (Instant/now)
      chiming (chime/chime-at (chime/periodic-seq now (Duration/ofSeconds 1))
                              (fn [time]
                                (log/info :future-task @(future (count {})))
                                (log/info :chime (str time)))
                              {:error-handler (fn [e]
                                                (log/error :chime-failed e)
                                                true)
                               :on-finished #(log/info :done)})]
  (Thread/sleep 1000)
  (.close chiming))

outputs:

2020-09-07 15:51:25.548 INFO  :future-task 0
2020-09-07 15:51:25.549 INFO  :chime 2020-09-07T14:51:25.547924Z
2020-09-07 15:51:26.550 INFO  :done
2020-09-07 15:51:26.550 ERROR :chime-failed #error {
 :cause nil
 :via
 [{:type java.lang.InterruptedException
   :message nil
   :at [java.util.concurrent.FutureTask awaitDone FutureTask.java 418]}]
 :trace
 [[java.util.concurrent.FutureTask awaitDone FutureTask.java 418]
  [java.util.concurrent.FutureTask get FutureTask.java 190]
  [clojure.core$deref_future invokeStatic core.clj 2300]
  [clojure.core$future_call$reify__8454 deref core.clj 6974]
  [clojure.core$deref invokeStatic core.clj 2320]
  [clojure.core$deref invoke core.clj 2306]
  ...
  [chime.core$chime_at$schedule_loop__13568$task__13572$fn__13573 invoke core.clj 91]
  [chime.core$chime_at$schedule_loop__13568$task__13572 invoke core.clj 90]
  [clojure.lang.AFn run AFn.java 22]
  [java.util.concurrent.Executors$RunnableAdapter call Executors.java 515]
  [java.util.concurrent.FutureTask run FutureTask.java 264]
  [java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask run ScheduledThreadPoolExecutor.java 304]
  [java.util.concurrent.ThreadPoolExecutor runWorker ThreadPoolExecutor.java 1128]
  [java.util.concurrent.ThreadPoolExecutor$Worker run ThreadPoolExecutor.java 628]
  [java.lang.Thread run Thread.java 834]]}

Note, might have to run this a couple of times to see the exception, as it doesn't always happen.

Violation of pre-condition not caught by error handler

Hi!

I am using chime 0.3.2 with Clojure 1.10.1 and have found that the :error-handler function currently does not catch violations of pre-conditions.

From looking at the code, chime seems to only catch java.lang.Exception:

(chime/chime-at
    (take 3 (rest (chime/periodic-seq (java.time.Instant/now)
                                      (java.time.Duration/ofSeconds 1))))
    (fn [timestamp] (throw (Exception. (str timestamp))))
    {:error-handler (fn [e] (println (str e)))})

but not java.lang.Error:

(chime/chime-at
    (take 3 (rest (chime/periodic-seq (java.time.Instant/now)
                                      (java.time.Duration/ofSeconds 1))))
    (fn [timestamp] (throw (AssertionError. (str timestamp))))
    {:error-handler (fn [e] (println (str e)))})

Could you please change the code and catch java.lang.Throwable, the common ancestor of errors and exceptions?

Thanks a lot! ๐Ÿ˜„

Martin

:error-handler does not seem to be called

Hello,

Given this very simple test case:

(defn test-exception-with-handler []
  (chime-at (rest
    (periodic-seq (t/now)
                  (-> 5 t/seconds))
            (fn [time]
              (throw (Exception. "uh oh")))
            {:error-hander (fn [e]
                             (log/error e "something happened"))}))

It appears that the error handler is not called. Here's the printed output:

Exception in thread "async-thread-macro-11" java.lang.Error: java.lang.Exception: uh oh
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1148)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.Exception: uh oh

Am I missing something?
Thanks in advance!

Core.async integration doc-string

Originally posted by @jimpil in #37 (comment)

Re: the core-async integration:

At first glance, I don't see how the stuff we're discussing breaks chime-ch. However, I did notice something else. The doc-string states:

ch - (optional) Channel to chime on - defaults to a new unbuffered channel
Closing this channel stops the schedule.

I see no evidence of this statement. I suspect you meant to say closing the returned channel stops the schedule, but then that should be a few lines up. If the intention of the doc-string is correct, then I think the chiming-fn should be something like the following:

(fn [time]
  (when-not (a/>!! ch time) 
    (a/close ch)))

With the risk of rushing to conclusions, i will say that this looks like another remnant of a time when chime would actually consider the return of the chime-fn. However, at the moment it is not (true is returned after it). I apologise in advance if that's far-reaching...

chime-at calls callback after schedule is cancelled if a previous callback overruns

This may be by design, but I thought it was confusing and wanted to bring it to your attention just in case.

(defn do-stuff [now]
  (println "starting" now)
  (Thread/sleep 7000) ;; some overrunning task
  (println "done" now))

(def cancel-stuff! (chime-at (rest (periodic-seq (now) (seconds 5))) do-stuff))

Timeline:

 0 sec -- schedule starts
 5 sec -- do-stuff call 1 starts
10 sec -- time for do-stuff call 2 put into chime-ch channel
12 sec -- do-stuff call 1 finishes, do-stuff call 2 starts
15 sec -- time for do-stuff call 3 put into chime-ch channel
16 sec -- cancel-stuff! called
17 sec -- do-stuff call 2 finishes, do-stuff call 3 starts
24 sec -- do-stuff call 3 finishes

Is this a bug, or the expected behavior? I can see an argument for the latter, since cancel-stuff! is called after the next time in the schedule (and thus after it's added to chime-at's internal chime-ch channel). This is somewhat non-intuitive, however; generally I'd expect a job that hasn't started yet to stay that way if the schedule is cancelled.

I'm aware the README says that with chime-at cancelling overrunning jobs is the caller's responsibility, but this is slightly different situation. If this is by design, you may want to update the documentation to clarify this.

Thank you!

Expose :thread-factory option

Basically, along with :error-handler and :on-finished, one could/should(?) be able to provide an (optional) :thread-factory. We're talking for a few characters change here... Any thoughts?

Dynamic bindings not conveyed

One of the most annoying things about working with Java Executors is that binding-conveyance is on you. Observe this:

(def ^:dynamic *foo* :nothing)
=> #'chime.core/*foo*
(binding [*foo* :something] 
  (chime-at [(.plusSeconds (Instant/now) 1)] (fn [_] (println *foo*))))
=> #object[chime.core$chime_at$reify__1917 0x6303b429 {:status :pending, :val nil}]
:nothing ;; after 1 second

This comes down to going beyond the language (i.e. as long as you stay within Clojure futures,agents,refs you don't need to
worry about it). Thankfully the fix is super simple - use bound-fn:

(.schedule pool ^Runnable (bound-fn [] (task)) ...) ;; wrap task with `bound-fn`

Hope that helps...

(binding [*foo* :something] 
  (chime-at [(.plusSeconds (Instant/now) 1)] (bound-fn [_] (println *foo*))))
=> #object[chime.core$chime_at$reify__1917 0x1df32999 {:status :pending, :val nil}]
:something

Multiple invocations of `on-finished`

I'm not sure what the correct behaviour should be here, but might be worth thinking about a little.

Given the following code:

(let [now (Instant/now)
        chiming (chime/chime-at [(.plusSeconds now 1)
                                 (.plusSeconds now 3)]
                                (fn [time]
                                  (prn :chime (str time)))
                                {:on-finished #(prn :done)})]
    (Thread/sleep 4000)
    (.close chiming))

... we can see that on-finished is called twice:

:chime "2020-09-07T13:52:33.193745Z"
:chime "2020-09-07T13:52:35.193745Z"
:done
:done

This was surprising to me - perhaps on-finished should have an invariant that it is only ever called once?

Another option is that perhaps the value which on-finished evaluates to should be delivered to the invocation of close, meaning the fn can be evaluated once, but value delivered to multiple places...? Seems like this could be useful.

Thread names don't include numbers

Despite this code, I'm not seeing chime's threads include a number in their name, making debugging slightly more difficult.

Here's a log excerpt produced by my code, showing the problematic thread names:

2020-11-13T00:00:00.001627+00:00 app[bot.1]: INFO  [chime-] futbot.core - Youtube channel -unknown (UCmzFaEBQlLmMTWS0IQ90tgA)- job started...
2020-11-13T00:00:00.001960+00:00 app[bot.1]: INFO  [chime-] futbot.core - Daily schedule job started...
2020-11-13T00:00:00.108573+00:00 app[bot.1]: ERROR [chime-] futbot.core - Unexpected exception in Youtube channel -unknown (UCmzFaEBQlLmMTWS0IQ90tgA)- job
2020-11-13T00:00:00.108583+00:00 app[bot.1]: clojure.lang.ExceptionInfo: Google API call (https://www.googleapis.com/youtube/v3/search?part=snippet&order=date&type=video&maxResults=50&channelId=UCmzFaEBQlLmMTWS0IQ90tgA&publishedAfter=2020-11-12T00:00:00.002427Z&key=REDACTED) failed
2020-11-13T00:00:00.108755+00:00 app[bot.1]: INFO  [chime-] futbot.core - Youtube channel -unknown (UCmzFaEBQlLmMTWS0IQ90tgA)- job finished
2020-11-13T00:00:00.920143+00:00 app[bot.1]: INFO  [chime-] futbot.jobs - No matches remaining today - not scheduling any reminders.
2020-11-13T00:00:00.920324+00:00 app[bot.1]: INFO  [chime-] futbot.core - Daily schedule job finished

This excerpt shows two different jobs ("Youtube channel -unknown (UCmzFaEBQlLmMTWS0IQ90tgA)- job" and "Daily schedule job") that just happen to be scheduled to run at the same time (midnight UTC), resulting in their log output being interleaved. This makes it somewhat more difficult to properly correlate individual log entries with each job.

Oh and for reference, here's the log output pattern I'm using (my app uses Logback classic) - note in particular that it does not truncate the thread name component, meaning if the thread names had included a number, it would be visible:

      <pattern>%date %-5level [%thread] %logger{30} - %msg%n</pattern>

IllegalArgumentException when supplying an empty list for a schedule

Sometimes, I would like to specify an empty schedule, but when I supply [] or nil as the schedule arguments to chime-ch or chime-at, both will attempt to call clj-time.core/before? on nil:

user> (chime/chime-ch nil)
Exception in thread "async-dispatch-1" 
java.lang.IllegalArgumentException: No implementation of method: :before? of protocol: #'clj-time.core/DateTimeProtocol found for class: nil
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
	at clj_time.core$eval11045$fn__11090$G__11022__11097.invoke(core.clj:102)
	at chime$ms_between.invokeStatic(chime.clj:8)
	at chime$ms_between.invoke(chime.clj:7)
	at chime$chime_ch$fn__19258$state_machine__17295__auto____19259$fn__19261.invoke(chime.clj:38)
	at chime$chime_ch$fn__19258$state_machine__17295__auto____19259.invoke(chime.clj:38)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:1011)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:1010)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:1015)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:1013)
	at chime$chime_ch$fn__19258.invoke(chime.clj:38)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
#object[chime$chime_ch$reify__19297 0x6f223434 "chime$chime_ch$reify__19297@6f223434"]
user> (chime/chime-at nil (fn [_] (println "called")))
Exception in thread "async-dispatch-3" 
java.lang.IllegalArgumentException: No implementation of method: :before? of protocol: #'clj-time.core/DateTimeProtocol found for class: nil
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
	at clj_time.core$eval11045$fn__11090$G__11022__11097.invoke(core.clj:102)
	at chime$ms_between.invokeStatic(chime.clj:8)
	at chime$ms_between.invoke(chime.clj:7)
	at chime$chime_ch$fn__19258$state_machine__17295__auto____19259$fn__19261.invoke(chime.clj:38)
	at chime$chime_ch$fn__19258$state_machine__17295__auto____19259.invoke(chime.clj:38)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:1011)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:1010)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:1015)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:1013)
	at chime$chime_ch$fn__19258.invoke(chime.clj:38)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
#function[chime/chime-at/cancel!--19353]

Both calls will return the expected channel/cancel function, but the exception is still worrisome and is causing my tests to fail.

I tried running the likely culprit:

    (go-loop [now (t/now)
              [next-time & more-times] (->> (times-fn)
                                            (map tc/to-date-time)
                                            (drop-while #(t/before? % now)))]
      ...)

But I could not reproduce the problem in my REPL.

By the way, thanks for a great project! I really like the abstraction provided by chime-ch.

How to schedule multiple recurring jobs, called with a specific context?

Coming from Quartzite (Quartz), how should I schedule multiple recurring "triggers", each with a particular context? Need to be able to modify or cancel these periodic schedules.

It's not clear from the documentation whether I can call chime-at with an infinite sequence of times. Perhaps you could add some example usages?

Specifically, I need to trigger multiple recurring jobs at regular time on certain days of the week, e.g. 9am every Tuesday, and/or 11am on Tuesdays and Wednesdays.

Is chime-at supposed to be blocking or asynchronous?

In the README for 0.3.2, it says:

chime-at returns an AutoCloseable that can be closed to cancel the schedule.

To me, this implies that chime-at runs asynchronously, and that we can cancel its execution using the returned AutoCloseable. But when I run the example in the REPL, it blocks until it runs all the times in the sequence, then it returns the object (or never if it's infinite).

user=> (let [now (Instant/now)]
  #_=>   (chime/chime-at [(.plusSeconds now 2)
  #_=>                    (.plusSeconds now 4)]
  #_=>                   (fn [time]
  #_=>                     (println "Chiming at" time))))
Chiming at #object[java.time.Instant 0x1f5ffdd2 2020-08-25T00:14:32.904Z]
Chiming at #object[java.time.Instant 0x11981049 2020-08-25T00:14:34.904Z]
#object[chime.core$chime_at$reify__4951 0x77b586bf {:status :ready, :val nil}]
user=> 

Or perhaps I'm misunderstanding the purpose of the AutoCloseable. Does it serve a purpose after all the times have elapsed?

parameterize the executor

Hey, thanks for this library, exactly what I needed,

Instead of defaulting to Executors/newSingleThreadScheduledExecutor, can we get this parameterized, e.g. as a kwargs to chime-at. The reason for this is i'm trying to write unit tests with simulated time that aren't multi-threaded and mocked out the java.util.concurrent.ScheduledExecutorService interface.

Thanks!

Error in the doc-string?

In the doc-string of chime-ch there's a usage example:

Usage: 

(let [chimes (chime-ch [(.plusSeconds (Instant/now) -2) ; has already passed, will be ignored.
                        (.plusSeconds (Instant/now) 2)
                        (.plusSeconds (Instant/now) 2)])]
  (a/<!! (go-loop []
           (when-let [msg (<! chimes)]
             (prn "Chiming at:" msg)
             (recur)))))

The comment states that the first time will be ignored because it has passed, but running this code will print all 3 times.

Examples assume Dates instead of Instants?

I'm trying to use a filter on the result of periodic-seq like in this example in the README:

(->> (chime/periodic-seq (-> (.adjustInto (LocalTime/of 0 0)
                                          (ZonedDateTime/now (ZoneId/of "America/New_York")))
                             .toInstant)
                         (Period/ofDays 1))

     (filter (comp #{DayOfWeek/TUESDAY DayOfWeek/FRIDAY}
                   #(.getDayOfWeek %))))

However when I try try example, I get this error:

No matching field found: getDayOfWeek for class java.time.Instant

The function seems to return Instants while the example assumes Dates. Am I doing something wrong or is there a problem with the example? Thanks!

multiple chime-at calls

Is it safe to call chime-at multiple times with different schedules?
Will this run in different thread pools?

periodic-seq NullPointerException

Thanks for creating chime! It's been incredibly helpful. I ran into an error running "0.3.2" and "0.3.3-SNAPSHOT". Here is the calling code:

(defn -main
  [& args]
  (send "testing db...") ;; send text
  (chime/chime-at (chime/periodic-seq 
                   (-> (LocalTime/of 8 0 0)
                       (.adjustInto (ZonedDateTime/now (ZoneId/of "America/Chicago")))
                       .toInstant)
                   (Period/ofDays 1))
                  (send-rand) ;; send random text
                  {:on-finished (send "done chiming.")}))

Two things are interesting, I get the error below, but also, before this exception is thrown, it calls my chime fn (send-rand) and then calls :on finished (send "done chiming"). It should definitely not call (send-rand) as the periodic-seqis for another time.

:not-delivered>Jan 02, 2021 8:46:29 AM chime.core invoke WARNING: Error running scheduled fn java.lang.NullPointerException at chime.core$chime_at$schedule_loop__1079$task__1083$fn__1084.invoke(core.clj:91) at chime.core$chime_at$schedule_loop__1079$task__1083.invoke(core.clj:90) at clojure.lang.AFn.run(AFn.java:22) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)

Again, thanks for making this! I know it can be frustrating to just have people paste in stack traces and want help. I am hoping other people have, or will, see the same issue and this will help them.

Feature request: Allow adding new times to a chimes-ch channel

I'd like to create a channel then put! new times into it, like so:

(def chimes (chimes-ch []))

(a/<!! (go-loop []
           (when-let [msg (<! chimes)]
             (prn "Chiming at:" msg)
             (recur))))

(put! [(.plusSeconds (Instant/now) 3) (.plusSeconds (Instant/now) 5)])
;; currently, I get:
;; Execution error (IllegalArgumentException) at clojure.core.async.impl.protocols/eval2088$fn$G (protocols.clj:18).
;; No implementation of method: :put! of protocol: #'clojure.core.async.impl.protocols/WritePort found for class: chime.core_async$chime_ch$reify__37328

But perhaps there is a way to do this already? (I tried passing a channel to the ch param of chimes-ch and putting times into it, but that didn't work either).

Dependency to clj-time unnecessary

Hi there,

First of all many thanks for this great library ๐Ÿ‘ - much appreciated! Secondly, would you consider getting rid of the clj-time dependency, or at least making it optional (using a require expression as opposed to a :require clause)? From what I can see only joda_time.clj depends on it, and frankly that protocol extension can live in the README, right?

Thanks again :)

Exception thrown in README examples (when using java-time)

Hi all,

When I run the README examples of chime-at with java (8) time in the repl

(require '[chime :refer [chime-at]])  ;; [jarohen/chime "0.2.2"]
(import '[java.time Instant])

(chime-at [(.plusSeconds (Instant/now) 2)
           (.plusSeconds (Instant/now) 4)]
  (fn [time] (println "Chiming at" time)))

I get the following exception:

{:what :uncaught-exception, :exception #error {
 :cause No implementation of method: :to-date-time of protocol: #'clj-time.coerce/ICoerce found for class: java.time.Instant
 :via
 [{:type java.lang.IllegalArgumentException
   :message No implementation of method: :to-date-time of protocol: #'clj-time.coerce/ICoerce found for class: java.time.Instant
   :at [clojure.core$_cache_protocol_fn invokeStatic core_deftype.clj 583]}]
 :trace
 [[clojure.core$_cache_protocol_fn invokeStatic core_deftype.clj 583]
  [clojure.core$_cache_protocol_fn invoke core_deftype.clj 575]
  [clj_time.coerce$eval30076$fn__30077$G__30067__30082 invoke coerce.clj 18]
  [clojure.core$map$fn__5866 invoke core.clj 2753]
  [clojure.lang.LazySeq sval LazySeq.java 42]
  [clojure.lang.LazySeq seq LazySeq.java 51]
  [clojure.lang.RT seq RT.java 535]
  [clojure.core$seq__5402 invokeStatic core.clj 137]
  [clojure.core$drop_while$step__5940 invoke core.clj 2972]
  [clojure.core$drop_while$fn__5943 invoke core.clj 2977]
  [clojure.lang.LazySeq sval LazySeq.java 42]
  [clojure.lang.LazySeq seq LazySeq.java 51]
  [clojure.lang.RT seq RT.java 535]
  [clojure.core$seq__5402 invokeStatic core.clj 137]
  [clojure.core$seq__5402 invoke core.clj 137]
  [chime$chime_ch$fn__30271$state_machine__25597__auto____30286$fn__30288 invoke chime.clj 38]
  [chime$chime_ch$fn__30271$state_machine__25597__auto____30286 invoke chime.clj 38]
  [clojure.core.async.impl.ioc_macros$run_state_machine invokeStatic ioc_macros.clj 973]
  [clojure.core.async.impl.ioc_macros$run_state_machine invoke ioc_macros.clj 972]
  [clojure.core.async.impl.ioc_macros$run_state_machine_wrapped invokeStatic ioc_macros.clj 977]
  [clojure.core.async.impl.ioc_macros$run_state_machine_wrapped invoke ioc_macros.clj 975]
  [chime$chime_ch$fn__30271 invoke chime.clj 38]
  [clojure.lang.AFn run AFn.java 22]
  [java.util.concurrent.ThreadPoolExecutor runWorker ThreadPoolExecutor.java 1149]
  [java.util.concurrent.ThreadPoolExecutor$Worker run ThreadPoolExecutor.java 624]
  [java.lang.Thread run Thread.java 748]]}, :where Uncaught exception onasync-dispatch-4} 

Should I be using clj-time instead? Do the README examples need to be updated?

Thanks

Drop times that have already passed

In one of the examples in the README, it says: "Any times that have already passed will be dropped, as before" and this is the behavior that I would like in my application. However it seems that this is not the case: if I pass a time in the past to chime-at, the function is immediately executed with that time. For example:

(chime/chime-at [(.minus (Instant/now) 1 (ChronoUnit/HOURS))] println)

This prints immediately with the time an hour in the past.

My solution for now is to manually drop input times in the past, but I'm interested to hear whether I'm misunderstanding the example text, or something else is going on.

Error running scheduled fn java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null

3์›” 07, 2021 2:34:40 ์˜ค์ „ chime.core invoke
๊ฒฝ๊ณ : Error running scheduled fn
java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null
at java.base/java.io.StringReader.(StringReader.java:50)
at clojure.data.json$read_str.invokeStatic(json.clj:282)
at clojure.data.json$read_str.doInvoke(json.clj:278)
at clojure.lang.RestFn.invoke(RestFn.java:439)
at tray.pnix.route$jspSQ_loop.invokeStatic(route.clj:151)
at tray.pnix.route$jspSQ_loop.invoke(route.clj:150)
at tray.pnix.tray$START$fn__32985.invoke(tray.clj:52)
at chime.core$chime_at$schedule_loop__27086$task__27090$fn__27091.invoke(core.clj:91)
at chime.core$chime_at$schedule_loop__27086$task__27090.invoke(core.clj:90)
at clojure.lang.AFn.run(AFn.java:22)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:832)
}

periodic-seq example and DST changes

Thank you for this nice little library.

The README has an example of using periodic-seq for local times:

(chime/periodic-seq (-> (LocalTime/of 20 0 0)
                        (.adjustInto (ZonedDateTime/now (ZoneId/of "America/New_York")))
                        .toInstant)
                    (Period/ofDays 1))

I don't get this to work properly across DST changes. It seems to me that this example converts to instant before adding the period, which means that the period is always from UTC and not local time.

This shows the problem:

(binding [chime.core/*clock* (java.time.Clock/fixed (.toInstant #inst "2022-10-28T12:12:12Z") (java.time.ZoneId/of "UTC"))]
  (take 2
        (->> (chime.core/periodic-seq
              (-> (java.time.LocalTime/of 7 30)
                  (.adjustInto (-> (java.time.ZonedDateTime/now chime.core/*clock*)
                                   (.withZoneSameLocal (java.time.ZoneId/of "Europe/Oslo"))))
                  .toInstant)
              (java.time.Period/ofDays 1))
             (chime.core/without-past-times))))

Here we see that the result is the same UTC hour before before and after the recent DST change, which results in an incorrect local time for the second instant:

(#object[java.time.Instant 0x300c7735 "2022-10-29T05:30:00Z"]
 #object[java.time.Instant 0x54e5895a "2022-10-30T05:30:00Z"])

The problem seems to be that periodic-seq works with instants, but that the conversion to instant should instead be the final step. Not yet sure how to solve this in a nice way. A separate function for periodic-local-date-time-seq, and let the caller map to instants?

Or perhaps add an arity to periodic-seq for [LocalDateTime ZoneId duration-or-period] for calculating using LocalDateTimes, while still returning instants? (If so, maybe also include another arity for a final xform for doing complex schedules on the local date times, before returning the instants?)

In the mean time, here's a possible not-so-nice solution:

(binding [chime.core/*clock* (java.time.Clock/fixed (.toInstant #inst "2022-10-28T12:12:12Z") (java.time.ZoneId/of "UTC"))]
  (take 2
        (->> (map #(.toInstant (.atZone % (java.time.ZoneId/of "Europe/Oslo")))
                  (iterate
                   #(.plusDays % 1)
                   (-> (java.time.LocalTime/of 7 30)
                       (.adjustInto (java.time.LocalDateTime/now chime.core/*clock*)))))
             (chime.core/without-past-times))))

Here we see that the instants change as expected after DST change:

(#object[java.time.Instant 0x221b9091 "2022-10-29T05:30:00Z"]
 #object[java.time.Instant 0x49e3eb99 "2022-10-30T06:30:00Z"])

Use of deprecated clj-time function

I'm testing the basic periodic scheduler, and getting the following printed to screen:

Chiming at #<DateTime 2013-12-06T16:15:03.302Z>
DEPRECATION WARNING: DEPRECATED: use in-millis

It appears to relate to https://github.com/clj-time/clj-time#bugs-and-enhancements

Note: version 0.6.0 introduces a number of API changes to improve consistency. The API now uses second, seconds and millis where it previously had sec, secs and msecs.

How to evaluate a function correctly every minute using chime-at?

Here are my tests:

(require '[overtone.at-at :refer :all]
         '[chime :refer [chime-at]]
         '[clj-time.periodic :refer [periodic-seq]]
         '[clj-time.core :as t])

;; 1. Use of future

(defonce data1 (atom {:num 1}))

(defonce updater
  (future
    (while true
      (swap! data1 update-in [:num] inc)
      (Thread/sleep 60000))))


;; 2. Using at-at

(defonce data2 (atom {:num 1}))

(def my-pool (mk-pool))

(every 60000 #(swap! data2 update-in [:num] inc) my-pool)


;; 3. Using chime

(defonce data3 (atom {:num 1}))

(chime-at (periodic-seq (t/now) (-> 60 t/seconds))
          (fn [] (swap! data3 update-in [:num] inc))
          {:error-handler (fn [e] (str e))})

After 5 minutes:

@data1
;;=> {:num 5}
@data2
;;=> {:num 8}
@data3
;;=> {:num 1}

Why is chime not counting at all?

Thank you!

Non determenistic behavior

I have noticed that the schedular is not determenistic, for example using the code shown like so from the repl:

(def s (start 3000))
(s)

And then redo this ~10 times in the repl the following behavior will be observed:
Somethimes "Trigger 0" will be shown right away and sometimes nothing will be shown.

The expected behavior i would like on every restart is that NO value should be shown until after 3000sec after the most current restart.

(ns hello-world.c
  (:require
   [clojure.core.async :as a :refer (<! go-loop)]
   [chime :refer (chime-ch)]
   [clj-time.core :as t]
   [clj-time.periodic :refer (periodic-seq)]
   ))

(defn start
  [keep-alive-period]
  (let [timer-ch (chime-ch (periodic-seq (t/now)
                                         (-> keep-alive-period t/seconds)))]
    (go-loop [i 0]
      (when-let [trigger-time (<! timer-ch)]
        (println "Trigger: " i)
        (recur (inc i))))
    (fn stop-fn [] (a/close! timer-ch))))

Support for times already past in `chime-at`

I'd like to expire independent items (the expiry date is determined ahead of time) from a database, on application startup I schedule all outstanding expiries. In some cases this can mean the expiration date has already passed, which causes this issue:

=> (chime-at [(-> -2 secs from-now)] (fn [t] (println "HEY")))
#<IllegalArgumentException java.lang.IllegalArgumentException: No implementation of method: :before? of protocol: #'clj-time.core/DateTimeProtocol found for class: nil>

Without having to first filter expiries that have already passed (and manually expire them) it would be convenient if passed schedules could be immediately called.

(I'm not sure if there is perhaps a better way of achieving this or if I missed something in the documentation.)

chime-at API changes

Currently the function being called by chime-at just takes the time as argument, and you can just pass one function.

It might be nice if chime-at would allow you to pass the time intervals and either:

  • allow you to pass the function and a list with all the arguments to use at each call
  • allow you to pass just a list of functions, which would solve the above as well I guess if you just do (map #(partial f %) arguments)

This would allow to generate the input for chime-at more functionally.

(def args [["first"] ["second"]])
(defn f [args] ...) 
(chime-at intervals f args)

or just

(def args [["first"] ["second"]])
(defn f [args] ...) 
(def fns (map #(partial f %) args))
(chime-at intervals fns)

On-finished weird behaviours

I can reproduce a behaviour that I think is weird with something like

(chime/chime-at
  [10 20 30]
  (fn [time] (println time))
  {:on-finished (fn [] (println "one") (Thread/sleep 100) (println "two"))})

And what happens is that the one gets printed but two never does.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.