aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxime Devos <maximedevos@telenet.be>2021-05-24 18:07:44 +0200
committerMaxime Devos <maximedevos@telenet.be>2021-09-21 12:08:41 +0200
commit08d98c025e7f50d1c6bafd94dfadd2c384fe8260 (patch)
treef3b9053612de1b17d0c7f192c2a8d1ba4e5137ea
parent8f041a1762721dd25dcf1959d04816b8f8d3974a (diff)
downloadgnunet-scheme-08d98c025e7f50d1c6bafd94dfadd2c384fe8260.tar.gz
gnunet-scheme-08d98c025e7f50d1c6bafd94dfadd2c384fe8260.zip
mq: Define envelope data type, again.
The new envelope data type can be used without fibers or multi-threading. * Makefile.am (modules): Remove replaced gnu/gnunet/message/envelope.scm. (%.go: %.scm): Do not unset GUILE_LOAD_COMPILED_PATH as that would interfere with guile-pfds. * README.org (Modules): Remove the obsolete gnu/gnunet/message/envelope.scm. (Message queues): Document new envelope module. Adjust message queue blurb for the future. * gnu/gnunet/mq/envelope.scm: Define new envelope module. * gnu/gnunet/message/envelope.scm: Delete. * tests/envelope.scm: Test the new envelope module.
-rw-r--r--Makefile.am8
-rw-r--r--README.org22
-rw-r--r--gnu/gnunet/message/envelope.scm139
-rw-r--r--gnu/gnunet/mq/envelope.scm195
-rw-r--r--guix.scm4
-rw-r--r--tests/envelope.scm468
6 files changed, 571 insertions, 265 deletions
diff --git a/Makefile.am b/Makefile.am
index bf1f3a8..51273bf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,10 +34,10 @@ modules = \
34 gnu/gnunet/scripts/guix-stuff.scm \ 34 gnu/gnunet/scripts/guix-stuff.scm \
35 \ 35 \
36 gnu/gnunet/message/protocols.scm \ 36 gnu/gnunet/message/protocols.scm \
37 gnu/gnunet/message/envelope.scm \
38 \ 37 \
39 gnu/gnunet/concurrency/update.scm \ 38 gnu/gnunet/concurrency/update.scm \
40 \ 39 \
40 gnu/gnunet/mq/envelope.scm \
41 gnu/gnunet/mq/handler.scm \ 41 gnu/gnunet/mq/handler.scm \
42 gnu/gnunet/mq/prio-prefs.scm \ 42 gnu/gnunet/mq/prio-prefs.scm \
43 gnu/gnunet/mq/prio-prefs2.scm \ 43 gnu/gnunet/mq/prio-prefs2.scm \
@@ -70,14 +70,14 @@ dist_guilesite_DATA = $(modules)
70if HAVE_GUILD 70if HAVE_GUILD
71nodist_guilesiteccache_DATA = $(modules:%.scm=%.go) 71nodist_guilesiteccache_DATA = $(modules:%.scm=%.go)
72 72
73# Unset 'GUILE_LOAD_COMPILED_PATH' so we can be sure that any .go file that we 73# Do not unset 'GUILE_LOAD_COMPILED_PATH', as guile-pfds as installed
74# load comes from the build directory. 74# in Guix does not have .scm files (but it does in Guile).
75# XXX: Use the C locale for when Guile lacks 75# XXX: Use the C locale for when Guile lacks
76# <https://git.sv.gnu.org/cgit/guile.git/commit/?h=stable-2.0&id=e2c6bf3866d1186c60bacfbd4fe5037087ee5e3f>. 76# <https://git.sv.gnu.org/cgit/guile.git/commit/?h=stable-2.0&id=e2c6bf3866d1186c60bacfbd4fe5037087ee5e3f>.
77%.go: %.scm 77%.go: %.scm
78 $(AM_V_GUILEC)$(MKDIR_P) "`dirname "$@"`" ; \ 78 $(AM_V_GUILEC)$(MKDIR_P) "`dirname "$@"`" ; \
79 $(AM_V_P) && out=1 || out=- ; \ 79 $(AM_V_P) && out=1 || out=- ; \
80 unset GUILE_LOAD_COMPILED_PATH ; LC_ALL=C \ 80 LC_ALL=C \
81 builddir="$(top_builddir)" \ 81 builddir="$(top_builddir)" \
82 GUILE_AUTO_COMPILE=0 \ 82 GUILE_AUTO_COMPILE=0 \
83 $(GUILD) compile --target="$(host)" \ 83 $(GUILD) compile --target="$(host)" \
diff --git a/README.org b/README.org
index f6e8108..d2a6979 100644
--- a/README.org
+++ b/README.org
@@ -45,8 +45,6 @@
45 + a nice Scheme interface to GNUnet! 45 + a nice Scheme interface to GNUnet!
46* Modules 46* Modules
47 + gnu/gnunet/directory.scm: directory construction 47 + gnu/gnunet/directory.scm: directory construction
48 + gnu/gnunet/message/envelope.scm: some program data around
49 message types (e.g. priority, notify-on-sent)
50 + gnu/gnunet/concurrency/update.scm: a box with a value, 48 + gnu/gnunet/concurrency/update.scm: a box with a value,
51 that can be updated, resulting in a new box. Updates 49 that can be updated, resulting in a new box. Updates
52 can be waited upon. 50 can be waited upon.
@@ -63,11 +61,18 @@
63 the ‘good’ tag. 61 the ‘good’ tag.
64 62
65** Message queues :spec: 63** Message queues :spec:
66 Message queues have three parts: the input queue, the output
67 queue and the transport, that are respectively a read+close request
68 capability, a write+close request capability and a capability
69 for all the previous, reacting to a close request and injecting errors.
70 64
65 Message queues have a handler for normal incoming messages and for errors.
66 If a transport receives an incoming message, it should add it (‘inject’)
67 to the incoming messages, which may result in a message handler being
68 called. The user of the queue can also try to cancel sending a message
69 and will receive a notification when the message cannot be unsent anymore.
70
71 Message queues can be used concurrently. (TODO destruction)
72
73 + gnu/gnunet/mq/envelope.scm: a wrapper around a message, with a callback
74 for cancelling the sending of the message (if not too late) and a callback
75 for notifying it cannot be unsent anymore.
71 + gnu/gnunet/mq/prio-prefs.scm: message priorities & preferences 76 + gnu/gnunet/mq/prio-prefs.scm: message priorities & preferences
72 77
73 Preferences: is out-of-order allowed or not, 78 Preferences: is out-of-order allowed or not,
@@ -78,8 +83,9 @@
78 Different message types may need need different 83 Different message types may need need different
79 capabilities; the interposition can be used to adjust 84 capabilities; the interposition can be used to adjust
80 the ambient authority appropriately. 85 the ambient authority appropriately.
81 + gnu/gnunet/mq/message-io.scm: like soft ports, but using 86 + gnu/gnunet/mq/message-io.scm: SCRAPPED
82 fibers channels and for messages. 87 + gnu/gnunet/mq.scm: the message queue itself!
88
83 + TODO actual queues? Maybe we don't need them? 89 + TODO actual queues? Maybe we don't need them?
84 + TODO filling the queues 90 + TODO filling the queues
85** Configuration :test:good: 91** Configuration :test:good:
diff --git a/gnu/gnunet/message/envelope.scm b/gnu/gnunet/message/envelope.scm
deleted file mode 100644
index 2849787..0000000
--- a/gnu/gnunet/message/envelope.scm
+++ /dev/null
@@ -1,139 +0,0 @@
1;; This file is part of scheme-GNUnet.
2;; Copyright (C) 2012-2019 GNUnet e.V.
3;; Copyright (C) 2021 Maxime Devos
4;;
5;; scheme-GNUnet is free software: you can redistribute it and/or modify it
6;; under the terms of the GNU Affero General Public License as published
7;; by the Free Software Foundation, either version 3 of the License,
8;; or (at your option) any later version.
9;;
10;; scheme-GNUnet is distributed in the hope that it will be useful, but
11;; WITHOUT ANY WARRANTY; without even the implied warranty of
12;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13;; Affero General Public License for more details.
14;;
15;; You should have received a copy of the GNU Affero General Public License
16;; along with this program. If not, see <http://www.gnu.org/licenses/>.
17;;
18;; SPDX-License-Identifier: AGPL3.0-or-later
19
20;; Upstream GNUnet:
21;; @author Florian Dold
22;; @file util/mq.c
23;; @brief general purpose request queue
24;;
25;; Scheme-GNUnet:
26;; @author Maxime Devos
27;; @file gnu/gnunet/message/envelope.scm
28
29(define-library (gnu gnunet message envelope)
30 (export <envelope> make-envelope envelope?
31 <envelope/dll> make-envelope/dll envelope/dll?
32 envelope-message-slice envelope-priority
33 notify-sent! wait-sent-operation
34 envelope-dll-check envelope-dll
35 &double-notify-sent make-double-notify-sent
36 double-notify-sent?)
37 (import (gnu gnunet utils bv-slice)
38 (srfi srfi-26)
39 (rnrs base)
40 (ice-9 optargs)
41 (rnrs records syntactic)
42 (only (rnrs exceptions)
43 raise)
44 (only (fibers conditions)
45 make-condition
46 signal-condition!
47 wait-operation)
48 (only (rnrs conditions)
49 condition &violation
50 make-who-condition
51 make-message-condition
52 define-condition-type))
53 (begin
54 (define-record-type (<envelope> make-envelope envelope?)
55 (fields (immutable message-slice envelope-message-slice)
56 (immutable message-prio envelope-priority)
57 ;; Signalled when the message has been sent.
58 (immutable message-sent envelope-message-sent-condition))
59 (protocol
60 (lambda (%make)
61 (lambda* (mh #:key (priority 0))
62 "Make a fresh message envelope for the message @var{mh}
63(a readable bytevector slice) and priority @var{priority}
64(a numeric value from @code{gnu gnunet util mq-enum})."
65 ;; FIXME also check if @var{mh} is large enough?
66 (assert (slice-readable? mh))
67 (assert (and (integer? priority)
68 (exact? priority)
69 (<= 0 priority)
70 ;; XXX magic number
71 (< priority 512)))
72 (%make mh priority (make-condition)))))
73 (sealed #f)
74 (opaque #t))
75
76 (define-condition-type &double-notify-sent &violation
77 make-double-notify-sent double-notify-sent?)
78
79 ;; See notify-sent!.
80 (define (notify-sent-condition)
81 (condition (make-double-notify-sent)
82 (make-who-condition 'notify-sent!)
83 (make-message-condition
84 "notify-sent! was called twice on same envelope")))
85
86 (define (notify-sent! ev)
87 "Mark the envelope @var{ev} as sent.
88
89This is the responsibility of the transport.
90
91Conceptually, this should only be available to the message
92transport, but the only other potential user would be
93the message sender, so this shouldn't matter.
94
95It is an error to call more than one. Currently,
96an appropriate @code{&double-notify-sent} is raised."
97 (if (signal-condition! (envelope-message-sent-condition ev))
98 (values)
99 (raise (notify-sent-condition))))
100
101 (define (wait-sent-operation ev)
102 "Return an operation for waiting until the envelope
103@var{ev} has been sent. If the message has already been sent,
104this operation returns immediately."
105 (wait-operation (envelope-message-sent-condition ev)))
106
107 ;; XXX I doubt this will see any use.
108 (define-record-type (<envelope/dll> make-envelope/dll envelope/dll?)
109 (fields (immutable capability %envelope/dll-capability)
110 (mutable left %envelope/dll-previous
111 %set-envelope/dll-previous!)
112 (mutable right %envelope/dll-next
113 %set-envelope/dll-next!))
114 (protocol
115 (lambda (%make)
116 (lambda (cap . args)
117 "A variant of @code{make-envelope}, that organises envelopes
118in a linked list. The capability @var{cap} will be required for accessing
119and modifying this list."
120 ((apply %make args) cap #f #f))))
121 (parent <envelope>)
122 (sealed #f)
123 (opaque #t))
124
125 (define (envelope-dll-check ev/dll cap)
126 "Verify whether the capability @var{cap} can be used
127for accessing the underlying DLL of the envelope @var{ev}.
128If not, raise an exception. Otherwise, return truth."
129 ;; FIXME &bad-capability exception?
130 (assert (eq? (%envelope/dll-capability ev/dll) cap)))
131
132 (define (envelope-dll ev/dll cap)
133 "Return the DLL procedures of the DLL envelope @var{ev/dll},
134using the capability @var{cap}."
135 (envelope-dll-check ev/dll cap)
136 (values (cute %envelope/dll-previous ev/dll)
137 (cute %set-envelope/dll-previous! ev/dll <>)
138 (cute %envelope/dll-next ev/dll)
139 (cute %set-envelope/dll-next! ev/dll <>)))))
diff --git a/gnu/gnunet/mq/envelope.scm b/gnu/gnunet/mq/envelope.scm
new file mode 100644
index 0000000..e0c94a2
--- /dev/null
+++ b/gnu/gnunet/mq/envelope.scm
@@ -0,0 +1,195 @@
1;; This file is part of GNUnet.
2;; Copyright (C) 2012-2019 GNUnet e.V.
3;; Copyright (C) 2021 Maxime Devos (<maximedevos@telenet.be>)
4;;
5;; GNUnet is free software: you can redistribute it and/or modify it
6;; under the terms of the GNU Affero General Public License as published
7;; by the Free Software Foundation, either version 3 of the License,
8;; or (at your option) any later version.
9;;
10;; GNUnet is distributed in the hope that it will be useful, but
11;; WITHOUT ANY WARRANTY; without even the implied warranty of
12;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13;; Affero General Public License for more details.
14;;
15;; You should have received a copy of the GNU Affero General Public License
16;; along with this program. If not, see <http://www.gnu.org/licenses/>.
17;;
18;; SPDX-License-Identifier: AGPL3.0-or-later
19
20;; Author: Florian Dold
21;; Author: Maxime Devos
22;; C file: util/mq.c
23;; Scheme module: (gnu gnunet mq envelope)
24;;
25;; Limitation: the format of messages is still in flux,
26;; so no type checks there.
27(define-library (gnu gnunet mq envelope)
28 (export <envelope> make-envelope envelope?
29 attempt-cancel! attempt-irrevocable-sent!)
30 (import (gnu gnunet utils hat-let)
31 (only (guile) define* lambda* exact-integer?)
32 (only (ice-9 match) match)
33 (only (ice-9 atomic)
34 make-atomic-box atomic-box-ref
35 atomic-box-compare-and-swap!)
36 (only (rnrs base)
37 lambda assert letrec let begin define
38 syntax-rules let-syntax define-syntax
39 procedure? eq? >= = <= < if quote ...
40 identifier-syntax values and let*
41 vector vector-ref vector? vector-length)
42 (only (rnrs records syntactic) define-record-type))
43 (begin
44 (define-record-type (<envelope> make-envelope envelope?)
45 ;; Atomic box:
46 ;; #t: cancelled
47 ;; #f: to late to cancel, message has been irrevocabily sent!
48 ;;
49 ;; (Unless you play tricks like pulling out the Ethernet
50 ;; cable before the message is received by the router)
51 ;; #(message prio notify-sent! cancel!)
52 (fields (immutable state %cancellation-state))
53 (protocol
54 (lambda (%make)
55 (lambda* (cancel! message #:key (priority 0) (notify-sent! values))
56 "Make a message envelope; i.e., a record containing the message
57(@var{message}, @var{priority}) and information on how to cancel the sending
58of the message (@var{cancel!}) and who should be notified when the message
59cannot be unsent anymore (@var{notify-sent!}).
60
61Once marked as cancelled or irrevocabily sent, the record drops its
62references to @var{message}, @var{cancel!} and @var{notify-sent!}.
63When being marked as cancelled, the thunk @var{cancel!} is called."
64 (assert (and (procedure? cancel!) (procedure? notify-sent!)
65 (exact-integer? priority)
66 (<= 0 priority 511)))
67 (%make (make-atomic-box
68 (vector message priority notify-sent! cancel!)))))))
69
70 (define (%attempt-irrevocable-sent! envelope already-sent go cancelled)
71 (bind-atomic-boxen
72 ((state (%cancellation-state envelope) swap!))
73 (let spin ((old state))
74 (match old
75 ;; See comment at %attempt-cancel! for
76 ;; why we don't do #(message prio notify-sent! cancel!)
77 ((? vector?)
78 (if (eq? old (swap! old #f))
79 (let^ ((!! (= (vector-length old) 4))
80 (! message (vector-ref old 0))
81 (! prio (vector-ref old 1))
82 (! notify-sent! (vector-ref old 2)))
83 (notify-sent!)
84 (go message prio))
85 (spin state)))
86 (#t (cancelled))
87 (#f (already-sent))))))
88
89 (define-syntax attempt-irrevocable-sent!
90 (syntax-rules (go cancelled already-sent)
91 "If @var{envelope} is not cancelled and has not yet been sent,
92mark the message as irrevocably sent, call the notify-sent callback and
93evaluate @var{exp/go} in an environment where the message @var{message}
94and its priority @var{priority} are bound.
95
96If the message has already been marked as irrevocabily sent,
97evaluate @var{exp/already-sent} instead. If the message is cancelled,
98evaluate @var{exp/cancelled} instead.
99
100Even if this operation (and perhaps @code{attempt-cancel!}) is used concurrently
101on the same @var{envelope}, whether by multi-threading, asynchronicities
102(via @code{system-async-mark}) or by recursion, the following properties hold:
103
104@begin itemize
105@item the notify-sent callback of @var{envelope} is called at most once
106@item the notify-sent callback is never called if @var{envelope} is cancelled
107 at any point in time
108@item likewise, the code in @var{exp/go} is at most evaluated once
109@end itemize"
110 ((_ envelope
111 ((go message priority) . exp/go)
112 ((cancelled) . exp/cancelled)
113 ((already-sent) . exp/already-sent))
114 (%attempt-irrevocable-sent! envelope
115 (lambda () . exp/already-sent)
116 (lambda (message priority) . exp/go)
117 (lambda () . exp/cancelled)))))
118
119 (define (%attempt-cancel! envelope now-cancelled already-cancelled
120 already-sent)
121 (bind-atomic-boxen
122 ((state (%cancellation-state envelope) swap!))
123 (let spin ((old state))
124 (match old
125 ;; Do _not_ use #(message prio notify-sent! cancel!)
126 ;; here! Instead, delay the bounds check and accessing
127 ;; the elements of the vector after the swap!. That way:
128 ;;
129 ;; Premature optimisation.
130 ;; We save a little time in case two threads try to concurrently
131 ;; @var{state}.
132 ;;
133 ;; Meager excuse: self-healing (in case of memory corruption).
134 ;; Suppose a cosmic ray flipped a few bits and now
135 ;; @var{state} contains another vector, of different length.
136 ;; Then by performing the swap before the bounds check,
137 ;; the envelope is brought into a valid state. (And an
138 ;; exception will still result.)
139 ((? vector?)
140 (if (eq? old (swap! old #t))
141 (let^ ((!! (= (vector-length old) 4))
142 (! cancel! (vector-ref old 3)))
143 (cancel!)
144 (now-cancelled))
145 (spin state)))
146 (#t (already-cancelled))
147 ;; XXX maybe make the meager excuse less meager
148 ;; and add a 'default' case where @var{state} is
149 ;; set to #f when bad (and an exception is raised)?
150 ;; Seems like some dedicated exception types for
151 ;; memory corruption are required then ...
152 ;; And tests.
153 (#f (already-sent))))))
154
155 (define-syntax attempt-cancel!
156 (syntax-rules (now-cancelled already-cancelled already-sent)
157 "If @var{envelope} is not yet marked as cancelled or sent,
158mark it as cancelled, call the corresponding cancellation callback
159and evaluate @var{exp/now-cancelled}.
160
161If @var{envelope} is already marked as cancelled, do not mutate
162anything or call any callback and evaluate @var{exp/already-cancelled}.
163Likewise, if @var{envelope} is marked as irrevocably sent, evaluate
164@var{exp/already-sent} instead.
165
166If this operation is interrupted before @var{exp/now-cancelled} is
167evaluated, the envelope may be marked as cancelled even if the
168cancellation callback has not yet been called or has not yet returned.
169
170However, by tolerating this limitation, it can be (and is) guaranteed
171that the cancellation callback is called at most once. Likewise, the
172code in @var{exp/now-cancelled} is only be called at most once.
173Also, the cancellation callback and is never called (and @var{exp/now-cancelled}
174never evaluated) if @var{envelope} is marked as sent at any point in time."
175 ((_ envelope
176 ((now-cancelled) . exp/now-cancelled)
177 ((already-cancelled) . exp/already-cancelled)
178 ((already-sent) . exp/already-sent))
179 (%attempt-cancel! envelope
180 (lambda () . exp/now-cancelled)
181 (lambda () . exp/already-cancelled)
182 (lambda () . exp/already-sent)))))
183
184 (define-syntax bind-atomic-boxen
185 (syntax-rules ()
186 ((_ () exp exp* ...)
187 (let () exp exp* ...))
188 ((_ ((variable box swap!) . etc) exp exp* ...)
189 (let ((stashed-box box))
190 (let-syntax ((variable (identifier-syntax
191 (atomic-box-ref box))))
192 (let ((swap! (lambda (expected desired)
193 (atomic-box-compare-and-swap! box expected
194 desired))))
195 (bind-atomic-boxen etc exp exp* ...)))))))))
diff --git a/guix.scm b/guix.scm
index c9291cf..6db8c36 100644
--- a/guix.scm
+++ b/guix.scm
@@ -66,8 +66,10 @@ random inputs and seeing if it holds.")
66 (propagated-inputs `(("guile-zlib" ,guile-zlib) 66 (propagated-inputs `(("guile-zlib" ,guile-zlib)
67 ("guile-bytestructures" ,guile-bytestructures) 67 ("guile-bytestructures" ,guile-bytestructures)
68 ("guile-fibers" ,guile-fibers) 68 ("guile-fibers" ,guile-fibers)
69 ("guile-json" ,guile-json-4))) 69 ("guile-json" ,guile-json-4)
70 ("guile-pfds" ,guile-pfds)))
70 (native-inputs `(("guile" ,guile-3.0) 71 (native-inputs `(("guile" ,guile-3.0)
72 ("guile-pfds" ,guile-pfds)
71 ("automake" ,automake) 73 ("automake" ,automake)
72 ;; Only used for testing. 74 ;; Only used for testing.
73 ("guile-quickcheck" ,guile-quickcheck) 75 ("guile-quickcheck" ,guile-quickcheck)
diff --git a/tests/envelope.scm b/tests/envelope.scm
index eb6f39f..3444df0 100644
--- a/tests/envelope.scm
+++ b/tests/envelope.scm
@@ -16,116 +16,358 @@
16;; 16;;
17;; SPDX-License-Identifier: AGPL3.0-or-later 17;; SPDX-License-Identifier: AGPL3.0-or-later
18 18
19(use-modules (gnu gnunet message envelope) 19(use-modules (ice-9 control)
20 (gnu gnunet utils bv-slice) 20 (srfi srfi-26)
21 (ice-9 control) 21 ((rnrs base) #:select (assert))
22 (ice-9 receive) 22 ((rnrs conditions) #:select (&assertion))
23 (fibers operations) 23 (gnu gnunet mq envelope)
24 (rnrs bytevectors) 24 (gnu gnunet mq prio-prefs)
25 (srfi srfi-26)) 25 (gnu gnunet mq prio-prefs2))
26 26
27(test-begin "envelope") 27(define *msg* (cons #f #t))
28 28
29(define %arbitrary-slice (make-slice/read-write 50)) 29(define (no-cancel!)
30(define %arbitrary-bv (make-bytevector 50)) 30 (error "cancel?"))
31(define %arbitrary-priority 7) 31(define (no-notify-sent!)
32(define (%bogus-notify-sent!) (throw 'what)) 32 (error "notify-sent?"))
33 33
34;; Priorities (represented by raw integers) 34(test-begin "notify-sent!")
35(let ((mk-prio (cute make-envelope %arbitrary-slice 35
36 #:priority <>)) 36;; First test things without any kind of concurrency,
37 (acceptable-priorities 37;; and without stack overflows and OOM.
38 '(0 1 511))) 38;; (No recursion, no asynchronics, no threads, no interrupts.)
39 (test-equal "priorities are preserved" 39(test-assert "notify-sent!: called by attempt-irrevocable-sent! (before 'go')"
40 acceptable-priorities 40 (let/ec ec
41 (map (compose envelope-priority mk-prio) 41 (attempt-irrevocable-sent!
42 acceptable-priorities)) 42 (make-envelope no-cancel! *msg*
43 (test-error "priorities ≥ 512 are rejected" #t 43 #:notify-sent!
44 (mk-prio 512)) 44 (lambda () (ec #t)))
45 (test-error "priorities < 0 are rejected" #t 45 ((go message priority) (error "unreachable"))
46 (mk-prio -1)) 46 ((cancelled) (error "cancelled?"))
47 (test-error "inexact priorities are rejected" #t 47 ((already-sent) (error "already sent?")))
48 (mk-prio 0.)) 48 (ec #f)))
49 (test-error "fractional priorities are rejected" #t 49
50 (mk-prio 1/2))) 50(test-eq "notify-sent!: only called once (--> already-sent)"
51 51 'already-sent
52;; Notify sent events 52 (let* ((notify-sent!? #f)
53(let ((mk (cute make-envelope %arbitrary-slice))) 53 (first-part-done? #f)
54 (test-equal "notify-sent! is usable" 54 (notify-sent!
55 '() 55 (lambda ()
56 (receive result (notify-sent! (mk)) 56 (if notify-sent!?
57 result)) 57 (error "called twice")
58 58 (set! notify-sent!? #t)))))
59 (test-equal "wait-sent-operation returns after notify-sent!" 59 (let ((envelope (make-envelope no-cancel! *msg*
60 '() 60 #:notify-sent! notify-sent!)))
61 (let ((a (mk))) 61 (attempt-irrevocable-sent!
62 (notify-sent! a) 62 envelope
63 (receive result (perform-operation (wait-sent-operation a)) 63 ((go message priority)
64 result))) 64 (assert notify-sent!?)
65 65 (assert (eq? message *msg*))
66 (test-equal "wait-sent-operation can be used twice" 66 (assert (= priority 0))
67 '(() . ()) 67 ;; the assignment should only be done once
68 (let ((a (mk))) 68 (assert (not first-part-done?))
69 (notify-sent! a) 69 (set! first-part-done? #t))
70 (cons 70 ((cancelled) (error "cancelled?"))
71 (receive result (perform-operation (wait-sent-operation a)) 71 ((already-sent) (error "done?")))
72 result) 72 (assert first-part-done?)
73 (receive result (perform-operation (wait-sent-operation a)) 73 (attempt-irrevocable-sent!
74 result)))) 74 envelope
75 75 ((go message priority) (error "go?/2"))
76 (test-error "don't call notify-sent! twice" 76 ((cancelled) (error "cancelled?/2"))
77 &double-notify-sent 77 ((already-sent) 'already-sent)))))
78 (let ((a (mk))) 78
79 (notify-sent! a) 79(test-equal "notify-sent!: not called if cancelled (--> cancelled)"
80 (notify-sent! a)))) 80 '(seems-ok . seems-ok/2)
81 81 (let* ((cancelled? #f)
82;; Whether wait-sent blocking cannot really be tested 82 (cancel!
83;; without resorting to time-outs. 83 (lambda ()
84 84 (if cancelled?
85;; Message slice 85 (error "what")
86(let ((mk-slice (cute make-envelope <>))) 86 (set! cancelled? #t))))
87 (test-error "message must be a slice (bv)" 87 (envelope (make-envelope cancel! *msg* #:notify-sent!
88 #t 88 no-notify-sent!))
89 (mk-slice %arbitrary-bv)) 89 (result/1
90 (test-error "message must be a slice (#f)" 90 (attempt-cancel!
91 #t 91 envelope
92 (mk-slice #f)) 92 ((now-cancelled)
93 (test-error "slice must be readable" 93 (assert cancelled?)
94 #t 94 'seems-ok)
95 (mk-slice (slice/write-only (make-slice/read-write 50)))) 95 ((already-cancelled) (error "what/cancelled"))
96 (test-expect-fail 1) 96 ((already-sent) (error "what/sent"))))
97 (test-error "slice may be writable" 97 (result/2
98 #t 98 (attempt-irrevocable-sent!
99 (mk-slice (make-slice/read-write 50))) 99 envelope
100 ;; It isn't required that they be eq?, per se, 100 ((go message priority) (error "go?"))
101 ;; but rather, it should point to the same 101 ((cancelled) 'seems-ok/2)
102 ;; memory region. 102 ((already-sent) (error "what/sent/2")))))
103 (test-eq "slice is preserved" 103 (cons result/1 result/2)))
104 %arbitrary-slice 104
105 (envelope-message-slice (mk-slice %arbitrary-slice)))) 105;; Concurrency by recursion.
106 106(test-eq "notify-sent!: not called if cancelled (inside post-cancellation)"
107;; Envelope DLL 107 'seems-ok
108(let ((mk-dll (cute make-envelope/dll <> %arbitrary-slice))) 108 (let* ((cancel-ok? (make-parameter #t))
109 (mk-dll 'stuff) 109 (cancel!
110 (test-equal "make-envelope/dll allows #:priority" 110 (lambda ()
111 444 111 (unless (cancel-ok?)
112 (envelope-priority 112 (error "what"))))
113 (make-envelope/dll 'cap %arbitrary-slice 113 (envelope
114 #:priority 444))) 114 (make-envelope cancel! *msg* #:notify-sent! no-notify-sent!)))
115 (test-assert "envelope/dll? implies envelope?" 115 (attempt-cancel!
116 (envelope? (mk-dll 'check))) 116 envelope
117 (test-assert "envelope? does not imply envelope/dll?" 117 ((now-cancelled)
118 (not (envelope/dll? (make-envelope %arbitrary-slice)))) 118 (parameterize ((cancel-ok? #f))
119 119 (attempt-irrevocable-sent!
120 (test-assert "capability check success" 120 envelope
121 (envelope-dll-check (mk-dll 'cap) 'cap)) 121 ((go message priority) (error "go?"))
122 (test-error "capability check failure" #t 122 ((cancelled) 'seems-ok)
123 (envelope-dll-check (mk-dll 'cap) 'imposter)) 123 ((already-sent) (error "what/sent/2")))))
124 124 ((already-cancelled) (error "what/cancelled"))
125 (test-error "envelope-dll checks capability (failure)" #t 125 ((already-sent) (error "what/sent")))))
126 (envelope-dll (mk-dll 'cap) 'imposter)) 126
127 (test-assert "envelope-dll checks capability (success)" 127(test-eq "notify-sent!: only called once (nested)"
128 (envelope-dll (mk-dll 'cap) 'cap))) 128 'seems-ok
129;; XXX test DLL, this requires a DLL library 129 (let* ((sent? #f)
130 130 (notify-sent!
131(test-end "envelope") 131 (lambda ()
132 (if sent?
133 (error "but I was already sent!")
134 (set! sent? #t))))
135 (envelope (make-envelope no-cancel! *msg* #:notify-sent! notify-sent!)))
136 (attempt-irrevocable-sent!
137 envelope
138 ((go message priority)
139 (assert sent?)
140 (attempt-irrevocable-sent!
141 envelope
142 ((go message priority) (error "but I was already sent!"))
143 ((cancelled) (error "cancelled/2?"))
144 ((already-sent) 'seems-ok)))
145 ((cancelled) (error "cancelled/1"))
146 ((already-sent) (error "aleady-sent?")))))
147
148;; TODO: asynchronics, multi-threading.
149;; How does one reliably test these things anyways?
150;; Maybe the VM trap interface can be used
151;; (to delay asynchronics to inopportune times).
152;; This seems a project of its own though.
153(test-end "notify-sent!")
154
155(test-begin "cancel!")
156
157(test-eq "cancel!: only called once (nested)"
158 'seems-ok
159 (let* ((cancelled? #f)
160 (cancel! (lambda ()
161 (if cancelled?
162 (error "cancelled at wrong time / too often")
163 (set! cancelled? #t))))
164 (envelope
165 (make-envelope cancel! *msg* #:notify-sent! no-notify-sent!)))
166 (attempt-cancel!
167 envelope
168 ((now-cancelled)
169 (assert cancelled?)
170 (attempt-cancel!
171 envelope
172 ((now-cancelled) 'twice-now-cancelled)
173 ((already-cancelled) 'seems-ok)
174 ((already-sent) (error "what send/1"))))
175 ((already-cancelled) 'too-early-cancel)
176 ((already-sent) (error "what send/2")))))
177
178(test-eq "cancel!: not after sent (sequential)"
179 'ok-already-sent
180 (let* ((envelope (make-envelope no-cancel! *msg*))
181 (first-step-done? #f)
182 (second-step-done? #f))
183 (attempt-irrevocable-sent!
184 envelope
185 ((go message priority)
186 (assert (not first-step-done?))
187 (set! first-step-done? #t))
188 ((cancelled) (error "what / cancelled"))
189 ((already-sent) (error "what / sent")))
190 (assert first-step-done?)
191 (attempt-cancel!
192 envelope
193 ((now-cancelled) (error "but I was sent"))
194 ((already-cancelled) (error "cancelled?"))
195 ((already-sent)
196 (assert (not second-step-done?))
197 (set! second-step-done? #t)
198 'ok-already-sent))))
199
200(test-eq "cancel!: not after sent (nested)"
201 'ok-already-sent
202 (let* ((envelope (make-envelope no-cancel! *msg*)))
203 (attempt-irrevocable-sent!
204 envelope
205 ((go message priority)
206 (attempt-cancel!
207 envelope
208 ((now-cancelled) (error "but I was sent"))
209 ((already-cancelled) (error "cancelled?"))
210 ((already-sent) 'ok-already-sent)))
211 ((cancelled) (error "what / cancelled"))
212 ((already-sent) (error "what / sent")))))
213
214(test-eq "cancel!: only called once (sequential)"
215 'ok
216 (let* ((cancelled? #f)
217 (cancel! (lambda ()
218 (if cancelled?
219 (error "cancelled at wrong time / too often")
220 (set! cancelled? #t))))
221 (first-step-done? #f)
222 (second-step-done? #f)
223 (envelope
224 (make-envelope cancel! *msg* #:notify-sent! no-notify-sent!)))
225 (attempt-cancel!
226 envelope
227 ((now-cancelled)
228 (assert cancelled?)
229 (assert (not first-step-done?))
230 (set! first-step-done? #t))
231 ((already-cancelled) (error "too early already cancelled"))
232 ((already-sent) (error "too early send")))
233 (assert cancelled?)
234 (assert first-step-done?)
235 (attempt-cancel!
236 envelope
237 ((now-cancelled) 'double-cancel)
238 ((already-cancelled)
239 (assert (not second-step-done?))
240 (set! second-step-done? #t)
241 'ok)
242 ((already-sent) (error "should not have been sent")))))
243
244(test-end "cancel!")
245
246;; We will now test whether references
247;; to the notify-sent, cancel and message are dropped
248;; when the message is marked as sent.
249
250;; Current versions of guile (at least 3.0.5) use a conservative
251;; garbage collector, so these tests might sometimes fail without
252;; indicating a bug. For reprodicible builds, allow skipping these
253;; tests.
254
255(define conservative-gc?
256 (if (equal? "yes" (getenv "TOLERATE_CONSERVATIVE_COLLECTORS"))
257 #t
258 #f))
259
260(test-begin "gc")
261
262;; Compilation of the source code of this test file
263;; prevents procedures made by writing (lambda () STUFF)
264;; from being garbage-collected.
265(define (fresh-gc-thunk)
266 (eval '(lambda () 'fresh) (current-module)))
267
268(define (do-nothing) 'nothing)
269
270(test-skip (if conservative-gc? 4 0))
271
272(test-equal "references dropped after cancel"
273 '(#t #t #t)
274 (let* ((fresh-message (vector 0 1 2 3))
275 (fresh-cancel (fresh-gc-thunk))
276 (fresh-notify-sent (fresh-gc-thunk))
277 (message-guard (make-guardian))
278 (cancel-guard (make-guardian))
279 (notify-sent-guard (make-guardian))
280 (envelope (make-envelope fresh-cancel fresh-message
281 #:notify-sent! fresh-notify-sent)))
282 (message-guard fresh-message)
283 (cancel-guard fresh-cancel)
284 (notify-sent-guard fresh-notify-sent)
285 (attempt-cancel!
286 envelope
287 ((now-cancelled)
288 (gc)
289 (list (->bool (message-guard))
290 (->bool (cancel-guard))
291 (->bool (notify-sent-guard))))
292 ((already-cancelled) (error "what/cancelled"))
293 ((already-sent) (error "what/sent")))))
294
295(test-equal "references dropped after sent"
296 '(#t #t #t)
297 (let* ((fresh-message (vector 0 1 2 3))
298 (fresh-cancel (fresh-gc-thunk))
299 (fresh-notify-sent (fresh-gc-thunk))
300 (message-guard (make-guardian))
301 (cancel-guard (make-guardian))
302 (notify-sent-guard (make-guardian))
303 (envelope (make-envelope fresh-cancel fresh-message
304 #:notify-sent! fresh-notify-sent)))
305 (message-guard fresh-message)
306 (cancel-guard fresh-cancel)
307 (notify-sent-guard fresh-notify-sent)
308 (attempt-irrevocable-sent!
309 envelope
310 ((go message priority)
311 (gc)
312 (list (->bool (message-guard))
313 (->bool (cancel-guard))
314 (->bool (notify-sent-guard))))
315 ((cancelled) (error "cancelled"))
316 ((already-sent) (error "what/cancelled")))))
317
318(test-assert "reference to envelope dropped after cancel"
319 (let ((envelope (make-envelope (lambda () 'ok) *msg*))
320 (envelope-guard (make-guardian)))
321 (envelope-guard envelope)
322 (attempt-cancel!
323 envelope
324 ((now-cancelled)
325 (gc)
326 (list (->bool (envelope-guard))))
327 ((already-cancelled) (error "what/cancelled"))
328 ((already-sent) (error "what/sent")))))
329
330(test-assert "reference to envelope dropped after send"
331 (let ((envelope (make-envelope no-cancel! *msg*))
332 (envelope-guard (make-guardian)))
333 (envelope-guard envelope)
334 (attempt-irrevocable-sent!
335 envelope
336 ((go message priority)
337 (gc)
338 (list (->bool (envelope-guard))))
339 ((cancelled) (error "what/cancelled"))
340 ((already-sent) (error "what/sent")))))
341
342(test-end "gc")
343
344(test-begin "arguments")
345
346(define %max-prio (- (expt 2 9) 1))
347
348(test-equal "non-standard priority"
349 %max-prio
350 (attempt-irrevocable-sent!
351 (make-envelope no-cancel! *msg* #:priority %max-prio)
352 ((go message priority) *msg* %max-prio)
353 ((cancelled) (error "what/cancelled"))
354 ((already-sent) (error "what/sent"))))
355(test-error "no negative priority"
356 &assertion
357 (make-envelope no-cancel! *msg* #:priority -1))
358(test-error "no inexact priority"
359 &assertion
360 (make-envelope no-cancel! *msg* #:priority 0.0))
361(test-error "no fractional priority"
362 &assertion
363 (make-envelope no-cancel! *msg* #:priority 5/7))
364(test-error "no overly large priority"
365 &assertion
366 (make-envelope no-cancel! *msg* #:priority 512))
367
368(test-end "arguments")
369
370;; TODO for completeness: test recursion from
371;; the notify-sent! callback and from cancel!
372;; callback and that references are dropped
373;; there as well.