diff options
| author | Christopher Baines <mail@cbaines.net> | 2024-05-11 17:53:20 +0100 |
|---|---|---|
| committer | Christopher Baines <mail@cbaines.net> | 2024-05-13 19:31:17 +0100 |
| commit | 3db1a8341c815af3673c367518fbb193f5592864 (patch) | |
| tree | e598b06a59d95c4943ec7983d2605fbd3fa2c23c | |
| parent | 1632fd0f1b1ef6e55e495161d4a056d7164b8581 (diff) | |
store: Add with-store/non-blocking.
For some applications, it's important to establish a non-blocking connection
rather than just making the socket non-blocking after the connection is
established. This is because there is I/O on the socket that will block during
the handshake.
I've noticed this blocking during the handshake causing issues in the build
coordinator for example.
This commit adds a new with-store variant to avoid changing the behaviour of
with-store/open-connection to ensure that this change can't break anything
that depends on the blocking nature of the socket.
* guix/store.scm (open-unix-domain-socket, open-inet-socket): Take
#:non-blocking? and use SOCK_NONBLOCK when calling socket if appropriate.
(connect-to-daemon, open-connection, call-with-store): Take #:non-blocking?
and pass it on.
(with-store/non-blocking): New syntax rule.
* .dir-locals.el (scheme-mode): Add entry for with-store/non-blocking.
Change-Id: I8225762b78448bc1f7b698c8de5d736e13f577bf
| -rw-r--r-- | .dir-locals.el | 1 | ||||
| -rw-r--r-- | guix/store.scm | 55 |
2 files changed, 40 insertions, 16 deletions
diff --git a/.dir-locals.el b/.dir-locals.el index d2f6037afe7..d0c25b867b5 100644 --- a/.dir-locals.el +++ b/.dir-locals.el | |||
| @@ -88,6 +88,7 @@ | |||
| 88 | (eval . (put 'manifest-pattern 'scheme-indent-function 0)) | 88 | (eval . (put 'manifest-pattern 'scheme-indent-function 0)) |
| 89 | (eval . (put 'substitute-keyword-arguments 'scheme-indent-function 1)) | 89 | (eval . (put 'substitute-keyword-arguments 'scheme-indent-function 1)) |
| 90 | (eval . (put 'with-store 'scheme-indent-function 1)) | 90 | (eval . (put 'with-store 'scheme-indent-function 1)) |
| 91 | (eval . (put 'with-store/non-blocking 'scheme-indent-function 1)) | ||
| 91 | (eval . (put 'with-external-store 'scheme-indent-function 1)) | 92 | (eval . (put 'with-external-store 'scheme-indent-function 1)) |
| 92 | (eval . (put 'with-error-handling 'scheme-indent-function 0)) | 93 | (eval . (put 'with-error-handling 'scheme-indent-function 0)) |
| 93 | (eval . (put 'with-mutex 'scheme-indent-function 1)) | 94 | (eval . (put 'with-mutex 'scheme-indent-function 1)) |
diff --git a/guix/store.scm b/guix/store.scm index a238cb627ac..58ddaa8d152 100644 --- a/guix/store.scm +++ b/guix/store.scm | |||
| @@ -106,6 +106,7 @@ | |||
| 106 | port->connection | 106 | port->connection |
| 107 | close-connection | 107 | close-connection |
| 108 | with-store | 108 | with-store |
| 109 | with-store/non-blocking | ||
| 109 | set-build-options | 110 | set-build-options |
| 110 | set-build-options* | 111 | set-build-options* |
| 111 | valid-path? | 112 | valid-path? |
| @@ -462,12 +463,17 @@ | |||
| 462 | (file file) | 463 | (file file) |
| 463 | (errno errno)))))))) | 464 | (errno errno)))))))) |
| 464 | 465 | ||
| 465 | (define (open-unix-domain-socket file) | 466 | (define* (open-unix-domain-socket file #:key non-blocking?) |
| 466 | "Connect to the Unix-domain socket at FILE and return it. Raise a | 467 | "Connect to the Unix-domain socket at FILE and return it. Raise a |
| 467 | '&store-connection-error' upon error." | 468 | '&store-connection-error' upon error. If NON-BLOCKING?, make the socket |
| 469 | non-blocking." | ||
| 468 | (let ((s (with-fluids ((%default-port-encoding #f)) | 470 | (let ((s (with-fluids ((%default-port-encoding #f)) |
| 469 | ;; This trick allows use of the `scm_c_read' optimization. | 471 | ;; This trick allows use of the `scm_c_read' optimization. |
| 470 | (socket PF_UNIX (logior SOCK_STREAM SOCK_CLOEXEC) 0))) | 472 | (socket PF_UNIX |
| 473 | (if non-blocking? | ||
| 474 | (logior SOCK_STREAM SOCK_CLOEXEC SOCK_NONBLOCK) | ||
| 475 | (logior SOCK_STREAM SOCK_CLOEXEC)) | ||
| 476 | 0))) | ||
| 471 | (a (make-socket-address PF_UNIX file))) | 477 | (a (make-socket-address PF_UNIX file))) |
| 472 | 478 | ||
| 473 | (system-error-to-connection-error file | 479 | (system-error-to-connection-error file |
| @@ -478,9 +484,10 @@ | |||
| 478 | ;; Default port when connecting to a daemon over TCP/IP. | 484 | ;; Default port when connecting to a daemon over TCP/IP. |
| 479 | 44146) | 485 | 44146) |
| 480 | 486 | ||
| 481 | (define (open-inet-socket host port) | 487 | (define* (open-inet-socket host port #:key non-blocking?) |
| 482 | "Connect to the Unix-domain socket at HOST:PORT and return it. Raise a | 488 | "Connect to the Unix-domain socket at HOST:PORT and return it. Raise a |
| 483 | '&store-connection-error' upon error." | 489 | '&store-connection-error' upon error. If NON-BLOCKING?, make the socket |
| 490 | non-blocking." | ||
| 484 | (define addresses | 491 | (define addresses |
| 485 | (getaddrinfo host | 492 | (getaddrinfo host |
| 486 | (if (number? port) (number->string port) port) | 493 | (if (number? port) (number->string port) port) |
| @@ -495,7 +502,10 @@ | |||
| 495 | ((ai rest ...) | 502 | ((ai rest ...) |
| 496 | (let ((s (socket (addrinfo:fam ai) | 503 | (let ((s (socket (addrinfo:fam ai) |
| 497 | ;; TCP/IP only | 504 | ;; TCP/IP only |
| 498 | (logior SOCK_STREAM SOCK_CLOEXEC) IPPROTO_IP))) | 505 | (if non-blocking? |
| 506 | (logior SOCK_STREAM SOCK_CLOEXEC SOCK_NONBLOCK) | ||
| 507 | (logior SOCK_STREAM SOCK_CLOEXEC)) | ||
| 508 | IPPROTO_IP))) | ||
| 499 | 509 | ||
| 500 | (catch 'system-error | 510 | (catch 'system-error |
| 501 | (lambda () | 511 | (lambda () |
| @@ -514,9 +524,10 @@ | |||
| 514 | (errno (system-error-errno args))))) | 524 | (errno (system-error-errno args))))) |
| 515 | (loop rest))))))))) | 525 | (loop rest))))))))) |
| 516 | 526 | ||
| 517 | (define (connect-to-daemon uri) | 527 | (define* (connect-to-daemon uri #:key non-blocking?) |
| 518 | "Connect to the daemon at URI, a string that may be an actual URI or a file | 528 | "Connect to the daemon at URI, a string that may be an actual URI or a file |
| 519 | name, and return an input/output port. | 529 | name, and return an input/output port. If NON-BLOCKING?, use a non-blocking |
| 530 | socket when using the file, unix or guix URI schemes. | ||
| 520 | 531 | ||
| 521 | This is a low-level procedure that does not perform the initial handshake with | 532 | This is a low-level procedure that does not perform the initial handshake with |
| 522 | the daemon. Use 'open-connection' for that." | 533 | the daemon. Use 'open-connection' for that." |
| @@ -533,11 +544,13 @@ the daemon. Use 'open-connection' for that." | |||
| 533 | (match (uri-scheme uri) | 544 | (match (uri-scheme uri) |
| 534 | ((or #f 'file 'unix) | 545 | ((or #f 'file 'unix) |
| 535 | (lambda (_) | 546 | (lambda (_) |
| 536 | (open-unix-domain-socket (uri-path uri)))) | 547 | (open-unix-domain-socket (uri-path uri) |
| 548 | #:non-blocking? non-blocking?))) | ||
| 537 | ('guix | 549 | ('guix |
| 538 | (lambda (_) | 550 | (lambda (_) |
| 539 | (open-inet-socket (uri-host uri) | 551 | (open-inet-socket (uri-host uri) |
| 540 | (or (uri-port uri) %default-guix-port)))) | 552 | (or (uri-port uri) %default-guix-port) |
| 553 | #:non-blocking? non-blocking?))) | ||
| 541 | ((? symbol? scheme) | 554 | ((? symbol? scheme) |
| 542 | ;; Try to dynamically load a module for SCHEME. | 555 | ;; Try to dynamically load a module for SCHEME. |
| 543 | ;; XXX: Errors are swallowed. | 556 | ;; XXX: Errors are swallowed. |
| @@ -557,7 +570,8 @@ the daemon. Use 'open-connection' for that." | |||
| 557 | (connect uri)) | 570 | (connect uri)) |
| 558 | 571 | ||
| 559 | (define* (open-connection #:optional (uri (%daemon-socket-uri)) | 572 | (define* (open-connection #:optional (uri (%daemon-socket-uri)) |
| 560 | #:key port (reserve-space? #t) cpu-affinity) | 573 | #:key port (reserve-space? #t) cpu-affinity |
| 574 | non-blocking?) | ||
| 561 | "Connect to the daemon at URI (a string), or, if PORT is not #f, use it as | 575 | "Connect to the daemon at URI (a string), or, if PORT is not #f, use it as |
| 562 | the I/O port over which to communicate to a build daemon. | 576 | the I/O port over which to communicate to a build daemon. |
| 563 | 577 | ||
| @@ -565,7 +579,9 @@ When RESERVE-SPACE? is true, instruct it to reserve a little bit of extra | |||
| 565 | space on the file system so that the garbage collector can still operate, | 579 | space on the file system so that the garbage collector can still operate, |
| 566 | should the disk become full. When CPU-AFFINITY is true, it must be an integer | 580 | should the disk become full. When CPU-AFFINITY is true, it must be an integer |
| 567 | corresponding to an OS-level CPU number to which the daemon's worker process | 581 | corresponding to an OS-level CPU number to which the daemon's worker process |
| 568 | for this connection will be pinned. Return a server object." | 582 | for this connection will be pinned. If NON-BLOCKING?, use a non-blocking |
| 583 | socket when using the file, unix or guix URI schemes. Return a server | ||
| 584 | object." | ||
| 569 | (define (handshake-error) | 585 | (define (handshake-error) |
| 570 | (raise (condition | 586 | (raise (condition |
| 571 | (&store-connection-error (file (or port uri)) | 587 | (&store-connection-error (file (or port uri)) |
| @@ -577,7 +593,8 @@ for this connection will be pinned. Return a server object." | |||
| 577 | ;; really a connection error. | 593 | ;; really a connection error. |
| 578 | (handshake-error))) | 594 | (handshake-error))) |
| 579 | (let*-values (((port) | 595 | (let*-values (((port) |
| 580 | (or port (connect-to-daemon uri))) | 596 | (or port (connect-to-daemon |
| 597 | uri #:non-blocking? non-blocking?))) | ||
| 581 | ((output flush) | 598 | ((output flush) |
| 582 | (buffering-output-port port | 599 | (buffering-output-port port |
| 583 | (make-bytevector 8192)))) | 600 | (make-bytevector 8192)))) |
| @@ -657,9 +674,10 @@ connection. Use with care." | |||
| 657 | "Close the connection to SERVER." | 674 | "Close the connection to SERVER." |
| 658 | (close (store-connection-socket server))) | 675 | (close (store-connection-socket server))) |
| 659 | 676 | ||
| 660 | (define (call-with-store proc) | 677 | (define* (call-with-store proc #:key non-blocking?) |
| 661 | "Call PROC with an open store connection." | 678 | "Call PROC with an open store connection. Pass NON-BLOCKING? to |
| 662 | (let ((store (open-connection))) | 679 | open-connection." |
| 680 | (let ((store (open-connection #:non-blocking? non-blocking?))) | ||
| 663 | (define (thunk) | 681 | (define (thunk) |
| 664 | (parameterize ((current-store-protocol-version | 682 | (parameterize ((current-store-protocol-version |
| 665 | (store-connection-version store))) | 683 | (store-connection-version store))) |
| @@ -678,6 +696,11 @@ connection. Use with care." | |||
| 678 | automatically close the store when the dynamic extent of EXP is left." | 696 | automatically close the store when the dynamic extent of EXP is left." |
| 679 | (call-with-store (lambda (store) exp ...))) | 697 | (call-with-store (lambda (store) exp ...))) |
| 680 | 698 | ||
| 699 | (define-syntax-rule (with-store/non-blocking store exp ...) | ||
| 700 | "Bind STORE to an non-blocking open connection to the store and evaluate | ||
| 701 | EXPs; automatically close the store when the dynamic extent of EXP is left." | ||
| 702 | (call-with-store (lambda (store) exp ...) #:non-blocking? #t)) | ||
| 703 | |||
| 681 | (define current-store-protocol-version | 704 | (define current-store-protocol-version |
| 682 | ;; Protocol version of the store currently used. XXX: This is a hack to | 705 | ;; Protocol version of the store currently used. XXX: This is a hack to |
| 683 | ;; communicate the protocol version to the build output port. It's a hack | 706 | ;; communicate the protocol version to the build output port. It's a hack |
