Skip to content

proxy: propagate vehicle request cancellation with startup cleanup - #535

Open
maharshi-coding wants to merge 1 commit into
teslamotors:mainfrom
maharshi-coding:fix/vehicle-command-cancellation
Open

maharshi-coding wants to merge 1 commit into
teslamotors:mainfrom
maharshi-coding:fix/vehicle-command-cancellation

Conversation

@maharshi-coding

Copy link
Copy Markdown
Contributor

Description

When an HTTP client cancels a signed vehicle-command request, the proxy currently continues using a timeout derived from context.Background(). Simply propagating the request context exposes a second problem: Dispatcher.Start can return before the listener publishes its termination state, while the handler only defers Disconnect after successful Connect.

This patch derives the vehicle-handler timeout from req.Context(), registers cleanup before connection startup, and gives each dispatcher listener explicit ownership before launching it. Failed startup stops and joins that listener. Separate readiness/completion signals also make repeated starts and concurrent stops safe without an orphaned completion sender.

Cancellation checks at setup boundaries prevent a successful setup return from advancing a canceled request, including the unsupported-protocol transition to REST fallback. Existing timeout bounds, vehicle error mappings, and cache-before-disconnect ordering remain intact.

Addresses the vehicle-command portion of #491. This deliberately leaves REST forwarding to #492 and the separate handshake result-sender leak to #494/#495/#515. It revisits the lifecycle concern identified in closed #496 without including its REST or documentation changes.

Cancellation does not recall a transmitted command or establish that it did not execute. The command retry/outcome policy is unchanged; a regression test verifies that cancellation after a synthetic send retains possible success, closes the receiver, and does not retry.

Regression evidence

Deterministic fake connectors and setup hooks cover canceled lookup/Connect/handshake stages, cleanup after failures, successful startup, parent deadlines and proxy timeouts, VIN serialization, independent VINs, unsupported fallback, concurrent startup waiters, and repeated/concurrent Stop.

  • Original dispatcher: startup-cancellation regression fails 100/100 times; patched lifecycle cases pass 600/600 runs and 120/120 race runs.
  • Original proxy: 13 failing / 4 passing cases; patched proxy: 17/17 pass and 1,700/1,700 repeated race cases.
  • No real vehicle, Fleet API, or BLE activity was exercised.

Validation

Validated commit: 523adcc9707e37ac50dc80dbbd8e78fd56b0b040 (base d6239bf377b8a5859e8f5c9d64409df2a057934a).

Local macOS arm64, CI-pinned Go 1.27.2 and golangci-lint v2.14.0:

  • go test ./...
  • go test -race ./...
  • go vet ./...
  • go build ./...
  • go test -cover ./...
  • go install ./cmd/...
  • golangci-lint run -v --timeout 30s
  • Changed-file formatting and git diff --check

These are local results; upstream GitHub Actions are not claimed as passed.

Scope and limitations

The separately tracked #494 worker leak remains unchanged, so this PR does not claim global goroutine-leak freedom. Listener joining assumes connector Receive and message processing return promptly, as the SDK's channel-returning connectors do. Cancellation inside REST forwarding remains #492's scope.

Author and AI assistance

Implementation and regression tests by Maharshi Barot (@maharshi-coding), with AI assistance for investigation, implementation, testing, and review.

Type of change

  • New feature
  • Breaking change
  • Bug fix
  • Documentation update

Checklist

  • My code follows the style of this project.
  • I have performed a self-review of my code.
  • No user-facing configuration or public API documentation changes are needed.
  • I have added regression tests for the change.

Derive vehicle-command timeouts from the incoming request, establish
cleanup ownership before Connect, and stop and join failed dispatcher
startup without affecting another listener generation.

Cover cancellation boundaries, exactly-once cleanup, startup concurrency,
deadline bounds, and possible-success behavior after a synthetic send.

Addresses the vehicle-command portion of teslamotors#491; REST forwarding and the
separate StartSessions result-sender leak remain outside this change.

Authored by Maharshi Barot, with AI assistance for investigation,
implementation, testing, and review.
@maharshi-coding

Copy link
Copy Markdown
Contributor Author

Ready for maintainer review — based on current main (d6239bf), CI green.

Scope (re: #491). This addresses only the vehicle-command path (handleVehicleCommand) plus the dispatcher Start/Stop lifecycle cleanup the fix depends on. It is complementary to #492 (REST-forwarding path), not a duplicate — per the note on #491 that the vehicle-command half was intentionally deferred. It does not carry #496's REST/documentation changes.

Core change. Derive the per-command timeout from req.Context() instead of context.Background(), with ctx.Err() checks at each setup boundary, and move defer car.Disconnect() ahead of Connect so a canceled/failed startup can't leak the connector. The dispatcher now gives each listener explicit ownership and joins it on failed startup, so a Start canceled mid-handshake no longer orphans a goroutine.

Validation (local, Go 1.27.x + golangci-lint v2.14.0):

  • go build ./..., go vet ./..., golangci-lint run — clean
  • go test -cover ./... — pass
  • go test -race -count=10 ./internal/dispatcher ./pkg/proxy ./pkg/vehicle — no data race
  • Regression check: with the production changes reverted to main (new tests kept), the tests fail on exactly the tesla-http-proxy does not propagate incoming request cancellation #491 symptoms — request context not canceled, command still executed after client cancel, connector not disconnected, dispatcher startup leak — and pass with the fix.

An additional review pass (correctness / security / lifecycle), done with AI assistance, flagged no blocking issues. Implementation, design, and tests are my own; AI was used only as an assistant for investigation, validation, and review.

Happy to split the dispatcher change into its own commit, or trim scope, if that's preferred.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant