diff --git a/CHANGELOG.md b/CHANGELOG.md index 32cf4fb..439f895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +v1.7.1 - 2018-01- +------------------- + +- Timeout requests to query mirror instead of hanging - [pull #1338][issue1338] + +[issue1338]: https://github.com/dlang/dub/issues/1338 + v1.7.0 - 2018-01-01 ------------------- diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index 341df84..d85e262 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -239,12 +239,19 @@ Any redirects will be followed until the actual file resource is reached or if the redirection limit of 10 is reached. Note that only HTTP(S) is currently supported. + + The download times out if a connection cannot be established within + `timeout` ms, or if the average transfer rate drops below 10 bytes / s for + more than `timeout` seconds. Pass `0` as `timeout` to disable both timeout + mechanisms. + + Note: Timeouts are only implemented when curl is used (DubUseCurl). */ -void download(string url, string filename) +void download(string url, string filename, uint timeout = 8) { version(DubUseCurl) { auto conn = HTTP(); - setupHTTPClient(conn); + setupHTTPClient(conn, timeout); logDebug("Storing %s...", url); static if (__VERSION__ <= 2075) { @@ -265,16 +272,16 @@ } else assert(false); } /// ditto -void download(URL url, NativePath filename) +void download(URL url, NativePath filename, uint timeout = 8) { - download(url.toString(), filename.toNativeString()); + download(url.toString(), filename.toNativeString(), timeout); } /// ditto -ubyte[] download(string url) +ubyte[] download(string url, uint timeout = 8) { version(DubUseCurl) { auto conn = HTTP(); - setupHTTPClient(conn); + setupHTTPClient(conn, timeout); logDebug("Getting %s...", url); static if (__VERSION__ <= 2075) { @@ -298,9 +305,9 @@ } else assert(false); } /// ditto -ubyte[] download(URL url) +ubyte[] download(URL url, uint timeout = 8) { - return download(url.toString()); + return download(url.toString(), timeout); } /// Returns the current DUB version in semantic version format @@ -321,7 +328,7 @@ } version(DubUseCurl) { - void setupHTTPClient(ref HTTP conn) + void setupHTTPClient(ref HTTP conn, uint timeout) { static if( is(typeof(&conn.verifyPeer)) ) conn.verifyPeer = false; @@ -333,6 +340,13 @@ if (noProxy.length) conn.handle.set(CurlOption.noproxy, noProxy); conn.handle.set(CurlOption.encoding, ""); + if (timeout) { + // connection (TLS+TCP) times out after 8s + conn.handle.set(CurlOption.connecttimeout, timeout); + // transfers time out after 8s below 10 byte/s + conn.handle.set(CurlOption.low_speed_limit, 10); + conn.handle.set(CurlOption.low_speed_time, 5); + } conn.addRequestHeader("User-Agent", "dub/"~getDUBVersion()~" (std.net.curl; +https://github.com/rejectedsoftware/dub)"); } diff --git a/source/dub/version_.d b/source/dub/version_.d index 7556900..ce0db95 100644 --- a/source/dub/version_.d +++ b/source/dub/version_.d @@ -1,2 +1,2 @@ module dub.version_; -enum dubVersion = "v1.7.0"; +enum dubVersion = "v1.7.1-beta.1"; diff --git a/test/run-unittest.sh b/test/run-unittest.sh index 78a9af5..8d14e33 100755 --- a/test/run-unittest.sh +++ b/test/run-unittest.sh @@ -4,11 +4,11 @@ . $(dirname "${BASH_SOURCE[0]}")/common.sh function log() { - echo -e "\033[0;33m[INFO] "$@"\033[0m" + echo -e "\033[0;33m[INFO] $@\033[0m" } function logError() { - echo -e 1>&2 "\033[0;31m[ERROR] "$@"\033[0m" + echo -e 1>&2 "\033[0;31m[ERROR] $@\033[0m" any_errors=1 } diff --git a/test/timeout.sh b/test/timeout.sh new file mode 100755 index 0000000..615c28c --- /dev/null +++ b/test/timeout.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 + +log ' Testing unconnectable registry' +if timeout 1s $DUB fetch dub --skip-registry=all --registry=http://localhost:$PORT; then + die 'Fetching from unconnectable registry should fail.' +elif [ $? -eq 124 ]; then + die 'Fetching from unconnectable registry should fail immediately.' +fi + +log ' Testing non-responding registry' +cat | nc --listen $PORT >/dev/null & +PID=$! +if timeout 10s $DUB fetch dub --skip-registry=all --registry=http://localhost:$PORT; then + die 'Fetching from non-responding registry should fail.' +elif [ $? -eq 124 ]; then + die 'Fetching from non-responding registry should time-out within 8s.' +fi +kill $PID 2>/dev/null || true + +log ' Testing too slow registry' +{ + res=$(printf 'HTTP/1.1 200 OK\r +Server: dummy\r +Content-Type: application/json\r +Content-Length: 2\r +\r +{}') + for i in $(seq 0 $((${#res} - 1))); do + echo -n "${res:$i:1}" + sleep 1 + done +} | nc --listen $PORT >/dev/null & +PID=$! +if timeout 10s time $DUB fetch dub --skip-registry=all --registry=http://localhost:$PORT; then + die 'Fetching from too slow registry should fail.' +elif [ $? -eq 124 ]; then + die 'Fetching from too slow registry should time-out within 8s.' +fi +kill $PID 2>/dev/null || true