idiomatic terminating infinite loops

147 views
Skip to first unread message

Colin Yates

unread,
May 15, 2013, 8:29:06 AM5/15/13
to clo...@googlegroups.com
Hi all,

I have a scheduler which creates a future that basically does a (while true (let [next-job (.take queue)]...)), where queue is a LinkedBlockingQueue.  The problem is that once it is running, because futures aren't daemon threads it hangs lein.  It will ultimately run inside a compojure web app where, whist it doesn't stop the web server being shut down, it should do it more elegantly by explicitly being told to shut down.

It is necessarily a singleton because it is sized to allow the maximum amount of concurrency.  Starting up more than one scheduler would swamp the system.

What is the idiomatic way of managing this?

In terms of accessing the singleton I could:
 - use a root level *binding* which smells of global state
 - pass in the instance to the consumers of the scheduler.  Fine, but that means threading it through from the bootstrapping code all the way down the call stack to the function that needs it.
 - add a (get-scheduler) accessor in the scheduler ns

In terms of controlling the lifecycle I could:
 - add a  "with-scheduler" construct which starts it, delegates to the delegate in a try/finally and then closes it in the finally clause.  Given that this is infrastructural and will be used in lots of places I would need to wrap the whole application inside with-scheduler.
 - add (start-engine) and (stop-engine) functions in the scheduler ns which are then called from the relevant parts in the Compojure/testing framework lifecycle

I could also not use a future and use a deamon thread instead but this feels like I am working against Clojure a little.  It also side steps this question which I want to resolve.

I am trying to separate the 'lifecycle' concerns from the 'getting hold of it' concern as they are orthogonal I think.  the with-scheduler pattern seems to combine both concerns.

I am currently deciding between:
 - expose a (start-engine) which delegates to an internal (defonce).  This is called on compojure's start up
 - expose an (get-engine) in the scheduler namespace which delegates to an internal (defonce)
 - expose a (shutdown) function/shutdown protocol method in the scheduler namespace which is called on Compojure's shut down
 - create a test utility (def with-scheduler [delegate] ...) or use pre/post setup hooks which manages the lifecycle of the scheduler

or

 - expose a (with-scheduler) which is integrated into Compojure's lifecycle such that it wraps Compojure's lifecycle.  Not sure this is even possible.
 - expose a (get-engine) in the scheduler namespace which delegates to an internal (defonce)
 - use the (with-scheduler) for testing

Any and all advice is welcome.

Thanks,

Col

Gary Trakhman

unread,
May 15, 2013, 5:34:01 PM5/15/13
to clo...@googlegroups.com
A couple of approaches:
1. Stop using compojure's 'defroutes'.  Generate routes via 'routes' that close over your service.  Try not to do anything that would make this expensive.
2. Pass it along on a request map via some middleware.

The relevant principle: lifecycle of your component and usage shouldn't be coupled.

You said it well: 'I am trying to separate the 'lifecycle' concerns from the 'getting hold of it' concern as they are orthogonal I think.'

Dependency injection is all about handing things what they need instead of having them reach out and get it.


--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Stuart Sierra

unread,
May 15, 2013, 6:38:25 PM5/15/13
to clo...@googlegroups.com
Colin Yates wrote:
> I have a scheduler which creates a future that basically
> does a (while true (let [next-job (.take queue)]...)),
> where queue is a LinkedBlockingQueue. The problem is that
> once it is running, because futures aren't daemon threads
> it hangs lein.

Instead of .take, you can use .poll with a timeout, then
have your loop check if it's been told to shut down, e.g. by
setting an Atom.

Alternately, inject a "poison" message into the queue that
tells the loop to shut down.

My preferred approach to handling run-time services like
this is to inject them into the app when it starts, thereby
avoiding global Vars.

-S

Colin Yates

unread,
May 16, 2013, 4:53:38 AM5/16/13
to clo...@googlegroups.com

Thanks both, good suggestions. 

--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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 a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/D4brGvqreSo/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages