tags:

views:

376

answers:

3

I'm looking for a macro that will throw an exception if an expression takes longer than X seconds to complete.

+1  A: 

I'm not sure this is possible without running the expression in a separate thread. The reason being, if the thread is busy processing the expression, you can't inject code to throw an exception.

A version with a monitor thread that throws an exception if the expression takes too long is definitely possible, however, the exception thrown would be from the monitor thread, not the thread in which the expression is running. Then, there'd be no way of stopping it short of sending that thread an interrupt, which it might ignore if you haven't coded for it in the expression.

If it's acceptable to have a version which runs the expression in a separate thread, let me know and I can post some sample code. Otherwise, your best bet sound like it would be to write the main loop/recursion of the expression in such a way that it checks to see how long it has taken at every iteration and throws an exception if it's exceeded the bound. Sorry if that's not quite what you need...

levand
Maybe the (with-timeout... could act like loop, and be a recur point, and every time you hit the (with-timeout it checks timer and stops execution (or throws an exception) if it's been running to long. But that usage would need to be *within* a function, not wrapping the function. But isn't causing side effects to other functions generally discouraged [I'm thinking of all the warnings around (binding ]?
nilamo
+14  A: 

Futures to the rescue!

user=> (let [f (future (reduce * (range 1 1001)))]
  (.get f 1 java.util.concurrent.TimeUnit/MILLISECONDS))
java.util.concurrent.TimeoutException (NO_SOURCE_FILE:0)

And to make a macro of it:

(defmacro time-limited [ms & body]
  `(let [f# (future ~@body)]
     (.get f# ~ms java.util.concurrent.TimeUnit/MILLISECONDS)))

So you can do this:

user=> (time-limited 1 (reduce * (range 1 1001)))
java.util.concurrent.TimeoutException (NO_SOURCE_FILE:0)
user=> (time-limited 1 (reduce * (range 1 101)))
93326215443944152681699238856266700490715968264381621468592963895217599993229915
608941463976156518286253697920827223758251185210916864000000000000000000000000
Timothy Pratley
Just to be clear - future definitely does work, but it will run the provided expr in a separate thread. Most the time this is no problem, particularly if you're writing referentially transparent code, but if you're using (binding ...) it could definitely cause some problems.
levand
A: 

(try (time-limited 1 (reduce * (range 1 100000))) (catch java.util.concurrent.TimeoutException _ nil))

I tried above code. Does the execution of (reduce * (range 1 100000)) stop after returning nil? CPU usage by JVM remains very high even after the return.

nipra