In the general case, side effects within the swap! function are a bad idea
because of the optimistic locking. In your first code snippet, if there is
any contention on the atom (and maybe in your app you know there is none
because it's only ever accesses by the same single thread), you run the
risk of having orphaned futures.

As far as I know there should be no such problem with the agent version.
I'm not really sure about the nesting of send-off calls though; that might
be the source of your stack overflow. I seem to remember that this was not
supported up until 1.4 or 1.5; not sure what the current semantics is.

Depending on how many different event types you're watching for (and how
many differet actions you need to take), it might be worth having a single
thread managing the queue. Somethin along the line of having a single atom
containing a priority queue (or a sorted map?) with, for each event type,
the last time the event was observed. At some interval, that event thread
could check the queue and run the required handlers based on the current
time. When an event arrives, it resets the time associated to its type in
the queue.

Whether this is better will depend on your usage pattern. I would just like
to point out that creating a future has some non trivial overhead as it
also creates a thread (at least, the last time I checked, futures where not
created out of a limited thread pool).

On Tuesday, 2 December 2014, Erik Price <e...@zensight.co> wrote:

> Coincidentally, we recently wrote code to do something very similar. The
> following function will invoke f after period milliseconds, unless a
> value is sent on events-ch, in which case the timeout is reset (and
> starts counting down again):
>
> (defn invoke-after-uninterrupted-delay
>   ([period events-ch f]
>     (invoke-after-uninterrupted-delay period events-ch f []))
>   ([period events-ch f & args]
>     (async/go-loop []
>       (let [[_ p] (async/alts! [(async/timeout period) events-ch])]
>         (if (= p events-ch)
>           (recur)
>           (apply f args))))))
>
> e
> ​
>
> On Mon, Dec 1, 2014 at 6:50 PM, Brian Craft <craft.br...@gmail.com
> <javascript:_e(%7B%7D,'cvml','craft.br...@gmail.com');>> wrote:
>
>> That version has the unfortunate behavior that (func) can be interrupted
>> if (event) is called while it is running. Here's another version using an
>> agent:
>>
>> (defn queue-with-delay2 [period func]
>>   (let [q (agent nil)]
>>     (fn []
>>       (send-off q (fn [t]
>>                     (when t
>>                       (future-cancel t))
>>                     (future (Thread/sleep period) (send-off q (fn [_]
>> (func) nil))))))))
>>
>> Running with a sleep to see that (func) is not canceled by subsequence
>> (event) calls:
>>
>> (def event (queue-with-delay2 2000 #(do (println "running") (Thread/sleep
>> 2000) (println "ending"))))
>>
>> Oddly, if calling (event) between "running" and "ending" messages, the
>> repl will stack-overflow on the return value. No idea what that's about.
>> But, running like this is fine:
>>
>> (do (event) nil)
>>
>>
>>
>>
>>
>> On Monday, December 1, 2014 1:37:56 PM UTC-8, Brian Craft wrote:
>>>
>>> I have need to perform an action when a series of events is quiet for
>>> some period. That is, if one event arrives an action is queued to execute
>>> after some timeout. If a second event arrives the timeout is reset, and
>>> so-forth.
>>>
>>> The following code seems to work, however I'm wondering if calling
>>> 'future' from 'swap!' is a bad idea (side effecting), and if there's a
>>> better way.
>>>
>>> (defn queue-with-delay [period func]
>>>   (let [f (atom nil)]
>>>     (fn []
>>>       (when @f
>>>         (future-cancel @f))
>>>       (swap! f (fn [_] (future (Thread/sleep period) (func)))))))
>>>
>>>
>>> Use like
>>>
>>> (def event (queue-with-delay 2000 #(println "running")))
>>> (event)
>>> (event)
>>> (event)  ; pause 2 sec
>>> "running"
>>>
>>>
>>>
>>>  --
>> You received this message because you are subscribed to the Google
>> Groups "Clojure" group.
>> To post to this group, send email to clojure@googlegroups.com
>> <javascript:_e(%7B%7D,'cvml','clojure@googlegroups.com');>
>> Note that posts from new members are moderated - please be patient with
>> your first post.
>> To unsubscribe from this group, send email to
>> clojure+unsubscr...@googlegroups.com
>> <javascript:_e(%7B%7D,'cvml','clojure%2bunsubscr...@googlegroups.com');>
>> For more options, visit this group at
>> http://groups.google.com/group/clojure?hl=en
>> ---
>> You received this message because you are subscribed to the Google Groups
>> "Clojure" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to clojure+unsubscr...@googlegroups.com
>> <javascript:_e(%7B%7D,'cvml','clojure%2bunsubscr...@googlegroups.com');>.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>  --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> <javascript:_e(%7B%7D,'cvml','clojure@googlegroups.com');>
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> <javascript:_e(%7B%7D,'cvml','clojure%2bunsubscr...@googlegroups.com');>
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to clojure+unsubscr...@googlegroups.com
> <javascript:_e(%7B%7D,'cvml','clojure%2bunsubscr...@googlegroups.com');>.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to