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
- Ensure clients send periodic ping/heartbeat to keep the session alive
- Check client network paths (NAT idle timeouts) and lower keepalive intervals
- If legitimate long-idle system sessions get killed, raise hangSessionTimeoutSeconds
- 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
- Send pings from clients at an interval well under hangSessionTimeoutSeconds
- Enable TCP keepalive on client sockets
- Handle reconnect on socket close with exponential backoff
- Give system-account sessions explicit longer timeouts in client tooling
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
- addSession: getWorkspaceInfo failed
- request hang found
- Wrong video options specified
- No video track found
- The "ws" package is required for NodeWebSocketFactory.
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/d62341873c0068b1.
Report an issue: GitHub.