Skip to content

Latest commit

 

History

History
107 lines (86 loc) · 3.13 KB

0195-trivial-timeout.org

File metadata and controls

107 lines (86 loc) · 3.13 KB

trivial-timeout

Today I found that :read-timeout option of the Dexador does not work as expected and remembered about this small but useful library. It provides the only one macro which executes code and limits it’s execution to a given number of seconds.

For illustration, I’ll use https://httpbin.org. This is a service which helps you to test HTTP libraries. If you didn’t hear about it, I recommend to look at.

Let’s retrieve an URL, which responds in 10 seconds. Even with :read-timeout option, dexador waits 10 seconds:

POFTHEDAY> (time
            (nth-value 1
              (dex:get "https://httpbin.org/delay/10"
                       :read-timeout 2)))
Evaluation took:
  10.692 seconds of real time
  
200

If the site is not responding, a request may hang and block your application. Here is where trivial-timeout comes to the rescue!

POFTHEDAY> (trivial-timeout:with-timeout (2)
             (time
              (nth-value 1
                (dex:get "https://httpbin.org/delay/10"))))
Evaluation took:
  2.003 seconds of real time
  before it was aborted by a non-local transfer of control.
  
; Debugger entered on #<COM.METABANG.TRIVIAL-TIMEOUT:TIMEOUT-ERROR {10055B5373}>

Internally, this library generates the implementation-specific code to interrupt the code execution. Here how our example will look like for SBCL:

(let ((seconds 2))
  (flet ((doti ()
           (progn
             (time (nth-value 1
                     (dexador:get "https://httpbin.org/delay/10"))))))
    (cond
      (seconds
       (handler-case
           (sb-ext:with-timeout seconds
             (doti))
         (sb-ext:timeout (com.metabang.trivial-timeout::c)
           (declare (ignore com.metabang.trivial-timeout::c))
           (error 'com.metabang.trivial-timeout:timeout-error))))
      (t (doti)))))

And this is the same code, expanded on ClozureCL:

(let ((seconds 2))
  (flet ((doit nil
           (progn (time (nth-value 1
                          (dexador:get "https://httpbin.org/delay/10"))))))
    (cond (seconds
           (let* ((semaphore (ccl:make-semaphore))
                  (result)
                  (process
                    (ccl:process-run-function
                     "Timed Process process"
                     (lambda nil
                       (setf result
                             (multiple-value-list (doit)))
                       (ccl:signal-semaphore semaphore)))))
             (cond ((ccl:timed-wait-on-semaphore
                     semaphore
                     seconds)
                    (values-list result))
                   (t
                    (ccl:process-kill process)
                    (error 'com.metabang.trivial-timeout:timeout-error)))))
          (t (doit)))))

Don’t know if such running the code in the separate thread can have some side-effects. At least, library’s README says that it might be dangerous :)))