Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 85 additions & 38 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"]
documentation = "https://docs.rs/wireframe"

[workspace]
members = [".", "crates/wireframe-verification"]
members = [".", "crates/wireframe-verification", "wireframe_testing"]
default-members = ["."]
resolver = "3"

Expand Down Expand Up @@ -61,6 +61,8 @@ wireframe = { path = ".", features = ["test-support", "pool", "testkit"] }
wireframe_testing = { path = "./wireframe_testing" }
logtest = "2.0.0"
proptest = "1.7.0"
googletest = "0.14.3"
pretty_assertions = "1.4.1"
loom = "0.7.2"
async-stream = "0.3.6"
serial_test = "3.2.0"
Expand Down
15 changes: 3 additions & 12 deletions benches/codec_performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,15 @@
//! - fragmented versus unfragmented payload-wrapping overhead.

use criterion::{BenchmarkId, Criterion, Throughput, black_box};

#[path = "../tests/common/codec_benchmark_support.rs"]
mod codec_benchmark_support;

#[path = "../tests/common/codec_fragmentation_benchmark_support.rs"]
mod codec_fragmentation_benchmark_support;

use codec_benchmark_support::{
use wireframe_testing::codec_benchmarks::{
FRAGMENT_PAYLOAD_CAP_BYTES,
Measurement,
MeasurementExt as _,
PayloadClass,
VALIDATION_ITERATIONS,
benchmark_workloads,
measure_decode,
measure_encode,
};
use codec_fragmentation_benchmark_support::{
FRAGMENT_PAYLOAD_CAP_BYTES,
MeasurementExt as _,
measure_fragmentation_overhead,
measure_fragmented_wrap,
measure_unfragmented_wrap,
Expand Down
12 changes: 3 additions & 9 deletions benches/codec_performance_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,13 @@ use wireframe::codec::{
LengthDelimitedFrameCodec,
examples::{HotlineAdapter, HotlineFrameCodec},
};

#[path = "../tests/common/codec_benchmark_support.rs"]
mod codec_benchmark_support;

#[path = "../tests/common/codec_alloc_benchmark_support.rs"]
mod codec_alloc_benchmark_support;

use codec_alloc_benchmark_support::{AllocationBaseline, allocation_label};
use codec_benchmark_support::{
use wireframe_testing::codec_benchmarks::{
AllocationBaseline,
BenchmarkWorkload,
CodecUnderTest,
LARGE_PAYLOAD_BYTES,
VALIDATION_ITERATIONS,
allocation_label,
benchmark_workloads,
measure_decode,
measure_encode,
Expand Down
26 changes: 26 additions & 0 deletions docs/adr-005-serializer-abstraction.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,32 @@ Accepted implementation details:
- Supporting multiple serializers can increase maintenance overhead.
- Metadata-aware decoding requires a clear definition of what metadata is
exposed and when it is safe to consume it.
- The current `Serializer::serialize` contract returns `Vec<u8>`. App outbound
encoding therefore still converts serialized messages into `Bytes` before
codec wrapping. This keeps the 2026-06-05 audit refactor behaviour-preserving
and source-compatible, but it retains one allocation-backed buffer handoff on
outbound response paths. That cost is acceptable for the audit milestone
because changing the serializer return type would be a public contract change
with broad downstream impact. The zero-copy migration is tracked in
<https://github.com/leynos/wireframe/issues/538> and should be reviewed
before the next public API-breaking release or during the zero-copy frame and
payload roadmap work.

### Deferred zero-copy serializer output migration

Issue #538 owns the deliberate migration away from `Vec<u8>` serializer output.
The expected implementation steps are:

- Choose the public byte container for serializer output, aligning with the
zero-copy frame and payload roadmap.
- Change `Serializer::serialize` and serializer adaptor implementations to
return that container.
- Update app outbound helpers such as `encode_message_frame` and
`send_response_framed` to remove `Bytes::from(Vec<u8>)` conversion points.
- Validate compatibility and performance with existing bincode, Serde bridge,
app response, and codec-driver tests.
- Add targeted regression tests for the selected zero-copy behaviour where the
chosen container exposes observable ownership or sharing semantics.

## Outstanding Decisions

Expand Down
83 changes: 72 additions & 11 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,45 @@ Use this checklist before merging API naming changes:
- Label cross-layer terms (for example, `correlation_id`) explicitly as shared
metadata.

## App inbound and outbound helper boundaries

Application response paths must keep message serialization and codec frame
wrapping in one place. Use `app::outbound_encoding::encode_message_frame` when
an app path needs to turn an `EncodeWith<S>` message into a
`FrameCodec::Frame`. Callers should keep transport-specific work at the edge:

- Raw stream response methods encode the returned codec frame into a byte
buffer and write that buffer to `AsyncWrite`.
- Framed response methods send the returned codec frame through the supplied
framed sink.
- The length-delimited compatibility path intentionally sends the raw
serialized message to `LengthDelimitedCodec`; do not wrap that payload with
the app codec first, because `framed.send` supplies the length prefix.

Inbound connection handling should also preserve the phase boundary:
`build_dispatchable_envelope` owns decode, fragment reassembly, message
assembly, and the successful deserialization-counter reset. Individual failure
policy remains in `DeserFailureTracker`, so logging, metrics, and threshold
decisions do not drift across inbound call sites.

Builder methods that change `WireframeApp` type parameters should route through
the shared rebuild helpers in `app::builder::core`. Serializer and codec
transitions use `rebuild_with_params`; connection-state transitions use
`rebuild_with_connection_state`. A teardown hook is typed to the old connection
state, so `on_connection_setup` clears any teardown hook registered before it.
Register teardown after setup when both hooks are required.

Client pool internals should use `client::pool::sync::lock_or_recover` for
poison-tolerant `Mutex` access. Keep the policy local to pool synchronization
code so scheduler and slot state recovery cannot drift. Recovery logs a warning
and increments the `wireframe_pool_bookkeeping_poison_recoveries_total`
counter. Client connection construction should flow through
`WireframeClientBuilder::into_parts()` and `ClientBuildParts`, which keeps
single-client and pooled-client socket setup, preamble exchange, lifecycle
hooks, request hooks, and tracing configuration on the same path. Pooled lease
methods should go through `PooledClientLease::dispatch_on_connection` so
checkout and recycle-on-error policy stay in one place.

## Error surface conventions

Library-facing errors should stay typed and inspectable by default. Use
Expand Down Expand Up @@ -139,11 +178,11 @@ continuous integration (CI).
Wireframe now uses a hybrid root manifest: the repository root `Cargo.toml`
contains both `[package]` and `[workspace]`.

After roadmap items 15.1.1 and 15.1.2 the workspace explicitly lists the root
package and the internal verification crate, while keeping only the root
package as a default member:
The workspace explicitly lists the root package, the internal verification
crate, and the testing helper crate, while keeping only the root package as a
default member:

- `members = [".", "crates/wireframe-verification"]`
- `members = [".", "crates/wireframe-verification", "wireframe_testing"]`
- `default-members = ["."]`

That means ordinary root-level commands such as `cargo build`, `cargo check`,
Expand All @@ -153,19 +192,19 @@ to target the main `wireframe` package by default.
Use plain root-level Cargo commands for day-to-day work on the main crate.
Reach for `--workspace` when a task is explicitly meant to cover every current
workspace member, for example repository-wide validation in CI or when a change
also touches `crates/wireframe-verification`.
also touches `crates/wireframe-verification` or `wireframe_testing`.

Use `cargo test -p wireframe-verification` to exercise the Stateright crate in
isolation. The existing `Makefile` targets still focus on the root `wireframe`
crate because the dedicated formal-verification targets belong to later roadmap
items.

One Cargo nuance is worth knowing: `cargo metadata` for this repository still
reports the in-tree helper crate `wireframe_testing` in `workspace_members`
because it lives under the repository root as a path dependency. That sits
alongside the explicit `wireframe-verification` member and does not change
day-to-day command ergonomics because `default-members = ["."]` keeps plain
root-level Cargo commands focused on the main `wireframe` package.
Use `cargo test -p wireframe_testing` when changing shared test fixtures,
observability helpers, codec drivers, or other support APIs exported by the
testing helper crate. Use `cargo test -p wireframe` for the root crate when a
change should stay limited to the published library. Plain root-level commands
keep their day-to-day ergonomics because `default-members = ["."]` leaves the
main `wireframe` package as the only default member.

### Workspace manifest test support

Expand Down Expand Up @@ -194,6 +233,28 @@ behaviour-driven development (BDD) fixture for these assertions. Extend it by
loading more workspace-state inputs in `load()` and adding focused verification
methods that the step definitions and scenario can reuse.

## Example and benchmark support

TCP server examples that share the standard
`WireframeApp<BincodeSerializer, (), Envelope>` runtime shape should use
`examples/support/runtime_bootstrap.rs` for tracing setup, runtime app
construction, listener binding, connection spawning, shutdown-aware accept
loops, and current-thread Tokio runtime startup. Keep example-specific address
parsing, app construction, handlers, and middleware in the example file.

Codec benchmark helpers live in `wireframe_testing::codec_benchmarks`. Bench
targets, direct unit tests, and BDD fixtures should import the workload matrix,
measurement helpers, fragmentation helpers, and allocation-label helpers from
that module instead of coupling to files under `tests/common` with `#[path]`.

Fragment transport integration tests import `tests/common/fragment_helpers.rs`
as a facade. Keep the public re-export surface stable there, and place helper
implementation details in responsibility-focused modules under
`tests/common/fragment_helpers/`: app construction and spawning, assertions,
fragmentation configuration, envelope building, error types, and framed
transport. New fragment helpers should be added to the smallest matching module
and re-exported only when more than one test binary needs the helper.

## Formal verification tooling

Formal-verification tools are pinned in repository metadata and installed
Expand Down
Loading
Loading