Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
120c06e
refactor: S11.01 pool CircularBuffer instance struct, keep ring calle…
DavidCozens May 17, 2026
95544e5
feat: S11.01 pool exhaustion + LockConfig wrap + tunable; useStlAlgor…
DavidCozens May 17, 2026
19bd20c
style: S11.01 clang-format pool migration files
DavidCozens May 17, 2026
37b8d61
feat: S11.01 Destroy reports WARNING on unknown handle
DavidCozens May 17, 2026
b5f3cfd
refactor: S11.01 CircularBuffer_Cleanup takes base, drop suppression
DavidCozens May 17, 2026
7d4e841
refactor: S11.01 extract pool helpers in CircularBufferStatic
DavidCozens May 17, 2026
4a34777
refactor: S11.01 lock per slot probe, Initialise outside the lock
DavidCozens May 17, 2026
f5dccc8
refactor: S11.01 inline pool walks back into Create/Destroy
DavidCozens May 17, 2026
170b1b1
test: S11.01 lock-count tests + Pool-group diagnostics
DavidCozens May 17, 2026
b077ed1
refactor: S11.01 drive Create slot-walk by pointer not index
DavidCozens May 17, 2026
69a03bf
refactor: S11.01 extract CircularBuffer_TryClaimSlot(i)
DavidCozens May 17, 2026
bc3e463
refactor: S11.01 rename TryClaimSlot -> AcquireIfFree, claimed -> handle
DavidCozens May 17, 2026
db2d166
refactor: S11.01 maintain function order in CircularBufferStatic
DavidCozens May 17, 2026
4eaf456
refactor: S11.01 extract CircularBuffer_HandleIsValid
DavidCozens May 17, 2026
571760c
refactor: S11.01 extract CircularBuffer_AcquireFirstFree
DavidCozens May 17, 2026
c881c08
refactor: S11.01 rename loop/parameter i to poolIndex
DavidCozens May 17, 2026
5d06140
refactor: S11.01 extract CircularBuffer_PoolItemIsFree
DavidCozens May 17, 2026
142571f
refactor: S11.01 extract CircularBuffer_Acquire
DavidCozens May 17, 2026
6500440
feat: S11.01 drop Fallback special-case from _Destroy
DavidCozens May 17, 2026
a396e1c
refactor: S11.01 _Destroy splits scan-then-release, drops per-probe lock
DavidCozens May 17, 2026
e478b74
refactor: S11.01 extract CircularBuffer_HandleFromIndex
DavidCozens May 17, 2026
ed0dbdd
refactor: S11.01 extract MarkInUse / MarkFree pair
DavidCozens May 17, 2026
0da5318
refactor: S11.01 extract CircularBuffer_IndexFromHandle
DavidCozens May 17, 2026
8f5f89f
refactor: S11.01 extract CircularBuffer_PoolIndexIsValid
DavidCozens May 17, 2026
9712f67
refactor: S11.01 chain PoolItemIsFree through PoolItemIsInUse
DavidCozens May 17, 2026
d671f52
refactor: S11.01 extract CircularBuffer_FreeIfInUse
DavidCozens May 17, 2026
10c6b1b
refactor: S11.01 IndexFromHandle uses poolIndex as the loop var
DavidCozens May 17, 2026
539d9a2
test: S11.01 pin the stale-handle branch in FreeIfInUse
DavidCozens May 17, 2026
7ae931a
test: S11.01 readability pass on SolidSyslogCircularBufferTest
DavidCozens May 17, 2026
286194b
docs: update DEVLOG for S11.01 pool migration body
DavidCozens May 17, 2026
9bd1d39
fix: S11.01 IWYU + CodeRabbit accepted feedback
DavidCozens May 18, 2026
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
5 changes: 2 additions & 3 deletions Bdd/Targets/FreeRtos/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ enum
BDD_TARGET_BUFFER_MESSAGES = 8
};

static SolidSyslogCircularBufferStorage
bufferStorage[SOLIDSYSLOG_CIRCULAR_BUFFER_STORAGE_SIZE(BDD_TARGET_BUFFER_MESSAGES)];
static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)];
static SolidSyslogFreeRtosMutexStorage mutexStorage;

/* Lifecycle mutex serialises SolidSyslog_Service against the rebuild path
Expand Down Expand Up @@ -825,7 +824,7 @@ static void InteractiveTask(void* argument)
* is the Service task; its Write side is whichever task calls
* SolidSyslog_Log. */
bufferMutex = SolidSyslogFreeRtosMutex_Create(&mutexStorage);
buffer = SolidSyslogCircularBuffer_Create(bufferStorage, sizeof(bufferStorage), bufferMutex);
buffer = SolidSyslogCircularBuffer_Create(bufferMutex, bufferRing, sizeof(bufferRing));

/* Lifecycle mutex created up front so the Service task can take it
* from its very first iteration without a NULL check. */
Expand Down
5 changes: 2 additions & 3 deletions Bdd/Targets/Windows/BddTargetWindows.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ static const char* const THRESHOLD_MARKER_PATH = "Bdd/output/solidsyslog_thresho

static SolidSyslogWinsockTcpStreamStorage tcpStreamStorage;
static SolidSyslogStreamSenderStorage tcpSenderStorage;
static SolidSyslogCircularBufferStorage
bufferStorage[SOLIDSYSLOG_CIRCULAR_BUFFER_STORAGE_SIZE(BDD_TARGET_BUFFER_MESSAGES)];
static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)];
static SolidSyslogWindowsMutexStorage mutexStorage;
static SolidSyslogWindowsAtomicCounterStorage counterStorage;
static volatile bool shutdownFlag;
Expand Down Expand Up @@ -304,7 +303,7 @@ int BddTargetWindows_Run(int argc, char* argv[])
struct SolidSyslogStore* store = CreateStore(&options);

struct SolidSyslogMutex* mutex = SolidSyslogWindowsMutex_Create(&mutexStorage);
struct SolidSyslogBuffer* buffer = SolidSyslogCircularBuffer_Create(bufferStorage, sizeof(bufferStorage), mutex);
struct SolidSyslogBuffer* buffer = SolidSyslogCircularBuffer_Create(mutex, bufferRing, sizeof(bufferRing));
struct SolidSyslogAtomicCounter* counter = SolidSyslogWindowsAtomicCounter_Create(&counterStorage);
struct SolidSyslogMetaSdConfig metaConfig = {
.Counter = counter,
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*`
| `SolidSyslogBufferDefinition.h` | Buffer implementors (extension point) | `SolidSyslogBuffer` vtable struct |
| `SolidSyslogNullBuffer.h` | System setup code (single-task, no buffering) | `SolidSyslogNullBuffer_Create`, `_Destroy` |
| `SolidSyslogPosixMessageQueueBuffer.h` | System setup code using POSIX message queue buffer | `SolidSyslogPosixMessageQueueBuffer_Create`, `_Destroy` |
| `SolidSyslogCircularBuffer.h` | System setup code using an in-memory ring buffer (bare-metal / RTOS / Windows) | `SolidSyslogCircularBufferStorage`, `SOLIDSYSLOG_CIRCULARBUFFER_STORAGE_SIZE(maxMessages)` (friendly: N max-sized messages), `SOLIDSYSLOG_CIRCULARBUFFER_STORAGE_SIZE_BYTES(ringBytes)` (raw byte capacity), `SolidSyslogCircularBuffer_Create(storage, sizeof(storage), mutex)`, `_Destroy(buffer)` (uint16-length-prefixed records, drop-newest on overflow, no-split wrap, mutex-injected synchronization) |
| `SolidSyslogCircularBuffer.h` | System setup code using an in-memory ring buffer (bare-metal / RTOS / Windows) | `SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES`, `SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(maxMessages)` (friendly: N max-sized messages → ring bytes), `SolidSyslogCircularBuffer_Create(mutex, ring, ringBytes)`, `_Destroy(buffer)` (uint16-length-prefixed records, drop-newest on overflow, no-split wrap, mutex-injected synchronization). Instance struct lives in a library-internal static pool (E11); caller supplies the ring memory only. |
| `SolidSyslogMutex.h` | Any code holding a mutex handle | `SolidSyslogMutex_Lock`, `_Unlock` |
| `SolidSyslogMutexDefinition.h` | Mutex implementors (extension point) | `SolidSyslogMutex` vtable struct (`Lock`, `Unlock`) |
| `SolidSyslogNullMutex.h` | System setup code (single-task, no synchronization) | `SolidSyslogNullMutex_Create`, `_Destroy` |
Expand Down
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ if(ENABLE_CPPCHECK)
"--enable=warning,style,performance,portability"
"--error-exitcode=1"
"--suppress=missingIncludeSystem"
"--suppress=useStlAlgorithm"
"--inline-suppr"
"--std=c++17"
)
Expand Down Expand Up @@ -104,6 +105,23 @@ if(SOLIDSYSLOG_OPENSSL)
endif()
endif()

# Allocation strategy (E11). Currently only "static" ships — internal static
# pools allocate instance bookkeeping for stateful classes. A future epic
# may add "dynamic" (heap-backed). Selecting "dynamic" now errors hard so
# downstream builds fail fast rather than silently linking against missing
# TUs.
set(SOLIDSYSLOG_ALLOCATION_STRATEGY "static" CACHE STRING
"Allocation strategy for instance bookkeeping: static (internal pools) or dynamic (heap, not yet shipped)")
set_property(CACHE SOLIDSYSLOG_ALLOCATION_STRATEGY PROPERTY STRINGS static dynamic)
if(SOLIDSYSLOG_ALLOCATION_STRATEGY STREQUAL "dynamic")
message(FATAL_ERROR
"SOLIDSYSLOG_ALLOCATION_STRATEGY=dynamic is reserved for a future epic; "
"no dynamic TUs ship yet. Set SOLIDSYSLOG_ALLOCATION_STRATEGY=static (the default).")
elseif(NOT SOLIDSYSLOG_ALLOCATION_STRATEGY STREQUAL "static")
message(FATAL_ERROR
"SOLIDSYSLOG_ALLOCATION_STRATEGY must be 'static' (got: '${SOLIDSYSLOG_ALLOCATION_STRATEGY}').")
endif()

# Build-time tunables (E21: Port-Time Configurability). Integrators override
# library defaults by setting SOLIDSYSLOG_USER_TUNABLES_FILE to the absolute
# path of their own header containing #define lines for whichever tunables
Expand Down
22 changes: 6 additions & 16 deletions Core/Interface/SolidSyslogCircularBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,19 @@ EXTERN_C_BEGIN
struct SolidSyslogBuffer;
struct SolidSyslogMutex;

typedef size_t SolidSyslogCircularBufferStorage;

enum
{
SOLIDSYSLOG_CIRCULAR_BUFFER_OVERHEAD = 7,
SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES = sizeof(uint16_t)
};

/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- cannot compute array size as a constexpr in C */
#define SOLIDSYSLOG_CIRCULAR_BUFFER_STORAGE_SIZE_BYTES(ringBytes) \
(SOLIDSYSLOG_CIRCULAR_BUFFER_OVERHEAD + \
(((ringBytes) + sizeof(SolidSyslogCircularBufferStorage) - 1U) / sizeof(SolidSyslogCircularBufferStorage)))

/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- cannot compute array size as a constexpr in C */
#define SOLIDSYSLOG_CIRCULAR_BUFFER_STORAGE_SIZE(maxMessages) \
SOLIDSYSLOG_CIRCULAR_BUFFER_STORAGE_SIZE_BYTES( \
(size_t) (maxMessages) * (SOLIDSYSLOG_MAX_MESSAGE_SIZE + SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES) \
)
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- C array-size const-expr; not a function-like expression */
#define SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(maxMessages) \
((maxMessages) * (SOLIDSYSLOG_MAX_MESSAGE_SIZE + SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES))

struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create(
SolidSyslogCircularBufferStorage * storage,
size_t storageBytes,
struct SolidSyslogMutex* mutex
struct SolidSyslogMutex * mutex,
uint8_t* ring,
size_t ringBytes
);
void SolidSyslogCircularBuffer_Destroy(struct SolidSyslogBuffer * base);

Expand Down
22 changes: 22 additions & 0 deletions Core/Interface/SolidSyslogTunablesDefaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,26 @@
#error "SOLIDSYSLOG_MAX_MESSAGE_SIZE must be >= 64"
#endif

/*
* Number of SolidSyslogCircularBuffer instances the library's internal
* static pool can simultaneously hold. Each instance is a small
* bookkeeping struct (vtable, mutex pointer, ring pointer, head/tail/wrap)
* — roughly 64 bytes on a 64-bit target, 32 on a 32-bit target. The
* caller's ring memory is separate (passed to _Create).
*
* Most integrators only ever create one CircularBuffer per process;
* default 1. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if the integrator
* needs multiple concurrent buffer instances.
*
* Floor: 1. Sub-floor values rejected at compile time.
*/
#ifndef SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */
#define SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE 1U
#endif

#if SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE < 1
#error "SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE must be >= 1"
#endif

#endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */
1 change: 1 addition & 0 deletions Core/Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(SOURCES
SolidSyslogMetaSd.c
SolidSyslogNullBuffer.c
SolidSyslogCircularBuffer.c
SolidSyslogCircularBufferStatic.c
SolidSyslogMutex.c
SolidSyslogNullMutex.c
SolidSyslogSender.c
Expand Down
65 changes: 21 additions & 44 deletions Core/Source/SolidSyslogCircularBuffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <string.h>

#include "SolidSyslogBufferDefinition.h"
#include "SolidSyslogMacros.h"
#include "SolidSyslogCircularBufferPrivate.h"
#include "SolidSyslogMutex.h"
#include "SolidSyslogTunables.h"

Expand All @@ -15,28 +15,9 @@ enum
HEADER_BYTES = SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES
};

struct SolidSyslogCircularBuffer
{
struct SolidSyslogBuffer Base;
struct SolidSyslogMutex* Mutex;
size_t Capacity;
size_t Head;
size_t Tail;
size_t WrapPoint;
uint8_t Storage[];
};

SOLIDSYSLOG_STATIC_ASSERT(
sizeof(struct SolidSyslogCircularBuffer) ==
(SOLIDSYSLOG_CIRCULAR_BUFFER_OVERHEAD * sizeof(SolidSyslogCircularBufferStorage)),
"SOLIDSYSLOG_CIRCULAR_BUFFER_OVERHEAD does not match struct layout"
);

static bool CircularBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead);
static void CircularBuffer_Write(struct SolidSyslogBuffer* base, const void* data, size_t size);

static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromStorage(SolidSyslogCircularBufferStorage* storage
);
static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromBase(struct SolidSyslogBuffer* base);

static inline bool CircularBuffer_IsEmpty(const struct SolidSyslogCircularBuffer* self);
Expand All @@ -51,44 +32,35 @@ static inline void CircularBuffer_StoreRecord(struct SolidSyslogCircularBuffer*
static inline void CircularBuffer_LoadRecord(struct SolidSyslogCircularBuffer* self, void* data, size_t* bytesRead);
static inline size_t CircularBuffer_PeekRecordSize(const struct SolidSyslogCircularBuffer* self);

struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create(
SolidSyslogCircularBufferStorage* storage,
size_t storageBytes,
struct SolidSyslogMutex* mutex
void CircularBuffer_Initialise(
struct SolidSyslogBuffer* base,
struct SolidSyslogMutex* mutex,
uint8_t* ring,
size_t ringBytes
)
{
struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromStorage(storage);
struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
self->Base.Read = CircularBuffer_Read;
self->Base.Write = CircularBuffer_Write;
self->Mutex = mutex;
self->Capacity = storageBytes - sizeof(struct SolidSyslogCircularBuffer);
self->Ring = ring;
self->Capacity = ringBytes;
CircularBuffer_ResetToStart(self);
return &self->Base;
}

static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromStorage(SolidSyslogCircularBufferStorage* storage
)
{
return (struct SolidSyslogCircularBuffer*) storage;
}

void SolidSyslogCircularBuffer_Destroy(struct SolidSyslogBuffer* base)
void CircularBuffer_Cleanup(struct SolidSyslogBuffer* base)
{
struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
self->Base.Read = NULL;
self->Base.Write = NULL;
self->Mutex = NULL;
self->Ring = NULL;
self->Capacity = 0;
self->Head = 0;
self->Tail = 0;
self->WrapPoint = 0;
}

static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromBase(struct SolidSyslogBuffer* base)
{
return (struct SolidSyslogCircularBuffer*) base;
}

static bool CircularBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead)
{
struct SolidSyslogCircularBuffer* self = CircularBuffer_SelfFromBase(base);
Expand All @@ -114,6 +86,11 @@ static bool CircularBuffer_Read(struct SolidSyslogBuffer* base, void* data, size
return delivered;
}

static inline struct SolidSyslogCircularBuffer* CircularBuffer_SelfFromBase(struct SolidSyslogBuffer* base)
{
return (struct SolidSyslogCircularBuffer*) base;
}

static inline bool CircularBuffer_IsEmpty(const struct SolidSyslogCircularBuffer* self)
{
return self->Head == self->Tail;
Expand All @@ -133,13 +110,13 @@ static inline void CircularBuffer_ConsumeWrapMarker(struct SolidSyslogCircularBu
static inline size_t CircularBuffer_PeekRecordSize(const struct SolidSyslogCircularBuffer* self)
{
/* Little-endian read of the 2-byte length header out of the uint8_t ring. */
return ((size_t) self->Storage[self->Head]) | (((size_t) self->Storage[self->Head + 1U]) << 8U);
return ((size_t) self->Ring[self->Head]) | (((size_t) self->Ring[self->Head + 1U]) << 8U);
}

static inline void CircularBuffer_LoadRecord(struct SolidSyslogCircularBuffer* self, void* data, size_t* bytesRead)
{
size_t recordSize = CircularBuffer_PeekRecordSize(self);
(void) memcpy(data, &self->Storage[self->Head + HEADER_BYTES], recordSize);
(void) memcpy(data, &self->Ring[self->Head + HEADER_BYTES], recordSize);
self->Head += HEADER_BYTES + recordSize;
*bytesRead = recordSize;
}
Expand Down Expand Up @@ -209,8 +186,8 @@ static inline void CircularBuffer_WrapTail(struct SolidSyslogCircularBuffer* sel
static inline void CircularBuffer_StoreRecord(struct SolidSyslogCircularBuffer* self, const void* data, size_t size)
{
/* Little-endian write of the 2-byte length header into the uint8_t ring. */
self->Storage[self->Tail] = (uint8_t) (size & 0xFFU);
self->Storage[self->Tail + 1U] = (uint8_t) ((size >> 8U) & 0xFFU);
(void) memcpy(&self->Storage[self->Tail + HEADER_BYTES], data, size);
self->Ring[self->Tail] = (uint8_t) (size & 0xFFU);
self->Ring[self->Tail + 1U] = (uint8_t) ((size >> 8U) & 0xFFU);
(void) memcpy(&self->Ring[self->Tail + HEADER_BYTES], data, size);
self->Tail += HEADER_BYTES + size;
}
30 changes: 30 additions & 0 deletions Core/Source/SolidSyslogCircularBufferPrivate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef SOLIDSYSLOGCIRCULARBUFFERPRIVATE_H
#define SOLIDSYSLOGCIRCULARBUFFERPRIVATE_H

#include <stddef.h>
#include <stdint.h>

#include "SolidSyslogBufferDefinition.h"

struct SolidSyslogMutex;

struct SolidSyslogCircularBuffer
{
struct SolidSyslogBuffer Base;
struct SolidSyslogMutex* Mutex;
uint8_t* Ring;
size_t Capacity;
size_t Head;
size_t Tail;
size_t WrapPoint;
};

void CircularBuffer_Initialise(
struct SolidSyslogBuffer* base,
struct SolidSyslogMutex* mutex,
uint8_t* ring,
size_t ringBytes
);
void CircularBuffer_Cleanup(struct SolidSyslogBuffer* base);

#endif /* SOLIDSYSLOGCIRCULARBUFFERPRIVATE_H */
Loading
Loading