Skip to content

Client-canceled filesystem requests are recorded as HTTP 500 instead of 499 #758

Description

@mashenjun

Bug Report

1. Minimal reproduce step (Required)

  1. Send an authenticated DELETE request to /v1/fs/* and let it enter the backend or datastore operation.
  2. Cancel the client request before the backend operation returns.
  3. Observe a delete_failed log with context canceled, followed by an http_request log whose status is 500.
  4. Repeat with at least 10 requests in a five-minute window. The misclassified responses can trigger Drive9RouteHTTPErrorRateCritical.

Production evidence from O11Y-2619575 on 2026-07-20:

  • DELETE /v1/fs/* produced 47 HTTP 500 responses.
  • 43 failures were context canceled.
  • 3 were database dial operations canceled by the same request context.
  • 1 reported an already committed or rolled-back transaction.
  • Another 8 cancellations were correctly recorded as HTTP 499 during an earlier request phase.
  • The five-minute 5xx ratio reached 36.71 percent and triggered the critical alert.

2. What did you expect to see? (Required)

Client-initiated cancellation should be classified consistently as HTTP 499 for server logs and metrics, regardless of whether cancellation occurs during authentication or inside a business handler.

Server-owned timeouts, shutdowns, and genuine internal errors must retain their appropriate 5xx status. Classification should use the request context or cancellation cause rather than matching error strings.

3. What did you see instead (Required)

The authentication path detects client cancellation and records status 499:

drive9/pkg/server/auth.go

Lines 148 to 159 in e36a45f

func isClientCanceled(ctx context.Context, err error) bool {
return errors.Is(err, context.Canceled) || errors.Is(ctx.Err(), context.Canceled)
}
func logAuthClientCanceled(ctx context.Context, event string, kv ...any) {
logger.Info(ctx, "server_event", eventFields(ctx, event, kv...)...)
metricEvent(ctx, "auth", "result", "client_canceled")
}
func writeClientCanceled(w http.ResponseWriter) {
w.WriteHeader(statusClientClosedRequest)
}

The DELETE handler special-cases not-found errors but maps every other error, including request context cancellation, to status 500:

drive9/pkg/server/server.go

Lines 3161 to 3172 in e36a45f

if err != nil {
if errors.Is(err, datastore.ErrNotFound) {
logger.Warn(r.Context(), "server_event", eventFields(r.Context(), "delete_not_found", "path", path, "recursive", recursive, "kind", kind)...)
errJSON(w, http.StatusNotFound, err.Error())
return
}
if errJSONInvalidRootDentry(w, err) {
return
}
logger.Error(r.Context(), "server_event", eventFields(r.Context(), "delete_failed", "path", path, "recursive", recursive, "kind", kind, "error", err)...)
errJSON(w, http.StatusInternalServerError, sanitizeClientError(err))
return

The observed production cancellation burst also affected stat, mkdir, and git-workspace request paths, suggesting that cancellation handling is not centralized across handlers.

4. What is your drive9 version? (Required)

Observed on the production dat9 deployment on 2026-07-20; the exact deployed image tag was not captured. Current main commit e36a45f contains the same inconsistent status mapping.

Suggested acceptance criteria

  1. Requests canceled by the client are recorded as 499 in business handlers as well as authentication middleware.
  2. Internal cancellation, server timeout, shutdown, and genuine backend failures are not blindly converted to 499.
  3. DELETE cancellation has a regression test covering the emitted HTTP status and request metric.
  4. Other handlers that directly convert backend errors to 500 are audited for the same inconsistency.
  5. Client cancellations no longer contribute to Drive9 5xx-ratio alerts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions