distributed-hash-table.scm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. ;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
  2. ;; Copyright (C) 2021, 2022 GNUnet e.V.
  3. ;;
  4. ;; scheme-GNUnet is free software: you can redistribute it and/or modify it
  5. ;; under the terms of the GNU Affero General Public License as published
  6. ;; by the Free Software Foundation, either version 3 of the License,
  7. ;; or (at your option) any later version.
  8. ;;
  9. ;; scheme-GNUnet is distributed in the hope that it will be useful, but
  10. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Affero General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Affero General Public License
  15. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. ;;
  17. ;; SPDX-License-Identifier: AGPL-3.0-or-later
  18. (define-module (test-distributed-hash-table))
  19. (import (ice-9 match)
  20. (ice-9 binary-ports)
  21. (quickcheck)
  22. (quickcheck arbitrary)
  23. (quickcheck generator)
  24. (quickcheck property)
  25. (gnu gnunet dht client)
  26. (gnu gnunet dht network)
  27. (gnu gnunet dht struct)
  28. (gnu gnunet utils bv-slice)
  29. (gnu gnunet utils hat-let)
  30. (gnu gnunet util struct)
  31. (gnu gnunet netstruct syntactic)
  32. (only (gnu gnunet netstruct procedural) u64/big)
  33. (gnu gnunet hashcode struct)
  34. (gnu gnunet block)
  35. (gnu gnunet message protocols)
  36. (gnu gnunet mq)
  37. (gnu gnunet mq error-reporting)
  38. (gnu gnunet mq handler)
  39. (gnu gnunet mq-impl stream)
  40. (gnu extractor enum)
  41. (rnrs exceptions)
  42. (rnrs conditions)
  43. (rnrs base)
  44. (rnrs bytevectors)
  45. (srfi srfi-26)
  46. (srfi srfi-64)
  47. (fibers conditions)
  48. (fibers channels)
  49. (fibers operations)
  50. (fibers scheduler)
  51. (fibers timers) ; sleep
  52. (tests utils))
  53. ;; Use the @code{error} from Guile, not RnRS.
  54. (define error (@ (guile) error))
  55. ;; Copied from tests/bv-slice.scm.
  56. (define-syntax-rule (test-missing-caps test-case what permitted required code)
  57. (test-equal test-case
  58. (list what permitted required)
  59. (guard (c ((missing-capabilities? c)
  60. (list (missing-capabilities-what c)
  61. (missing-capabilities-permitted c)
  62. (missing-capabilities-required c))))
  63. code)))
  64. (define-syntax-rule (test-overly-large-datum test-case who canonical-type
  65. length)
  66. (test-equal test-case
  67. (list who canonical-type length)
  68. (guard (c ((overly-large-datum? c)
  69. (list (overly-large-datum-who c)
  70. (missing-capabilities-permitted c)
  71. (missing-capabilities-required c))))
  72. code)))
  73. ;; It's easy to accidentally swap the min and the max,
  74. ;; or use theoretical bounds instead of effective bounds.
  75. (test-begin "bound-replication-level")
  76. (define-syntax test-bound-equals
  77. (syntax-rules (->)
  78. ((_ (name argument -> expected) ...)
  79. (begin
  80. (test-equal name (list expected)
  81. (call-with-values
  82. (lambda ()
  83. (bound-replication-level argument))
  84. list))
  85. ...))))
  86. (test-bound-equals
  87. ;; Boundaries of set of fixed points
  88. ("effective minimum" %effective-minimum-replication-level
  89. -> %effective-minimum-replication-level)
  90. ("effective maximum" %effective-maximum-replication-level
  91. -> %effective-maximum-replication-level)
  92. ;; off by one
  93. ("zero" ; remove this test if %effective-minimum-replication-level becomes zero
  94. (begin (assert (> %effective-minimum-replication-level %minimum-replication-level))
  95. %effective-minimum-replication-level)
  96. -> %effective-minimum-replication-level)
  97. ("effective maximum + 1"
  98. (begin (assert (< %effective-maximum-replication-level %maximum-replication-level))
  99. (+ 1 %effective-maximum-replication-level))
  100. -> %effective-maximum-replication-level)
  101. ;; Extreme values
  102. ("theoretical minimum" %minimum-replication-level
  103. -> %effective-minimum-replication-level)
  104. ("theoretical maximum" %maximum-replication-level
  105. -> %effective-maximum-replication-level))
  106. (define between
  107. (map (cut + %effective-minimum-replication-level <>)
  108. (iota (- %effective-maximum-replication-level
  109. %effective-minimum-replication-level))))
  110. ;; Inner fixed points
  111. (test-equal "between effective extrema"
  112. between
  113. (map bound-replication-level between))
  114. (test-error "too large" (bound-replication-level (+ 1 %maximum-replication-level)))
  115. (test-error "way too large" (bound-replication-level (* #e1e20 %maximum-replication-level)))
  116. (test-error "too small" (bound-replication-level (- %minimum-replication-level 1)))
  117. (test-error "way too small" (bound-replication-level (- %minimum-replication-level #e1e20)))
  118. (test-error "non-numeric" (bound-replication-level 'what))
  119. (define (make-slice/read-write* size)
  120. "Like @code{make-slice/read-write*}, but fill the slice with random data."
  121. (define s (make-slice/read-write size))
  122. (let^ ((/o/ loop (i 0))
  123. (? (>= i size) s))
  124. (slice-u8-set! s i (random 256))
  125. (loop (+ i 1))))
  126. (define* (make-a-datum #:key
  127. (type 0)
  128. (key (make-slice/read-write* (sizeof /hashcode:512 '())))
  129. (value (make-slice/read-write 0))
  130. (expiration (random (expt 2 64))))
  131. (make-datum type key value #:expiration expiration))
  132. (test-assert "datum?"
  133. (datum? (make-a-datum)))
  134. (test-equal "not a datum"
  135. '(#false #false #false)
  136. (map datum? (list #false 'symbol (make-slice/read-write 0))))
  137. ;; For efficiency reasons, make sure the storage is reused.
  138. ;;
  139. ;; This verifies constructing a record and extracting a field from the record
  140. ;; end ups with the value passed to the constructor, as a readable bytevector
  141. ;; slice -- the writability of the original slice, if any, is removed.
  142. (define (slice-property-test test-case generate-slice stuff? slice->stuff stuff-slice)
  143. (test-assert test-case
  144. ;; only evaluate once, because eq? will be required
  145. (let* ((slice (generate-slice))
  146. (stuff (slice->stuff slice))
  147. (new-slice (stuff-slice stuff)))
  148. (and (stuff? stuff)
  149. (slice-readable? new-slice)
  150. (not (slice-writable? new-slice))
  151. (eq? (slice-bv slice) (slice-bv new-slice))
  152. (= (slice-length slice) (slice-length new-slice))))))
  153. (define-syntax-rule (datum-key-test test-case k)
  154. (slice-property-test test-case (lambda () k) datum?
  155. (lambda (s) (make-a-datum #:key s)) datum-key))
  156. (define-syntax-rule (datum-value-test test-case v)
  157. (slice-property-test test-case (lambda () v) datum?
  158. (lambda (s) (make-a-datum #:value s)) datum-value))
  159. (define-syntax-rule (datum-type-test test-case type type/integer)
  160. (test-equal test-case
  161. type/integer
  162. (datum-type (make-a-datum #:type type))))
  163. (datum-key-test "datum-key"
  164. (make-slice/read-write* (sizeof /hashcode:512 '())))
  165. (datum-key-test "datum-key, read-only is sufficient"
  166. (slice/read-only
  167. (make-slice/read-write*
  168. (sizeof /hashcode:512 '()))))
  169. (test-missing-caps
  170. "datum key must be readable"
  171. 'key
  172. CAP_WRITE
  173. CAP_READ
  174. (make-a-datum #:key (slice/write-only (make-slice/read-write*
  175. (sizeof /hashcode:512 '())))))
  176. ;; AFAIK a zero length value is allowed, albeit somewhat pointless?
  177. (datum-value-test "datum-value, length 0" (make-slice/read-write 0))
  178. (datum-value-test "datum-value, maximal length"
  179. (make-slice/read-write* %max-datum-value-length))
  180. (datum-value-test "datum-value" (make-slice/read-write* 900))
  181. (define (test-datum-overly-large test-case type type/integer length)
  182. (test-equal test-case
  183. (list 'make-datum type/integer length)
  184. (guard (c ((overly-large-datum? c)
  185. (list (condition-who c)
  186. (overly-large-datum-type c)
  187. (overly-large-datum-length c))))
  188. (make-a-datum #:type type #:value (make-slice/read-write* length)))))
  189. (test-datum-overly-large
  190. "datum-value, too large (1, numeric type)" 19 19
  191. (* 2 %max-datum-value-length))
  192. (test-datum-overly-large
  193. "datum-value, too large (2, numeric type)" 19 19
  194. (* 2 %max-datum-value-length))
  195. (test-datum-overly-large
  196. "datum-value, too large (1, symbolic type)" (symbol-value block-type block:revocation) 12
  197. (* 2 %max-datum-value-length))
  198. (datum-type-test "datum-key, symbolic type (1)"
  199. (symbol-value block-type block:consensus-element) 25)
  200. (datum-type-test "datum-key, symbolic type (2)"
  201. (symbol-value block-type block:dht:hello) 7)
  202. (datum-type-test "datum-key, known numeric type (1)" 7 7)
  203. (datum-type-test "datum-key, known numeric type (2)" 8 8)
  204. (datum-type-test "datum-key, unknown numeric type" 4294967295 4294967295)
  205. (test-error "datum-type, out-of-bounds" (make-a-datum #:type 4294967296))
  206. (test-error "datum-type, wrong enumeration"
  207. (make-a-datum #:type (symbol-value message-type msg:util:dummy)))
  208. (test-error "datum-type, wrong type (1)" (make-a-datum #:type 'foo))
  209. ;; This detected a bug!
  210. (test-error "datum-type, wrong type (2)" (make-a-datum #:type 1.0))
  211. (define (slice->bytevector s)
  212. (define b (make-bytevector (slice-length s)))
  213. (define s2 (bv-slice/read-write b))
  214. (slice-copy! s s2)
  215. b)
  216. (define (query->sexp z)
  217. (list (query-type z) (slice->bytevector (query-key z))
  218. (query-desired-replication-level z)))
  219. (define (datum->sexp z)
  220. (list (datum-type z)
  221. (slice->bytevector (datum-key z))
  222. (slice->bytevector (datum-value z))
  223. (datum-expiration z)))
  224. (define (insertion->sexp z)
  225. (list (datum->sexp (insertion->datum z))
  226. (insertion-desired-replication-level z)))
  227. (define (search-result->sexp z)
  228. (list (slice->bytevector (search-result-get-path z))
  229. (slice->bytevector (search-result-put-path z))
  230. (datum->sexp (search-result->datum z))))
  231. (define (query=? x y)
  232. (equal? (query->sexp x) (query->sexp y)))
  233. (define (datum=? x y)
  234. (equal? (datum->sexp x) (datum->sexp y)))
  235. (define (search-result=? x y)
  236. (equal? (search-result->sexp x) (search-result->sexp y)))
  237. (define (insertion=? x y)
  238. (equal? (insertion->sexp x) (insertion->sexp y)))
  239. (define (query-independent? x y)
  240. (slice-independent? (query-key x) (query-key y)))
  241. (define (datum-independent? x y)
  242. (and (slice-independent? (datum-key x) (datum-key y))
  243. (slice-independent? (datum-value x) (datum-value y))))
  244. (define (insertion-independent? x y)
  245. (datum-independent? (insertion->datum x) (insertion->datum y)))
  246. (define (search-result-independent? x y)
  247. (and (datum-independent? (search-result->datum x) (search-result->datum y))
  248. (slice-independent? (search-result-get-path x)
  249. (search-result-get-path y))
  250. (slice-independent? (search-result-put-path x)
  251. (search-result-put-path y))))
  252. (test-assert "copy-query: equal and independent"
  253. (let* ((old-key (make-slice/read-write* (sizeof /hashcode:512 '())))
  254. (type (random 65536))
  255. (desired-replication-level (+ 1 %maximum-replication-level))
  256. (old (make-query type old-key))
  257. (new (copy-query old)))
  258. (and (query=? old new)
  259. (query-independent? old new))))
  260. (test-assert "copy-datum: equal and independent"
  261. ;; A least in Guile 3.0.5, all bytevectors of length 0 are eq?,
  262. ;; so let the value be non-empty such that datum-independent?
  263. ;; can return #true.
  264. (let* ((old-key (make-slice/read-write* (sizeof /hashcode:512 '())))
  265. (old-value (make-slice/read-write* 70))
  266. (old (make-a-datum #:key old-key #:value old-value #:expiration 777))
  267. (new (copy-datum old)))
  268. (and (datum=? old new)
  269. (datum-independent? old new))))
  270. (define (path-length->size l)
  271. (* l (sizeof /dht:path-element '())))
  272. ;; Detected a bug: the datum was not copied
  273. (test-assert "copy-search-result: equal and independent"
  274. (let* ((old-key (make-slice/read-write* (sizeof /hashcode:512 '())))
  275. (old-value (make-slice/read-write* 70))
  276. (old-get-path (make-slice/read-write* (path-length->size 5)))
  277. (old-put-path (make-slice/read-write* (path-length->size 9)))
  278. (old-datum (make-a-datum #:value old-value #:expiration 555))
  279. (old (datum->search-result old-datum #:get-path old-get-path
  280. #:put-path old-put-path))
  281. (new (copy-search-result old)))
  282. (and (search-result=? old new)
  283. (search-result-independent? old new))))
  284. (test-assert "copy-insertion: equal and independent"
  285. (let* ((old-value (make-slice/read-write* 71))
  286. (old-datum (make-a-datum #:value old-value))
  287. (old
  288. (datum->insertion old-datum #:desired-replication-level (random 8)))
  289. (new (copy-insertion old)))
  290. (and (insertion=? old new)
  291. (insertion-independent? old new))))
  292. (define-syntax-rule (search-result-get-path-slice-test test-case k)
  293. (slice-property-test test-case (lambda () k) search-result?
  294. (lambda (s) (datum->search-result (make-a-datum)
  295. #:get-path s))
  296. search-result-get-path))
  297. (define-syntax-rule (search-result-put-path-slice-test test-case k)
  298. (slice-property-test test-case (lambda () k) search-result?
  299. (lambda (s) (datum->search-result (make-a-datum)
  300. #:put-path s))
  301. search-result-put-path))
  302. ;; These detected a bug: the capabilities were not restricted!
  303. ;; TODO: can there be a get path without a put path?
  304. (search-result-get-path-slice-test
  305. "search-result-get-path, slice" (make-slice/read-write* (path-length->size 7)))
  306. (search-result-get-path-slice-test
  307. "search-result-get-path, empty" (make-slice/read-write 0))
  308. (search-result-put-path-slice-test
  309. "search-result-put-path, slice" (make-slice/read-write* (path-length->size 7)))
  310. (search-result-put-path-slice-test
  311. "search-result-put-path, empty" (make-slice/read-write 0))
  312. (test-equal "search-result-get-path, none"
  313. (list #false) ; TODO: drop 'list' when SRFI-64 bug is fixed
  314. (list (search-result-get-path (datum->search-result (make-a-datum)))))
  315. (test-equal "search-result-put-path, none"
  316. (list #false) ; TODO: drop 'list' when SRFI-64 bug is fixed
  317. (list (search-result-put-path (datum->search-result (make-a-datum)))))
  318. (test-missing-caps
  319. "search-result get-path must be readable"
  320. 'get-path
  321. CAP_WRITE
  322. CAP_READ
  323. (datum->search-result
  324. (make-a-datum) #:get-path
  325. (slice/write-only (make-slice/read-write* (path-length->size 7)))))
  326. (test-missing-caps
  327. "search-result put-path must be readable"
  328. 'put-path
  329. CAP_WRITE
  330. CAP_READ
  331. (datum->search-result
  332. (make-a-datum) #:put-path
  333. (slice/write-only (make-slice/read-write* (path-length->size 7)))))
  334. (define (test-malformed-path test-case what keyword)
  335. (test-assert test-case
  336. (quickcheck
  337. (property
  338. ((elements $natural)
  339. (remainder
  340. (arbitrary
  341. (gen (choose-integer 1 (- (sizeof /dht:path-element '()) 1)))
  342. (xform #false))))
  343. (let ((size (+ remainder (* (sizeof /dht:path-element '())))))
  344. (equal? (guard (c ((malformed-path? c)
  345. (list (condition-who c)
  346. (malformed-path-what c)
  347. (malformed-path-size c))))
  348. (datum->search-result
  349. (make-a-datum) keyword
  350. (make-slice/read-write* size)))
  351. (list 'datum->search-result what size)))))))
  352. (test-malformed-path
  353. "get-path size must be a multiple of the size of a path element"
  354. 'get-path #:get-path)
  355. (test-malformed-path
  356. "put-path size must be a multiple of the size of a path element"
  357. 'put-path #:put-path)
  358. ;;;
  359. ;;; Test client<->service communication.
  360. ;;;
  361. ;;; Currently, the following operations are tested:
  362. ;;;
  363. ;;; * [x] insertion (@code{put!})
  364. ;;; * [x] retrieval (@code{start-get!})
  365. ;;; * [x] disconnecting
  366. ;;; * [ ] monitoring
  367. ;;;
  368. ;;; In the following contexts:
  369. ;;;
  370. ;;; * [x] nothing special
  371. ;;; * [ ] after a reconnection
  372. ;;; * [ ] requested before a reconnection, without being processed
  373. ;;; before the reconnection.
  374. ;;; * [ ] requested (and started) before a reconnection and continued
  375. ;;; after the reconnection
  376. ;;;
  377. ;;; Cancelling, closing the connection, parallelism and multiple
  378. ;;; in-progress requests are currently untested (TBD and implemented!).
  379. (define i (datum->insertion (make-a-datum) #:desired-replication-level 7))
  380. (define (no-error-handler . e)
  381. (pk 'e e)
  382. (error "no error handler"))
  383. ;; TODO: would be nice to turn this in a real service
  384. ;; (gnu gnunet dht service).
  385. (define* (simulate-dht-service #:optional (explode (make-condition)))
  386. "Simulate a DHT service, remembering all insertions and ignoring expiration
  387. and replication. Cancellation is ignored (TODO). Only a single client is
  388. supported. When @var{explode} is signalled, the connection is closed."
  389. (define (slice->bv slice)
  390. (define bv (make-bytevector (slice-length slice)))
  391. (define bv/slice (bv-slice/read-write bv))
  392. (slice-copy! slice bv/slice)
  393. bv)
  394. (define (query->key query)
  395. (cons (query-type query) (slice->bv (query-key query))))
  396. (define (insertion->key insertion)
  397. (define datum (insertion->datum insertion))
  398. (cons (datum-type datum) (slice->bv (datum-key datum))))
  399. ;; Mapping from (numeric type + key bytevector)
  400. ;; --> (list of value . interested mq channels)
  401. (define table (make-hash-table))
  402. (define table-channel (make-channel))
  403. (define mq)
  404. (define mq-defined (make-condition))
  405. (define (handle-table spawn-fiber)
  406. (define (put-message/async channel message)
  407. (assert (channel? channel))
  408. (spawn-fiber
  409. (lambda ()
  410. (put-message channel message))))
  411. (match (perform-operation
  412. (choice-operation (get-operation table-channel)
  413. (wrap-operation (wait-operation explode)
  414. (lambda () 'explode))))
  415. ('explode
  416. (wait mq-defined)
  417. (close-queue! mq))
  418. (('start-get! query response-channel)
  419. (let* ((key (query->key query))
  420. (old (hash-ref table key '(() . ())))
  421. (old-values (car old))
  422. (channels (cdr old)))
  423. ;; Send currently known values.
  424. (for-each
  425. (lambda (old-value)
  426. (put-message/async response-channel old-value))
  427. old-values)
  428. ;; Send future values to the channel as well.
  429. (hash-set! table key
  430. `(,old-values ,response-channel ,@channels))))
  431. (('put! insertion)
  432. (let* ((key (insertion->key insertion))
  433. (old (hash-ref table key '(() . ())))
  434. (old-values (car old))
  435. (channels (cdr old))
  436. (new-values (cons insertion old-values)))
  437. ;; Send the new value.
  438. (for-each
  439. (lambda (response-channel)
  440. (put-message/async response-channel insertion))
  441. channels)
  442. (hash-set! table key `(,new-values . ,channels)))))
  443. (handle-table spawn-fiber))
  444. (lambda (port spawn-fiber)
  445. (spawn-fiber (lambda () (handle-table spawn-fiber)))
  446. (let^ ((! (simple-message-handler type* handle!*)
  447. (message-handler
  448. (type type*)
  449. ((interpose foo) foo)
  450. ((well-formed? s) #true)
  451. ((handle! slice) (handle!* slice))))
  452. (!^ (handle/put! message)
  453. "Respond to a @code{/:msg:dht:client:put} message."
  454. ((<-- (insertion _) (analyse-client-put message))
  455. (! insertion (copy-insertion insertion)))
  456. (put-message table-channel `(put! ,insertion)))
  457. (!^ (handle/start-get! message)
  458. ""
  459. ((! channel (make-channel))
  460. (<-- (query unique-id _) (analyse-client-get message))
  461. (! query (copy-query query)))
  462. (put-message table-channel `(start-get! ,query ,channel))
  463. (spawn-fiber
  464. (lambda ()
  465. (let^ ((/o/ loop)
  466. (! insertion (get-message channel))
  467. ;; The tests don't require get-path/put-path.
  468. (! search-result (datum->search-result
  469. (insertion->datum insertion)))
  470. (! message (construct-client-result search-result
  471. unique-id)))
  472. (wait mq-defined)
  473. (send-message! mq message)
  474. (loop))))
  475. (values))
  476. (! h (message-handlers
  477. (simple-message-handler
  478. (symbol-value message-type msg:dht:client:put)
  479. handle/put!)
  480. ;; TODO: handle properly
  481. (simple-message-handler
  482. (symbol-value message-type msg:dht:client:get:stop)
  483. (lambda (slice) (values)))
  484. (simple-message-handler
  485. (symbol-value message-type msg:dht:client:get)
  486. handle/start-get!))))
  487. (set! mq
  488. (port->message-queue port h no-error-handler #:spawn spawn-fiber))
  489. (signal-condition! mq-defined)
  490. (values))))
  491. (test-equal "put! sends one message to service, after connecting"
  492. i
  493. (let^ ((! connected? #false)
  494. (! (connected)
  495. (assert (not connected?))
  496. (set! connected? #true))
  497. (! message #false)
  498. (! message-received (make-condition))
  499. (! (handle slice)
  500. (when message
  501. (error "already received"))
  502. (set! message slice)
  503. (signal-condition! message-received))
  504. (! h (message-handlers
  505. (message-handler
  506. (type (symbol-value message-type msg:dht:client:put))
  507. ((interpose foo) foo)
  508. ((well-formed? s) #true)
  509. ((handle! slice) (handle slice))))))
  510. (call-with-services/fibers
  511. `(("dht" . ,(lambda (port spawn-fiber)
  512. (define mq
  513. (port->message-queue port h no-error-handler
  514. #:spawn spawn-fiber))
  515. (values))))
  516. (lambda (config spawn-fiber)
  517. (define server
  518. (connect config #:connected connected #:spawn spawn-fiber))
  519. (put! server i)
  520. (wait message-received)
  521. (pk 'server server) ; keep 'server' reachable
  522. (assert connected?)
  523. (assert message)
  524. (let^ ((<-- (insertion _)
  525. (analyse-client-put message)))
  526. ;; TODO: copy to make equal? work
  527. ;; (TODO: define equal? for slices)
  528. (copy-insertion insertion))))))
  529. ;; Squat two message types for tests below.
  530. (define type:ping 7)
  531. (define type:pong 8)
  532. (test-assert "synchronuous ping-pong with multiple balls (no interruptions, no cancellation)"
  533. (call-with-services/fibers
  534. `(("dht" . ,(simulate-dht-service)))
  535. (lambda (config spawn-fiber)
  536. (define N_ROUNDS 50)
  537. (define server
  538. (connect config #:spawn spawn-fiber))
  539. (define (round->key round)
  540. (define key (make-slice/read-write (sizeof /hashcode:512 '())))
  541. (slice-u64-set! key 0 round (endianness little))
  542. key)
  543. (define (make-a-insertion type round j)
  544. (define key (round->key round))
  545. (define value (make-slice/read-write 8))
  546. (slice-u64-set! value 0 j (endianness little))
  547. (datum->insertion (make-datum type key value)))
  548. (define (make-a-query type round)
  549. (define key (round->key round))
  550. (make-query type key))
  551. (define (n-responses-for-round round)
  552. (+ 1 (mod round 8)))
  553. (define (ping/pong type round)
  554. ;; round: number (used as key)
  555. ;; j: value
  556. ;;
  557. ;; Multiple values are inserted for the same key,
  558. ;; to test iteration.
  559. (let loop ((j 0))
  560. (when (< j (n-responses-for-round round))
  561. (put! server (make-a-insertion type round j))
  562. (loop (+ 1 j)))))
  563. (define (search-result->j type search-result)
  564. (define datum (search-result->datum search-result))
  565. (define value (datum-value datum))
  566. (assert (= (slice-length value) 8)) ; u64
  567. (assert (= type (datum-type datum)))
  568. (slice-u64-ref value 0 (endianness little)))
  569. (define (wait-for-values type round)
  570. (define done (make-condition))
  571. (define responses '())
  572. (define (found search-result)
  573. (set! responses
  574. (cons (search-result->j type search-result) responses))
  575. (define length/current (length responses))
  576. (define length/expected (n-responses-for-round round))
  577. (when (>= length/current length/expected)
  578. ;; Duplicated responses might happen in practice, but should
  579. ;; be avoided when feasible.
  580. (assert (= length/current length/expected))
  581. (assert (equal? (sort responses <) (iota length/expected)))
  582. ;; TODO: cancel query
  583. (signal-condition! done)))
  584. (define search (start-get! server (make-a-query type round) found
  585. ;; Not testing cancellation on GC here.
  586. #:linger? #true))
  587. (wait done))
  588. (define* (ping/pong* this-type other-type round)
  589. (when (< round N_ROUNDS)
  590. (ping/pong this-type round)
  591. (wait-for-values other-type round)
  592. (ping/pong* this-type other-type (+ 1 round))))
  593. (define (spawn-ping/pong* this-type other-type)
  594. (define done (make-condition))
  595. (spawn-fiber
  596. (lambda ()
  597. (ping/pong* this-type other-type 0)
  598. (signal-condition! done)))
  599. done)
  600. (define ping (spawn-ping/pong* type:ping type:pong))
  601. (define pong (spawn-ping/pong* type:pong type:ping))
  602. (wait ping)
  603. (wait pong)
  604. #true)))
  605. (test-assert "(DHT) close, not connected --> all fibers stop, no callbacks called"
  606. (close-not-connected-no-fallbacks "dht" connect disconnect!))
  607. (test-assert "(DHT) garbage collectable"
  608. (garbage-collectable "dht" connect))
  609. (define* (determine-reported-errors proc #:key (n-connections 1) (n-errors 1))
  610. (call-with-spawner/wait*
  611. (lambda (config spawn)
  612. (define errors '())
  613. (define currently-connected? #false)
  614. (define times-connected 0)
  615. (define times-errored 0)
  616. (define finally-disconnected-c (make-condition))
  617. (define all-errors-c (make-condition))
  618. (parameterize ((error-reporter (lambda foo
  619. (assert (> times-connected 0))
  620. (set! times-errored (+ 1 times-errored))
  621. (set! errors (cons foo errors))
  622. (when (>= times-errored n-errors)
  623. (signal-condition! all-errors-c)))))
  624. (define (connected)
  625. (assert (not currently-connected?))
  626. (set! currently-connected? #true)
  627. (set! times-connected (+ 1 times-connected))
  628. (assert (<= times-connected n-connections)))
  629. (define (disconnected)
  630. (assert currently-connected?)
  631. (set! currently-connected? #false)
  632. (when (= times-connected n-connections)
  633. (signal-condition! finally-disconnected-c)))
  634. (define server
  635. (connect config #:connected connected #:disconnected disconnected
  636. #:spawn spawn))
  637. ;; Give 'error-reporter' a chance to be called too often
  638. (sleep 0.001)
  639. ;; The error handler and 'disconnected' are called in no particular
  640. ;; order, so we have to wait for both.
  641. (wait finally-disconnected-c)
  642. (wait all-errors-c)
  643. ;; keep 'server' reachable long enough.
  644. (pk server)
  645. (and (not currently-connected?)
  646. (= times-connected n-connections) errors)))
  647. `(("dht" . ,proc))))
  648. (define (put-ill-formed-message port)
  649. (define b (make-bytevector (sizeof /:message-header '())))
  650. (define s (slice/write-only (bv-slice/read-write b)))
  651. (set%! /:message-header '(type) s
  652. (value->index (symbol-value message-type msg:dht:client:result)))
  653. (set%! /:message-header '(size) s (slice-length s))
  654. (put-bytevector port b))
  655. (test-equal "(DHT) ill-formed message from service --> all fibers stop, 'connected' and 'disconnected' called"
  656. `((logic:ill-formed
  657. ,(value->index (symbol-value message-type msg:dht:client:result))))
  658. (determine-reported-errors
  659. (lambda (port spawn-fiber)
  660. (put-ill-formed-message port)
  661. (close-port port))))
  662. ;; Allow reconnecting a few times and eventually ensure a permanent
  663. ;; disconnecting to make the test terminate.
  664. (define n-connections 7)
  665. (test-equal "(DHT) end-of-file --> reconnect (all fibers eventually stop)"
  666. `((logic:ill-formed
  667. ,(value->index (symbol-value message-type msg:dht:client:result))))
  668. (determine-reported-errors
  669. (let ((i 0))
  670. (lambda (port spawn-fiber)
  671. (set! i (+ i 1))
  672. (assert (<= i n-connections))
  673. (when (= i n-connections)
  674. (put-ill-formed-message port))
  675. (close-port port)))
  676. #:n-connections n-connections))
  677. ;; TODO: would be nice to test that old requests are submitted again
  678. ;; The aim is to show that the search callback can start search requests
  679. ;; of its own without any problems. While we're at it, the search results
  680. ;; are verified.
  681. ;;
  682. ;; First 'loop' searches for key 0, then for key 1 inside the search result
  683. ;; callback, etc.
  684. (test-assert "search callback re-entrancy"
  685. (call-with-services/fibers
  686. `(("dht" . ,(simulate-dht-service)))
  687. (lambda (config spawn-fiber)
  688. (define server (connect config))
  689. (define ROUNDS 20)
  690. (define type 0) ; arbitrary
  691. (define (make-a-query round)
  692. (define key (make-slice/read-write (sizeof /hashcode:512 '())))
  693. (slice-u64-set! key 0 round (endianness big))
  694. (make-query type key))
  695. (define (value round)
  696. (expt 2 round))
  697. (define done (make-condition))
  698. (let loop ((round 0))
  699. (define found? #false)
  700. (if (< round ROUNDS)
  701. (start-get! server (make-a-query round)
  702. (lambda (search-result)
  703. (define d (search-result->datum search-result))
  704. (assert (= round
  705. (slice-u64-ref (datum-key d) 0
  706. (endianness big))))
  707. (assert (= (value round)
  708. (slice-u64-ref (datum-value d) 0
  709. (endianness big))))
  710. (assert (not found?))
  711. (set! found? #true)
  712. (loop (+ round 1)))
  713. ;; Cancellation is tested elsewhere, don't automatically
  714. ;; cancel.
  715. #:linger? #true)
  716. (signal-condition! done)))
  717. (let loop ((round 0))
  718. (define key-s (make-slice/read-write (sizeof /hashcode:512 '())))
  719. (define value-s (make-slice/read-write (sizeof u64/big '())))
  720. (slice-u64-set! key-s 0 round (endianness big))
  721. (slice-u64-set! value-s 0 (value round) (endianness big))
  722. (put! server (datum->insertion (make-datum type key-s value-s)))
  723. (when (< round (- ROUNDS 1))
  724. (loop (+ round 1))))
  725. (wait done)
  726. #true)))
  727. ;; TODO: would be nice to verify that the necessary messages are sent to the
  728. ;; DHT service.
  729. (test-assert "cancelling a search within a search callback does not hang"
  730. (call-with-services/fibers
  731. `(("dht" . ,(simulate-dht-service)))
  732. (lambda (config spawn-fiber)
  733. (define server (connect config))
  734. (define datum (make-a-datum))
  735. (define query (make-query (datum-type datum) (datum-key datum)))
  736. (define search-defined (make-condition))
  737. (define done (make-condition))
  738. (define search
  739. (start-get! server query (lambda (a-result)
  740. (wait search-defined)
  741. (stop-get! search)
  742. (signal-condition! done))
  743. ;; The 'found' callback is responsible for cancellation.
  744. #:linger? #true))
  745. (signal-condition! search-defined)
  746. (put! server (datum->insertion datum))
  747. (wait done)
  748. #true)))
  749. (test-assert "cancelling a search multiple times does not hang"
  750. (call-with-services/fibers
  751. `(("dht" . ,(simulate-dht-service)))
  752. (lambda (config spawn-fiber)
  753. (define server (connect config))
  754. (define datum (make-a-datum))
  755. (define query (make-query (datum-type datum) (datum-key datum)))
  756. (define search (start-get! server query (lambda (foo) (values))
  757. ;; Not testing cancellation on GC here.
  758. #:linger? #true))
  759. (let loop ((n 0))
  760. (when (< n 40)
  761. (stop-get! search)
  762. (loop (+ n 1))))
  763. #true)))
  764. (test-assert "searches restarted after disconnect"
  765. (let ((stop-first-server (make-condition))
  766. (first-accepted (make-condition)))
  767. (call-with-services/fibers
  768. `(("dht" . ,(lambda args
  769. (if (signal-condition! first-accepted)
  770. (apply (simulate-dht-service stop-first-server) args)
  771. (apply (simulate-dht-service) args)))))
  772. (lambda (config spawn-fiber)
  773. (define connected/condition (make-condition))
  774. (define disconnected/condition (make-condition))
  775. (define (connected)
  776. (signal-condition! connected/condition))
  777. (define (disconnected)
  778. (signal-condition! disconnected/condition))
  779. (define server (connect config #:connected connected
  780. #:disconnected disconnected
  781. #:spawn spawn-fiber))
  782. ;; Start a search
  783. (define datum (make-a-datum))
  784. (define found/condition (make-condition))
  785. (define (found search-result)
  786. (unless (datum=? datum (search-result->datum search-result))
  787. (error "wrong search result"))
  788. (unless (signal-condition! found/condition)
  789. (error "multiple results")))
  790. (define query (make-query (datum-type datum) (datum-key datum)))
  791. (define search (start-get! server query found))
  792. ;; Give @var{server} a chance to actually send the request.
  793. ;; Removing the 'let loop' is possible, but would test some
  794. ;; different code paths (TODO enveloppe confirmation/cancellation).
  795. (wait connected/condition)
  796. (wait first-accepted)
  797. (let loop ((n 0))
  798. (when (< n 100)
  799. (yield-current-task)))
  800. ;; Break the connection, letting @var{server} reconnect.
  801. (signal-condition! stop-first-server)
  802. (wait disconnected/condition)
  803. ;; Insert the datum, such that @var{search} can complete (assuming
  804. ;; that @var{server} remembered to start the search again!).
  805. (put! server (datum->insertion datum))
  806. (wait found/condition)
  807. ;; Explicitely cancel 'search' such that it is not cancelled too
  808. ;; early due to GC.
  809. (stop-get! search)
  810. #true))))
  811. (test-end)