Description
In middleware/http_connection_ttl_middleware.go, removeExpiredConnection calls connectionState(conn) as its first step. connectionState always creates a new map entry if one doesn't exist:
func (m *httpConnectionTTLMiddleware) removeExpiredConnection(conn string) bool {
state := m.connectionState(conn) // creates entry if missing
if !state.isExpired() {
return false
}
// ...
}
func (m *httpConnectionTTLMiddleware) connectionState(conn string) *connectionState {
// ...
state, cachedConn := m.connections[conn]
if cachedConn {
state.lastSeen = now
} else {
ttl := m.calculateTTL(conn)
state = &connectionState{ttl: ttl, created: now, lastSeen: now}
m.connections[conn] = state // always creates
}
// ...
}
This means that if a connection was previously cleaned up (e.g. by the idle ticker in removeIdleExpiredConnections), the next request from the same RemoteAddr will:
- Re-create the entry with a fresh TTL and
created timestamp
- Increment
totalOpenConnections (phantom open)
- See
isExpired() == false (just created) and return false — no Connection: close is sent
The net effect is that an expired connection that was garbage-collected silently gets a brand new TTL instead of being immediately signalled to close.
Suggested fix
Separate "get-or-create" from "get-if-exists". In removeExpiredConnection, use a read-only lookup. Only create new entries in Wrap (or a dedicated tracking method) when the connection is not being evicted.
Description
In
middleware/http_connection_ttl_middleware.go,removeExpiredConnectioncallsconnectionState(conn)as its first step.connectionStatealways creates a new map entry if one doesn't exist:This means that if a connection was previously cleaned up (e.g. by the idle ticker in
removeIdleExpiredConnections), the next request from the sameRemoteAddrwill:createdtimestamptotalOpenConnections(phantom open)isExpired() == false(just created) and return false — noConnection: closeis sentThe net effect is that an expired connection that was garbage-collected silently gets a brand new TTL instead of being immediately signalled to close.
Suggested fix
Separate "get-or-create" from "get-if-exists". In
removeExpiredConnection, use a read-only lookup. Only create new entries inWrap(or a dedicated tracking method) when the connection is not being evicted.