Skip to content

httpConnectionTTLMiddleware: removeExpiredConnection unconditionally re-creates cleaned-up entries #907

@aknuds1

Description

@aknuds1

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:

  1. Re-create the entry with a fresh TTL and created timestamp
  2. Increment totalOpenConnections (phantom open)
  3. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions