XTLS/Xray-core · warning · errors.Error

connection ends

Error message

connection ends

What it means

Terminal wrapper around any error from the requestDone/responseDone task group: task.Run collects the first failure and freedom reports it as "connection ends". It is the outermost error a caller of Process sees, so the real cause ("failed to process request/response" or a task-level timeout/cancel) is one level down in the error chain.

Source

Thrown at proxy/freedom/freedom.go:459

		}
		var reader buf.Reader
		if destination.Network == net.Network_TCP {
			reader = buf.NewReader(conn)
		} else {
			reader = NewPacketReader(conn, h, defaultRule, UDPOverride, destination)
		}
		if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
			return errors.New("failed to process response").Base(err)
		}
		return nil
	}

	if newCtx != nil {
		ctx = newCtx
	}

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

	return nil
}

func NewPacketReader(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
	iConn := conn
	statConn, ok := iConn.(*stat.CounterConnection)
	if ok {
		iConn = statConn.Connection
	}
	var counter stats.Counter
	if statConn != nil {
		counter = statConn.ReadCounter
	}
	if c, ok := iConn.(*internet.PacketConnWrapper); ok {
		isOverridden := false
		if UDPOverride.Address != nil || UDPOverride.Port != 0 {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Unwrap with errors.Unwrap/errors.Is to find the embedded cause before acting
  2. io.EOF / context.Canceled / io.ErrClosedPipe underneath are benign — route them to info-level logging instead of alerting
  3. If the cause is a timeout, adjust policy timeouts as in the process-request/response errors
  4. Do not retry blindly; retry only when the base cause is a network error, not EOF/cancel

Example fix

```go
err := freedomHandler.Process(ctx, link, dialer)
if err != nil {
  if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) {
    // normal teardown
  }
}
```
Defensive patterns

Strategy: try-catch

Type guard

```go
func isBenignTeardown(err error) bool {
    return errors.Is(err, io.EOF) ||
        errors.Is(err, context.Canceled) ||
        errors.Is(err, io.ErrClosedPipe)
}
```

Try / catch

```go
err := h.Process(ctx, link, dialer)
if err != nil && !isBenignTeardown(err) {
    cause := errors.Unwrap(err) // real failure lives here
    log.Error().Err(cause).Msg("freedom connection failed")
}
```

Prevention

When it happens

Trigger: Either uplink or downlink copy fails, the context is cancelled (client disconnect propagates), or a policy timer aborts the task group. Essentially every non-dial failure of a freedom connection funnels here.

Common situations: Normal connection teardown being logged as an error; context cancellation when users close browsers/apps; timeouts as in 566/567.

Related errors


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