19

What would be an idiomatic way of executing a function within a time limit? Something like,

(with-timeout 5000
 (do-somthing))

Unless do-something returns within 5000 throw an exception or return nil.

EDIT: before someone points it out there is,

clojure (with-timeout ... macro)

but with that the future keeps executing that does not work in my case.

1
  • Hamza Yerlikaya asks Clojure questions. Still sounds weird after 3-4 years. Selamlar :) Commented Feb 3, 2015 at 7:38

6 Answers 6

18

I think you can do this reasonably reliably by using the timeout capability within futures:

  (defmacro with-timeout [millis & body]
    `(let [future# (future ~@body)]
      (try
        (.get future# ~millis java.util.concurrent.TimeUnit/MILLISECONDS)
        (catch java.util.concurrent.TimeoutException x# 
          (do
            (future-cancel future#)
            nil)))))

A bit of experimenting verified that you need to do a future-cancel to stop the future thread from continuing to execute....

Sign up to request clarification or add additional context in comments.

Comments

17

What about?

    (defn timeout [timeout-ms callback]
     (let [fut (future (callback))
           ret (deref fut timeout-ms ::timed-out)]
       (when (= ret ::timed-out)
         (future-cancel fut))
       ret))

    (timeout 100 #(Thread/sleep 1000))

    ;=> :user/timed-out

1 Comment

This should be the accepted answer - no dependencies, no macros :)
14

This isn't something you can do 100% reliably on the JVM. The only way to stop something after a while is to give it a new thread, and then send that thread an exception when you want it to stop. But their code can catch the exception, or they can spin up another thread that you don't control, or...

But most of the time, and especially if you control the code that's being timed out, you can do something like we do in clojail:

If you wanted to make that prettier you could define a macro like

(defmacro with-timeout [time & body]
  `(thunk-timeout (fn [] ~@body) ~time))

3 Comments

works great, in my case code executed belongs to me so I do not thing I am gonna have trouble.
Link above is dead. Maybe more up to date: github.com/flatland/clojail/blob/master/src/clojail/…
Hah, probably over a year out of date. Thanks, I'll fix it up.
6

It's a quite a breeze using clojure's channel facilities https://github.com/clojure/core.async

require respective namespace

(:require [clojure.core.async :refer [>! alts!! timeout chan go]])

the function wait takes a timeout [ms], a function [f] and optional parameters [args]

(defn wait [ms f & args]
  (let [c (chan)]
    (go (>! c (apply f args)))
    (first (alts!! [c (timeout ms)]))))

third line pops off the call to f to another thread. fourth line consumes the result of the function call or (if faster) the timeout.

consider the following example calls

(wait 1000 (fn [] (do (Thread/sleep 100) 2)))
=> 2

but

(wait 50 (fn [] (do (Thread/sleep 100) 2)))
=> nil

2 Comments

i dont understand why this (old) answer isn't upvoted more. I came to this thread looking for some builtin that is easier than the core.async way (which i normally use). But all the answers with more votes seem like more hassle to me.
@mat_dw one reason this might not be the most upvoted is that the core.async version here does not actually cancel the execution of f. If f is a long running function which drains resources, it will continue to execute even though the calling thread has returned
0

You can probably use an agent, and then await-for it.

1 Comment

The agent can still hang around for ever. This is why await-for has a return value.
-2

Adding a possible (macro-less) alternative to the mix (though the macro isn't required in the accepted answer of course)

(defn with-timeout [f ms]
  (let [p (promise)
        h (future
            (deliver p (f)))
        t (future
            (Thread/sleep ms)
            (future-cancel h)
            (deliver p nil))]
    @p))

Requires two threads, but just an idea.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.