aboutsummaryrefslogtreecommitdiff
path: root/doc/service-communication.tm
blob: 5f6cdf9ab64514b1c57a99a552ad03b931fc4ac9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
<TeXmacs|2.1>

<project|scheme-gnunet.tm>

<style|tmmanual>

<\body>
  To connect with a GNUnet service<index|services> \V this applies to both
  the C and Scheme implementation, the GNUnet service must bind a local
  domain socket<\footnote>
    The C implementation supports Internet sockets as well.
  </footnote> somewhere on the file system and the client (possibly another
  service) must connect to it.<space|1em>Connections to a service can be made
  with the <scm|connect/fibers><index|connect/fibers> procedure from
  <scm|(gnu gnunet mq-impl stream)><index|(gnu gnunet mq-impl stream)>, like
  this:

  <\scm-code>
    (define mq (connect/fibers config "nse" handlers error-handler))
  </scm-code>

  The returned object <var|mq> is named a <dfn|message queue><index|message
  queue><label|message queue>.

  <section|Asynchronuously connecting><index|connecting to services>

  This is an asynchronuous operation: it will \<#2018\>complete\<#2019\>
  immediately and the connection will actually be formed in the
  background.<space|1em>When the connection has actually be formed, the
  <scm|error-handler><index|error-handler> is called with the symbol
  <scm|connection:connected><index|connection:connected>.<space|1em>To
  demonstrate, the following code asynchronuously connects to the NSE
  service, and prints the text <scm|"connected!"> when the connection has
  actually been formed.

  <\scm-code>
    ;; XXX test this, explain 'config' ...

    (define (error-handler error . args)

    \ \ (case error

    \ \ \ \ ((connection:connected)

    \ \ \ \ \ (format #t "connected!~%"))

    \ \ \ \ (else (format #t "unknown error: ~a ~a~%" error args))))

    \;

    (define mq

    \ \ (connect/fibers config "nse" (message-handlers) error-handler))
  </scm-code>

  <section|Message handler>

  <index|message handler>When a message is received by the message queue, the
  corresponding message handler is invoked.<space|1em>Message handlers can be
  constructed with the <scm|message-handler><index|message-handler> macro and
  the <scm|make-message-handler><index|make-message-handler> procedure from
  <scm|(gnu gnunet mq handler)>, as follows:

  <\scm-code>
    (import (gnu gnunet mq handler)

    \ \ \ \ \ \ \ \ (gnu extractor enum)

    \ \ \ \ \ \ \ \ (gnu gnunet message protocols)

    \ \ \ \ \ \ \ \ (gnu gnunet util struct)

    \ \ \ \ \ \ \ \ (gnu gnunet utils bv-slice)

    \ \ \ \ \ \ \ \ (gnu gnunet netstruct syntactic))

    \;

    (define handler/syntactic

    \ \ (message-handler

    \ \ \ (type (symbol-value message-type msg:util:dummy))

    \ \ \ ((interpose code) code)

    \ \ \ ((well-formed? slice)

    \ \ \ \ (= (slice-length slice)

    \ \ \ \ \ \ \ (sizeof /:message-header '())))

    \ \ \ ((handle! slice)

    \ \ \ \ (pk 'message: slice))))

    \;

    (define handler/procedural

    \ \ (make-message-handler

    \ \ \ (symbol-value message-type msg:util:dummy)

    \ \ \ (lambda (thunk) (thunk))

    \ \ \ (lambda (slice)

    \ \ \ \ \ (= (slice-length slice)

    \ \ \ \ \ \ \ \ (sizeof /:message-header '())))

    \ \ \ (lambda (slice)

    \ \ \ \ \ (pk 'message: slice))))
  </scm-code>

  As illustrated in the example code above, a message handler has four
  components: the <with|font-shape|italic|type> of message<subindex|message
  type|of handler> it handles, an <with|font-shape|italic|interposer><index|interposer>
  which will be explained later, the <with|font-shape|italic|verifier><index|verifier>
  deciding if a message is well-formed and the
  <with|font-shape|italic|handler procedure><index|handler procedure>.

  The verifier is passed a bytevector slice with the message and should
  return <scm|#true> if the message is well-formed and <scm|#false> if it
  isn't.<space|1em>It may assume that the length of the slice corresponds to
  the length <em|in> the message header and is at least the length <em|of>
  the message header and that the type in the message header corresponds to
  the type of the message handler.<space|1em>Messages will only be passed to
  the handler procedue if the verifiers returns <scm|#true>.

  The handler procedure is passed a bytevector slice with the message, but
  only if the verifier considers it well-formed.<space|1em>The handler
  procedure and verifier are run from the
  <with|font-shape|italic|interposer>.<space|1em>The interposer is passed a
  thunk to execute and may e.g. install exception handlers and parameterise
  parameters.<space|1em>It can change the current input, output and error
  ports for example.

  <todo|document the message type database, various procedures>

  <section|Message type database><label|sec:message type><subindex|message
  type|database>

  The module <scm|(gnu gnunet message protocols)><index|(gnu gnunet message
  protocols)> has a mapping of symbolic names of every message type known to
  scheme-GNUnet to their numeric value.<space|1em>To use it, the macro
  <scm|symbol-value><index|symbol-value> from <scm|(gnu extractor
  enum)><index|(gnu extractor enum)> is required and possibly
  <scm|value-\<gtr\>index><index|value-\<gtr\>index> as well.<space|1em>To
  determine the numeric value of the message type <scm|msg:nse:estimate>, one
  would write:

  <\scm-code>
    (define numeric-type

    \ \ (value-\<gtr\>index (symbol-value message-type msg:nse:estimate)))
  </scm-code>

  <todo|other various enum procedures for introspection, documentation,
  <text-dots>?>

  <todo|how to define new message types>

  <section|Sending messages>

  Messages can be sent with the <scm|send-message!><index|send-message!><label|send-message!>
  procedure, which can be called as <scm|(send-message! <var|mq>
  <var|message> #:priority <var|priority>)>, where <var|mq> is the message
  queue and <var|message> is the message to send as a readable bytevector
  slice. This is an asynchronuous operation, so this procedure can return
  before the service has processed the message.

  <label|mq-prio-prefs>Depending on the transport, it might be possible for
  messages to be lost or received out-of-order. Some transports allow to
  explicitely allow messages to be lost or received out-of-order and would by
  default retransmit lost messages and reorder out-of-order messages; this
  behaviour can to a degree be controlled by setting the
  <dfn|priority-preference> flags.

  These flags are not absolute, e.g. even if reliable transmission is
  requested, it is possible that the transport fail to transmit the message.
  The exact behaviour is transport-dependent!

  <\description>
    <item*|<scm|pref:unreliable>>Unreliable delivery is acceptable.

    <item*|<scm|pref:low-latency>>Low latency is desired, this cannot be
    meaningfully combined with <scm|pref:cork-allowed>.

    <item*|<scm|pref:cork-allowed>>The transmission of a message can be
    delayed to combine this message with other messages into a larger
    transmission with less per-message overhead.

    <item*|<scm|pref:good-throughput>>High bandwith is desired; the method
    chosen for transmission should focus on overall throughput.

    <item*|<scm|pref:out-of-order>>Out-of-order delivery is acceptable.
  </description>

  These flags can be combined into a numeric value with the macro
  <scm|prio-prefs> from <scm|(gnu gnunet mq prio-prefs)>; the following code
  defines <var|x> as the numeric value of the flags <scm|pref:unreliable> and
  <scm|pref:out-of-order>:

  <\scm>
    (import (gnu gnunet mq prio-prefs))

    (define x (prio-prefs pref:unreliable pref:out-of-order))
  </scm>

  This numeric priority-preference can be passsed to <scm|send-message!> as
  the optional <var|priority> keyword argument of <scm|send-message!>. The
  transport of <scm|connect/fibers> is always reliable and in-order.
  <todo|notify-sent! callbacks><todo|cancellation><todo|queue size limits,
  <scm|%suspicious-length>>

  <section|Error handler><index|error handler>

  The message queue implementation usually just sends and receives messages,
  but some exceptional situations cannot be communicated with
  <scm|send-message!> or <scm|inject-message!>.<space|1em>For those, there is
  the <scm|inject-error!><index|inject-error!> procedure.<space|1em>This
  variadic procedure accepts a message queue to inject the error into, a
  <with|font-shape|italic|key><index|key> (usually a symbol) describing the
  exceptional situation and rest arguments.<space|1em>It calls the
  <with|font-shape|italic|error handler> of the message queue with the key
  and rest arguments.<space|1em>The following errors can currently be
  reported by the built-in message queue implementations:

  <\explain>
    <scm|connection:connected><index|connection:connected>
  <|explain>
    The connection to the server has been established.
  </explain>

  <\explain>
    <scm|connection:interrupted><index|connection:interrupted>
  </explain|The message queue has been closed before the connection to the
  server could be established.>

  <\explain>
    <scm|input:regular-end-of-file><index|input:regular-end-of-file>
  <|explain>
    The connection has been closed by the server.

    For people wondering about what happens if a connection becomes
    half-duplex: GNUnet does not have a notion of half-duplex message
    streams.<space|1em>If it is detected the underlying stream became
    half-duplex anyways, it will be treated as closed by scheme-GNUnet,
    resulting in this error.<space|1em>However, note that currently broken
    pipes cannot be reliably detected.
  </explain>

  <\explain>
    <scm|input:premature-end-of-file><index|input:premature-end-of-file>
  </explain|The connection was closed by the server while a message was still
  being read.<space|1em>This can happen if the server was stopped while it
  was still sending the rest of the message.>

  <\explain>
    <scm|input:overly-small> <var|type> <var|size><index|input:overly-small>
  </explain|The message size in the header was smaller than the minimal
  message size.<space|1em>Sometimes, but not always, the message type
  <var|type> and message size <var|size> are available (as exact
  naturals).<space|1em>When they are not available, <var|type> and <var|size>
  are <scm|#false> instead.<space|1em>This can only happen if the server or
  connection to the server is buggy.>

  <\explain>
    <scm|logic:no-handler> <var|type> . <var|rest><index|logic:no-handler>
  <|explain>
    The received message of type <var|type> (as an integer) does not have a
    corresponding message handler.<space|1em><var|rest> is currently
    unspecified.
  </explain>

  <\explain>
    <scm|logic:ill-formed> <var|type> . <var|rest><index|logic:ill-formed>
  </explain|The received message of type (as an integer) is ill-formed
  according to the message handler.<space|1em><var|rest> is currently
  unspecified.>

  Consider automatically reconnecting after
  <scm|<scm|input:regular-end-of-file>> and
  <scm|<scm|input:premature-end-of-file>>, to allow the server to restart
  without having to manually restart every individual
  application.<space|1em>To report errors, see the section
  <reference|sec:error reporting> Error reporting.

  <section|Ordering of injected errors and messages and sent messages>

  This section describes how injected errors and messages and sent messages
  are ordered with respect to each other in the default message queue
  implementation.<space|1em>Messages are handled or corresponding
  <scm|logic:no-handler> or <scm|logic:ill-formed> errors are injected in the
  order that the messages are received.<space|1em>Before messages are read,
  <scm|connection:connected> is injected.<space|1em>This error is injected at
  most once.

  <em|Soon> after all messages are read (and therefore
  <with|font-shape|italic|soon> after all handled messages or corresponding
  errors), the error <scm|input:regular-end-of-file>,
  <scm|input:overly-small> or <scm|input:premature-end-of-file> is
  injected.<space|1em>Only one of those errors can be injected for the entire
  lifetime of the message queue.

  Be aware that <em|soon> is not <em|immediate> here!<space|1em>For example,
  it is possible for a message to be received, the port closed, a message
  queued for sending, the closing of the port being detected by the write
  fiber, <scm|input:regular-end-of-file> being injected from the write fiber
  and the read fiber handling the received message, and the read fiber
  exiting because the port is closed, in that order.

  Messages are sent (and received on the other side) in the order they were
  enqueued for sending.<space|1em>Likewise, the notify-sent callback of
  enqueued messages are called in order.<space|1em>If the notify-sent
  callback is called, it is before the message is received by the other
  side.<space|1em>The message and its notify-sent callback are only received
  by the other side and called after the message has been injected and
  <scm|connection:connected> has been injected.<space|1em>It is possible for
  the notify-sent callback to be called without the message being received by
  the other side, e.g. if the port was closed during the notify-sent
  callback.

  If a message is received by the other side, all previously-sent messages
  have be received before.<space|1em>If a notify-sent callback is invoked,
  all notify-sent callbacks of previous messages have been invoked before,
  except the messages that are eventually cancelled.

  The errors <scm|logic:no-handler> and <scm|logic:ill-formed> are not fatal:
  later messages can still be read and handled.<space|1em>If
  <scm|connection:interrupted> is injected, no other errors are ever
  injected, whether in the past or in the future.<space|1em>This error can
  only be injected once.

  <todo|I/O errors>

  <todo|envelopes>

  <section|Disconnecting><index|disconnecting>

  A message queue can be closed with the <scm|close-queue!><index|close-queue!>
  procedure from <scm|(gnu gnunet mq)>.<space|1em>In the default message
  queue implementation, this asynchronuously closes the port and stops
  associated fibers.<space|1em>Closing ports when they won't be used anymore
  is important for limiting resource consumption, especially for servers that
  can have many connections.<space|1em>Closing message queues is an
  idempotent operation: closing a message queue twice is the same as closing
  it once.<space|1em> If a message queue is closed before a connection could
  be formed, <scm|connection:interrupted><index|connection:interrupted> is
  injected instead of <scm|connection:connected> and
  <scm|connection:regular-end-of-file>.

  <section|Error reporting><label|sec:error reporting>

  <index|error reporting>Errors can be reported with the procedure
  <scm|report-error> from the module <scm|(gnu gnunet mq
  error-reporting)><index|(gnu gnunet mq error-reporting)>.<space|1em>It can
  be called as <scm|(report-error key argument ...)>, e.g. <scm|(report-error
  'logic:no-handler 3)><index|report-error>.<space|1em>By default, it reports
  the error to the current error port.<space|1em>If this is not desired, the
  output can be sent to another port by setting the parameter
  <scm|textual-error-reporting-port><index|textual-error-reporting-port>.<space|1em>If
  textual error reporting is not desired, the parameter
  <scm|error-reporter><index|error-reporter> can be set to a procedure with
  the same interface as <scm|report-error>.<space|1em>Such a procedure could
  e.g. open a GUI dialog, sent the message to the system logger or ignore the
  error.

  Error messages are translated for the current locale.<todo|TODO actually
  call bindtextdomain>

  <section|Testing service code>

  The module <scm|(tests utils)> has \ a few procedures for testing service
  code:

  <\explain>
    <scm|(garbage-collectable <var|service> <var|connect>)>
  <|explain>
    Test that the server object is properly garbage collectable \U i.e., make
    sure that when the server object is not used anymore, all new fibers are
    stopped after a GC. Due to Guile's conservative GC, this has some false
    negatives (i.e., it passes when it shouldn't) and in theory false
    positives (i.e., it fails when it shouldn't), although the test is
    written in such a way that false positives should be rare.

    Here, <var|service> is a string like <scm|"dht"> or <scm|"gns">. It is
    required for generating the configuration file. <var|connect> is a
    procedure that can be called as <scm|(connect config #:spawn _
    #:connected _ #:disconnected _>), such as the <scm|connect> procedure
    from DHT <reference|dht:connect> and NSE <reference|nse:connect>.

    This procedure returns a boolean value suitable for <scm|test-assert>.
  </explain>

  <\explain>
    <scm|(close-not-connected-no-callbacks <var|service> <var|connect>
    <var|disconnect!> #:rest)>
  <|explain>
    This tests the connection and disconnection callbacks. It verifies that
    if the service daemon is down, the connection and disconnection callbacks
    are not called. The optional argument <var|rest> is a list of extra
    arguments to pass to <var|connect>.

    When run sufficiently slowly, false negatives are possible.
  </explain>

  <\explain>
    <scm|(connect-after-eof-after-connected <var|service> <var|connect>)>
  </explain|This tests the connection and disconnection callbacks, in case
  the server disconnects without sending or receiving anything.<space|1em>It
  verifies that the connection and disconnection callback is called and that
  the disconnection happens after connection. It does not test automatic
  reconnection.>

  <\explain>
    <scm|(reconnects service <var|service> <var|connect>)>
  </explain|This tests the reconnection logic, by repeatedly closing the
  connection from the server side and verifying that the connection and
  disconnection callbacks are called in the right order and sufficiently
  often.>

  <\explain>
    <scm|(determine-reported-errors <var|service> <var|connect> <var|proc>
    #:key (<var|n-connections> 1) (<var|n-errors> 1))>
  <|explain>
    This is not a test by itself, but can be used as basis for writing tests
    on error reporting logic. It connects to a service simulated by
    <var|proc>, builds a list of errors passed to <scm|error-reporter> and
    returns it. After a disconnect, it will automatically reconnect until
    <var|n-connections> have been made. It also waits for <var|n-errors> to
    be gathered and verifies that all fibers complete.

    The simulation is done by the procedure <var|proc>. It is a procedure
    accepting the connction port as seen by the server and can e.g. write to
    the port and close it.
  </explain>

  <todo|document more>

  <\example>
    In <verbatim|tests/distributed-hash-table.scm>, we have the following
    tests:

    <\scm-code>
      (import (srfi srfi-64) (test utils) (gnu gnunet dht client)

      (test-assert "(DHT) close, not connected --\<gtr\> all fibers stop, no
      callbacks called"

      \ \ (close-not-connected-no-callbacks "dht" connect disconnect!))

      (test-assert "(DHT) garbage collectable"

      \ \ (garbage-collectable "dht" connect))
    </scm-code>
  </example>

  <section|Writing service communication code>

  <todo|More procedures! Unify the various services more!>

  In the previous sections, the basic building blocks for communication were
  explained. However, it has not yet been explained how to piece them
  together with nice properties such as automatic disconnection after the
  server object becomes unreachable, automatic reconnection when the remote
  server disconnects (e.g. for an update, or other reasons) and automatically
  restarting old operations.

  To do this, the module <scm|(gnu gnunet server)> can be used. In this
  module, a <dfn|server object> has a control channel. When something is
  requested of the server object, a message is sent to the <dfn|control
  channel><index|control channel>. The control loop receives these messages
  and acts on them.

  In Guile-Fibers, a message is only sent to a channel if at the same time it
  is received elsewhere. However, the server object might have been
  disconnected so sending a message would hang forever! To solve this, the
  concept of <em|maybe-sending><index|maybe-sending> is introduced. In
  addition to the control channel, there is the <dfn|terminal
  condition><index|terminal condition>. This condition keeps track of whether
  the control loop is still active. When the control loop stops, it must
  signal the condition. <dfn|Maybe-sending> then is: either send the message
  to the control channel, or wait for the terminal condition to be signalled
  (but not both!).

  <todo|unify implementation of control loop?>

  <todo|TODO: module is not yet complete!>

  <\explain>
    <scm|\<less\>server\<gtr\>><index|\<less\>server\<gtr\>><label|\<less\>server\<gtr\>>
  <|explain>
    The record type of server objects. The control channel and terminal
    condition can be retrieved with <scm|server-terminal-condition> and
    <scm|server-control-channel> respectively. In practice, you will need to
    define a subtype of <scm|\<less\>server\<gtr\>> with
    <scm|define-record-type> from R6RS. That way, new fields can be added and
    a type predicate becomes available.

    <\example>
      The server type of <acronym|NSE> (network size estimation) is
      implemented more-or-less like this:

      <\scm>
        (define-record-type (\<less\>server:nse\<gtr\> make-server
        server:nse?)

        \ \ (parent \<less\>server\<gtr\>)

        \ \ (fields (immutable estimate/box server-estimate/box))

        \ \ (protocol

        \ \ \ \ (lambda (%make)

        \ \ \ \ \ \ (lambda ()

        \ \ \ \ \ \ \ \ ((%make) (make-atomic-box #false))))))

        (define server (make-server))

        (server:nse? server) ; -\<gtr\> #true

        (atomic-box? (server-estimate/box)) ; -\<gtr\> #true
      </scm>
    </example>
  </explain>

  <\explain>
    <scm|(maybe-send-control-message! <var|server> . <var|message>)>
  <|explain>
    Maybe-send the message <var|message> to the service. If sent, return
    <scm|#true>. If the control loop is not active anymore (i.e., the
    terminal condition was signalled), return <scm|#false> instead.
  </explain>

  <\explain>
    <scm|(maybe-send-control-message!* <var|terminal-condition>
    <var|control-channel> . <var|message>)>
  </explain|This is like <scm|maybe-send-control-message!>, except it doesn't
  need a reference to the <scm|\<less\>server\<gtr\>> object, which sometimes
  is required for GC reasons.>

  <\explain>
    <scm|(make-disconnect! <var|type?>)><index|make-disconnect!>
  <|explain>
    It is assumed the <reference|server object> type is a subtype of
    <scm|<reference|\<less\>server\<gtr\>>>.

    Make a procedure that implements the procedure
    <reference|disconnect!><index|disconnect!> of the <em|<reference|server
    object>> pattern. The predicate <var|type?>, when passed an object,
    checks that the type of the object is the right subtype of
    <scm|\<less\>server\<gtr\>>. � priori, the object passed to the predicate
    might not even be a <scm|\<less\>server\<gtr\>>.

    More technically, this <em|maybe-sends> a <scm|disconnect!> message to
    the control channel of the server object.
  </explain>
</body>

<\initial>
  <\collection>
    <associate|page-medium|paper>
    <associate|save-aux|false>
  </collection>
</initial>