Files
pyxis/lib/tdeck_ui/UI/LXMF/CallLinkOwnership.h

137 lines
5.0 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#ifndef UI_LXMF_CALLLINKOWNERSHIP_H
#define UI_LXMF_CALLLINKOWNERSHIP_H
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
namespace UI {
namespace LXMF {
// Publication of the exact 128-bit Reticulum link ID together with its call
// generation. Writers are serialized by CallGenerationGuard in UIManager.
// Every publication-marker and ID-word operation is sequentially consistent:
// in that single total order, a reader that observes any replacement ID word
// must have its final generation read after the writer's preceding detach (0),
// and therefore cannot accept the old nonzero generation. A reader accepts only
// equal nonzero generation reads around all four exact ID words.
class CallLinkOwnership {
public:
using LinkId = std::array<uint8_t, 16>;
static constexpr uint32_t MAX_GENERATION = 0x7fffffffu;
bool publish(uint32_t generation, const LinkId& id) {
if (generation == 0 || generation > MAX_GENERATION) return false;
// Detach the previous publication before changing any ID word. The SC
// total-order argument documented above makes mixed snapshots reject.
_generation.store(0, std::memory_order_seq_cst);
for (size_t word = 0; word < _id_words.size(); ++word) {
_id_words[word].store(packWord(id, word),
std::memory_order_seq_cst);
}
_close_state.store(generation << 1, std::memory_order_seq_cst);
_generation.store(generation, std::memory_order_seq_cst);
return true;
}
bool owns(uint32_t generation, const LinkId& id) const {
if (generation == 0 ||
_generation.load(std::memory_order_seq_cst) != generation) {
return false;
}
for (size_t word = 0; word < _id_words.size(); ++word) {
if (_id_words[word].load(std::memory_order_seq_cst) !=
packWord(id, word)) {
return false;
}
}
// A writer may have detached and started replacing the ID after the
// first generation read. Accept only a stable publication.
return _generation.load(std::memory_order_seq_cst) == generation;
}
uint32_t generationFor(const LinkId& id) const {
const uint32_t generation =
_generation.load(std::memory_order_seq_cst);
return owns(generation, id) ? generation : 0;
}
bool markClosed(uint32_t generation, const LinkId& id) {
if (!owns(generation, id)) return false;
uint32_t expected = generation << 1;
return _close_state.compare_exchange_strong(
expected, expected | 1u, std::memory_order_seq_cst,
std::memory_order_seq_cst);
}
// Returns the generation whose pending close was consumed, or zero. The
// exact encoded generation prevents an old callback from clearing or
// replacing a newer owner's pending close.
uint32_t takeClosed() {
const uint32_t generation = _generation.load(std::memory_order_seq_cst);
if (generation == 0) return 0;
uint32_t expected = (generation << 1) | 1u;
if (!_close_state.compare_exchange_strong(
expected, generation << 1, std::memory_order_seq_cst,
std::memory_order_seq_cst)) {
return 0;
}
return _generation.load(std::memory_order_seq_cst) == generation
? generation
: 0;
}
bool clear(uint32_t generation) {
if (generation == 0) return false;
uint32_t expected_generation = generation;
if (!_generation.compare_exchange_strong(
expected_generation, 0, std::memory_order_seq_cst,
std::memory_order_seq_cst)) {
return false;
}
// A validated close callback may pause before its CAS. Keep clearing
// only this generation's two exact states until neither remains; a
// newer generation's slot is never overwritten.
for (;;) {
uint32_t state = _close_state.load(std::memory_order_seq_cst);
if ((state >> 1) != generation) break;
if (_close_state.compare_exchange_weak(
state, 0, std::memory_order_seq_cst,
std::memory_order_seq_cst)) {
break;
}
}
return true;
}
private:
static uint32_t packWord(const LinkId& id, size_t word) {
const size_t offset = word * 4;
return static_cast<uint32_t>(id[offset]) |
(static_cast<uint32_t>(id[offset + 1]) << 8) |
(static_cast<uint32_t>(id[offset + 2]) << 16) |
(static_cast<uint32_t>(id[offset + 3]) << 24);
}
std::array<std::atomic<uint32_t>, 4> _id_words{};
std::atomic<uint32_t> _generation{0};
// (generation << 1) | pending; generation is bounded to 31 bits.
std::atomic<uint32_t> _close_state{0};
};
} // namespace LXMF
} // namespace UI
#endif // UI_LXMF_CALLLINKOWNERSHIP_H