diff options
author | Maxime Devos <maximedevos@telenet.be> | 2021-05-24 18:07:44 +0200 |
---|---|---|
committer | Maxime Devos <maximedevos@telenet.be> | 2021-09-21 12:08:41 +0200 |
commit | 08d98c025e7f50d1c6bafd94dfadd2c384fe8260 (patch) | |
tree | f3b9053612de1b17d0c7f192c2a8d1ba4e5137ea | |
parent | 8f041a1762721dd25dcf1959d04816b8f8d3974a (diff) | |
download | gnunet-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.am | 8 | ||||
-rw-r--r-- | README.org | 22 | ||||
-rw-r--r-- | gnu/gnunet/message/envelope.scm | 139 | ||||
-rw-r--r-- | gnu/gnunet/mq/envelope.scm | 195 | ||||
-rw-r--r-- | guix.scm | 4 | ||||
-rw-r--r-- | tests/envelope.scm | 468 |
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) | |||
70 | if HAVE_GUILD | 70 | if HAVE_GUILD |
71 | nodist_guilesiteccache_DATA = $(modules:%.scm=%.go) | 71 | nodist_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)" \ |
@@ -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 | |||
89 | This is the responsibility of the transport. | ||
90 | |||
91 | Conceptually, this should only be available to the message | ||
92 | transport, but the only other potential user would be | ||
93 | the message sender, so this shouldn't matter. | ||
94 | |||
95 | It is an error to call more than one. Currently, | ||
96 | an 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, | ||
104 | this 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 | ||
118 | in a linked list. The capability @var{cap} will be required for accessing | ||
119 | and 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 | ||
127 | for accessing the underlying DLL of the envelope @var{ev}. | ||
128 | If 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}, | ||
134 | using 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 | ||
58 | of the message (@var{cancel!}) and who should be notified when the message | ||
59 | cannot be unsent anymore (@var{notify-sent!}). | ||
60 | |||
61 | Once marked as cancelled or irrevocabily sent, the record drops its | ||
62 | references to @var{message}, @var{cancel!} and @var{notify-sent!}. | ||
63 | When 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, | ||
92 | mark the message as irrevocably sent, call the notify-sent callback and | ||
93 | evaluate @var{exp/go} in an environment where the message @var{message} | ||
94 | and its priority @var{priority} are bound. | ||
95 | |||
96 | If the message has already been marked as irrevocabily sent, | ||
97 | evaluate @var{exp/already-sent} instead. If the message is cancelled, | ||
98 | evaluate @var{exp/cancelled} instead. | ||
99 | |||
100 | Even if this operation (and perhaps @code{attempt-cancel!}) is used concurrently | ||
101 | on 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, | ||
158 | mark it as cancelled, call the corresponding cancellation callback | ||
159 | and evaluate @var{exp/now-cancelled}. | ||
160 | |||
161 | If @var{envelope} is already marked as cancelled, do not mutate | ||
162 | anything or call any callback and evaluate @var{exp/already-cancelled}. | ||
163 | Likewise, if @var{envelope} is marked as irrevocably sent, evaluate | ||
164 | @var{exp/already-sent} instead. | ||
165 | |||
166 | If this operation is interrupted before @var{exp/now-cancelled} is | ||
167 | evaluated, the envelope may be marked as cancelled even if the | ||
168 | cancellation callback has not yet been called or has not yet returned. | ||
169 | |||
170 | However, by tolerating this limitation, it can be (and is) guaranteed | ||
171 | that the cancellation callback is called at most once. Likewise, the | ||
172 | code in @var{exp/now-cancelled} is only be called at most once. | ||
173 | Also, the cancellation callback and is never called (and @var{exp/now-cancelled} | ||
174 | never 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* ...))))))))) | ||
@@ -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. | ||