{"record":{"id":"58e4cd7a00f45a12","repo":"fatedier/frp","slug":"work-connection-pool-is-full-discarding","errorCode":null,"errorMessage":"work connection pool is full, discarding","messagePattern":"work connection pool is full, discarding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"server/control.go","lineNumber":335,"sourceCode":"\t}\n\tdefer entry.runMu.Unlock()\n\tif entry.ctl != ctl || entry.id != ctl.controlID {\n\t\treturn fmt.Errorf(\"client control for run id [%s] is no longer current\", ctl.runID)\n\t}\n\n\tctl.lifecycleMu.Lock()\n\tdefer ctl.lifecycleMu.Unlock()\n\tif ctl.state != controlStateRunning {\n\t\treturn fmt.Errorf(\"client control for run id [%s] is not running\", ctl.runID)\n\t}\n\n\tselect {\n\tcase ctl.workConnCh <- conn:\n\t\tctl.xl.Debugf(\"new work connection registered\")\n\t\treturn nil\n\tdefault:\n\t\tctl.xl.Debugf(\"work connection pool is full, discarding\")\n\t\treturn fmt.Errorf(\"work connection pool is full, discarding\")\n\t}\n}\n\nfunc (cm *ControlManager) Close() error {\n\tcm.mu.Lock()\n\tcm.closed = true\n\tctls := make([]*Control, 0, len(cm.ctlsByRunID))\n\tfor _, entry := range cm.ctlsByRunID {\n\t\tctls = append(ctls, entry.ctl)\n\t}\n\tcm.mu.Unlock()\n\n\tfor _, ctl := range ctls {\n\t\tcm.Remove(ctl)\n\t\t_ = ctl.Close()\n\t}\n\treturn nil\n}","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/control.go#L317-L353","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Tune frpc's transport.poolCount (and server maxPoolCount) to match your connection concurrency.","It is debug-level and expected under bursts — no action needed if frequency is low; frpc will reopen work connections on demand.","If it floods logs, lower pool count or reduce connection churn (enable TCP mux for user conns where applicable)."],"exampleFix":"# frpc.toml\n[transport]\npoolCount = 5   # size to expected concurrent user connections","handlingStrategy":"fallback","validationCode":"// client side: only open what was requested\nif inflightWorkConns >= poolCount { return /* skip opening more */ }","typeGuard":null,"tryCatchPattern":"if err := cm.RegisterWorkConn(ctl, conn); err != nil {\n    conn.Close()\n    // full pool is expected under bursts; log at debug only\n}","preventionTips":["Size transport.poolCount to real concurrency.","Only open work connections in response to ReqWorkConn, not speculatively."],"tags":["go","frp","workconn","pool","backpressure"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}