Skip to content

Harden Link lifecycle and release validation for v0.1.0 - #3

Merged
zekageri merged 28 commits into
mainfrom
fix/v0.1.0-release-hardening
Jul 19, 2026
Merged

Harden Link lifecycle and release validation for v0.1.0#3
zekageri merged 28 commits into
mainfrom
fix/v0.1.0-release-hardening

Conversation

@zekageri

Copy link
Copy Markdown
Collaborator

Summary

Harden Link before v0.1.0 by eliminating the submission/shutdown semaphore race, serializing complete lifecycle transitions, making response ownership explicitly fallible, validating every value narrowed into ESP-IDF signed parameters, aligning buffered and streaming redirect handling, and making the complete test suite release-gating.

Closes #2.

Root causes

  • fetch() previously published a queue entry, released the runtime mutex, and only then signaled _items. Shutdown could drain the queue, delete the semaphore, and leave the submitting task with a stale handle.
  • init() exposed Starting and partially allocated runtime storage while deinit() could run concurrently and free it before startup completed.
  • Public timeout and body-size values could be narrowed from unsigned types into ESP-IDF int parameters without representability checks.
  • Implicit copies of allocation-backed response objects could silently lose headers or body data while preserving a successful response status.
  • Buffered requests accumulated intermediate redirect bodies before deciding whether to follow the redirect.
  • Persistent-client host tests lived outside the workflow that gates release-tag creation.

Implementation

Lifecycle and submission safety

  • add a dedicated recursive lifecycle mutex that serializes complete init() and deinit() transitions
  • keep submission independent from the lifecycle mutex so callbacks racing shutdown cannot deadlock the worker that deinit() is waiting for
  • prepare and publish each bounded request under one runtime critical section
  • publish the queue slot and worker semaphore permit as one transaction
  • roll back queue tail, count, slot ownership, and request storage if signaling fails
  • retain reserved shutdown permits for every worker when the accepted-request queue is full
  • preserve timed-out deinit() behavior: state remains Stopping and runtime storage stays alive for a later retry
  • make partial startup failure stop created workers, release all partial resources, and restore a reusable Uninitialized instance

ESP-IDF bounds

  • reject defaultTimeoutMs > INT_MAX
  • reject explicit per-request timeouts outside the ESP-IDF range with InvalidTimeout
  • reject maxRequestBodySize > INT_MAX
  • reject streamChunkSize > INT_MAX
  • make header-total validation overflow-safe

Explicit response ownership

  • make LinkOwnedBuffer, LinkHeaders, LinkBody, and LinkResponse move-only
  • retain explicit result-returning copyFrom() operations
  • give LinkResponse::copyFrom() a strong guarantee: failed header/body duplication leaves the destination unchanged
  • add a deterministic host allocation-failure seam and tests

Redirect behavior

  • determine redirect disposition before exposing or buffering body data
  • discard intermediate redirect bodies in both buffered and streaming modes
  • preserve final-body limits, redirect limits, origin checks, downgrade policy, and cross-origin header stripping

Validation and release gates

  • expand host tests for move-only contracts, allocation failure, timeout/body/buffer boundaries, repeated init/deinit, and queue capacity invariants
  • replace the sequential shutdown sketch with concurrent producers racing repeated deinit() / init() in both connection modes
  • verify exactly one terminal callback for every accepted request, balanced diagnostics, no post-shutdown callbacks, and heap integrity
  • consolidate persistent-client tests into ci.yml
  • require persistent tests for example builds, Arduino CLI builds, and release creation
  • compile against ArduinoJson 7.0.0 in PIOArduino and current ArduinoJson v7 in Arduino CLI
  • declare ArduinoJson (>=7.0.0) consistently in Arduino metadata
  • add a physical-device release qualification document for lifecycle stress and persistent HTTPS soak evidence

Compatibility impact

This intentionally changes allocation-backed response types to move-only before the first stable release. Existing code that implicitly copies LinkResponse, LinkHeaders, LinkBody, or LinkOwnedBuffer must move ownership or call the explicit copyFrom() API and handle its result.

The default HTTP mode remains PerRequest. Persistent per-worker clients remain opt-in. No automatic request replay was added.

Validation

GitHub Actions is the automated source of truth for this branch and must pass:

  • metadata validation
  • clang-format
  • embedded source audit
  • general host tests
  • persistent-client host tests
  • PIOArduino example builds on ESP32, ESP32-S3, ESP32-C3, and ESP32-P4
  • Arduino CLI example builds on the same families
  • concurrent lifecycle stress sketch compilation
  • minimum and current ArduinoJson v7 compatibility

Physical ESP32 execution and the persistent HTTPS soak cannot be performed through the GitHub connector. They remain explicit release gates documented in docs/release-validation.md; the PR should stay draft until that evidence is attached.

Copy link
Copy Markdown
Collaborator Author

Implementation validation update

Final branch head: 16522bd4ec4905a85473e80bf78d6fa1d053f5e5.

Completed validation:

  • reconstructed the final formatted source and ran the general host test executable with g++ -std=c++20 -Wall -Wextra -pedantic: passed
  • ran the persistent-client host test executable with the same warnings: passed
  • an earlier PR CI run passed source audit, metadata, both host suites, all four PIOArduino example-build lanes, all four Arduino CLI lanes, and compilation of the concurrent lifecycle stress sketch; its only failure was clang-format
  • exact clang-format output from the Actions runner was applied to the changed C++ and Arduino sources
  • the temporary formatting workflow was removed from the branch

The final CI run for the current head is queued behind obsolete runs generated during the implementation commits. It must still complete successfully before the PR leaves draft.

Physical execution of tests/esp32/shutdown-stress and the persistent HTTPS soak remain required release qualification. The required evidence and invariants are documented in docs/release-validation.md.

@zekageri
zekageri marked this pull request as ready for review July 19, 2026 14:55
@zekageri
zekageri merged commit b19966b into main Jul 19, 2026
29 of 37 checks passed
@zekageri
zekageri deleted the fix/v0.1.0-release-hardening branch July 26, 2026 17:54
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.

Release hardening: lifecycle synchronization, request signaling, bounds, and CI coverage

1 participant