XTLS/Xray-core · warning

connection ends

Error message

connection ends

What it means

Generic wrapper returned when task.Run finishes the TCP session and either the request goroutine (client→server relay) or the response goroutine (server→client relay, plus writer close) exited with an error. The Base error carries the real cause — commonly one of the sibling errors (failed to write request / payload) or a response read failure. Seeing this alone means the actual failure is one level down in the error chain.

Source

Thrown at proxy/shadowsocks/client.go:150

			}

			return buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer))
		}

		responseDone := func() error {
			defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)

			responseReader, err := ReadTCPResponse(user, conn)
			if err != nil {
				return err
			}

			return buf.Copy(responseReader, link.Writer, buf.UpdateActivity(timer))
		}

		responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
		if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
			return errors.New("connection ends").Base(err)
		}

		return nil
	}

	if request.Command == protocol.RequestCommandUDP {

		requestDone := func() error {
			defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)

			writer := &UDPWriter{
				Writer:  conn,
				Request: request,
			}

			if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
				return errors.New("failed to transport all UDP request").Base(err)
			}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Inspect the wrapped (Base) error in the log line — it names which direction and which operation actually failed; fix that root cause.
  2. If it is io.EOF/context.Canceled on user abort, treat as normal session end, not a fault.
  3. For mid-stream resets, verify server stability and policy timeouts (UplinkOnly/DownlinkOnly) on both ends.
  4. Check for MTU/fragmentation issues if resets correlate with large uploads/downloads.
Defensive patterns

Strategy: try-catch

Try / catch

if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
  if errors.Is(err, context.Canceled) || errors.Is(err, io.EOF) {
    return nil // normal session end
  }
  return errors.New("connection ends").Base(err)
}

Prevention

When it happens

Trigger: Any TCP Shadowsocks client session where requestDone or responseDoneAndCloseWriter returns non-nil: mid-stream connection reset in either direction, ReadTCPResponse failing to parse the response header, or context cancellation while relaying.

Common situations: Server drops the session mid-transfer (idle timeout, restart); destination site resets the tunnelled connection; user cancels the request (browser stop / app close) which cancels ctx; response header decrypt failure after server-side key rotation.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/d1fa979b81e2bbbf. Report an issue: GitHub.