clojure (with-timeout … macro)

前端 未结 3 717
-上瘾入骨i
-上瘾入骨i 2021-02-09 01:50

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

相关标签:
3条回答
  • 2021-02-09 02:39

    This question has better answers here: Executing a function with a timeout

    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
    
    0 讨论(0)
  • 2021-02-09 02:48

    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...

    0 讨论(0)
  • 2021-02-09 02:48

    I came across this thread recently while asking the same question. I wasn't completely satisfied with the answers given so I cobbled together an alternative solution. This solution will run your code in the current thread and spin of a future to interrupt it after a set timeout in ms.

    (defn invoke-timeout [f timeout-ms]
      (let [thr (Thread/currentThread)
            fut (future (Thread/sleep timeout-ms)
                        (.interrupt thr))]
        (try (f)
             (catch InterruptedException e
               (throw (TimeoutException. "Execution timed out!")))
             (finally
               (future-cancel fut)))))
    
    (defmacro timeout [ms & body] `(invoke-timeout (fn [] ~@body) ~ms))
    

    You would use it in your code like this:

    (timeout 1000 your-code)
    

    OR

    (invoke-timeout #(your-code) 1000)
    

    One caveat to keep in mind is that your-code must not catch the InterruptedException used to trigger the TimeoutException. I use this for testing and it works well.

    See the Thread.interrupt() javadoc for additional caveats.

    You can see this code in use here.

    0 讨论(0)
提交回复
热议问题