Summary
The -read_timeout flag help states "zero or negative value means no timeout", but passing 0 does not disable the timeout — the built-in 15s default is applied instead. Only a negative value (e.g. -1s) actually disables it. -write_timeout is unaffected only because its default is already 0.
Environment
- Version: v2 (branch
v2), reproduced with the mayth/simple-upload-server image
- Docker, Linux
Expected
Per the flag help, -read_timeout 0 should disable the read timeout (map to http.Server.ReadTimeout = 0, which Go treats as no timeout), consistent with -write_timeout 0.
Actual
0 is treated as "unset" and replaced by the 15s default. Startup logs:
# no -read_timeout flag
config from flag: ReadTimeout:0
merged flag config: ReadTimeout:15000000000
configured: ReadTimeout:15s
# -read_timeout=0 (explicit) -> still 15s
# -read_timeout=-1s
configured: ReadTimeout:-1s # works: no timeout
Consequence: any upload whose body transfer exceeds 15s is cut mid-stream — the handler's r.FormFile read fails with failed to obtain form file: ... i/o timeout and returns 500 — making large uploads (multi-GB over ordinary links) impossible unless the operator discovers the undocumented -1s workaround.
Root cause
In app.go, ParseConfig builds configFromFlags and merges it over DefaultConfig:
config := DefaultConfig // ReadTimeout: Duration(15 * time.Second)
configFromFlags := ServerConfig{ /* ... */ ReadTimeout: Duration(a.readTimeout) }
mergo.Merge(&config, configFromFlags, mergo.WithOverride)
mergo.Merge with WithOverride does not override a destination field when the source field is a zero value. An explicit -read_timeout 0 parses to a.readTimeout == 0, which is indistinguishable from the flag being unset, so mergo skips it and the 15s DefaultConfig.ReadTimeout stands. Only a non-zero (negative) duration overrides.
Proposed fix
Distinguish "flag not provided" from "explicitly set" for the timeout flags — e.g. re-apply them after the merge via flag.Visit — so -read_timeout 0 maps to ReadTimeout: 0 (no timeout) as documented. This mirrors the existing IsSet() handling used for the bool flags. PR to follow.
Workaround
-read_timeout=-1s (any negative duration).
Summary
The
-read_timeoutflag help states "zero or negative value means no timeout", but passing0does not disable the timeout — the built-in 15s default is applied instead. Only a negative value (e.g.-1s) actually disables it.-write_timeoutis unaffected only because its default is already0.Environment
v2), reproduced with themayth/simple-upload-serverimageExpected
Per the flag help,
-read_timeout 0should disable the read timeout (map tohttp.Server.ReadTimeout = 0, which Go treats as no timeout), consistent with-write_timeout 0.Actual
0is treated as "unset" and replaced by the 15s default. Startup logs:Consequence: any upload whose body transfer exceeds 15s is cut mid-stream — the handler's
r.FormFileread fails withfailed to obtain form file: ... i/o timeoutand returns 500 — making large uploads (multi-GB over ordinary links) impossible unless the operator discovers the undocumented-1sworkaround.Root cause
In
app.go,ParseConfigbuildsconfigFromFlagsand merges it overDefaultConfig:mergo.MergewithWithOverridedoes not override a destination field when the source field is a zero value. An explicit-read_timeout 0parses toa.readTimeout == 0, which is indistinguishable from the flag being unset, so mergo skips it and the 15sDefaultConfig.ReadTimeoutstands. Only a non-zero (negative) duration overrides.Proposed fix
Distinguish "flag not provided" from "explicitly set" for the timeout flags — e.g. re-apply them after the merge via
flag.Visit— so-read_timeout 0maps toReadTimeout: 0(no timeout) as documented. This mirrors the existingIsSet()handling used for the bool flags. PR to follow.Workaround
-read_timeout=-1s(any negative duration).