gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnunet-scheme] 02/02: dht/client: Support disconnecting and reconnectin


From: gnunet
Subject: [gnunet-scheme] 02/02: dht/client: Support disconnecting and reconnecting.
Date: Wed, 02 Feb 2022 22:58:32 +0100

This is an automated email from the git hooks/post-receive script.

maxime-devos pushed a commit to branch master
in repository gnunet-scheme.

commit 0b7dcf4ba638830e71ce893732982ee8dc036065
Author: Maxime Devos <maximedevos@telenet.be>
AuthorDate: Wed Feb 2 21:51:22 2022 +0000

    dht/client: Support disconnecting and reconnecting.
    
    Reconnecting is currently untested.
    
    * doc/scheme-gnunet.tm (Accessing the DHT): Document 'connect' and
      'disconnect!'.
    * gnu/gnunet/dht/client.scm (<server>): Replace 'request-close?/box'
      and 'request-close-condition' by 'terminal-condition'.
      (maybe-send-control-message!*): New procedure, extracted from ...
      (maybe-send-control-message): ... here.
      (disconnect!): Tweak documentation.  Send a control message.
      (connect): Add 'disconnected' argument.  Pass it to 'reconnect'.
      Adjust call to '%make-server' for new fields.  Tweak documentation.
      (reconnect): Add 'disconnected' argument.
      
(reconnect)[error-handler]<input:regular-end-of-file,input:premature-end-of-file>:
      Call 'disconnected'.  Send a control message instead of directly 
reconnecting.
      (reconnect)[control]<disconnect!,reconnect!>: New cases.
    * tests/distributed-hash-table.scm
      ("(DHT) close, not connected --> all fibers stop, no callbacks called"):
      New test case.
    * tests/network-size.scm (make-nse-config, "close, not connected -->
      all fibers stop, no callbacks called"): Extract some functionality to ...
    * tests/utils.scm (trivial-service-config, call-with-absent-service)
      (close-not-connected-no-fallbacks): ... here.
---
 doc/scheme-gnunet.tm             | 22 ++++++++++-
 gnu/gnunet/dht/client.scm        | 82 ++++++++++++++++++++++++++--------------
 tests/distributed-hash-table.scm |  4 ++
 tests/network-size.scm           | 31 +++------------
 tests/utils.scm                  | 51 ++++++++++++++++++++++++-
 5 files changed, 132 insertions(+), 58 deletions(-)

diff --git a/doc/scheme-gnunet.tm b/doc/scheme-gnunet.tm
index ae413c2..4cfe7ac 100644
--- a/doc/scheme-gnunet.tm
+++ b/doc/scheme-gnunet.tm
@@ -1054,8 +1054,26 @@
   GNUnet has a service that maintains a <em|distributed hash table>, mapping
   keys to values. The module <scm|(gnu gnunet dht client)> can be used to
   interact with the service. The connection can be made with the procedure
-  <scm|connect> <todo|document parameters>. It returns a <em|DHT server
-  object>. <todo|disconnection / reconnection>
+  <scm|connect>. It returns a <em|DHT server object>.
+
+  <\explain>
+    <scm|(connect <var|config> <var|#:connected> <var|#:disconnected>
+    <var|#:spawn-fiber>)>
+  <|explain>
+    Connect to the DHT service, using the configuration <var|config>. The
+    connection is made asynchronuously; the optional thunk <var|connected> is
+    called when the connection has been made. The connection can break; the
+    optional thunk <var|disconnected> is called when it does. If the
+    connection breaks, the client code automatically tries to reconnect, so
+    <var|connected> can be called after <var|disconnected>.
+  </explain>
+
+  <\explain>
+    <scm|(disconnect <var|server>)>
+  <|explain>
+    Asynchronuously disconnect from the DHT service and stop reconnecting,
+    even if not connected. This is an idempotent operation.
+  </explain>
 
   <section|Data in the DHT>
 
diff --git a/gnu/gnunet/dht/client.scm b/gnu/gnunet/dht/client.scm
index fac95f9..39f8d81 100644
--- a/gnu/gnunet/dht/client.scm
+++ b/gnu/gnunet/dht/client.scm
@@ -556,9 +556,8 @@ currently unsupported."
     ;; Operations must be put in id->operation-map before sending them
     ;; to the service!
     (define-record-type (<server> %make-server server?)
-      (fields (immutable request-close?/box server-request-close?/box)
-             (immutable request-close-condition
-                        server-request-close-condition)
+      ;; terminal-condition: a disconnect has been requested
+      (fields (immutable terminal-condition server-terminal-condition)
              (immutable control-channel server-control-channel)
              ;; Atomic box holding an unsigned 64-bit integer.
              (immutable next-unique-id/box server-next-unique-id/box)
@@ -566,14 +565,21 @@ currently unsupported."
              ;; <get> object.
              (immutable id->operation-map server-id->operation-map)))
 
+    (define (maybe-send-control-message!* terminal-condition control-channel
+                                         . message)
+      "See @code{maybe-send-control-message!}."
+      (perform-operation
+       (choice-operation
+       ;; Nothing to do when the <server> is permanently disconnected,
+       ;; or is being disconnected.
+       (wait-operation terminal-condition)
+       (put-operation control-channel message))))
+
     (define (maybe-send-control-message! server . message)
       "Send @var{message} to the control channel of @var{server}, or don't
 do anything if @var{server} has been permanently disconnected."
-      (perform-operation
-       (choice-operation
-       ;; Nothing to do when the permanently disconnected!
-       (wait-operation (server-request-close-condition server))
-       (put-operation (server-control-channel server) message))))
+      (apply maybe-send-control-message!* (server-terminal-condition server)
+            (server-control-channel server) message))
 
     (define-record-type (<get> %make-get get?)
       (fields (immutable server get:server)
@@ -683,31 +689,35 @@ message header is assumed to be correct."
     ;; (gnu gnunet client) as in the C implementation?
     (define (disconnect! server)
       "Asynchronuously disconnect from the DHT service and stop reconnecting,
-even if not connected.  This is an idempotent operation."
-      (atomic-box-set! (server-request-close?/box server) #t)
-      (signal-condition! (server-request-close-condition server)))
+even if not connected.  This is an idempotent operation.  This is an
+asynchronuous request; it won't be fulfilled immediately."
+      (maybe-send-control-message! server 'disconnect!))
 
-    (define* (connect config #:key (connected values)
+    (define* (connect config #:key (connected values) (disconnected values)
                      (spawn spawn-fiber))
-      "Connect to the DHT service in the background."
-      (define request-close?/box (make-atomic-box #f))
-      (define request-close-condition (make-condition))
+      "Connect to the DHT service, using the configuration @var{config}.  The
+connection is made asynchronuously; the optional thunk @var{connected} is 
called
+when the connection has been made.  The connection can break; the optional 
thunk
+@var{disconnected} is called when it does. If the connection breaks, the client
+code automatically tries to reconnect, so @var{connected} can be called after
+@var{disconnected}.  This procedure returns a DHT server object."
+      (define terminal-condition (make-condition))
       (define id->operation-map (make-hash-table))
       (define control-channel (make-channel))
-      (reconnect request-close?/box request-close-condition config
+      (reconnect terminal-condition config
                 id->operation-map control-channel
                 #:connected connected
+                #:disconnected disconnected
                 #:spawn spawn)
-      (%make-server request-close?/box request-close-condition
-                   control-channel
+      (%make-server terminal-condition control-channel
                    ;; Any ‘small’ exact natural number will do.
                    (make-atomic-box 0)
                    id->operation-map))
 
-    (define* (reconnect request-close?/box request-close-condition config
+    (define* (reconnect terminal-condition config
                        id->operation-map control-channel
                        #:key (spawn spawn-fiber)
-                       connected
+                       connected disconnected
                        #:rest rest)
       (define handlers
        (message-handlers
@@ -782,15 +792,17 @@ even if not connected.  This is an idempotent operation."
           (values))
          ;; TODO: signal (and wait for) current fibers to stop?
          ((input:regular-end-of-file input:premature-end-of-file)
-          (unless (atomic-box-ref request-close?/box)
-            (apply reconnect
-                   request-close?/box request-close-condition
-                   config id->operation-map control-channel rest)))
+          (disconnected)
+          ;; Tell the event loop that it is time to restart,
+          ;; unless it is already stopping.
+          (maybe-send-control-message!* terminal-condition 'reconnect!))
          ;; TODO: is this cargo-copying from (gnu gnunet nse client)
          ;; correct?
          ((connection:interrupted)
           (values))
          (else
+          ;; TODO: does 'disconnected' need to be called here,
+          ;; does 'disconnect!' need to be sent to the control channel?
           (apply report-error error arguments)
           (close-queue! mq))))
       (define mq (connect/fibers config "dht" handlers error-handler
@@ -799,15 +811,29 @@ even if not connected.  This is an idempotent operation."
       (define (control)
        "The main event loop."
        (match (perform-operation (get-operation control-channel))
+         (('disconnect!)
+          ;; Ignore future requests instead of blocking.
+          (signal-condition! terminal-condition)
+          ;; Close networking ports.
+          (close-queue! mq)
+          ;; And the fibers of the <server> object are now done!
+          (values))
+         (('reconnect!)
+          ;; Restart the loop with a new message queue.
+          (apply reconnect terminal-condition config id->operation-map
+                 control-channel rest))
          (('start-get! get)
           ;; Register the new get operation, such that we remember
           ;; where to send responses to.
           (hashv-set! id->operation-map (get:unique-id get) get)
-          ;; (Asynchronuously) send the GET message
-          (send-get! mq get))
+          ;; (Asynchronuously) send the GET message.
+          (send-get! mq get)
+          ;; Continue!
+          (control))
          (('put! put)
           ;; Send the put operation to the DHT service.
-          (send-message! mq (put:message put))))
-       (control))
+          (send-message! mq (put:message put))
+          ;; Continue!
+          (control))))
       ;; Start the main event loop.
       (spawn control))))
diff --git a/tests/distributed-hash-table.scm b/tests/distributed-hash-table.scm
index c249256..7f3c6f2 100644
--- a/tests/distributed-hash-table.scm
+++ b/tests/distributed-hash-table.scm
@@ -366,6 +366,7 @@
 ;;;
 ;;;  * [x] insertion (@code{put!})
 ;;;  * [x] retrieval (@code{start-get!})
+;;;  * [x] disconnecting
 ;;;  * [ ] monitoring
 ;;;
 ;;; In the following contexts:
@@ -599,4 +600,7 @@ supported."
      (wait pong)
      #true)))
 
+(test-assert "(DHT) close, not connected --> all fibers stop, no callbacks 
called"
+  (close-not-connected-no-fallbacks "dht" connect disconnect!))
+
 (test-end)
diff --git a/tests/network-size.scm b/tests/network-size.scm
index 1c8d4da..0b27d29 100644
--- a/tests/network-size.scm
+++ b/tests/network-size.scm
@@ -1,5 +1,5 @@
 ;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
-;; Copyright (C) 2021 GNUnet e.V.
+;; Copyright © 2021, 2022 GNUnet e.V.
 ;;
 ;; scheme-GNUnet is free software: you can redistribute it and/or modify it
 ;; under the terms of the GNU Affero General Public License as published
@@ -271,31 +271,10 @@
        (assert connected?)
        #t))))
 
-(define (make-nse-config where)
-  "Make a configuration where the socket location of the NSE service
-is @var{where}."
-  (define config (hash->configuration
-                 (rnrs:make-hashtable hash-key key=?)))
-  (set-value! identity config "nse" "UNIXPATH" where)
-  config)
-
 (test-assert "close, not connected --> all fibers stop, no callbacks called"
-  (call-with-spawner/wait
-   (lambda (spawn)
-     (call-with-temporary-directory
-      (lambda (somewhere)
-       (define where (in-vicinity somewhere "sock.et"))
-       (define config (make-nse-config where))
-       (define (#{don't-call-me}# . rest)
-         (error "oops ~a" rest))
-       (define server (nse:connect config #:spawn spawn
-                                   #:connected #{don't-call-me}#
-                                   #:disconnected #{don't-call-me}#
-                                   #:updated #{don't-call-me}#))
-       (sleep 0.001)
-       (nse:disconnect! server)
-       (sleep 0.001)
-       #t)))))
+  (close-not-connected-no-fallbacks
+   "nse" nse:connect nse:disconnect!
+   #:rest (list #:disconnected #{don't-call-me}#)))
 
 (test-assert "close, connected --> all fibers stop, two callbacks called"
   (call-with-spawner/wait
@@ -303,7 +282,7 @@ is @var{where}."
      (call-with-temporary-directory
       (lambda (somewhere)
        (define where (in-vicinity somewhere "sock.et"))
-       (define config (make-nse-config where))
+       (define config (trivial-service-config "nse" where))
        (define (#{don't-call-me}# . rest)
          (error "oops ~a" rest))
        (define connected? #f)
diff --git a/tests/utils.scm b/tests/utils.scm
index 36a4306..a117eca 100644
--- a/tests/utils.scm
+++ b/tests/utils.scm
@@ -1,5 +1,5 @@
 ;; This file is part of scheme-GNUnet.
-;; Copyright (C) 2021 GNUnet e.V.
+;; Copyright © 2021, 2022 GNUnet e.V.
 ;;
 ;; scheme-GNUnet is free software: you can redistribute it and/or modify it
 ;; under the terms of the GNU Affero General Public License as published
@@ -24,6 +24,7 @@
   #:use-module ((rnrs base) #:select (assert))
   #:use-module ((fibers) #:prefix #{fibers:}#)
   #:autoload (fibers conditions) (make-condition signal-condition! wait)
+  #:autoload (fibers timers) (sleep)
   #:autoload (gnu gnunet config db)
   (hash->configuration hash-key key=? set-value!)
   #:export (conservative-gc? calls-in-tail-position?
@@ -32,7 +33,11 @@
                             call-with-spawner
                             call-with-spawner/wait
                             call-with-temporary-directory
-                            make-nonblocking!))
+                            make-nonblocking!
+                            call-with-absent-service
+                            trivial-service-config
+                            #{don't-call-me}#
+                            close-not-connected-no-fallbacks))
 
 (define (make-nonblocking! sock)
   (fcntl sock F_SETFL
@@ -172,3 +177,45 @@ the services and each tails is a list of a procedure 
accepting ports
           (for-each wait (hash-map->list (lambda (x y) x) h))
           (apply values return-values))
         args))
+
+(define (trivial-service-config what where)
+  "Make a configuration where the socket location of the @var{what} service
+is @var{where}."
+  (define config (hash->configuration
+                 (rnrs:make-hashtable hash-key key=?)))
+  (set-value! identity config what "UNIXPATH" where)
+  config)
+
+(define (call-with-absent-service what proc)
+  "Call @var{proc} with a configuration in which the @var{what} service
+cannot be connected to."
+  (call-with-temporary-directory
+   (lambda (somewhere)
+     ;; Something like "/dev/this-file-does-not-exist" would do as well.
+     (define where (in-vicinity somewhere "sock.et"))
+     (define config (trivial-service-config what where))
+     (proc config))))
+
+(define (#{don't-call-me}# . rest)
+  (error "oops ~a" rest))
+
+(define* (close-not-connected-no-fallbacks service connect disconnect!
+                                          #:key (rest '()))
+  "Try to connect to the @var{service} service in an environment where
+the service daemon is down.  Verify that the 'connected' and 'disconnected'
+callbacks were not called.  Also verify that all spawned fibers exit."
+  (call-with-spawner/wait
+   (lambda (spawn)
+     (call-with-absent-service
+      service
+      (lambda (config)
+       (define server (apply connect config #:spawn spawn
+                             #:connected #{don't-call-me}#
+                             #:disconnected #{don't-call-me}#
+                             rest))
+       ;; Sleep to give the client fibers a chance to mistakenly
+       ;; call a callback.
+       (sleep 0.001)
+       (disconnect! server)
+       (sleep 0.001)
+       #t)))))

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]