ITADN

httpLbs is not interruptable by timeout when dns is down

#553Openu0-uu-0u 创建于 2025-02-10
U
u0-uu-0ucommented
When I do `timeout 1000000 (httpLbs req httpman)` and have bad dns settings, the request can take much longer than those 1000000 microseconds (1s), and in fact only fails when the local dns times out (which on my system is 20 seconds) (I thought this was just an inherent issue in timeout, but nh2 at https://old.reddit.com/r/haskell/comments/1ierl0f/myth_and_truth_in_haskell_asynchronous_exceptions/ indicated it might actually be a bug in the network library, so reporting here in case it actually is unexpected.) To reproduce $ cat dnsbug.cabal cabal-version: 2.4 name: dnsbug version: 0.1.0.0 build-type: Simple executable dnsbug main-is: Bug.hs build-depends: base >=4.12 && <5 , http-client default-language: GHC2021 $ cat Bug.hs {-# LANGUAGE OverloadedStrings #-} module Main where import Network.HTTP.Client import System.Timeout (timeout) main = do httpman <- newManager defaultManagerSettings req <- parseRequest "http://example.com" res <- timeout 1000000 (httpLbs req httpman) print res $ grep ^PRETTY /etc/os-release PRETTY_NAME="Ubuntu 24.04.1 LTS" $ ghc --version The Glorious Glasgow Haskell Compilation System, version 9.12.1 (also tested on 9.2.8) With working DNS this does the expected thing: $ cabal build Up to date $ cabal run Just (Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Accept-Ranges","bytes"),("Content-Type","text/html"),("ETag","\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\""),("Last-Modified","Mon, 13 Jan 2025 20:11:20 GMT"),("Vary","Accept-Encoding"),("Content-Encoding","gzip"),("Content-Length","648"),("Cache-Control","max-age=1158"),("Date","Thu, 06 Feb 2025 10:17:28 GMT"),("Connection","keep-alive")], responseBody = "<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose, responseOriginalRequest = Request { host = "example.com" port = 80 secure = False requestHeaders = [] path = "/" queryString = "" method = "GET" proxy = Nothing rawBody = False redirectCount = 10 responseTimeout = ResponseTimeoutDefault requestVersion = HTTP/1.1 proxySecureMode = ProxySecureWithConnect } , responseEarlyHints = []}) Now change DNS to a fake IP, so that DNS requests will hang (until the local resolver times out): $ sudo resolvectl dns eth0 192.0.2.1 $ time cabal run dnsbug: Uncaught exception http-client-0.7.18-8b51d1b15e5c25165b3bb85934d446140d1bbf69417f7f85bf9c607f9642027b:Network.HTTP.Client.Types.HttpException: HttpExceptionRequest Request { host = "example.com" port = 80 secure = False requestHeaders = [] path = "/" queryString = "" method = "GET" proxy = Nothing rawBody = False redirectCount = 10 responseTimeout = ResponseTimeoutDefault requestVersion = HTTP/1.1 proxySecureMode = ProxySecureWithConnect } (ConnectionFailure Network.Socket.getAddrInfo (called with preferred socket type/protocol: AddrInfo {addrFlags = [], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 0, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}, host name: "example.com", service name: "80"): does not exist (Name or service not known)) While handling HttpExceptionContentWrapper {unHttpExceptionContentWrapper = ConnectionFailure Network.Socket.getAddrInfo (called with preferred socket type/protocol: AddrInfo {addrFlags = [], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 0, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}, host name: "example.com", service name: "80"): does not exist (Name or service not known)} HasCallStack backtrace: throwIO, called at ./Network/HTTP/Client/Core.hs:214:29 in http-client-0.7.18-8b51d1b15e5c25165b3bb85934d446140d1bbf69417f7f85bf9c607f9642027b:Network.HTTP.Client.Core real 0m20,164s user 0m0,093s sys 0m0,063s So that took 20s to time out where I asked for 1s. strace of the above: https://termbin.com/n2y2 Now I re-enable working DNS and try again: $ sudo systemctl restart systemd-resolved $ time cabal run Just (Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Accept-Ranges","bytes"),("Content-Type","text/html"),("ETag","\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\""),("Last-Modified","Mon, 13 Jan 2025 20:11:20 GMT"),("Vary","Accept-Encoding"),("Content-Encoding","gzip"),("Cache-Control","max-age=2835"),("Date","Thu, 06 Feb 2025 10:19:15 GMT"),("Content-Length","648"),("Connection","keep-alive")], responseBody = "<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose, responseOriginalRequest = Request { host = "example.com" port = 80 secure = False requestHeaders = [] path = "/" queryString = "" method = "GET" proxy = Nothing rawBody = False redirectCount = 10 responseTimeout = ResponseTimeoutDefault requestVersion = HTTP/1.1 proxySecureMode = ProxySecureWithConnect } , responseEarlyHints = []}) real 0m0,442s user 0m0,090s sys 0m0,037s And if I shorten the timeout even more, still with working dns, it times out the expected way: $ vim Bug.hs $ cabal build &>/dev/null $ time cabal run Nothing real 0m0,222s user 0m0,078s sys 0m0,046s
8 条评论