{"record":{"id":"36aa4e76c3545c8b","repo":"XTLS/Xray-core","slug":"unable-to-dispatch-control-connection","errorCode":null,"errorMessage":"unable to dispatch control connection","messagePattern":"unable to dispatch control connection","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/reverse/portal.go","lineNumber":249,"sourceCode":"\ttimer    *signal.ActivityTimer\n}\n\nfunc NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {\n\topt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}\n\tuplinkReader, uplinkWriter := pipe.New(opt...)\n\tdownlinkReader, downlinkWriter := pipe.New(opt...)\n\n\tctx := context.Background()\n\toutbounds := []*session.Outbound{{\n\t\tTarget: net.UDPDestination(net.DomainAddress(internalDomain), 0),\n\t}}\n\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\tf := client.Dispatch(ctx, &transport.Link{\n\t\tReader: uplinkReader,\n\t\tWriter: downlinkWriter,\n\t})\n\tif !f {\n\t\treturn nil, errors.New(\"unable to dispatch control connection\")\n\t}\n\tterminate := func() {\n\t\tclient.Close()\n\t}\n\tw := &PortalWorker{\n\t\tclient: client,\n\t\treader: downlinkReader,\n\t\twriter: uplinkWriter,\n\t\ttimer:  signal.CancelAfterInactivity(ctx, terminate, 24*time.Hour), // // prevent leak\n\t}\n\tw.control = &task.Periodic{\n\t\tExecute:  w.heartbeat,\n\t\tInterval: time.Second * 2,\n\t}\n\tw.control.Start()\n\treturn w, nil\n}\n","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/reverse/portal.go#L231-L267","documentation":"NewPortalWorker fails to establish the control connection: it creates a pipe pair, synthesizes a context whose sole outbound targets the internal domain over UDP port 0, and calls client.Dispatch on the mux client; when Dispatch returns false the worker cannot be created. The control channel is what carries heartbeat/registration for the worker, so the tunnel setup aborts and surfaces (wrapped) as error 92's 'failed to create portal worker'.","triggerScenarios":"mux.ClientWorker.Dispatch rejecting the control stream at construction time — worker already closed, its connection capacity exhausted, or internal mux state refusing new substreams before the worker finishes initializing.","commonSituations":"Race where the carrier connection to the portal dies between accept and control-dispatch; mux capacity edge conditions under load; version mismatches between bridge and portal cores. Generally transient, since bridges reconnect periodically.","solutions":["If occasional, rely on the bridge's automatic reconnect (monitor task) and treat as noise; persistent occurrence needs deeper checks.","Match xray-core versions on both ends of the reverse tunnel.","Check the carrier connection stability (NAT timeouts killing the tunnel at ~the same age, TLS issues) — a dying carrier makes Dispatch fail immediately."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"null // internal to NewPortalWorker; prevent by keeping the carrier link alive","typeGuard":null,"tryCatchPattern":"if err := portal.HandleConnection(ctx, link); err != nil {\n    cause := deepestCause(err) // unwrap: 'failed to create portal worker' -> 'unable to dispatch control connection'\n    if strings.Contains(cause, \"unable to dispatch control connection\") {\n        return bridgeMonitor.ReconnectSoon() // transient race; retry via periodic monitor\n    }\n    return err\n}","preventionTips":["Keep the portal-bridge carrier stable (keepalives, TLS, sane MTU)","Match core versions across the tunnel","Treat single occurrences as races; investigate only when persistent"],"tags":["go","xray","reverse-proxy","mux","control-channel"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}