fatedier/frp · info

work connection pool is full, discarding

Error message

work connection pool is full, discarding

What it means

ctl.workConnCh is a buffered channel sized poolCount + workConnPoolCapacityOffset. RegisterWorkConn uses a non-blocking select; if the buffer is full the connection is discarded with this error. This is intentional backpressure: the server already has as many idle work connections as the client's pool allows.

Source

Thrown at server/control.go:335

	}
	defer entry.runMu.Unlock()
	if entry.ctl != ctl || entry.id != ctl.controlID {
		return fmt.Errorf("client control for run id [%s] is no longer current", ctl.runID)
	}

	ctl.lifecycleMu.Lock()
	defer ctl.lifecycleMu.Unlock()
	if ctl.state != controlStateRunning {
		return fmt.Errorf("client control for run id [%s] is not running", ctl.runID)
	}

	select {
	case ctl.workConnCh <- conn:
		ctl.xl.Debugf("new work connection registered")
		return nil
	default:
		ctl.xl.Debugf("work connection pool is full, discarding")
		return fmt.Errorf("work connection pool is full, discarding")
	}
}

func (cm *ControlManager) Close() error {
	cm.mu.Lock()
	cm.closed = true
	ctls := make([]*Control, 0, len(cm.ctlsByRunID))
	for _, entry := range cm.ctlsByRunID {
		ctls = append(ctls, entry.ctl)
	}
	cm.mu.Unlock()

	for _, ctl := range ctls {
		cm.Remove(ctl)
		_ = ctl.Close()
	}
	return nil
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Tune frpc's transport.poolCount (and server maxPoolCount) to match your connection concurrency.
  2. It is debug-level and expected under bursts — no action needed if frequency is low; frpc will reopen work connections on demand.
  3. If it floods logs, lower pool count or reduce connection churn (enable TCP mux for user conns where applicable).

Example fix

# frpc.toml
[transport]
poolCount = 5   # size to expected concurrent user connections
Defensive patterns

Strategy: fallback

Validate before calling

// client side: only open what was requested
if inflightWorkConns >= poolCount { return /* skip opening more */ }

Try / catch

if err := cm.RegisterWorkConn(ctl, conn); err != nil {
    conn.Close()
    // full pool is expected under bursts; log at debug only
}

Prevention

When it happens

Trigger: frpc sends more NewWorkConn messages than the negotiated pool size — typically because the server requested work connections (ReqWorkConn) for user traffic, but the traffic finished before frpc's response arrived, leaving surplus connections. The default branch of the select fires and the conn is dropped.

Common situations: Bursty proxied traffic where many short-lived user connections each trigger ReqWorkConn; frpc with a large PoolCount plus latency spikes; clients that eagerly open work connections beyond the pool.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/58e4cd7a00f45a12. Report an issue: GitHub.