diff options
-rw-r--r-- | include/zix/ring.h | 73 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | src/ring.c | 44 |
3 files changed, 113 insertions, 6 deletions
diff --git a/include/zix/ring.h b/include/zix/ring.h index ae4b6c4..872f963 100644 --- a/include/zix/ring.h +++ b/include/zix/ring.h @@ -6,6 +6,7 @@ #include "zix/allocator.h" #include "zix/attributes.h" +#include "zix/common.h" #include <stdint.h> @@ -29,6 +30,20 @@ extern "C" { typedef struct ZixRingImpl ZixRing; /** + A transaction for writing data in multiple parts. + + The simple zix_ring_write() can be used to write an atomic message that will + immediately be visible to the reader, but transactions allow data to be + written in several chunks before being "committed" and becoming readable. + This can be useful for things like prefixing messages with a header without + needing an allocated buffer to construct the "packet". +*/ +typedef struct { + uint32_t read_head; ///< Read head at the start of the transaction + uint32_t write_head; ///< Write head if the transaction were committed +} ZixRingTransaction; + +/** Create a new ring. @param size Size in bytes (note this may be rounded up). @@ -131,6 +146,64 @@ zix_ring_write(ZixRing* ZIX_NONNULL ring, uint32_t size); /** + Begin a write. + + The returned transaction is initially empty. Data can be written to it by + calling zix_ring_amend_write() one or more times, then finishing with + zix_ring_commit_write(). + + Note that the returned "transaction" is not meant to be long-lived: a call + to this function should be (more or less) immediately followed by calls to + zix_ring_amend_write() then a call to zix_ring_commit_write(). + + @param ring The ring to write data to. + @return A new empty transaction. +*/ +ZixRingTransaction +zix_ring_begin_write(ZixRing* ZIX_NONNULL ring); + +/** + Amend the current write with some data. + + The data is written immediately after the previously amended data, as if + they were written contiguously with a single write call. This data is not + visible to the reader until zix_ring_commit_write() is called. + + If any call to this function returns an error, then the transaction is + invalid and must not be committed. No cleanup is necessary for an invalid + transaction. Any bytes written while attempting the transaction will remain + in the free portion of the buffer and be overwritten by subsequent writes. + + @param ring The ring this transaction is writing to. + @param tx The active transaction, from zix_ring_begin_write(). + @param src Pointer to the data to write. + @param size Length of data to write in bytes. + @return #ZIX_STATUS_NO_MEM or #ZIX_STATUS_SUCCESS. +*/ +ZixStatus +zix_ring_amend_write(ZixRing* ZIX_NONNULL ring, + ZixRingTransaction* ZIX_NONNULL tx, + const void* ZIX_NONNULL src, + uint32_t size); + +/** + Commit the current write. + + This atomically updates the state of the ring, so that the reader will + observe the data written during the transaction. + + This function usually shouldn't be called for any transaction which + zix_ring_amend_write() returned an error for. + + @param ring The ring this transaction is writing to. + @param tx The active transaction, from zix_ring_begin_write(). + @return #ZIX_STATUS_SUCCESS. +*/ +ZixStatus +zix_ring_commit_write(ZixRing* ZIX_NONNULL ring, + const ZixRingTransaction* ZIX_NONNULL tx); + +/** @} @} */ diff --git a/meson.build b/meson.build index e8609eb..8cfa24a 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ # SPDX-License-Identifier: CC0-1.0 OR ISC project('zix', ['c'], - version: '0.0.3', + version: '0.1.0', license: 'ISC', meson_version: '>= 0.56.0', default_options: [ @@ -2,6 +2,7 @@ // SPDX-License-Identifier: ISC #include "zix/ring.h" +#include "zix/common.h" #include "zix_config.h" @@ -215,25 +216,58 @@ zix_ring_skip(ZixRing* ring, uint32_t size) return size; } -uint32_t -zix_ring_write(ZixRing* ring, const void* src, uint32_t size) +ZixRingTransaction +zix_ring_begin_write(ZixRing* const ring) { const uint32_t r = zix_atomic_load(&ring->read_head); const uint32_t w = ring->write_head; + + const ZixRingTransaction tx = {r, w}; + return tx; +} + +ZixStatus +zix_ring_amend_write(ZixRing* const ring, + ZixRingTransaction* const tx, + const void* const src, + const uint32_t size) +{ + const uint32_t r = tx->read_head; + const uint32_t w = tx->write_head; if (write_space_internal(ring, r, w) < size) { - return 0; + return ZIX_STATUS_NO_MEM; } const uint32_t end = w + size; if (end <= ring->size) { memcpy(&ring->buf[w], src, size); - zix_atomic_store(&ring->write_head, end & ring->size_mask); + tx->write_head = end & ring->size_mask; } else { const uint32_t size1 = ring->size - w; const uint32_t size2 = size - size1; memcpy(&ring->buf[w], src, size1); memcpy(&ring->buf[0], (const char*)src + size1, size2); - zix_atomic_store(&ring->write_head, size2); + tx->write_head = size2; + } + + return ZIX_STATUS_SUCCESS; +} + +ZixStatus +zix_ring_commit_write(ZixRing* const ring, const ZixRingTransaction* const tx) +{ + zix_atomic_store(&ring->write_head, tx->write_head); + return ZIX_STATUS_SUCCESS; +} + +uint32_t +zix_ring_write(ZixRing* ring, const void* src, uint32_t size) +{ + ZixRingTransaction tx = zix_ring_begin_write(ring); + + if (zix_ring_amend_write(ring, &tx, src, size) || + zix_ring_commit_write(ring, &tx)) { + return 0; } return size; |