hcengineering/platform · warning

session hang, closing...

Error message

session hang, closing...

What it means

Session tick detects a session whose last request is older than hangSessionTimeoutSeconds (x10 for the system account) and logs this warning before force-closing the socket. A hung session holds a workspace open and consumes resources; the manager proactively closes it.

Source

Thrown at foundations/server/packages/server/src/sessionManager.ts:375

    this.ctx.measure('workspaces', this.workspaces.size, true)
    this.ctx.measure('workspaces-user', userWorkspaces, true)
    this.ctx.measure('workspaces-systemonly', sysOnlyWorkspaces, true)
  }

  private handleSessionTick (now: number): void {
    for (const s of this.sessions.values()) {
      const isCurrentUserTick = this.ticks % ticksPerSecond === s.tickHash

      if (isCurrentUserTick) {
        const wsId = s.session.workspace.uuid
        const lastRequestDiff = now - s.session.lastRequest

        let timeout = hangSessionTimeoutSeconds * 1000
        if (s.session.getUser() === systemAccountUuid) {
          timeout = timeout * 10
        }
        if (lastRequestDiff > timeout) {
          this.ctx.warn('session hang, closing...', {
            wsId,
            user: s.session.getUser()
          })

          // Force close workspace if only one client and it hang.
          void this.close(this.ticksContext, s.socket, wsId).catch((err) => {
            this.ctx.error('failed to close', err)
          })
          continue
        }
        if (
          lastRequestDiff + (1 / 10) * lastRequestDiff > this.timeouts.pingTimeout &&
          now - s.session.lastPing > this.timeouts.pingTimeout
        ) {
          // We need to check state and close socket if it broken
          // And ping other wize
          s.session.lastPing = now
          if (s.socket.checkState()) {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Ensure clients send periodic ping/heartbeat to keep the session alive
  2. Check client network paths (NAT idle timeouts) and lower keepalive intervals
  3. If legitimate long-idle system sessions get killed, raise hangSessionTimeoutSeconds
  4. Investigate client-side crash/freeze logs if the same users repeatedly hang

Example fix

// before
client ping interval: none (server closes after timeout)
// after
setInterval(() => socket.ping(), 30000) // < hangSessionTimeoutSeconds
Defensive patterns

Strategy: validation

Validate before calling

const lastPing = Date.now() - lastRequestTs
if (lastPing > hangSessionTimeoutSeconds * 1000) {
  await forceReconnect() // reconnect before the server closes the session
}

Type guard

function isSessionHung(lastRequestTs: number, timeoutSec: number, isSystem = false): boolean {
  return Date.now() - lastRequestTs > timeoutSec * 1000 * (isSystem ? 10 : 1)
}

Try / catch

socket.on('close', () => {
  scheduleReconnectWithBackoff()
})
// server-side force close surfaces as socket close — client should reconnect transparently

Prevention

When it happens

Trigger: handleSessionTick finds lastRequestDiff > timeout for a session — the client stopped sending requests/pings without a clean disconnect (dead network connection, paused/tab-suspended client, crashed process without TCP close).

Common situations: Mobile or laptop clients going to sleep; NAT/firewall silently dropping idle sockets; client bugs stopping the ping loop; system-account sessions with very long idle times.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/d62341873c0068b1. Report an issue: GitHub.