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
1 change: 1 addition & 0 deletions Core/Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(SOURCES
SolidSyslogNullBuffer.c
SolidSyslogCircularBuffer.c
SolidSyslogCircularBufferStatic.c
SolidSyslogPoolAllocator.c
SolidSyslogMutex.c
SolidSyslogNullMutex.c
SolidSyslogSender.c
Expand Down
124 changes: 17 additions & 107 deletions Core/Source/SolidSyslogCircularBufferStatic.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,36 @@

#include "SolidSyslogBufferDefinition.h"
#include "SolidSyslogCircularBufferPrivate.h"
#include "SolidSyslogConfigLock.h"
#include "SolidSyslogError.h"
#include "SolidSyslogErrorMessages.h"
#include "SolidSyslogPoolAllocator.h"
#include "SolidSyslogPrival.h"
#include "SolidSyslogTunables.h"

struct SolidSyslogMutex;

struct Slot
{
struct SolidSyslogCircularBuffer Object;
bool InUse;
};

static bool Fallback_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead);
static void Fallback_Write(struct SolidSyslogBuffer* base, const void* data, size_t size);
static struct SolidSyslogBuffer* CircularBuffer_AcquireFirstFree(void);
static struct SolidSyslogBuffer* CircularBuffer_AcquireIfFree(size_t poolIndex);
static inline bool CircularBuffer_PoolItemIsFree(size_t poolIndex);
static inline bool CircularBuffer_PoolItemIsInUse(size_t poolIndex);
static inline struct SolidSyslogBuffer* CircularBuffer_Acquire(size_t poolIndex);
static inline void CircularBuffer_MarkInUse(size_t poolIndex);
static inline struct SolidSyslogBuffer* CircularBuffer_HandleFromIndex(size_t poolIndex);
static inline bool CircularBuffer_HandleIsValid(const struct SolidSyslogBuffer* handle);
static size_t CircularBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base);
static inline bool CircularBuffer_PoolIndexIsValid(size_t poolIndex);
static bool CircularBuffer_FreeIfInUse(size_t poolIndex);
static inline void CircularBuffer_MarkFree(size_t poolIndex);
static void CircularBuffer_CleanupAtIndex(size_t index, void* context);

static struct Slot Pool[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE];
static bool InUse[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE];
static struct SolidSyslogCircularBuffer Pool[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE];
static struct SolidSyslogBuffer Fallback = {Fallback_Write, Fallback_Read};
static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE};

struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create(
struct SolidSyslogMutex* mutex,
uint8_t* ring,
size_t ringBytes
)
{
struct SolidSyslogBuffer* handle = CircularBuffer_AcquireFirstFree();
if (CircularBuffer_HandleIsValid(handle))
size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator);
struct SolidSyslogBuffer* handle = &Fallback;
if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index))
{
CircularBuffer_Initialise(handle, mutex, ring, ringBytes);
CircularBuffer_Initialise(&Pool[index].Base, mutex, ring, ringBytes);
handle = &Pool[index].Base;
}
else
{
Expand All @@ -56,71 +44,11 @@ struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create(
return handle;
}

static struct SolidSyslogBuffer* CircularBuffer_AcquireFirstFree(void)
{
struct SolidSyslogBuffer* handle = &Fallback;
for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE; poolIndex++)
{
handle = CircularBuffer_AcquireIfFree(poolIndex);
if (CircularBuffer_HandleIsValid(handle))
{
break;
}
}
return handle;
}

static struct SolidSyslogBuffer* CircularBuffer_AcquireIfFree(size_t poolIndex)
{
struct SolidSyslogBuffer* handle = &Fallback;
SolidSyslog_LockConfig();
if (CircularBuffer_PoolItemIsFree(poolIndex))
{
handle = CircularBuffer_Acquire(poolIndex);
}
SolidSyslog_UnlockConfig();
return handle;
}

static inline bool CircularBuffer_PoolItemIsFree(size_t poolIndex)
{
return !CircularBuffer_PoolItemIsInUse(poolIndex);
}

static inline bool CircularBuffer_PoolItemIsInUse(size_t poolIndex)
{
return Pool[poolIndex].InUse;
}

static inline struct SolidSyslogBuffer* CircularBuffer_Acquire(size_t poolIndex)
{
CircularBuffer_MarkInUse(poolIndex);
return CircularBuffer_HandleFromIndex(poolIndex);
}

static inline void CircularBuffer_MarkInUse(size_t poolIndex)
{
Pool[poolIndex].InUse = true;
}

static inline struct SolidSyslogBuffer* CircularBuffer_HandleFromIndex(size_t poolIndex)
{
return &Pool[poolIndex].Object.Base;
}

static inline bool CircularBuffer_HandleIsValid(const struct SolidSyslogBuffer* handle)
{
return handle != &Fallback;
}

void SolidSyslogCircularBuffer_Destroy(struct SolidSyslogBuffer* base)
{
size_t poolIndex = CircularBuffer_IndexFromHandle(base);
bool released = false;
if (CircularBuffer_PoolIndexIsValid(poolIndex))
{
released = CircularBuffer_FreeIfInUse(poolIndex);
}
size_t index = CircularBuffer_IndexFromHandle(base);
bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) &&
SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, CircularBuffer_CleanupAtIndex, NULL);
if (!released)
{
SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY);
Expand All @@ -132,7 +60,7 @@ static size_t CircularBuffer_IndexFromHandle(const struct SolidSyslogBuffer* bas
size_t result = SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE;
for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE; poolIndex++)
{
if (base == CircularBuffer_HandleFromIndex(poolIndex))
if (base == &Pool[poolIndex].Base)
{
result = poolIndex;
break;
Expand All @@ -141,28 +69,10 @@ static size_t CircularBuffer_IndexFromHandle(const struct SolidSyslogBuffer* bas
return result;
}

static inline bool CircularBuffer_PoolIndexIsValid(size_t poolIndex)
{
return poolIndex < SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE;
}

static bool CircularBuffer_FreeIfInUse(size_t poolIndex)
{
bool released = false;
SolidSyslog_LockConfig();
if (CircularBuffer_PoolItemIsInUse(poolIndex))
{
CircularBuffer_Cleanup(CircularBuffer_HandleFromIndex(poolIndex));
CircularBuffer_MarkFree(poolIndex);
released = true;
}
SolidSyslog_UnlockConfig();
return released;
}

static inline void CircularBuffer_MarkFree(size_t poolIndex)
static void CircularBuffer_CleanupAtIndex(size_t index, void* context)
{
Pool[poolIndex].InUse = false;
(void) context;
CircularBuffer_Cleanup(&Pool[index].Base);
}

static bool Fallback_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead)
Expand Down
75 changes: 75 additions & 0 deletions Core/Source/SolidSyslogPoolAllocator.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include "SolidSyslogPoolAllocator.h"

#include <stddef.h>

#include "SolidSyslogConfigLock.h"

static bool PoolAllocator_TryClaim(struct SolidSyslogPoolAllocator* self, size_t index);
static inline bool PoolAllocator_SlotIsFree(const struct SolidSyslogPoolAllocator* self, size_t index);
static inline bool PoolAllocator_SlotIsInUse(const struct SolidSyslogPoolAllocator* self, size_t index);
static inline void PoolAllocator_MarkInUse(struct SolidSyslogPoolAllocator* self, size_t index);
static inline void PoolAllocator_MarkFree(struct SolidSyslogPoolAllocator* self, size_t index);

size_t SolidSyslogPoolAllocator_AcquireFirstFree(struct SolidSyslogPoolAllocator* self)
{
size_t acquired = 0;
while ((acquired < self->Count) && !PoolAllocator_TryClaim(self, acquired))
{
acquired++;
}
return acquired;
}

static bool PoolAllocator_TryClaim(struct SolidSyslogPoolAllocator* self, size_t index)
{
bool claimed = false;
SolidSyslog_LockConfig();
if (PoolAllocator_SlotIsFree(self, index))
{
PoolAllocator_MarkInUse(self, index);
claimed = true;
}
SolidSyslog_UnlockConfig();
return claimed;
}

static inline bool PoolAllocator_SlotIsFree(const struct SolidSyslogPoolAllocator* self, size_t index)
{
return !PoolAllocator_SlotIsInUse(self, index);
}

static inline bool PoolAllocator_SlotIsInUse(const struct SolidSyslogPoolAllocator* self, size_t index)
{
return self->InUse[index];
}

static inline void PoolAllocator_MarkInUse(struct SolidSyslogPoolAllocator* self, size_t index)
{
self->InUse[index] = true;
}

bool SolidSyslogPoolAllocator_FreeIfInUse(
struct SolidSyslogPoolAllocator* self,
size_t index,
SolidSyslogPoolCleanup cleanup,
void* context
)
{
SolidSyslog_LockConfig();
bool released = PoolAllocator_SlotIsInUse(self, index);
if (released)
{
if (cleanup != NULL)
{
cleanup(index, context);
}
PoolAllocator_MarkFree(self, index);
}
SolidSyslog_UnlockConfig();
return released;
}

static inline void PoolAllocator_MarkFree(struct SolidSyslogPoolAllocator* self, size_t index)
{
self->InUse[index] = false;
}
35 changes: 35 additions & 0 deletions Core/Source/SolidSyslogPoolAllocator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef SOLIDSYSLOGPOOLALLOCATOR_H
#define SOLIDSYSLOGPOOLALLOCATOR_H

#include "ExternC.h"

#include <stdbool.h>
#include <stddef.h>

EXTERN_C_BEGIN

struct SolidSyslogPoolAllocator
{
bool* InUse;
size_t Count;
};

typedef void (*SolidSyslogPoolCleanup)(size_t index, void* context);

size_t SolidSyslogPoolAllocator_AcquireFirstFree(struct SolidSyslogPoolAllocator * self);

bool SolidSyslogPoolAllocator_FreeIfInUse(
struct SolidSyslogPoolAllocator * self,
size_t index,
SolidSyslogPoolCleanup cleanup,
void* context
);

static inline bool SolidSyslogPoolAllocator_IndexIsValid(const struct SolidSyslogPoolAllocator* self, size_t index)
{
return index < self->Count;
}

EXTERN_C_END

#endif /* SOLIDSYSLOGPOOLALLOCATOR_H */
87 changes: 87 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,92 @@
# Dev Log

## 2026-05-18 — S11.02: Extract SolidSyslogPoolAllocator helper (#395)

S11.01 left every E11 class about to copy the same 13-function pool
plumbing — slot walk, per-iteration `LockConfig`, claim/release,
fallback dispatch, reverse lookup. S11.02 hoists everything except the
reverse lookup into a TU-internal `SolidSyslogPoolAllocator` that each
`*Static.c` instantiates with a caller-supplied `bool[]`.

### Shape of the helper

Three operations + a predicate, all under `Core/Source/` (never on the
public include path):

- `SolidSyslogPoolAllocator_AcquireFirstFree(self)` — walks the pool
with `LockConfig` wrapping every per-slot probe (matches the pilot's
invariant exactly), returns first claimed index or `Count` on
exhaustion.
- `SolidSyslogPoolAllocator_FreeIfInUse(self, index, cleanup, ctx)` —
locks once, runs `cleanup` *inside* the lock before clearing
`InUse`, returns whether the slot was actually freed. The cleanup
callback is the one bit of class-specific behaviour the helper
can't see; passing it in keeps the lock-held-during-cleanup
invariant intact across every consumer.
- `SolidSyslogPoolAllocator_IndexIsValid(self, index)` — static-inline
predicate over `Count`.

The struct is two fields (`bool* InUse; size_t Count;`), declared
public so each `*Static.c` can do `static struct SolidSyslogPoolAllocator
Allocator = {InUseArray, POOL_SIZE};` at file scope — no `_Create`
needed, no storage cast.

### Cycle-by-cycle TDD pacing

Happy-path then locking, as agreed in the design discussion. Twelve
tests across five behaviours: `IndexIsValid` true/false, `AcquireFirstFree`
empty/walks/exhausted, `FreeIfInUse` happy/already-free/NULL-callback,
locking-per-probe (1 vs N), locking-exactly-once on free, and the
cleanup-inside-the-lock invariant (the spy captures `LockCount -
UnlockCount` at the moment of invocation and asserts it's exactly 1).
The cleanup-inside-the-lock test would catch any drift where someone
moves the cleanup call out of the critical section.

### Migration of `CircularBufferStatic.c`

107 deletions, 17 insertions. The `struct Slot` wrapper retired —
`Pool` is now a bare array of `SolidSyslogCircularBuffer`, `InUse` is
its own array, and direct `&Pool[index].Base` access replaces the
old `HandleFromIndex` helper. Ten file-scope helpers retired
(`AcquireFirstFree`, `AcquireIfFree`, `Acquire`, `PoolItemIsFree`,
`PoolItemIsInUse`, `MarkInUse`, `MarkFree`, `HandleFromIndex`,
`HandleIsValid`, `FreeIfInUse`). What's left in the file: `_Create`,
`_Destroy`, `IndexFromHandle` (the per-class reverse lookup —
intentionally kept per-class because hoisting it would require a
typeless callback that breaks the no-cast invariant), `CleanupAtIndex`
(3-line bridge from the helper's typeless cleanup signature to
`CircularBuffer_Cleanup`), and the two `Fallback` vtable entries.

### Test suite untouched

`SolidSyslogCircularBufferTest.cpp` was not modified — that was the
regression net. Every assertion including the lock-count tests
(`CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot`,
`CreateLocksOncePerSlotProbedWhenPoolIsFull`,
`DestroyOfPooledHandleLocksOnce`, `DestroyOfUnknownHandleDoesNotLock`)
passes against the new implementation because the helper preserves the
per-probe and per-free lock invariants exactly.

### Decisions confirmed

- **Helper does not report exhaustion errors itself** — each class wants
its own `SOLIDSYSLOG_ERROR_MSG_<CLASS>_POOL_EXHAUSTED` string.
- **`IndexFromHandle` stays per-class** — hoisting would need a
typeless callback or a `const void*` cast that breaks the no-cast
invariant; the 5 lines per class aren't worth it.
- **No public-header audience-table entry** — helper is TU-internal,
not an integration point.

### Gates

debug, clang-debug, sanitize, coverage (helper and migrated file both
at 100% line), tidy, cppcheck, cppcheck-misra (60 hits — one fewer than
pre-migration; zero new), clang-format, IWYU. Full suite revalidated
at `SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE=3` (the validation step from
end of S11.01) — 1139 tests pass.

---

## 2026-05-17 — S11.01 commits 2–N: CircularBuffer pool migration (E11 pilot, #392)

The body of S11.01 — every commit after the `SolidSyslog_SetConfigLock`
Expand Down
1 change: 1 addition & 0 deletions Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(TEST_SOURCES
SolidSyslogOriginSdTest.cpp
SolidSyslogNullBufferTest.cpp
SolidSyslogCircularBufferTest.cpp
SolidSyslogPoolAllocatorTest.cpp
SolidSyslogNullMutexTest.cpp
SolidSyslogNullStoreTest.cpp
SolidSyslogNullSecurityPolicyTest.cpp
Expand Down
Loading
Loading