☎️ rtchan
Realtime-safe channels (lock-free queues) for C++20
Loading...
Searching...
No Matches
rtchan.hpp
1/*
2 * rtchan - Realtime-safe channels via lock-free ringbuffer queues
3 * Copyright (c) 2025 Fawn <rubiefawn@gmail.com>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#pragma once
19#include <algorithm> // std::min()
20#include <atomic>
21#include <cstring> // std::memcpy()
22#include <optional>
23#include <new> // std::hardware_destructive_interference_size to eliminate false sharing between indicies
24#include <span>
25
26namespace rtchan {
27
28using std::size_t, std::span;
29static_assert(std::atomic_size_t::is_always_lock_free, "rtchan is not useful without lock-free atomic_size_t!");
30
31// TODO: Analyze with stoat
32// TODO: Dynamic extent variants that have their buffers allocated at runtime
33// TODO: Use optional<span<T>> to make failure more type-safe. Currently this
34// isn't done since sizeof(optional<span<T>>) > sizeof(span<T>), since optional
35// needs to have a way to tell if the span exists or not. I thought perhaps a
36// span containing nullptr would be the representation for nullopt, but this is
37// apparently not the case for the same reason you can have optional<nullptr_t>.
38// If this ever changes, using optional<span<T>> instead of sentinel values
39// in these classes would be a massive improvement.
40// TODO: Resiliency? If a thread is terminated while it has an uncommitted
41// reservation, the entire queue will eventually lock up.
42// TODO: Overloads for send() and recv() family of functions that allow a
43// custom wait strategy lambda to take the place of the spinlock
44// TODO: Variadic overloads for send() and recv() family of functions:
45// - std::initializer_list
46// - Template pack expansion
47
51template<typename T>
52class chan {
53public:
57 virtual auto try_send(T&& item) noexcept -> bool = 0;
58
62 virtual auto try_send(span<const T>&& items) noexcept -> span<const T> = 0;
63
67 virtual void send(T&& item) noexcept = 0;
68
72 virtual void send(span<const T>&& items) noexcept = 0;
73
77 virtual auto try_recv(T& dest) noexcept -> bool = 0;
78
82 virtual auto try_recv(span<T>& dest) noexcept -> span<T> = 0;
83
87 virtual void recv(T& dest) noexcept = 0;
88
92 virtual void recv(span<T>& dest) noexcept = 0;
93
97 // TODO: Specify in documentation that the return value is also a key to pass into commit_send()
98 // The returned span is contiguous; it should never wrap around the end of buf
99 virtual auto try_reserve_send(size_t count) noexcept -> span<T> = 0;
100
104 // TODO: Specify in documentation that the return value is also a key to pass into commit_send()
105 // The returned span is contiguous; it should never wrap around the end of buf
106 virtual auto reserve_send_up_to(size_t count) noexcept -> span<T> = 0;
107
112 // TODO: Specify in documentation that the return value is also a key to pass into commit_send()
113 virtual auto reserve_send(size_t count) noexcept -> span<T> = 0;
114
119 // TODO: Specify in documentation that @p reservation is obtained by either try_reserve_send(), reserve_send_up_to() or reserve_send()
120 virtual auto try_commit_send(span<const T>& reservation) noexcept -> bool = 0;
121
125 // TODO: Specify in documentation that @p reservation is obtained by either try_reserve_send(), reserve_send_up_to() or reserve_send()
126 virtual void commit_send(span<const T>& reservation) noexcept = 0;
127
131 // TODO: Specify in documentation that the return value is also a key to pass into commit_recv()
132 virtual auto try_reserve_recv(size_t count) noexcept -> span<T> = 0;
133
137 // TODO: Specify in documentation that the return value is also a key to pass into commit_recv()
138 virtual auto reserve_recv_up_to(size_t count) noexcept -> span<T> = 0;
139
143 // TODO: Specify in documentation that the return value is also a key to pass into commit_recv()
144 // TODO: How to handle wrapping? send/recv functions can do two memcpys, buf is private.
145 virtual auto reserve_recv(size_t count) noexcept -> span<T> = 0;
146
151 // TODO: Specify in documentation that @p reservation is obtained by either try_reserve_recv() or reserve_recv()
152 virtual auto try_commit_recv(span<const T>& reservation) noexcept -> bool = 0;
153
157 // TODO: Specify in documentation that @p reservation is obtained by either try_reserve_recv() or reserve_recv()
158 virtual void commit_recv(span<const T>& reservation) noexcept = 0;
159};
160
161} // namespace rtchan
Abstract common interface for all queues.
Definition rtchan.hpp:52
virtual auto reserve_recv(size_t count) noexcept -> span< T >=0
Reserves space for count items in the channel to be manually read from.
virtual auto try_send(T &&item) noexcept -> bool=0
Tries to send item over the channel, aborting if there is not enough room.
virtual auto try_commit_send(span< const T > &reservation) noexcept -> bool=0
Tries to commit the items in reservation, indicating that they are finished being sent and are ready ...
virtual void send(span< const T > &&items) noexcept=0
Sends all items in items over the channel. If the channel cannot fit all of items,...
virtual auto try_reserve_recv(size_t count) noexcept -> span< T >=0
Tries to reserve space for count items in the channel to be manually read from.
virtual auto reserve_send_up_to(size_t count) noexcept -> span< T >=0
Tries to reserve space for up to count items in the channel to be manually written into.
virtual auto reserve_send(size_t count) noexcept -> span< T >=0
Reserves space for count items in the channel to be manually written into. This function may spin unt...
virtual auto try_commit_recv(span< const T > &reservation) noexcept -> bool=0
Tries to release the items in reservation, indicating that they are finished being received and are s...
virtual void recv(span< T > &dest) noexcept=0
Receives items equal to the capacity of dest from the channel into dest. If the channel does not have...
virtual auto try_recv(span< T > &dest) noexcept -> span< T >=0
Tries to receive items up to the capacity of dest from the channel into dest.
virtual auto try_send(span< const T > &&items) noexcept -> span< const T >=0
Tries to send as many items in items over the channel as will fit.
virtual void commit_send(span< const T > &reservation) noexcept=0
Commits the items in reservation, indicating that they are finished being sent and are ready to be re...
virtual void send(T &&item) noexcept=0
Sends item over the channel. If the channel is full, this function will block until space is availabl...
virtual void recv(T &dest) noexcept=0
Receives an item from the channel into dest. If the channel is empty, this function will block until ...
virtual auto try_recv(T &dest) noexcept -> bool=0
Tries to receive an item from the channel, placing it into dest.
virtual void commit_recv(span< const T > &reservation) noexcept=0
Releases the items in reservation, indicating that they are finished being received and are safe to b...
virtual auto try_reserve_send(size_t count) noexcept -> span< T >=0
Tries to reserve space for count items in the channel to be manually written into.
virtual auto reserve_recv_up_to(size_t count) noexcept -> span< T >=0
Tries to reserve space for up to count items in the channel to be manually read from.