raco pkg: refine handling of HTTP status codes

Treat only 404 and 410 as "not found" errors, retry on 5xx
errors, and treat anything else as an error insteda of "not found".
This commit is contained in:
Matthew Flatt 2015-10-06 16:40:00 -06:00
parent 4d3852ae69
commit a5f6bf34dc
6 changed files with 82 additions and 39 deletions

View File

@ -9,20 +9,39 @@
#"text/s-expr" empty #"text/s-expr" empty
(λ (op) (write v op)))) (λ (op) (write v op))))
(define error-count (random 25))
(printf "Server error error generator starts as ~s\n" error-count)
(define (pkg-index/basic pkg-name->info all-pkgs) (define (pkg-index/basic pkg-name->info all-pkgs)
(define (write-info req pkg-name) (define (write-info req pkg-name)
(response/sexpr (pkg-name->info pkg-name))) (define i (pkg-name->info pkg-name))
;; Every 25 reseponses or so, generate a 5xx server error;
;; retries should mask the error:
(set! error-count (add1 error-count))
(cond
[(zero? (modulo error-count 5))
(response 500 #"Oops" (current-seconds) #f empty void)]
[i (response/sexpr i)]
[else
;; "Randomly" return #f or a 404, either of which should be
;; treated as not-found failure:
(if (odd? error-count)
(response 404 #"Not Found" (current-seconds) #f empty void)
(response/sexpr #f))]))
(define-values (dispatch get-url) (define-values (dispatch get-url)
(dispatch-rules (dispatch-rules
[("pkgs-all") (lambda (req) [("pkgs-all") (lambda (req)
(response/sexpr (all-pkgs)))] (response/sexpr (all-pkgs)))]
[("pkgs") (lambda (req) [("pkgs") (lambda (req)
(response/sexpr (hash-keys (all-pkgs))))] (response/sexpr (hash-keys (all-pkgs))))]
[("pkg" "broken")
(lambda (req)
(response 401 #"Broken" (current-seconds) #f empty void))]
[("pkg" (string-arg)) write-info])) [("pkg" (string-arg)) write-info]))
dispatch) dispatch)
(provide/contract (provide/contract
[pkg-index/basic [pkg-index/basic
(-> (-> string? (hash/c symbol? any/c)) (-> (-> string? (or/c #f (hash/c symbol? any/c)))
(-> hash?) (-> hash?)
(-> request? response?))]) (-> request? response?))])

View File

@ -69,6 +69,9 @@
(shelly-case (shelly-case
"fails due to unrecognized scheme" "fails due to unrecognized scheme"
$ "raco pkg install magic://download" =exit> 1) $ "raco pkg install magic://download" =exit> 1)
(shelly-case
"fails due to 401 status result"
$ "raco pkg install broken" =exit> 1 =stderr> #rx"401")
(shelly-case (shelly-case
"local directory name fails because not inferred as such (inferred as package name)" "local directory name fails because not inferred as such (inferred as package name)"
$ "raco pkg install test-pkgs" =exit> 1) $ "raco pkg install test-pkgs" =exit> 1)

View File

@ -224,7 +224,7 @@
" response: ~v") " response: ~v")
(url->string url) (url->string url)
s))]) s))])
(define bytes (call-with-url url port->bytes)) (define bytes (call/input-url+200 url port->bytes #:who who))
((if bytes ((if bytes
(with-handlers ([exn:fail:read? (lambda (exn) (with-handlers ([exn:fail:read? (lambda (exn)
(lambda () (failure bytes)))]) (lambda () (failure bytes)))])
@ -235,17 +235,6 @@
(failure bytes)))) (failure bytes))))
(lambda () (failure #f))))) (lambda () (failure #f)))))
;; uses a custodian to avoid leaks:
(define (call-with-url url handler)
(call-with-network-retries
(lambda ()
(define-values (p hs)
(get-pure-port/headers url #:redirections 25 #:status? #t))
(begin0
(and (string=? "200" (substring hs 9 12))
(handler p))
(close-input-port p)))))
(define (db-pkg-info pkg details?) (define (db-pkg-info pkg details?)
(if details? (if details?
(let ([tags (db:get-pkg-tags (db:pkg-name pkg) (let ([tags (db:get-pkg-tags (db:pkg-name pkg)
@ -281,7 +270,7 @@
(add-version-query (add-version-query
(combine-url/relative i "pkgs")) (combine-url/relative i "pkgs"))
(lambda (l) (and (list? l) (lambda (l) (and (list? l)
(andmap string? l))))) (andmap string? l)))))
;; Local database: ;; Local database:
(lambda () (lambda ()
(map db:pkg-name (db:get-pkgs))) (map db:pkg-name (db:get-pkgs)))

View File

@ -69,7 +69,8 @@
url url
(λ (ip) (copy-port ip op)) (λ (ip) (copy-port ip op))
#:auto-retry? #f #:auto-retry? #f
#:failure #:who 'download
#:not-found-handler
(lambda (reply-s) (lambda (reply-s)
(pkg-error (~a "error downloading package\n" (pkg-error (~a "error downloading package\n"
" URL: ~a\n" " URL: ~a\n"

View File

@ -1,10 +1,14 @@
#lang racket/base #lang racket/base
(require net/url (require net/url
racket/format
"print.rkt" "print.rkt"
"config.rkt") "config.rkt")
(provide call-with-network-retries (provide call-with-network-retries
call/input-url+200) call/input-url+200
exn:fail:can-retry)
(struct exn:fail:can-retry exn:fail ())
(define NETWORK-INITIAL-PAUSE 0.1) (define NETWORK-INITIAL-PAUSE 0.1)
@ -15,16 +19,18 @@
(define (call-with-network-retries thunk) (define (call-with-network-retries thunk)
(define retry-count (get-network-retries)) (define retry-count (get-network-retries))
(let loop ([retries 0] [pause-time NETWORK-INITIAL-PAUSE]) (let loop ([retries 0] [pause-time NETWORK-INITIAL-PAUSE])
(with-handlers* ([exn:fail:network? (lambda (exn) (define (maybe-retry exn)
(cond (cond
[(retries . >= . retry-count) [(retries . >= . retry-count)
(raise exn)] (raise exn)]
[else [else
;; Pause, then try again ;; Pause, then try again
(log-pkg-info "Network error; retrying after ~as" (log-pkg-info "Network error; retrying after ~as"
pause-time) pause-time)
(sleep pause-time) (sleep pause-time)
(loop (add1 retries) (* 2 pause-time))]))]) (loop (add1 retries) (* 2 pause-time))]))
(with-handlers* ([exn:fail:network? maybe-retry]
[exn:fail:can-retry? maybe-retry])
(define c (make-custodian)) (define c (make-custodian))
(parameterize ([current-custodian c]) (parameterize ([current-custodian c])
(dynamic-wind (dynamic-wind
@ -33,20 +39,42 @@
(lambda () (lambda ()
(custodian-shutdown-all c))))))) (custodian-shutdown-all c)))))))
(define (call/input-url+200 u fun (define success-codes '(200))
(define not-found-codes '(404 410))
(define other-retry-codes '(408)) ; not counting 5xx
(define (retry-code? c)
(or (and (integer? c) (<= 500 c 599))
(memv c other-retry-codes)))
(define (call/input-url+200 url handler
#:who [who 'download]
#:auto-retry? [auto-retry? #t] #:auto-retry? [auto-retry? #t]
#:headers [headers '()] #:headers [headers '()]
#:failure [fail-k (lambda (s) #f)]) #:not-found-handler [not-found-handler (lambda (s) #f)])
((if auto-retry? ((if auto-retry?
call-with-network-retries call-with-network-retries
(lambda (f) (f))) (lambda (f) (f)))
(lambda () (lambda ()
#;(printf "\t\tReading ~a\n" (url->string u)) (define-values (p hs)
(define-values (ip hs) (get-pure-port/headers u headers (get-pure-port/headers url headers
#:redirections 25 #:redirections 25
#:status? #t)) #:status? #t))
(if (string=? "200" (substring hs 9 12)) (define status (string->number (substring hs 9 12)))
(begin0 (cond
(fun ip) [(memv status success-codes)
(close-input-port ip)) (begin0
(fail-k hs))))) (handler p)
(close-input-port p))]
[(memv status not-found-codes)
(close-input-port p)
(not-found-handler hs)]
[else
(raise ((if (retry-code? status) exn:fail:can-retry exn:fail)
(format (~a "~a: error from server\n"
" URL: ~a\n"
" status code: ~a")
who
(url->string url)
status)
(current-continuation-marks)))]))))

View File

@ -434,6 +434,7 @@
(make-directory* package-path) (make-directory* package-path)
(define manifest (define manifest
(call/input-url+200 (call/input-url+200
#:who 'download-manifest
(url-like "MANIFEST") (url-like "MANIFEST")
port->lines)) port->lines))
(unless manifest (unless manifest
@ -773,6 +774,7 @@
(define api-bs (define api-bs
(call/input-url+200 (call/input-url+200
api-u port->bytes api-u port->bytes
#:who 'query-github
#:headers (list (format "User-Agent: raco-pkg/~a" (version))))) #:headers (list (format "User-Agent: raco-pkg/~a" (version)))))
(unless api-bs (unless api-bs
(error 'package-url->checksum (error 'package-url->checksum
@ -801,7 +803,8 @@
(download-printf "Downloading checksum for ~a\n" pkg-name) (download-printf "Downloading checksum for ~a\n" pkg-name)
(log-pkg-debug "Downloading checksum as ~a" u) (log-pkg-debug "Downloading checksum as ~a" u)
(call/input-url+200 (string->url u) (call/input-url+200 (string->url u)
port->string)])) port->string
#:who 'download-checksum)]))
(define (check-checksum given-checksum checksum what pkg-src cached-url) (define (check-checksum given-checksum checksum what pkg-src cached-url)
(when (and given-checksum (when (and given-checksum