[LOADED_DOCS: 7 files, no new loads]

AcBinary: add framing doc, buffer growth fixes, doc updates

- Added `BINARY_WHYUSE.md` for architectural framing and value proposition
- Updated `BINARY_FEATURES.md` and `README.md` to reference and prioritize the new doc
- Documented AsyncPipeWriterOutput chunk-size limitation and workarounds in `BINARY_ASYNCPIPE_ISSUES.md`
- Refactored buffer growth logic in `AcBinarySerializer.BinarySerializationContext.cs` to validate capacity after grow and throw clear exceptions on under-provisioning; removed dead method
- Fixed chunk size alignment bug in `AsyncPipeWriterOutput.cs` to prevent buffer under-provisioning
- Added `AYCODE_NATIVEAOT` build config support in `Program.cs`
- Improved documentation clarity and error diagnostics for streaming/buffered serialization edge cases
This commit is contained in:
Loretta 2026-05-11 13:28:43 +02:00
parent 96c09a65bb
commit 73d81ea580
8 changed files with 156 additions and 17 deletions

View File

@ -34,7 +34,9 @@ public static class Program
{
private const string ResultsDirectory = @"H:\Applications\Aycode\Source\AyCode.Core\Test_Benchmark_Results\Benchmark";
#if DEBUG
#if AYCODE_NATIVEAOT
private const string BuildConfiguration = "NativeAOT";
#elif DEBUG
private const string BuildConfiguration = "Debug";
#else
private const string BuildConfiguration = "Release";

View File

@ -364,18 +364,25 @@ public static partial class AcBinarySerializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int additionalBytes)
{
if (_position + additionalBytes > _bufferEnd)
Output.Grow(ref _buffer, ref _position, ref _bufferEnd, additionalBytes);
if (_position + additionalBytes > _bufferEnd) GrowAndValidate(additionalBytes);
}
/// <summary>
/// Ensures the buffer has enough space for the specified number of bytes.
/// Called before property writes to avoid mid-object Grow() calls.
/// Slow path for <see cref="EnsureCapacity"/>: invokes <c>Output.Grow</c> and revalidates that
/// the request was actually satisfied. A chunk-limited output (e.g. <c>AsyncPipeWriterOutput</c>
/// on a single-value write &gt; <c>MaxChunkSize</c> data bytes) may return WITHOUT growing the
/// active buffer to the requested size — silent under-provisioning would cause downstream
/// <c>ArgumentOutOfRangeException</c> in <c>AsSpan</c>/<c>CopyTo</c> at corrupt offsets.
/// <c>NoInlining</c> keeps the hot path of <see cref="EnsureCapacity"/> tiny and inline-friendly.
/// Related limit: see <c>BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9</c>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReserveCapacity(int bytes)
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowAndValidate(int additionalBytes)
{
EnsureCapacity(bytes);
Output.Grow(ref _buffer, ref _position, ref _bufferEnd, additionalBytes);
var available = _bufferEnd - _position;
if (available < additionalBytes) ThrowGrowFailedToSatisfy(additionalBytes, available);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -386,13 +393,6 @@ public static partial class AcBinarySerializer
_buffer[_position++] = value;
}
/// <summary>Writes a single byte without capacity check. Caller must ensure buffer space.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteByteUnsafe(byte value)
{
_buffer[_position++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteTwoBytes(byte b1, byte b2)
{
@ -1004,6 +1004,19 @@ public static partial class AcBinarySerializer
$"This limit is dictated by the writer's worst-case 'charLength * 4' UTF-8 byte allocation; " +
$"larger inputs would silently overflow int arithmetic.");
/// <summary>
/// Throw helper for <see cref="GrowAndValidate"/>: invoked when <c>Output.Grow</c> returns
/// without satisfying the requested capacity (chunk-limited output, single value larger than
/// the per-chunk data capacity). Marked <c>NoInlining</c> so the throw-site stays out of the
/// inlined caller body. See <c>BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9</c> for fix direction.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowGrowFailedToSatisfy(int requested, int available) =>
throw new InvalidOperationException(
$"Output.Grow did not satisfy the requested capacity: {requested} bytes requested, only {available} bytes available after Grow. " +
$"This typically indicates a chunk-limited output (e.g. AsyncPipeWriterOutput) with a single serialized value exceeding the per-chunk data capacity. " +
$"See BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9.");
#endregion
#region Bulk Array Writes inline

View File

@ -284,7 +284,22 @@ public struct AsyncPipeWriterOutput : IBinaryOutputBase
}
// Acquire new chunk with header reservation (common to both paths).
AcquireChunk(Math.Max(needed, _chunkSize), out buffer, out position, out bufferEnd);
//
// Unit alignment: `needed` is the data-byte count requested by the caller (via EnsureCapacity);
// `_chunkSize` is the chunk-on-wire total size (header + data) — same units AcquireChunk's
// `requestSize` parameter expects. Convert `needed` to the chunk-on-wire scale (`+ headerOffset`)
// before the Max() so apples-to-apples. AcquireChunk then subtracts `headerOffset` internally to
// size the data region.
//
// Without this alignment, a caller request of exactly `_chunkSize - headerOffset` data bytes
// would yield a chunk whose data region is `headerOffset` bytes SHORT of the request — every
// EnsureCapacity for a value larger than the chunk-data capacity would silently under-provision,
// surfacing later as `ArgumentOutOfRangeException` in `AsSpan`/`CopyTo` at corrupt offsets.
// The sanity guard in `BinarySerializationContext.EnsureCapacity` (post-Grow revalidation +
// `ThrowGrowFailedToSatisfy`) catches the remaining > MaxChunkSize case explicitly — see
// `BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9`.
var headerOffset = _multiMessage ? HeaderSize : 0;
AcquireChunk(Math.Max(needed + headerOffset, _chunkSize), out buffer, out position, out bufferEnd);
_currentChunkStart = position;
}

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,8 @@
Advanced serialization features on top of the wire format. Wire format: `BINARY_FORMAT.md` | Options/presets: `BINARY_OPTIONS.md` | Internal architecture: `BINARY_IMPLEMENTATION.md` | Source generation: `BINARY_SGEN.md`.
> **Architectural framing — why use AcBinary at all?** See [`BINARY_WHYUSE.md`](BINARY_WHYUSE.md) for the category positioning vs wire-only serializers (Protobuf / MessagePack / MemoryPack), the three-pillar value proposition (graph integrity + bandwidth + streaming), real-world WASM SignalR reference numbers, fit/not-fit decision lists, and the framing for how to read AcBinary's benchmark numbers.
## Optimization Policy (LLM)
AcBinary is a **general-purpose serializer**, not a benchmark-only implementation.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@ AcBinary serialization system. Primary goal: **speed** (two-phase scan+serialize
## Files in this folder
- [`BINARY_WHYUSE.md`](BINARY_WHYUSE.md) — Architectural framing: why AcBinary exists (category vs wire-only serializers, three-pillar value proposition, fit/not-fit decision lists, benchmark reading guide)
- [`BINARY_FEATURES.md`](BINARY_FEATURES.md) — High-level features and capabilities
- [`BINARY_FORMAT.md`](BINARY_FORMAT.md) — Wire format specification
- [`BINARY_OPTIONS.md`](BINARY_OPTIONS.md) — Configuration options (`AcBinaryOptions`)
@ -17,7 +18,7 @@ AcBinary serialization system. Primary goal: **speed** (two-phase scan+serialize
## Start here
Start with [`BINARY_FEATURES.md`](BINARY_FEATURES.md) (overview), then [`BINARY_FORMAT.md`](BINARY_FORMAT.md) (wire-level). [`BINARY_SGEN.md`](BINARY_SGEN.md) covers build-time code-gen integration.
Start with [`BINARY_WHYUSE.md`](BINARY_WHYUSE.md) (architectural framing — why this serializer over the alternatives), then [`BINARY_FEATURES.md`](BINARY_FEATURES.md) (feature overview), then [`BINARY_FORMAT.md`](BINARY_FORMAT.md) (wire-level). [`BINARY_SGEN.md`](BINARY_SGEN.md) covers build-time code-gen integration.
## Cross-references