{"record":{"id":"4e7de877d9f77953","repo":"XTLS/Xray-core","slug":"connection-idle-timeout","errorCode":null,"errorMessage":"connection idle timeout","messagePattern":"connection idle timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/geodata/download.go","lineNumber":49,"sourceCode":"type downloader struct {\n\tctx         context.Context\n\thttpClient  *http.Client\n\thttpsClient *http.Client\n}\n\ntype idleConn struct {\n\tnet.Conn\n}\n\nfunc (c *idleConn) Read(b []byte) (int, error) {\n\tt := time.AfterFunc(idleTimeout, func() {\n\t\t_ = c.Close()\n\t})\n\n\tn, err := c.Conn.Read(b)\n\tif !t.Stop() {\n\t\t_ = c.Close()\n\t\treturn n, errors.New(\"connection idle timeout\")\n\t}\n\treturn n, err\n}\n\nfunc (c *idleConn) Write(b []byte) (int, error) {\n\treturn c.Conn.Write(b)\n}\n\nfunc newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {\n\treturn &downloader{\n\t\tctx:         ctx,\n\t\thttpClient:  newClient(ctx, dispatcher, outbound, false),\n\t\thttpsClient: newClient(ctx, dispatcher, outbound, true),\n\t}\n}\n\nfunc newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {\n\tdial := func(ctx context.Context, network, address string) (net.Conn, error) {","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/geodata/download.go#L31-L67","documentation":"Produced by the idleConn wrapper around sockets used by the geodata downloader. Each Read arms a time.AfterFunc(idleTimeout) that closes the connection; if the timer already fired before Read returned (t.Stop() == false), the peer sent no bytes for idleTimeout and the connection was force-closed, so the read is reported as an idle timeout instead of hanging forever.","triggerScenarios":"Any geodata download (geosite.dat / geoip.dat update) where the remote server stalls — the response body stops arriving for longer than idleTimeout while io.Copy is still reading through idleConn.Read.","commonSituations":"Slow or rate-limited geodata mirrors (GitHub raw, jsdelivr) throttling mid-transfer; proxied outbound with an unstable upstream; very large geodata files over a lossy link where a read gap exceeds the timeout.","solutions":["Retry the update — transient stalls on geodata mirrors usually succeed on a second attempt","Point config.Assets / the asset URL at a faster, closer mirror","Route the geodata downloader through a healthy outbound (set the geodata outbound tag to a working proxy)","Pre-download geodata files manually into the working directory so the updater never needs the network"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before updating, probe the mirror with a short read deadline to detect stalls early:\nconn, err := net.DialTimeout(\"tcp\", mirrorHost, 5*time.Second)\nif err != nil { /* skip mirror, try next asset URL */ }\nconn.Close()","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 3; attempt++ {\n    err = downloader.Update()\n    if err == nil { break }\n    if strings.Contains(err.Error(), \"connection idle timeout\") {\n        time.Sleep(time.Duration(attempt+1) * time.Second) // backoff, then retry\n        continue\n    }\n    break // non-idle error, do not retry\n}","preventionTips":["Ship geodata files with the deployment so updates are optional","Prefer mirrors with good throughput to reduce stall windows","Schedule geodata cron during low-traffic hours"],"tags":["network","geodata","timeout","download"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}