summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-08-11 22:21:09 -0400
committerDavid Robillard <d@drobilla.net>2022-08-18 12:20:37 -0400
commitf8020557901a30245a7bdcecd3c0586bbe8760c2 (patch)
treefdee2ffc8b9667b851ef2df575228ad98133b180
parentb57d596a38b7d3d075a0ae48df3b3472ab6227b4 (diff)
downloadzix-f8020557901a30245a7bdcecd3c0586bbe8760c2.tar.gz
zix-f8020557901a30245a7bdcecd3c0586bbe8760c2.tar.bz2
zix-f8020557901a30245a7bdcecd3c0586bbe8760c2.zip
Add transactional ring API
-rw-r--r--include/zix/ring.h73
-rw-r--r--meson.build2
-rw-r--r--src/ring.c44
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: [
diff --git a/src/ring.c b/src/ring.c
index 5257ccb..8c0f039 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -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;