unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
@ 2021-03-21  0:43 Christopher Baines
  2021-03-21  0:56 ` [bug#47288] [PATCH v2] " Christopher Baines
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Christopher Baines @ 2021-03-21  0:43 UTC (permalink / raw)
  To: 47288

This isn't meant to change the way errors are handled, and arguably makes the
code harder to read, but it's a uninformed attempt to improve the
performance (following on from a performance regression in
205833b72c5517915a47a50dbe28e7024dc74e57).

I'm guessing something about Guile internals makes calling (loop ...) within
the catch bit less performant than avoiding this and calling (loop ...) after
the catch bit has finished. Since this happens lots, this seems to be
sufficient to make guix weather a lot slower than it was before.

Anecdotal testing of guix weather suggests this change might work.

* guix/http-client.scm (http-multiple-get): Tweak how the second catch
statement works.
---
 guix/http-client.scm | 77 +++++++++++++++++++++++---------------------
 1 file changed, 41 insertions(+), 36 deletions(-)

diff --git a/guix/http-client.scm b/guix/http-client.scm
index 4b4c14ed0b..a189cceecf 100644
--- a/guix/http-client.scm
+++ b/guix/http-client.scm
@@ -219,42 +219,47 @@ returning."
              (remainder
               (connect p remainder result))))
           ((head tail ...)
-           (catch #t
-             (lambda ()
-               (let* ((resp   (read-response p))
-                      (body   (response-body-port resp))
-                      (result (proc head resp body result)))
-                 ;; The server can choose to stop responding at any time,
-                 ;; in which case we have to try again.  Check whether
-                 ;; that is the case.  Note that even upon "Connection:
-                 ;; close", we can read from BODY.
-                 (match (assq 'connection (response-headers resp))
-                   (('connection 'close)
-                    (close-port p)
-                    (connect #f                       ;try again
-                             (drop requests (+ 1 processed))
-                             result))
-                   (_
-                    (loop tail (+ 1 processed) result))))) ;keep going
-             (lambda (key . args)
-               ;; If PORT was cached and the server closed the connection
-               ;; in the meantime, we get EPIPE.  In that case, open a
-               ;; fresh connection and retry.  We might also get
-               ;; 'bad-response or a similar exception from (web response)
-               ;; later on, once we've sent the request, or a
-               ;; ERROR/INVALID-SESSION from GnuTLS.
-               (if (or (and (eq? key 'system-error)
-                            (= EPIPE (system-error-errno `(,key ,@args))))
-                       (and (eq? key 'gnutls-error)
-                            (eq? (first args) error/invalid-session))
-                       (memq key
-                             '(bad-response bad-header bad-header-component)))
-                   (begin
-                     (close-port p)
-                     (connect #f      ; try again
-                              (drop requests (+ 1 processed))
-                              result))
-                   (apply throw key args))))))))))
+           (match
+               (catch #t
+                 (lambda ()
+                   (let* ((resp   (read-response p))
+                          (body   (response-body-port resp))
+                          (result (proc head resp body result)))
+                     ;; The server can choose to stop responding at any time,
+                     ;; in which case we have to try again.  Check whether
+                     ;; that is the case.  Note that even upon "Connection:
+                     ;; close", we can read from BODY.
+                     (match (assq 'connection (response-headers resp))
+                       (('connection 'close)
+                        (close-port p)
+                        'try-again-with-new-connection)
+                       (_
+                        result))))
+                 (lambda (key . args)
+                   ;; If PORT was cached and the server closed the connection
+                   ;; in the meantime, we get EPIPE.  In that case, open a
+                   ;; fresh connection and retry.  We might also get
+                   ;; 'bad-response or a similar exception from (web response)
+                   ;; later on, once we've sent the request, or a
+                   ;; ERROR/INVALID-SESSION from GnuTLS.
+                   (if (or (and (eq? key 'system-error)
+                                (= EPIPE (system-error-errno `(,key ,@args))))
+                           (and (eq? key 'gnutls-error)
+                                (eq? (first args) error/invalid-session))1
+                                (memq key
+                                      '(bad-response
+                                        bad-header
+                                        bad-header-component)))
+                       (begin
+                         (close-port p)
+                         'try-again-with-new-connection)
+                       (apply throw key args))))
+             ('try-again-with-new-connection
+              (connect #f
+                       (drop requests (+ 1 processed))
+                       result))
+             (result
+              (loop tail (+ 1 processed) result)))))))))
 
 \f
 ;;;
-- 
2.30.1





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH v2] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-21  0:43 [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Christopher Baines
@ 2021-03-21  0:56 ` Christopher Baines
  2021-03-24 14:55   ` [bug#47288] [PATCH] " Ludovic Courtès
  2021-03-24 14:55   ` Ludovic Courtès
  2021-03-21  8:36 ` Maxime Devos
  2021-03-25 11:03 ` [bug#47288] [PATCH v3 1/2] " Christopher Baines
  2 siblings, 2 replies; 11+ messages in thread
From: Christopher Baines @ 2021-03-21  0:56 UTC (permalink / raw)
  To: 47288

This isn't meant to change the way errors are handled, and arguably makes the
code harder to read, but it's a uninformed attempt to improve the
performance (following on from a performance regression in
205833b72c5517915a47a50dbe28e7024dc74e57).

I'm guessing something about Guile internals makes calling (loop ...) within
the catch bit less performant than avoiding this and calling (loop ...) after
the catch bit has finished. Since this happens lots, this seems to be
sufficient to make guix weather a lot slower than it was before.

Anecdotal testing of guix weather suggests this change might work.

* guix/http-client.scm (http-multiple-get): Tweak how the second catch
statement works.
---
 guix/http-client.scm | 77 +++++++++++++++++++++++++-------------------
 1 file changed, 43 insertions(+), 34 deletions(-)

diff --git a/guix/http-client.scm b/guix/http-client.scm
index 4b4c14ed0b..a9609445c8 100644
--- a/guix/http-client.scm
+++ b/guix/http-client.scm
@@ -219,42 +219,51 @@ returning."
              (remainder
               (connect p remainder result))))
           ((head tail ...)
-           (catch #t
-             (lambda ()
-               (let* ((resp   (read-response p))
-                      (body   (response-body-port resp))
-                      (result (proc head resp body result)))
-                 ;; The server can choose to stop responding at any time,
-                 ;; in which case we have to try again.  Check whether
-                 ;; that is the case.  Note that even upon "Connection:
-                 ;; close", we can read from BODY.
-                 (match (assq 'connection (response-headers resp))
-                   (('connection 'close)
-                    (close-port p)
-                    (connect #f                       ;try again
-                             (drop requests (+ 1 processed))
-                             result))
-                   (_
-                    (loop tail (+ 1 processed) result))))) ;keep going
-             (lambda (key . args)
-               ;; If PORT was cached and the server closed the connection
-               ;; in the meantime, we get EPIPE.  In that case, open a
-               ;; fresh connection and retry.  We might also get
-               ;; 'bad-response or a similar exception from (web response)
-               ;; later on, once we've sent the request, or a
-               ;; ERROR/INVALID-SESSION from GnuTLS.
-               (if (or (and (eq? key 'system-error)
-                            (= EPIPE (system-error-errno `(,key ,@args))))
-                       (and (eq? key 'gnutls-error)
-                            (eq? (first args) error/invalid-session))
-                       (memq key
-                             '(bad-response bad-header bad-header-component)))
-                   (begin
-                     (close-port p)
-                     (connect #f      ; try again
+           (match
+               (catch #t
+                 (lambda ()
+                   (let* ((resp   (read-response p))
+                          (body   (response-body-port resp))
+                          (result (proc head resp body result)))
+                     ;; The server can choose to stop responding at any time,
+                     ;; in which case we have to try again.  Check whether
+                     ;; that is the case.  Note that even upon "Connection:
+                     ;; close", we can read from BODY.
+                     (match (assq 'connection (response-headers resp))
+                       (('connection 'close)
+                        (close-port p)
+                        (list 'connect
+                              #f
                               (drop requests (+ 1 processed))
                               result))
-                   (apply throw key args))))))))))
+                       (_
+                        (list 'loop tail (+ 1 processed) result)))))
+                 (lambda (key . args)
+                   ;; If PORT was cached and the server closed the connection
+                   ;; in the meantime, we get EPIPE.  In that case, open a
+                   ;; fresh connection and retry.  We might also get
+                   ;; 'bad-response or a similar exception from (web response)
+                   ;; later on, once we've sent the request, or a
+                   ;; ERROR/INVALID-SESSION from GnuTLS.
+                   (if (or (and (eq? key 'system-error)
+                                (= EPIPE (system-error-errno `(,key ,@args))))
+                           (and (eq? key 'gnutls-error)
+                                (eq? (first args) error/invalid-session))1
+                                (memq key
+                                      '(bad-response
+                                        bad-header
+                                        bad-header-component)))
+                       (begin
+                         (close-port p)
+                         (list 'connect
+                               #f
+                               requests
+                               result))
+                       (apply throw key args))))
+             (('connect . args)
+              (apply connect args))
+             (('loop . args)
+              (apply loop args)))))))))
 
 \f
 ;;;
-- 
2.30.1





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-21  0:43 [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Christopher Baines
  2021-03-21  0:56 ` [bug#47288] [PATCH v2] " Christopher Baines
@ 2021-03-21  8:36 ` Maxime Devos
  2021-03-25 11:03 ` [bug#47288] [PATCH v3 1/2] " Christopher Baines
  2 siblings, 0 replies; 11+ messages in thread
From: Maxime Devos @ 2021-03-21  8:36 UTC (permalink / raw)
  To: Christopher Baines, 47288

[-- Attachment #1: Type: text/plain, Size: 1908 bytes --]

On Sun, 2021-03-21 at 00:43 +0000, Christopher Baines wrote:
> This isn't meant to change the way errors are handled, and arguably makes the
> code harder to read, but it's a uninformed attempt to improve the
> performance (following on from a performance regression in
> 205833b72c5517915a47a50dbe28e7024dc74e57).
> 
> I'm guessing something about Guile internals makes calling (loop ...) within
> the catch bit less performant than avoiding this and calling (loop ...) after
> the catch bit has finished. Since this happens lots, this seems to be
> sufficient to make guix weather a lot slower than it was before.

I took a look at the code, and it seems we did somthing like:

(let loop VARS
  (match sent
    NON-LOOPING-CASES
    (STUFF
     (catch #t
       THUNK-THAT-MIGHT-CALL-LOOP-IN-TAIL-POSITION
       SOME-HANDLER-THAT-DOES-NOT-CALL-LOOP)

A small demonstration of what could go wrong with such a construction
(run this in the Guile REPL):

(let loop ((attempts-todo 20))
  (catch 'oops
    (lambda ()
      (if (<= 0 attempts-todo)
          (loop (- attempts-todo 1))
          (throw 'oops)))
    (lambda _ (display 'too-bad!) (newline) (backtrace))))

Output:
too-bad!

Backtrace:
In ice-9/boot-9.scm:
  1731:15 19 (with-exception-handler #<procedure 7f73c7e615a0 at ice-9/boot-9.scm:1815:7 (exn)> _ # _ …)
  [The previous line repeated 17 times]
  1731:15  1 (with-exception-handler #<procedure 7f73c7e61240 at ice-9/boot-9.scm:1815:7 (exn)> _ # _ …)
In unknown file:
           0 (backtrace #<undefined>)

With this construction, we were nesting exception handlers within exception handlers
... So in hindsight it doesn't seem surprising this isn't very performant.
(THUNK-THAT-MIGHT-CALL-LOOP-IN-TAIL-POSITION itself is not called in the tail-position
of the '(let loop ...)' form.)

Hope that sheds some light on the matter,
Maxime

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-21  0:56 ` [bug#47288] [PATCH v2] " Christopher Baines
@ 2021-03-24 14:55   ` Ludovic Courtès
  2021-03-25 11:09     ` Christopher Baines
  2021-03-24 14:55   ` Ludovic Courtès
  1 sibling, 1 reply; 11+ messages in thread
From: Ludovic Courtès @ 2021-03-24 14:55 UTC (permalink / raw)
  To: Christopher Baines; +Cc: 47288

As discussed at <https://issues.guix.gnu.org/47283>, I’m still unsure
these exceptions need to be caught within ‘http-multiple-get’ and at
each iteration.

Just minor comments:

Christopher Baines <mail@cbaines.net> skribis:

> +               (catch #t
> +                 (lambda ()
> +                   (let* ((resp   (read-response p))
> +                          (body   (response-body-port resp))
> +                          (result (proc head resp body result)))
> +                     ;; The server can choose to stop responding at any time,
> +                     ;; in which case we have to try again.  Check whether
> +                     ;; that is the case.  Note that even upon "Connection:
> +                     ;; close", we can read from BODY.
> +                     (match (assq 'connection (response-headers resp))
> +                       (('connection 'close)
> +                        (close-port p)
> +                        (list 'connect
> +                              #f
>                                (drop requests (+ 1 processed))
>                                result))
> -                   (apply throw key args))))))))))
> +                       (_
> +                        (list 'loop tail (+ 1 processed) result)))))
> +                 (lambda (key . args)
> +                   ;; If PORT was cached and the server closed the connection
> +                   ;; in the meantime, we get EPIPE.  In that case, open a
> +                   ;; fresh connection and retry.  We might also get
> +                   ;; 'bad-response or a similar exception from (web response)
> +                   ;; later on, once we've sent the request, or a
> +                   ;; ERROR/INVALID-SESSION from GnuTLS.
> +                   (if (or (and (eq? key 'system-error)
> +                                (= EPIPE (system-error-errno `(,key ,@args))))
> +                           (and (eq? key 'gnutls-error)
> +                                (eq? (first args) error/invalid-session))1
> +                                (memq key
> +                                      '(bad-response
> +                                        bad-header
> +                                        bad-header-component)))
> +                       (begin
> +                         (close-port p)
> +                         (list 'connect
> +                               #f
> +                               requests
> +                               result))
> +                       (apply throw key args))))
> +             (('connect . args)
> +              (apply connect args))
> +             (('loop . args)
> +              (apply loop args)))))))))

This is not new to this patch, but I think the whole exception handling
bit should be factorized and written in such a way that
‘http-multiple-get’ still first on a horizontal screen (even though mine
is actually vertical :-)).  Otherwise one might easily overlook the core
logic of the function.

Ludo’.




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-21  0:56 ` [bug#47288] [PATCH v2] " Christopher Baines
  2021-03-24 14:55   ` [bug#47288] [PATCH] " Ludovic Courtès
@ 2021-03-24 14:55   ` Ludovic Courtès
  1 sibling, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2021-03-24 14:55 UTC (permalink / raw)
  To: Christopher Baines; +Cc: 47288

As discussed at <https://issues.guix.gnu.org/47283>, I’m still unsure
these exceptions need to be caught within ‘http-multiple-get’ and at
each iteration.

Just minor comments:

Christopher Baines <mail@cbaines.net> skribis:

> +               (catch #t
> +                 (lambda ()
> +                   (let* ((resp   (read-response p))
> +                          (body   (response-body-port resp))
> +                          (result (proc head resp body result)))
> +                     ;; The server can choose to stop responding at any time,
> +                     ;; in which case we have to try again.  Check whether
> +                     ;; that is the case.  Note that even upon "Connection:
> +                     ;; close", we can read from BODY.
> +                     (match (assq 'connection (response-headers resp))
> +                       (('connection 'close)
> +                        (close-port p)
> +                        (list 'connect
> +                              #f
>                                (drop requests (+ 1 processed))
>                                result))
> -                   (apply throw key args))))))))))
> +                       (_
> +                        (list 'loop tail (+ 1 processed) result)))))
> +                 (lambda (key . args)
> +                   ;; If PORT was cached and the server closed the connection
> +                   ;; in the meantime, we get EPIPE.  In that case, open a
> +                   ;; fresh connection and retry.  We might also get
> +                   ;; 'bad-response or a similar exception from (web response)
> +                   ;; later on, once we've sent the request, or a
> +                   ;; ERROR/INVALID-SESSION from GnuTLS.
> +                   (if (or (and (eq? key 'system-error)
> +                                (= EPIPE (system-error-errno `(,key ,@args))))
> +                           (and (eq? key 'gnutls-error)
> +                                (eq? (first args) error/invalid-session))1
> +                                (memq key
> +                                      '(bad-response
> +                                        bad-header
> +                                        bad-header-component)))
> +                       (begin
> +                         (close-port p)
> +                         (list 'connect
> +                               #f
> +                               requests
> +                               result))
> +                       (apply throw key args))))
> +             (('connect . args)
> +              (apply connect args))
> +             (('loop . args)
> +              (apply loop args)))))))))

This is not new to this patch, but I think the whole exception handling
bit should be factorized and written in such a way that
‘http-multiple-get’ still fits on a horizontal screen (even though mine
is actually vertical :-)).  Otherwise one might easily overlook the core
logic of the function.

Ludo’.




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH v3 1/2] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-21  0:43 [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Christopher Baines
  2021-03-21  0:56 ` [bug#47288] [PATCH v2] " Christopher Baines
  2021-03-21  8:36 ` Maxime Devos
@ 2021-03-25 11:03 ` Christopher Baines
  2021-03-25 11:03   ` [bug#47288] [PATCH v3 2/2] guix: http-client: Refactor http-multiple-get Christopher Baines
  2021-03-25 22:20   ` [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Ludovic Courtès
  2 siblings, 2 replies; 11+ messages in thread
From: Christopher Baines @ 2021-03-25 11:03 UTC (permalink / raw)
  To: 47288

This isn't meant to change the way errors are handled, and arguably makes the
code harder to read, but it's a uninformed attempt to improve the
performance (following on from a performance regression in
205833b72c5517915a47a50dbe28e7024dc74e57).

I'm guessing something about Guile internals makes calling (loop ...) within
the catch bit less performant than avoiding this and calling (loop ...) after
the catch bit has finished. Since this happens lots, this seems to be
sufficient to make guix weather a lot slower than it was before.

Anecdotal testing of guix weather suggests this change might work.

* guix/http-client.scm (http-multiple-get): Tweak how the second catch
statement works.
---
 guix/http-client.scm | 77 +++++++++++++++++++++++++-------------------
 1 file changed, 43 insertions(+), 34 deletions(-)

diff --git a/guix/http-client.scm b/guix/http-client.scm
index 4b4c14ed0b..adbfbc0d6e 100644
--- a/guix/http-client.scm
+++ b/guix/http-client.scm
@@ -219,42 +219,51 @@ returning."
              (remainder
               (connect p remainder result))))
           ((head tail ...)
-           (catch #t
-             (lambda ()
-               (let* ((resp   (read-response p))
-                      (body   (response-body-port resp))
-                      (result (proc head resp body result)))
-                 ;; The server can choose to stop responding at any time,
-                 ;; in which case we have to try again.  Check whether
-                 ;; that is the case.  Note that even upon "Connection:
-                 ;; close", we can read from BODY.
-                 (match (assq 'connection (response-headers resp))
-                   (('connection 'close)
-                    (close-port p)
-                    (connect #f                       ;try again
-                             (drop requests (+ 1 processed))
-                             result))
-                   (_
-                    (loop tail (+ 1 processed) result))))) ;keep going
-             (lambda (key . args)
-               ;; If PORT was cached and the server closed the connection
-               ;; in the meantime, we get EPIPE.  In that case, open a
-               ;; fresh connection and retry.  We might also get
-               ;; 'bad-response or a similar exception from (web response)
-               ;; later on, once we've sent the request, or a
-               ;; ERROR/INVALID-SESSION from GnuTLS.
-               (if (or (and (eq? key 'system-error)
-                            (= EPIPE (system-error-errno `(,key ,@args))))
-                       (and (eq? key 'gnutls-error)
-                            (eq? (first args) error/invalid-session))
-                       (memq key
-                             '(bad-response bad-header bad-header-component)))
-                   (begin
-                     (close-port p)
-                     (connect #f      ; try again
+           (match
+               (catch #t
+                 (lambda ()
+                   (let* ((resp   (read-response p))
+                          (body   (response-body-port resp))
+                          (result (proc head resp body result)))
+                     ;; The server can choose to stop responding at any time,
+                     ;; in which case we have to try again.  Check whether
+                     ;; that is the case.  Note that even upon "Connection:
+                     ;; close", we can read from BODY.
+                     (match (assq 'connection (response-headers resp))
+                       (('connection 'close)
+                        (close-port p)
+                        (list 'connect
+                              #f
                               (drop requests (+ 1 processed))
                               result))
-                   (apply throw key args))))))))))
+                       (_
+                        (list 'loop tail (+ 1 processed) result)))))
+                 (lambda (key . args)
+                   ;; If PORT was cached and the server closed the connection
+                   ;; in the meantime, we get EPIPE.  In that case, open a
+                   ;; fresh connection and retry.  We might also get
+                   ;; 'bad-response or a similar exception from (web response)
+                   ;; later on, once we've sent the request, or a
+                   ;; ERROR/INVALID-SESSION from GnuTLS.
+                   (if (or (and (eq? key 'system-error)
+                                (= EPIPE (system-error-errno `(,key ,@args))))
+                           (and (eq? key 'gnutls-error)
+                                (eq? (first args) error/invalid-session))
+                           (memq key
+                                 '(bad-response
+                                   bad-header
+                                   bad-header-component)))
+                       (begin
+                         (close-port p)
+                         (list 'connect
+                               #f
+                               (drop requests processed)
+                               result))
+                       (apply throw key args))))
+             (('connect . args)
+              (apply connect args))
+             (('loop . args)
+              (apply loop args)))))))))
 
 \f
 ;;;
-- 
2.30.1





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH v3 2/2] guix: http-client: Refactor http-multiple-get.
  2021-03-25 11:03 ` [bug#47288] [PATCH v3 1/2] " Christopher Baines
@ 2021-03-25 11:03   ` Christopher Baines
  2021-03-25 22:20   ` [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Ludovic Courtès
  1 sibling, 0 replies; 11+ messages in thread
From: Christopher Baines @ 2021-03-25 11:03 UTC (permalink / raw)
  To: 47288

Split the procedure in to three smaller procedures, rather than using two
longer let statements. This might make it easier to read.

* guix/http-client.scm (http-multiple-get): Refactor.
---
 guix/http-client.scm | 195 ++++++++++++++++++++++---------------------
 1 file changed, 101 insertions(+), 94 deletions(-)

diff --git a/guix/http-client.scm b/guix/http-client.scm
index adbfbc0d6e..b584feba5d 100644
--- a/guix/http-client.scm
+++ b/guix/http-client.scm
@@ -147,7 +147,7 @@ Raise an '&http-get-error' condition if downloading fails."
                                 (uri->string uri) code
                                 (response-reason-phrase resp))))))))))))
 
-(define* (http-multiple-get base-uri proc seed requests
+(define* (http-multiple-get base-uri proc seed all-requests
                             #:key port (verify-certificate? #t)
                             (open-connection guix:open-connection-for-uri)
                             (keep-alive? #t)
@@ -161,16 +161,90 @@ When PORT is specified, use it as the initial connection on which HTTP
 requests are sent; otherwise call OPEN-CONNECTION to open a new connection for
 a URI.  When KEEP-ALIVE? is false, close the connection port before
 returning."
-  (let connect ((port     port)
-                (requests requests)
-                (result   seed))
+  (define (send-batch-of-requests p batch)
+    ;; Send BATCH in a row.
+    ;; XXX: Do our own caching to work around inefficiencies when
+    ;; communicating over TLS: <http://bugs.gnu.org/22966>.
+    (let-values (((buffer get) (open-bytevector-output-port)))
+      ;; Inherit the HTTP proxying property from P.
+      (set-http-proxy-port?! buffer (http-proxy-port? p))
+
+      (for-each (cut write-request <> buffer)
+                batch)
+      (put-bytevector p (get))
+      (force-output p)))
+
+  (define (process-batch-of-responses p
+                                      all-remaining-requests
+                                      batch-remaining-requests
+                                      processed
+                                      result)
+    (if (null? batch-remaining-requests)
+        (match (drop all-remaining-requests processed)
+          (()
+           (unless keep-alive?
+             (close-port p))
+           (reverse result))
+          (remainder
+           (connect-and-make-requests p remainder result)))
+        (match
+            (catch #t
+              (lambda ()
+                (let* ((request (car batch-remaining-requests))
+                       (resp    (read-response p))
+                       (body    (response-body-port resp))
+                       (result  (proc request resp body result)))
+                  ;; The server can choose to stop responding at any time, in
+                  ;; which case we have to try again.  Check whether that is
+                  ;; the case.  Note that even upon "Connection: close", we can
+                  ;; read from BODY.
+                  (match (assq 'connection (response-headers resp))
+                    (('connection 'close)
+                     (close-port p)
+                     (list 'connect-and-make-requests
+                           #f
+                           (drop all-remaining-requests (+ 1 processed))
+                           result))
+                    (_
+                     (list 'process-batch-of-responses
+                           p
+                           all-remaining-requests
+                           (cdr batch-remaining-requests)
+                           (+ 1 processed)
+                           result)))))
+              (lambda (key . args)
+                ;; If PORT was cached and the server closed the connection in
+                ;; the meantime, we get EPIPE.  In that case, open a fresh
+                ;; connection and retry.  We might also get 'bad-response or a
+                ;; similar exception from (web response) later on, once we've
+                ;; sent the request, or a ERROR/INVALID-SESSION from GnuTLS.
+                (if (or (and (eq? key 'system-error)
+                             (= EPIPE (system-error-errno `(,key ,@args))))
+                        (and (eq? key 'gnutls-error)
+                             (eq? (first args) error/invalid-session))
+                        (memq key
+                              '(bad-response
+                                bad-header
+                                bad-header-component)))
+                    (begin
+                      (close-port p)
+                      (list 'connect-and-make-requests
+                            #f
+                            (drop all-remaining-requests processed)
+                            result))
+                    (apply throw key args))))
+
+          (('connect-and-make-requests . args)
+           (apply connect-and-make-requests args))
+          (('process-batch-of-responses . args)
+           (apply process-batch-of-responses args)))))
+
+  (define (connect-and-make-requests port remaining-requests result)
     (define batch
-      (if (>= batch-size (length requests))
-          requests
-          (take requests batch-size)))
+      (if (>= batch-size (length remaining-requests))
+          remaining-requests
+          (take remaining-requests batch-size)))
 
-    ;; (format (current-error-port) "connecting (~a requests left)..."
-    ;;         (length requests))
     (let ((p (or port (open-connection base-uri
                                        #:verify-certificate?
                                        verify-certificate?))))
@@ -178,92 +252,25 @@ returning."
       (when (file-port? p)
         (setvbuf p 'block (expt 2 16)))
 
-      ;; Send BATCH in a row.
-      ;; XXX: Do our own caching to work around inefficiencies when
-      ;; communicating over TLS: <http://bugs.gnu.org/22966>.
-      (let-values (((buffer get) (open-bytevector-output-port)))
-        ;; Inherit the HTTP proxying property from P.
-        (set-http-proxy-port?! buffer (http-proxy-port? p))
-
-        (catch #t
-          (lambda ()
-            (for-each (cut write-request <> buffer)
-                      batch)
-            (put-bytevector p (get))
-            (force-output p))
-          (lambda (key . args)
-            ;; If PORT becomes unusable, open a fresh connection and
-            ;; retry.
-            (if (or (and (eq? key 'system-error)
-                         (= EPIPE (system-error-errno `(,key ,@args))))
-                    (and (eq? key 'gnutls-error)
-                         (eq? (first args) error/invalid-session)))
-                (begin
-                  (close-port p)    ; close the broken port
-                  (connect #f
-                           requests
-                           result))
-                (apply throw key args)))))
+      (catch #t
+        (lambda ()
+          (send-batch-of-requests p batch))
+        (lambda (key . args)
+          ;; If PORT becomes unusable, open a fresh connection and retry.
+          (if (or (and (eq? key 'system-error)
+                       (= EPIPE (system-error-errno `(,key ,@args))))
+                  (and (eq? key 'gnutls-error)
+                       (eq? (first args) error/invalid-session)))
+              (begin
+                (close-port p)          ; close the broken port
+                (connect-and-make-requests #f
+                                           remaining-requests
+                                           result))
+              (apply throw key args))))
 
-      ;; Now start processing responses.
-      (let loop ((sent      batch)
-                 (processed 0)
-                 (result    result))
-        (match sent
-          (()
-           (match (drop requests processed)
-             (()
-              (unless keep-alive?
-                (close-port p))
-              (reverse result))
-             (remainder
-              (connect p remainder result))))
-          ((head tail ...)
-           (match
-               (catch #t
-                 (lambda ()
-                   (let* ((resp   (read-response p))
-                          (body   (response-body-port resp))
-                          (result (proc head resp body result)))
-                     ;; The server can choose to stop responding at any time,
-                     ;; in which case we have to try again.  Check whether
-                     ;; that is the case.  Note that even upon "Connection:
-                     ;; close", we can read from BODY.
-                     (match (assq 'connection (response-headers resp))
-                       (('connection 'close)
-                        (close-port p)
-                        (list 'connect
-                              #f
-                              (drop requests (+ 1 processed))
-                              result))
-                       (_
-                        (list 'loop tail (+ 1 processed) result)))))
-                 (lambda (key . args)
-                   ;; If PORT was cached and the server closed the connection
-                   ;; in the meantime, we get EPIPE.  In that case, open a
-                   ;; fresh connection and retry.  We might also get
-                   ;; 'bad-response or a similar exception from (web response)
-                   ;; later on, once we've sent the request, or a
-                   ;; ERROR/INVALID-SESSION from GnuTLS.
-                   (if (or (and (eq? key 'system-error)
-                                (= EPIPE (system-error-errno `(,key ,@args))))
-                           (and (eq? key 'gnutls-error)
-                                (eq? (first args) error/invalid-session))
-                           (memq key
-                                 '(bad-response
-                                   bad-header
-                                   bad-header-component)))
-                       (begin
-                         (close-port p)
-                         (list 'connect
-                               #f
-                               (drop requests processed)
-                               result))
-                       (apply throw key args))))
-             (('connect . args)
-              (apply connect args))
-             (('loop . args)
-              (apply loop args)))))))))
+      (process-batch-of-responses p remaining-requests batch 0 result)))
+
+  (connect-and-make-requests port all-requests seed))
 
 \f
 ;;;
-- 
2.30.1





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-24 14:55   ` [bug#47288] [PATCH] " Ludovic Courtès
@ 2021-03-25 11:09     ` Christopher Baines
  0 siblings, 0 replies; 11+ messages in thread
From: Christopher Baines @ 2021-03-25 11:09 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 47288


[-- Attachment #1.1: Type: text/plain, Size: 3664 bytes --]


Ludovic Courtès <ludo@gnu.org> writes:

> As discussed at <https://issues.guix.gnu.org/47283>, I’m still unsure
> these exceptions need to be caught within ‘http-multiple-get’ and at
> each iteration.
>
> Just minor comments:
>
> Christopher Baines <mail@cbaines.net> skribis:
>
>> +               (catch #t
>> +                 (lambda ()
>> +                   (let* ((resp   (read-response p))
>> +                          (body   (response-body-port resp))
>> +                          (result (proc head resp body result)))
>> +                     ;; The server can choose to stop responding at any time,
>> +                     ;; in which case we have to try again.  Check whether
>> +                     ;; that is the case.  Note that even upon "Connection:
>> +                     ;; close", we can read from BODY.
>> +                     (match (assq 'connection (response-headers resp))
>> +                       (('connection 'close)
>> +                        (close-port p)
>> +                        (list 'connect
>> +                              #f
>>                                (drop requests (+ 1 processed))
>>                                result))
>> -                   (apply throw key args))))))))))
>> +                       (_
>> +                        (list 'loop tail (+ 1 processed) result)))))
>> +                 (lambda (key . args)
>> +                   ;; If PORT was cached and the server closed the connection
>> +                   ;; in the meantime, we get EPIPE.  In that case, open a
>> +                   ;; fresh connection and retry.  We might also get
>> +                   ;; 'bad-response or a similar exception from (web response)
>> +                   ;; later on, once we've sent the request, or a
>> +                   ;; ERROR/INVALID-SESSION from GnuTLS.
>> +                   (if (or (and (eq? key 'system-error)
>> +                                (= EPIPE (system-error-errno `(,key ,@args))))
>> +                           (and (eq? key 'gnutls-error)
>> +                                (eq? (first args) error/invalid-session))1
>> +                                (memq key
>> +                                      '(bad-response
>> +                                        bad-header
>> +                                        bad-header-component)))
>> +                       (begin
>> +                         (close-port p)
>> +                         (list 'connect
>> +                               #f
>> +                               requests
>> +                               result))
>> +                       (apply throw key args))))
>> +             (('connect . args)
>> +              (apply connect args))
>> +             (('loop . args)
>> +              (apply loop args)))))))))
>
> This is not new to this patch, but I think the whole exception handling
> bit should be factorized and written in such a way that
> ‘http-multiple-get’ still first on a horizontal screen (even though mine
> is actually vertical :-)).  Otherwise one might easily overlook the core
> logic of the function.

I've sent a v3 now, which makes a few changes to the original patch, and
includes a second patch for a potential refactoring.

I tested three variants for performance, http-multiple-get with no error
handling, the first v3 patch and the first and second v3 patches, and at
least with the test I'm using, the performance seems similar. Assuming
the performance is lower with error handling, the impact seems to be
within the margin of error, at least for test I was using.


[-- Attachment #1.2: http-multiple-get-perf.scm --]
[-- Type: text/plain, Size: 1406 bytes --]

(use-modules (ice-9 binary-ports)
             (srfi srfi-19)
             (web uri)
             (web request)
             (web response)
             (guix http-client))

(define (call-with-time-logging requests thunk)
  (let ((start   (current-time time-utc)))
    (call-with-values
        thunk
      (lambda vals
        (let* ((end     (current-time time-utc))
               (elapsed (time-difference end start)))
          (display
           (format #f
                   "~f seconds (~f microseconds per request)~%"
                   (+ (time-second elapsed)
                      (/ (time-nanosecond elapsed) 1e9))
                   (* (/ (+ (time-second elapsed)
                            (/ (time-nanosecond elapsed) 1e9))
                         requests)
                      1e6)))
          (apply values vals))))))

(define requests
  (map (lambda _
         (build-request (string->uri "http://localhost/does-not-exist")
                        #:method 'GET))
       (iota 200000)))

(call-with-time-logging
 (length requests)
 (lambda ()
   (http-multiple-get (string->uri "http://localhost/")
                      (lambda (request response port result)
                        (get-bytevector-n
                         port
                         (response-content-length response))
                        '())
                      '()
                      requests)))

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 987 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-25 11:03 ` [bug#47288] [PATCH v3 1/2] " Christopher Baines
  2021-03-25 11:03   ` [bug#47288] [PATCH v3 2/2] guix: http-client: Refactor http-multiple-get Christopher Baines
@ 2021-03-25 22:20   ` Ludovic Courtès
  2021-03-26  8:39     ` Christopher Baines
  1 sibling, 1 reply; 11+ messages in thread
From: Ludovic Courtès @ 2021-03-25 22:20 UTC (permalink / raw)
  To: Christopher Baines; +Cc: 47288

[-- Attachment #1: Type: text/plain, Size: 4389 bytes --]

Hi!

Christopher Baines <mail@cbaines.net> skribis:

> This isn't meant to change the way errors are handled, and arguably makes the
> code harder to read, but it's a uninformed attempt to improve the
> performance (following on from a performance regression in
> 205833b72c5517915a47a50dbe28e7024dc74e57).
>
> I'm guessing something about Guile internals makes calling (loop ...) within
> the catch bit less performant than avoiding this and calling (loop ...) after
> the catch bit has finished. Since this happens lots, this seems to be
> sufficient to make guix weather a lot slower than it was before.

As Maxime wrote, the problem is that we were making non-tail calls,
thereby consuming stack space as well as accumulating exception
handlers.  As discussed earlier, ‘raise-exception’ exhibits quadratic
behavior in the number of exception handlers, which is okay in normal
situations, but not so much when there are thousands of handlers, as is
the case when asking for many substitutes.

> Anecdotal testing of guix weather suggests this change might work.

Don’t leave this last sentence in the actual commit.  :-)

Please mention <https://bugs.gnu.org/47283>.

> * guix/http-client.scm (http-multiple-get): Tweak how the second catch
> statement works.
> ---
>  guix/http-client.scm | 77 +++++++++++++++++++++++++-------------------
>  1 file changed, 43 insertions(+), 34 deletions(-)
>
> diff --git a/guix/http-client.scm b/guix/http-client.scm
> index 4b4c14ed0b..adbfbc0d6e 100644
> --- a/guix/http-client.scm
> +++ b/guix/http-client.scm
> @@ -219,42 +219,51 @@ returning."
>               (remainder
>                (connect p remainder result))))
>            ((head tail ...)

[...]

> +           (match
> +               (catch #t
> +                 (lambda ()
> +                   (let* ((resp   (read-response p))
> +                          (body   (response-body-port resp))
> +                          (result (proc head resp body result)))
> +                     ;; The server can choose to stop responding at any time,
> +                     ;; in which case we have to try again.  Check whether
> +                     ;; that is the case.  Note that even upon "Connection:
> +                     ;; close", we can read from BODY.
> +                     (match (assq 'connection (response-headers resp))
> +                       (('connection 'close)
> +                        (close-port p)
> +                        (list 'connect
> +                              #f
>                                (drop requests (+ 1 processed))
>                                result))
> -                   (apply throw key args))))))))))
> +                       (_
> +                        (list 'loop tail (+ 1 processed) result)))))
> +                 (lambda (key . args)
> +                   ;; If PORT was cached and the server closed the connection
> +                   ;; in the meantime, we get EPIPE.  In that case, open a
> +                   ;; fresh connection and retry.  We might also get
> +                   ;; 'bad-response or a similar exception from (web response)
> +                   ;; later on, once we've sent the request, or a
> +                   ;; ERROR/INVALID-SESSION from GnuTLS.
> +                   (if (or (and (eq? key 'system-error)
> +                                (= EPIPE (system-error-errno `(,key ,@args))))
> +                           (and (eq? key 'gnutls-error)
> +                                (eq? (first args) error/invalid-session))
> +                           (memq key
> +                                 '(bad-response
> +                                   bad-header
> +                                   bad-header-component)))
> +                       (begin
> +                         (close-port p)
> +                         (list 'connect
> +                               #f
> +                               (drop requests processed)
> +                               result))
> +                       (apply throw key args))))
> +             (('connect . args)
> +              (apply connect args))
> +             (('loop . args)
> +              (apply loop args)))))))))

OK to write it this way as the first commit, to ease review.

What about the approach below:


[-- Attachment #2: Type: text/x-patch, Size: 5247 bytes --]

diff --git a/guix/http-client.scm b/guix/http-client.scm
index 4b4c14ed0b..6351e2d051 100644
--- a/guix/http-client.scm
+++ b/guix/http-client.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
 ;;; Copyright © 2012, 2015 Free Software Foundation, Inc.
 ;;; Copyright © 2017 Tobias Geerinckx-Rice <me@tobias.gr>
@@ -147,6 +147,27 @@ Raise an '&http-get-error' condition if downloading fails."
                                 (uri->string uri) code
                                 (response-reason-phrase resp))))))))))))
 
+(define-syntax-rule (false-if-networking-error exp)
+  "Return #f if EXP triggers a network related exception."
+  ;; FIXME: Duplicated from 'with-cached-connection'.
+  (catch #t
+    (lambda ()
+      exp)
+    (lambda (key . args)
+      ;; If PORT was cached and the server closed the connection in the
+      ;; meantime, we get EPIPE.  In that case, open a fresh connection and
+      ;; retry.  We might also get 'bad-response or a similar exception from
+      ;; (web response) later on, once we've sent the request, or a
+      ;; ERROR/INVALID-SESSION from GnuTLS.
+      (if (or (and (eq? key 'system-error)
+                   (= EPIPE (system-error-errno `(,key ,@args))))
+              (and (eq? key 'gnutls-error)
+                   (eq? (first args) error/invalid-session))
+              (memq key
+                    '(bad-response bad-header bad-header-component)))
+          #f
+          (apply throw key args)))))
+
 (define* (http-multiple-get base-uri proc seed requests
                             #:key port (verify-certificate? #t)
                             (open-connection guix:open-connection-for-uri)
@@ -219,42 +240,27 @@ returning."
              (remainder
               (connect p remainder result))))
           ((head tail ...)
-           (catch #t
-             (lambda ()
-               (let* ((resp   (read-response p))
-                      (body   (response-body-port resp))
-                      (result (proc head resp body result)))
-                 ;; The server can choose to stop responding at any time,
-                 ;; in which case we have to try again.  Check whether
-                 ;; that is the case.  Note that even upon "Connection:
-                 ;; close", we can read from BODY.
-                 (match (assq 'connection (response-headers resp))
-                   (('connection 'close)
-                    (close-port p)
-                    (connect #f                       ;try again
-                             (drop requests (+ 1 processed))
-                             result))
-                   (_
-                    (loop tail (+ 1 processed) result))))) ;keep going
-             (lambda (key . args)
-               ;; If PORT was cached and the server closed the connection
-               ;; in the meantime, we get EPIPE.  In that case, open a
-               ;; fresh connection and retry.  We might also get
-               ;; 'bad-response or a similar exception from (web response)
-               ;; later on, once we've sent the request, or a
-               ;; ERROR/INVALID-SESSION from GnuTLS.
-               (if (or (and (eq? key 'system-error)
-                            (= EPIPE (system-error-errno `(,key ,@args))))
-                       (and (eq? key 'gnutls-error)
-                            (eq? (first args) error/invalid-session))
-                       (memq key
-                             '(bad-response bad-header bad-header-component)))
-                   (begin
-                     (close-port p)
-                     (connect #f      ; try again
-                              (drop requests (+ 1 processed))
-                              result))
-                   (apply throw key args))))))))))
+           (match (false-if-networking-error (read-response p))
+             ((? response? resp)
+              (let* ((body   (response-body-port resp))
+                     (result (proc head resp body result)))
+                ;; The server can choose to stop responding at any time,
+                ;; in which case we have to try again.  Check whether
+                ;; that is the case.  Note that even upon "Connection:
+                ;; close", we can read from BODY.
+                (match (assq 'connection (response-headers resp))
+                  (('connection 'close)
+                   (close-port p)
+                   (connect #f                    ;try again
+                            (drop requests (+ 1 processed))
+                            result))
+                  (_
+                   (loop tail (+ 1 processed) result)))))
+             (#f
+              (close-port p)
+              (connect #f                         ; try again
+                       (drop requests (+ 1 processed))
+                       result)))))))))
 
 \f
 ;;;

[-- Attachment #3: Type: text/plain, Size: 187 bytes --]


I believe it’s a bit more readable because it moves ‘catch’ out of sight
and avoids the sort of “mini DSL” where we return lists of arguments.

WDYT?

Thanks,
Ludo’.

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-25 22:20   ` [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Ludovic Courtès
@ 2021-03-26  8:39     ` Christopher Baines
  2021-03-27 17:15       ` Ludovic Courtès
  0 siblings, 1 reply; 11+ messages in thread
From: Christopher Baines @ 2021-03-26  8:39 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: 47288

[-- Attachment #1: Type: text/plain, Size: 6200 bytes --]


Ludovic Courtès <ludo@gnu.org> writes:

> What about the approach below:
>
> diff --git a/guix/http-client.scm b/guix/http-client.scm
> index 4b4c14ed0b..6351e2d051 100644
> --- a/guix/http-client.scm
> +++ b/guix/http-client.scm
> @@ -1,5 +1,5 @@
>  ;;; GNU Guix --- Functional package management for GNU
> -;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2020 Ludovic Courtès <ludo@gnu.org>
> +;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
>  ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
>  ;;; Copyright © 2012, 2015 Free Software Foundation, Inc.
>  ;;; Copyright © 2017 Tobias Geerinckx-Rice <me@tobias.gr>
> @@ -147,6 +147,27 @@ Raise an '&http-get-error' condition if downloading fails."
>                                  (uri->string uri) code
>                                  (response-reason-phrase resp))))))))))))
>  
> +(define-syntax-rule (false-if-networking-error exp)
> +  "Return #f if EXP triggers a network related exception."
> +  ;; FIXME: Duplicated from 'with-cached-connection'.
> +  (catch #t
> +    (lambda ()
> +      exp)
> +    (lambda (key . args)
> +      ;; If PORT was cached and the server closed the connection in the
> +      ;; meantime, we get EPIPE.  In that case, open a fresh connection and
> +      ;; retry.  We might also get 'bad-response or a similar exception from
> +      ;; (web response) later on, once we've sent the request, or a
> +      ;; ERROR/INVALID-SESSION from GnuTLS.
> +      (if (or (and (eq? key 'system-error)
> +                   (= EPIPE (system-error-errno `(,key ,@args))))
> +              (and (eq? key 'gnutls-error)
> +                   (eq? (first args) error/invalid-session))
> +              (memq key
> +                    '(bad-response bad-header bad-header-component)))
> +          #f
> +          (apply throw key args)))))
> +
>  (define* (http-multiple-get base-uri proc seed requests
>                              #:key port (verify-certificate? #t)
>                              (open-connection guix:open-connection-for-uri)
> @@ -219,42 +240,27 @@ returning."
>               (remainder
>                (connect p remainder result))))
>            ((head tail ...)
> -           (catch #t
> -             (lambda ()
> -               (let* ((resp   (read-response p))
> -                      (body   (response-body-port resp))
> -                      (result (proc head resp body result)))
> -                 ;; The server can choose to stop responding at any time,
> -                 ;; in which case we have to try again.  Check whether
> -                 ;; that is the case.  Note that even upon "Connection:
> -                 ;; close", we can read from BODY.
> -                 (match (assq 'connection (response-headers resp))
> -                   (('connection 'close)
> -                    (close-port p)
> -                    (connect #f                       ;try again
> -                             (drop requests (+ 1 processed))
> -                             result))
> -                   (_
> -                    (loop tail (+ 1 processed) result))))) ;keep going
> -             (lambda (key . args)
> -               ;; If PORT was cached and the server closed the connection
> -               ;; in the meantime, we get EPIPE.  In that case, open a
> -               ;; fresh connection and retry.  We might also get
> -               ;; 'bad-response or a similar exception from (web response)
> -               ;; later on, once we've sent the request, or a
> -               ;; ERROR/INVALID-SESSION from GnuTLS.
> -               (if (or (and (eq? key 'system-error)
> -                            (= EPIPE (system-error-errno `(,key ,@args))))
> -                       (and (eq? key 'gnutls-error)
> -                            (eq? (first args) error/invalid-session))
> -                       (memq key
> -                             '(bad-response bad-header bad-header-component)))
> -                   (begin
> -                     (close-port p)
> -                     (connect #f      ; try again
> -                              (drop requests (+ 1 processed))
> -                              result))
> -                   (apply throw key args))))))))))
> +           (match (false-if-networking-error (read-response p))
> +             ((? response? resp)
> +              (let* ((body   (response-body-port resp))
> +                     (result (proc head resp body result)))

Given body is a port, and that port is passed to proc, I'm guessing it's
possible for networking things to go wrong inside proc.

> +                ;; The server can choose to stop responding at any time,
> +                ;; in which case we have to try again.  Check whether
> +                ;; that is the case.  Note that even upon "Connection:
> +                ;; close", we can read from BODY.
> +                (match (assq 'connection (response-headers resp))
> +                  (('connection 'close)
> +                   (close-port p)
> +                   (connect #f                    ;try again
> +                            (drop requests (+ 1 processed))
> +                            result))
> +                  (_
> +                   (loop tail (+ 1 processed) result)))))
> +             (#f
> +              (close-port p)
> +              (connect #f                         ; try again
> +                       (drop requests (+ 1 processed))

I realised earlier in this series of patches that this should actually
be processed, rather than (+ 1 processed) since proc can't have been run
for the current response.

> +                       result)))))))))
>  
>  \f
>  ;;;
>
> I believe it’s a bit more readable because it moves ‘catch’ out of sight
> and avoids the sort of “mini DSL” where we return lists of arguments.
>
> WDYT?

It looks OK to me, I think the only thing to figure out for sure is
whether it's safe to not include the activity on the body port in the
error handling.

Thanks,

Chris

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 987 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling.
  2021-03-26  8:39     ` Christopher Baines
@ 2021-03-27 17:15       ` Ludovic Courtès
  0 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2021-03-27 17:15 UTC (permalink / raw)
  To: Christopher Baines; +Cc: 47288

Hi,

Christopher Baines <mail@cbaines.net> skribis:

> Ludovic Courtès <ludo@gnu.org> writes:

[...]

>> +           (match (false-if-networking-error (read-response p))
>> +             ((? response? resp)
>> +              (let* ((body   (response-body-port resp))
>> +                     (result (proc head resp body result)))
>
> Given body is a port, and that port is passed to proc, I'm guessing it's
> possible for networking things to go wrong inside proc.

Yes, but how is this different from a regular read(2) call as made by
‘get-bytevector-n’ or whatever?  We wouldn’t write every read(2) call in
‘catch’ because in general any error there is indeed exceptional.

I think the only bit that’s “less exceptional” here is that, because
we’re reusing cached connection, we know that the first read(2) or the
first write(2) to that port can trigger one of these errors—which, we
know are not “exceptional”.  Errors in subsequent read(2) or write(2)
calls remain exceptional/unrecoverable and should be treated as such
IMO.

Does that make sense?

(In that sense, I think wrapping every ‘read-response’ call rather than
just the first one is already too much, but that’s okay.)

>> +                ;; The server can choose to stop responding at any time,
>> +                ;; in which case we have to try again.  Check whether
>> +                ;; that is the case.  Note that even upon "Connection:
>> +                ;; close", we can read from BODY.
>> +                (match (assq 'connection (response-headers resp))
>> +                  (('connection 'close)
>> +                   (close-port p)
>> +                   (connect #f                    ;try again
>> +                            (drop requests (+ 1 processed))
>> +                            result))
>> +                  (_
>> +                   (loop tail (+ 1 processed) result)))))
>> +             (#f
>> +              (close-port p)
>> +              (connect #f                         ; try again
>> +                       (drop requests (+ 1 processed))
>
> I realised earlier in this series of patches that this should actually
> be processed, rather than (+ 1 processed) since proc can't have been run
> for the current response.

Oh, something to fix in a subsequent commit, then.

All in all, I propose to go with this patch if that’s fine with you.

Thanks!

Ludo’.




^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2021-03-27 17:25 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-21  0:43 [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Christopher Baines
2021-03-21  0:56 ` [bug#47288] [PATCH v2] " Christopher Baines
2021-03-24 14:55   ` [bug#47288] [PATCH] " Ludovic Courtès
2021-03-25 11:09     ` Christopher Baines
2021-03-24 14:55   ` Ludovic Courtès
2021-03-21  8:36 ` Maxime Devos
2021-03-25 11:03 ` [bug#47288] [PATCH v3 1/2] " Christopher Baines
2021-03-25 11:03   ` [bug#47288] [PATCH v3 2/2] guix: http-client: Refactor http-multiple-get Christopher Baines
2021-03-25 22:20   ` [bug#47288] [PATCH] guix: http-client: Tweak http-multiple-get error handling Ludovic Courtès
2021-03-26  8:39     ` Christopher Baines
2021-03-27 17:15       ` Ludovic Courtès

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).