[Summary]
StormByte Buffer is the byte-buffer module of the StormByte C++ suite.
It depends on StormByte Logger and StormByte System, which bring Base. This repository is not Base, Config, Crypto, Database, Logger, Multimedia, Network or System.
Public headers under StormByte/buffer/ cover FIFO, SharedFIFO, Ring, Producer/Consumer, Hopper, Sink, Bridge, Pipeline and StormByte::Buffer::IO (buffered binary sources and sinks). Octet payloads are StormByte::BinaryData. Byte lengths are StormByte::ByteSize. Hopper and Sink count items with StormByte::Size.
If you landed here from a release link and have not read the tree:
- What this module is, how to build it, and short examples: README.md
- License: GNU Lesser General Public License version 3 or later, LICENSE
Added
option(BUILD_SHARED_LIBS "Build shared libraries" ON)in the project root. Shared is the default so a consumer can redistribute without triggering LGPL static-link obligations. Static is opt-in (-DBUILD_SHARED_LIBS=OFF). CI passes-DBUILD_SHARED_LIBS=ON. Third-party StormByte pins passENABLE_TEST=OFF.- Nested
Parameterson all buffered reader/writer levels, with knobsReadAhead,MaxMemory,MaxWait,WriteChunkandBackPressure. Omitted knobs retain the previous defaults; omitting all device knobs makesSetup()probe. Brace-init and namedParametersare supported. Variadic knobs resolve in the caller underSTORMBYTE_FORCE_INLINE; the DLL receives only numbers and a probe flag. BufferedReaderpage cache and logical seek. Consumed bytes remain in RAM up toMaxMemory; garbage collection evicts farthest fromTell.SeekupdatesTellimmediately, avoids moving the origin on a cache hit, and resumes prefetch with oneOriginSeekwhen a later read leaves the cached range.Tellnever lies.BufferedWriterdirty-page cache and logical seek. Writes are lazy untilMaxMemory,FlushorClose; nearby corrections and far-future islands are supported while memory allows. Eviction prefers the oldest dirty page behind the origin cursor.Seekis logical; materializing a page (eviction, flush or close) moves the origin.- Layered telemetry in
StormByte::BufferandStormByte::Buffer::IO. Read/write counters hold delivered/accepted bytes; IO telemetry adds cache/origin/seek/wait counters.MeanRateis the caller-visible effective rate (ByteSize/s), including cache hits, not device throughput. Telemetry handles areconst StormByte::Safe::Shared<…>, stable for the life of the office; accumulators do not reset on close. Public writerFlushand the flush inClosecount toward the rate; internal worker/GC drains do not. Flattening providesoperator StormByte::Safe::Stringout of line and caller-sideSTORMBYTE_FORCE_INLINE operator std::string(). BufferedReader::Available(). Contiguous cached bytes atTell. Does not callOriginPull/OriginSeekand does not wait for prefetch.StormByte::Buffer::Pumper. Takes aBridgeby move and runsPassthroughon a worker until EoF or failure. Starts in the constructor; the destructor joins. NestedParameterswith knobsChunkandHighWater.Chunk0is automatic cycle size, not Bridge “current contents”.HighWaterapplies to the input only: omitted =0if the source is IO, otherwise the backend default (constexpr in the PIMPL.cxx); explicit0= no Pumper cap (intended when the IO source already limits itself). Non-IO sources are unbounded by design.Togglepauses/resumes.Cancelis terminal (Failed, no restart). Telemetry is forwarded from the owned Bridge.StormByte::Buffer::Pipe(pipe.hxx/pipe.cxx). Abstract stream stage (StormByte::Safe::Clonable+StormByte::Safe::Unique<Pipe>). Copyable and movable (special members out of line).Run(ReadOnly&, WriteOnly&, const Shared<Logger::Log>&).CloneandMoveare public and must be implemented by the leaf withUnique::MakePointerin the leaf TU.
Changed
- Breaking: Port Buffer to StormByte Base 2.0.0. Owned text is now
StormByte::Safe::String/Safe::WString; pointer and clonable APIs useStormByte::Safe; Buffer telemetry derives from BaseStormByte::Telemetryand measures operations with its named clocks; Buffer exceptions accept Base-ownedSafe::Stringmessages. - Breaking:
StormByte::Buffer::Datais gone. Octet payloads areStormByte::BinaryDatafrom Base.data.hxx/data.cxxandDataTestsare removed. - Breaking: byte counts are
StormByte::ByteSize(FIFO,Ring,SharedFIFO,Producer/Consumer,Bridge,Pipeline,IO).Hopper<T>andSink<T>count items withStormByte::Size(Capacity,Size,Buckets,Select). - Breaking:
AvailableBytes()isAvailable(). The return type is alreadyStormByte::ByteSize. - Breaking:
ExternalReader,ExternalWriter,ExternalBufferReaderandExternalBufferWriterare removed.Bridgeborrows non-IOReadOnly/WriteOnlytips directly; those buffers must outlive it. IO leaves remain owned by move. - Breaking:
Pipeline::PipeFunction(std::functionover External reader/writer types) is gone. A stage is a user leaf ofPipe.Pipeline::Add(const Pipe&)clones onto Base's heap and does not touch the caller;Pipeline::Add(Pipe&&)takesMove(). There is no boxing of callables and no publicUnique<Pipe>add.Pipelineis stream buffers only (ReadOnly/WriteOnly); IO joins throughBridge/Pumper.Process(Consumer, Shared<Logger::Log>, ExecutionMode)— mode last. - Breaking:
BufferedLocationReaderandBufferedLocationWritersit between the engines and the file leaves. A location is file-like: named byLocation()(StormByte::Safe::String, owned by Base), always seekable and sized. Path-onlySetup()lives here.Device()and the pureOriginDevice()returnStormByte::Safe::Shared<StormByte::System::Device>instead ofSystem::Deviceby value. A leaf may now hand out aSystem::Devicesubclass (for example a NIC device whose accessor is not a filesystem path) and the dynamic type survives, so overriddenThroughput()/Window()are honoured and the caller can keep the object alive. Build the owner withStormByte::Safe::Shared<StormByte::System::Device>::MakePointer<Leaf>(…)so the object lives on Base's heap and crosses the DLL boundary safely.- New protected
virtual bool OriginDeviceUsable(const StormByte::Safe::Shared<StormByte::System::Device>&) const noexcept. The default is the previous behaviour (non-empty owner andoperator bool()true, which probes the stored path). A leaf whose identifier is not a filesystem path overrides it and never reaches the non-virtual path probe. An empty owner is always unusable. Setup()staysfinal, callsOriginDevice()once, asksOriginDeviceUsable()and only then appliesWindow()on the dynamic object. When the device is not usable the per-leaf defaults (ReadAhead, orWriteChunk/BackPressure/MaxMemory) are kept. The device is never copied or sliced to the base type.
- Breaking:
BufferedFileReaderandBufferedFileWriterarefinal.CreateDevice()is gone.Path()(const String&) andLocation()(IO::Location) are set onBufferedReader/BufferedWriterand do not change. A file leaf passesLocation::Local. A socket on the lower layer can passLocation::Remote. The file leaves keep the plainSystem::Deviceand the real path probe, so their windows and defaults are unchanged. - Breaking: IO constructors no longer take positional windows (
read_ahead,max_memory,write_chunk,back_pressure,max_wait). One constructor per leaf: path plus that class’sParameters(default{}= probe). Explicit zeros stay zeros; they do not probe. - Breaking:
Buffer::ExceptionusesException::Path{"Buffer"}.what()isStormByte.Buffer: message;ReadErrorandWriteErroruseStormByte.Buffer.ReadandStormByte.Buffer.Write. Buffer-specific destructors are defined in this module. - Breaking:
Bridgeis a manual transfer again, not a worker. PublicPassthrough(ByteSize, Operation)is the only transfer;Operation::{Blocking, NonBlocking}applies to the read tip; writeTryAgainis retried until that call completes.n == 0is current contents (Available()). Non-IO tips areReadOnly&/WriteOnly&. IO tips are stolen by move as the concrete leaf.Failed()is sticky. Continuous pumping isPumper. - Reader
Seekis no longer “alwaysOriginSeek”. A cache hit is O(1) on the origin. A miss still costs a real seek plus whatever the device does. - Writer
Seekexists and is part of the public contract. It is not guaranteed O(1) when the target is not in the dirty map or when eviction must drain pages first. - Writer contract: lazy write up to
MaxMemory. More random access needs moreMaxMemoryor islands get evicted (a real write + seek). - Nested
BufferedReader::Telemetry/BufferedWriter::Telemetrystructs are gone. Counters live on the Shared objects; getters, not public fields. LockFreeRing::FrontSpanreturns a snapshot copied under the wait mutex so a concurrentGrowcannot invalidate the pointer the drain worker is pushing.- Origin I/O on the writer (
OriginSeek/OriginPush/OriginFlush/OriginOpen/OriginClose/OriginTruncate) is serialized against the drain worker.Flushwaits until the ring is empty and the worker has published the origin cursor (!m_drain_run). - Dual license layout:
LICENSEis the short header text;COPYING.LGPLv3is the LGPL text.
Fixed
- Writer drain vs
Grow:FrontSpanno longer aliasesm_storagewhile the producer reallocates (Macpatev-ring-onlycorruption). - Writer
Flushreturning beforem_origin_poswas stored, which let the nextEnsureOriginland a patch on the wrong offset. - Concurrent
FILE*/ofstreamuse from the writer thread and the drain worker. - Origin cursor after
OriginFlushtreated as untrusted until the nextEnsureOrigin(Darwin). Sequential drain after that first realign does not seek again. LockFreeRingClose,SetError,Clean,DropandConsumepublish under the wait mutex. A parallel pipeline stage waiting on an intermediate ring could miss the wake and leaveProcessspinning onIsWritable().- Doxygen: broken
\refon the public reader header; private storage types not listed as public API. - Missing virtual destructors on
BufferedLocationReader/BufferedLocationWriter. Leaves stayfinaland do not declarevirtualon the destructor.
Tests
BufferedFileReaderTests/BufferedFileWriterTests/BridgeTestsconstruct IO withParameters/ knobs (ReadAhead,MaxMemory,WriteChunk,BackPressure). Path-only still probes.- Predictable hex fixture, integrity of every
Readafter logical and cold seeks,Tellduring a logical seek, telemetry prints via*Telemetry(). - Writer close/flush integrity on hex files, holes, far islands, eviction + patch, ring-only / pages / direct knobs.