restore old behaviour as the default for package variance
[sbcl.git] / src / code / deadline.lisp
1 ;;;; global deadlines for blocking functions: a threadsafe alternative
2 ;;;; to asynch timeouts
3
4 ;;;; This software is part of the SBCL system. See the README file for
5 ;;;; more information.
6 ;;;;
7 ;;;; This software is derived from the CMU CL system, which was
8 ;;;; written at Carnegie Mellon University and released into the
9 ;;;; public domain. The software is in the public domain and is
10 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
11 ;;;; files for more information.
12
13 (in-package "SB!IMPL")
14
15 (!begin-collecting-cold-init-forms)
16
17 ;;; Current deadline as internal time units or NIL.
18 (declaim (type (or unsigned-byte null) *deadline*))
19 (defvar *deadline*)
20 (!cold-init-forms (setq *deadline* nil))
21
22 ;;; The relative number of seconds the current deadline corresponds
23 ;;; to. Used for continuing from TIMEOUT conditions.
24 (defvar *deadline-seconds*)
25 (!cold-init-forms (setq *deadline-seconds* nil))
26
27 (declaim (inline seconds-to-internal-time))
28 (defun seconds-to-internal-time (seconds)
29   (truncate (* seconds sb!xc:internal-time-units-per-second)))
30
31 (defmacro with-deadline ((&key seconds override)
32                          &body body)
33   "Arranges for a TIMEOUT condition to be signalled if an operation
34 respecting deadlines occurs either after the deadline has passed, or
35 would take longer than the time left to complete.
36
37 Currently only blocking IO operations, GET-MUTEX, and CONDITION-WAIT
38 respect deadlines, but this includes their implicit uses inside SBCL
39 itself.
40
41 Unless OVERRIDE is true, existing deadlines can only be restricted,
42 not extended. Deadlines are per thread: children are unaffected by
43 their parent's deadlines.
44
45 Experimental."
46   (with-unique-names (tmp deadline-seconds deadline)
47     ;; We're operating on a millisecond precision, so a single-float
48     ;; is enough, and is an immediate on 64bit platforms.
49     `(let* ((,tmp ,seconds)
50             (,deadline-seconds
51               (when ,tmp
52                 (coerce ,tmp 'single-float)))
53             (,deadline
54               (when ,deadline-seconds
55                 (+ (seconds-to-internal-time ,deadline-seconds)
56                    (get-internal-real-time)))))
57        (multiple-value-bind (*deadline* *deadline-seconds*)
58            (if ,override
59                (values ,deadline ,deadline-seconds)
60                (let ((old *deadline*))
61                  (if (and old (or (not ,deadline) (< old ,deadline)))
62                      (values old *deadline-seconds*)
63                      (values ,deadline ,deadline-seconds))))
64          ,@body))))
65
66 (declaim (inline decode-internal-time))
67 (defun decode-internal-time (time)
68   #!+sb-doc
69   "Returns internal time value TIME decoded into seconds and microseconds."
70   (multiple-value-bind (sec frac)
71       (truncate time sb!xc:internal-time-units-per-second)
72     (values sec (* frac sb!unix::micro-seconds-per-internal-time-unit))))
73
74 (defun signal-timeout (datum &rest arguments)
75   #!+sb-doc
76   "Signals a timeout condition while inhibiting further timeouts due to
77 deadlines while the condition is being handled."
78   ;; FIXME: Maybe we should make ERROR do WITH-INTERRUPTS instead of
79   ;; putting it all over the place (now that we have ALLOW-WITH-INTERRUPTS.)
80   (with-interrupts
81     ;; Don't signal a deadline while handling a non-deadline timeout.
82     (let ((*deadline* nil))
83       (apply #'error datum arguments))))
84
85 (defun signal-deadline ()
86   #!+sb-doc
87   "Signal a DEADLINE-TIMEOUT condition, and associate a DEFER-DEADLINE
88 restart with it. Implementors of blocking functions are responsible
89 for calling this when a deadline is reached."
90   ;; Make sure we don't signal the same deadline twice. LET is not good
91   ;; enough: we might catch the same deadline again while unwinding.
92   (when *deadline*
93     (setf *deadline* nil))
94   (with-interrupts
95     (restart-case
96         (error 'deadline-timeout :seconds *deadline-seconds*)
97       (defer-deadline (&optional (seconds *deadline-seconds*))
98         :report "Defer the deadline for SECONDS more."
99         :interactive (lambda ()
100                        (sb!int:read-evaluated-form
101                         "By how many seconds shall the deadline ~
102                          be deferred?: "))
103         (let* ((new-deadline-seconds (coerce seconds 'single-float))
104                (new-deadline (+ (seconds-to-internal-time new-deadline-seconds)
105                                 (get-internal-real-time))))
106           (setf *deadline* new-deadline
107                 *deadline-seconds* new-deadline-seconds)))
108       (cancel-deadline ()
109         :report "Cancel the deadline and continue."
110         (setf *deadline* nil *deadline-seconds* nil))))
111   nil)
112
113 (defun defer-deadline (seconds &optional condition)
114   "Find the DEFER-DEADLINE restart associated with CONDITION, and
115 invoke it with SECONDS as argument (deferring the deadline by that many
116 seconds.) Otherwise return NIL if the restart is not found."
117   (try-restart 'defer-deadline condition seconds))
118
119 (defun cancel-deadline (&optional condition)
120   "Find and invoke the CANCEL-DEADLINE restart associated with
121 CONDITION, or return NIL if the restart is not found."
122   (try-restart 'cancel-deadline condition))
123
124 (declaim (inline relative-decoded-times))
125 (defun relative-decoded-times (abs-sec abs-usec)
126   #!+sb-doc
127   "Returns relative decoded times: difference between SEC and USEC and
128 current real time."
129   (multiple-value-bind (now-sec now-usec)
130       (decode-internal-time (get-internal-real-time))
131     (let ((rel-sec (- abs-sec now-sec)))
132       (cond ((> now-usec abs-usec)
133              (values (max 0 (1- rel-sec))
134                      (- (+ abs-usec 1000000) now-usec)))
135             (t
136              (values (max 0 rel-sec)
137                      (- abs-usec now-usec)))))))
138
139 ;;; Returns TIMEOUT-SEC, TIMEOUT-USEC, DEADLINE-SEC, DEADLINE-USEC, SIGNALP
140 ;;;
141 ;;; Takes *DEADLINE* into account: if it occurs before given SECONDS,
142 ;;; the values are based on it, and DEADLINEP is true -- and the
143 ;;; receipent of the values should call SIGNAL-TIMEOUT if the decoded
144 ;;; timeout is reached.
145 ;;;
146 ;;; If SECONDS is NIL and there is no *DEADLINE* all returned values
147 ;;; are NIL.
148 (defun decode-timeout (seconds)
149   #!+sb-doc
150   "Decodes a relative timeout in SECONDS into five values, taking any
151 global deadlines into account: TO-SEC, TO-USEC, STOP-SEC, STOP-USEC,
152 DEADLINEP.
153
154 TO-SEC and TO-USEC indicate the relative timeout in seconds and microsconds.
155 STOP-SEC and STOP-USEC indicate the absolute timeout in seconds and
156 microseconds. DEADLINEP is true if the returned values reflect a global
157 deadline instead of the local timeout indicated by SECONDS.
158
159 If SECONDS is null and there is no global timeout all returned values will be
160 null. If a global deadline has already passed when DECODE-TIMEOUT is called,
161 it will signal a timeout condition."
162   (tagbody
163    :restart
164      (let* ((timeout (when seconds (seconds-to-internal-time seconds)))
165             (now (get-internal-real-time))
166             (deadline *deadline*)
167             (deadline-timeout
168              (when deadline
169                (let ((time-left (- deadline now)))
170                  (if (plusp time-left)
171                      time-left
172                      (progn
173                        (signal-deadline)
174                        (go :restart)))))))
175        (return-from decode-timeout
176          (multiple-value-bind (final-timeout final-deadline signalp)
177              ;; Use either *DEADLINE* or TIMEOUT to produce both a timeout
178              ;; and deadline in internal-time units
179              (cond ((and deadline timeout)
180                     (if (< timeout deadline-timeout)
181                         (values timeout (+ timeout now) nil)
182                         (values deadline-timeout deadline t)))
183                    (deadline
184                     (values deadline-timeout deadline t))
185                    (timeout
186                     (values timeout (+ timeout now) nil))
187                    (t
188                     (values nil nil nil)))
189            (if final-timeout
190                (multiple-value-bind (to-sec to-usec)
191                    (decode-internal-time final-timeout)
192                  (multiple-value-bind (stop-sec stop-usec)
193                      (decode-internal-time final-deadline)
194                    (values (max 0 to-sec) (max 0 to-usec) stop-sec stop-usec signalp)))
195                (values nil nil nil nil nil)))))))
196
197 (!defun-from-collected-cold-init-forms !deadline-cold-init)