ctxcancel

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package ctxcancel provides shared helpers for translating context cancellation surfaced from IO operations (DB scan, RPC call, message bus claim) into structured *errcode.Error values with the correct HTTP status for each ctx error variant:

  • context.Canceled → ErrClientCanceled → HTTP 499 + slog.Warn (real client-direction signal: keeps 5xx SLO clean)
  • context.DeadlineExceeded → ErrServerTimeout → HTTP 504 + slog.Error (real server-direction timeout: feeds 5xx alerting + SDK retry)

Splitting by ctx error variant aligns with NGINX (499 vs 504), Kratos transport/http/status (Canceled→499, DeadlineExceeded→504), and standard load-balancer / SDK retry expectations: 499 is benign client noise, 504 is a real timeout that should be alerted on.

Both wrapped errors carry CategoryInfra so existing IsInfraError predicates (health bucket, retry classifiers) preserve their semantics.

Index

Constants

View Source
const (
	ReasonCanceled         = "canceled"
	ReasonDeadlineExceeded = "deadline_exceeded"
)

Reason values surfaced via Details[DetailsKeyReason]. Low-cardinality enum safe to expose in HTTP response bodies, slog records, and tracing span attributes.

View Source
const DetailsKeyReason = "reason"

DetailsKeyReason is the slog.Attr key under which Wrap records the originating ctx-cancel reason ("canceled" | "deadline_exceeded"). Exported so consumers (HTTP boundary, tracing middleware, log4xx) reference one string instead of duplicating the literal at multiple call sites.

Variables

This section is empty.

Functions

func Detect

func Detect(err error) bool

Detect reports whether err is or wraps context.Canceled or context.DeadlineExceeded. Returns false for nil.

func ReasonFromDetails

func ReasonFromDetails(e *errcode.Error) string

ReasonFromDetails extracts and canonicalizes the reason value from an *errcode.Error, returning the empty string when no recognized reason is present. Callers (HTTP boundary, log4xx, tracing middleware) MUST go through this helper instead of inspecting Details directly, so the low-cardinality enum contract holds even when a non-canonical producer (third-party code, future Wrap variants that haven't migrated yet, malicious upstream) attaches an arbitrary attribute.

Fail-closed semantics: any value that is not exactly ReasonCanceled or ReasonDeadlineExceeded yields "" (treat as "unknown / instrumentation gap"). This prevents user-derived strings from polluting span attribute cardinality (Tempo/Jaeger backends are extremely sensitive to high cardinality on a single attribute) and from leaking arbitrary content into structured logs.

func Wrap

func Wrap(err error, op, identifier string) *errcode.Error

Wrap returns a structured *errcode.Error for context cancellation errors, classifying by ctx error variant; nil when err is not a ctx cancellation.

  • context.Canceled → Code=ErrClientCanceled, public Message "request canceled" (HTTP 499, log4xx, span Unset)
  • context.DeadlineExceeded → Code=ErrServerTimeout, public Message "request timed out" (HTTP 504, log5xx, span Error)

Parameters:

  • op: PascalCase operation label (e.g. "Insert", "ScanRow"); recorded in InternalDetails for operator triage.
  • identifier: caller-redacted resource locator (e.g. "key=foo", "configID=…"); recorded in InternalDetails only.

The wrapped err is preserved as Cause so errors.Is(returned, context.Canceled) or errors.Is(returned, context.DeadlineExceeded) still works for callers that need to detect cancellation up the stack.

Details["reason"] mirrors the variant ("canceled" / "deadline_exceeded") for observability fan-out: tracing span attribute (client.cancel.reason for 499 only) and log4xx cancel_reason field. The HTTP status carries the primary signal; the reason field provides supplementary low-cardinality dimension for dashboards that bucket by both.

Privacy contract:

  • Public Message and Details are consumed by pkg/httputil.writeErrcodeError and appear in 4xx HTTP response bodies (5xx responses sanitize Message to "internal server error" and strip Details per the standard pipeline). The reason enum is intentionally low-cardinality and free of user identifiers — safe to expose.
  • InternalDetails carries the operator-grade op/identifier (key names, config IDs) via the sealed InternalAttr constructor and is routed to slog only; pkg/httputil never writes InternalDetails to the response body, so identifier may freely contain such detail.

Maintainers extending Details MUST keep new fields enum-typed and caller-redacted, otherwise rotate them through InternalDetails instead.

func WrapOrInfra

func WrapOrInfra(err error, op, identifier string, fallbackCode errcode.Code, fallbackMsg string) error

WrapOrInfra is the canonical convenience for the IO-boundary pattern:

if cancelErr := ctxcancel.Wrap(err, op, id); cancelErr != nil {
    return cancelErr
}
return errcode.Wrap(errcode.KindInternal, fallbackCode, fallbackMsg, err, errcode.WithCategory(errcode.CategoryInfra))

One call replaces both branches: a context cancellation surfaces as the canonical 499/504 split (Canceled→ErrClientCanceled, DeadlineExceeded→ ErrServerTimeout) while every other error gets uniformly mapped to the caller-supplied infra-category errcode with err preserved as Cause.

Use at repository / RPC client / message-bus call sites where the only branching needed is ctx-cancel vs generic infra failure. When the fallback path needs *additional* differentiation (a domain not-found branch, a PascalCase InternalDetails template, a non-Infra Category), keep the inline pattern — this helper deliberately omits those degrees of freedom to avoid a six-parameter signature.

fallbackMsg MUST be a compile-time const literal (not fmt.Sprintf output, not string concatenation, not a runtime variable). The struct-literal escape from MESSAGE-CONST-LITERAL-01 is a static-only carve-out — runtime PII at this argument would silently leak into the wire Message field at 4xx status codes.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL