blob: efea579a7480ba7f19b1460a08029bab5d845b13 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <stdio.h>
#include <threads.h>
#include <array>
#include <atomic>
#include "librace.h"
#include "model-assert.h"
//
// Requirements:
//
// Design A SeqLock which is intended to allow concurrent reads, and write
// transactions from different threads running concurrently. The sequence
// number itself serves both as the transaction failure detector for readers, as
// well as the spinlock-style exclusive lock for writers.
//
// The SeqLock implementation described in this example must tolerate the
// following thread behaviors, and obey the following rules.
//
// ++ Readers are allowed to execute concurrently with writers
// ++ Writer may attempt to execute concurrently (from multiple threads), but
// the SeqLock implementation must not allow them to modify the payload at
// the same time.
// ++ Writers may read the payload contents during a transaction, in addition to
// writing them.
// ++ Relaxed payload loads/stores are used in this example. Fence-to-fence
// synchronization is used to guarantee proper memory ordering semantics.
//
// The outline of read and write transactions look like the following.
//
// :: Read Transaction ::
// 1) while ((before = Seq) & 0x1) {}; // Acquire semantics
// 2) ReadPayload() // Relaxed semantics for all members
// 3) Fence(Acquire)
// 4) after = Seq; // Relaxed semantics
// 5) if (before != after) goto 1;
//
// :: Write Transaction ::
// 1) while ((before = Seq) & 0x1) {}; // Relaxed semantics
// 2) if (!CMPX(Seq, before + 1)) goto 1; // Acquire semantics
// 3) Fence(Release)
// 4) WritePayload() // Relaxed semantics for all members
// 5) Seq += 1; // Release semantics
//
constexpr uint32_t kPayloadWords = 2;
constexpr uint32_t kTotalWriters = 2;
struct State {
std::atomic<uint64_t> seq;
std::array<std::atomic<uint32_t>, kPayloadWords> payload;
};
static void Read(State& s) {
uint64_t before, after;
std::array<uint32_t, kPayloadWords> vals;
do {
// spin until we see an even sequence number using acq loads.
while ((before = s.seq.load(std::memory_order_acquire)) & 1u) {
thrd_yield();
}
// Load all payloads with relaxed semantics.
for (uint32_t i = 0; i < vals.size(); ++i) {
vals[i] = s.payload[i].load(std::memory_order_relaxed);
}
// Now, drop an acquire-fence into the sequence, then load the sequence
// number again, this time with relaxed semantics. The fence guarantees
// that the load of the sequence number cannot move before the fence (and any
// of the payload loads)
std::atomic_thread_fence(std::memory_order_acquire);
after = s.seq.load(std::memory_order_relaxed);
} while (before != after);
MODEL_ASSERT((vals[0] % kPayloadWords) == 0);
for (uint32_t i = 1; i < vals.size(); ++i) {
MODEL_ASSERT((vals[i - 1] + 1) == vals[i]);
}
}
static void Write(State& s) {
// Spin until we observe an even sequence number using relaxed semantics.
// Then attempt to increment that even number using acquire semantics.
uint64_t seq;
do {
while ((seq = s.seq.load(std::memory_order_relaxed)) & 1u) {
thrd_yield();
}
} while (!s.seq.compare_exchange_strong(seq, seq + 1, std::memory_order_acquire,
std::memory_order_relaxed));
// Now, drop a release-fence into the sequence. This will prevent the relaxed
// store of the sequence number from moving past the fence. Therefore, the
// relaxed store of the sequence number cannot happen-after the stores of the
// payload.
std::atomic_thread_fence(std::memory_order_release);
// Store the payloads, each with relaxed semantics.
for (size_t i = 0; i < s.payload.size(); ++i) {
s.payload[i].fetch_add(kPayloadWords, std::memory_order_relaxed);
}
// Increment the sequence number again, this time with release semantics.
s.seq.fetch_add(1u, std::memory_order_release);
}
extern "C" int user_main(int argc, char** argv) {
State s;
printf("Seq = %p\n", &s.seq);
s.seq.store(0u);
for (uint32_t i = 0; i < s.payload.size(); ++i) {
printf("p[%i] = %p\n", i, &s.payload[i]);
s.payload[i].store(i);
}
thrd_t reader;
std::array<thrd_t, kTotalWriters> writers;
thrd_create(
&reader, [](void* ctx) { Read(*reinterpret_cast<State*>(ctx)); }, &s);
for (auto& writer : writers) {
thrd_create(
&writer, [](void* ctx) { Write(*reinterpret_cast<State*>(ctx)); }, &s);
}
thrd_join(reader);
for (auto& writer : writers) {
thrd_join(writer);
}
return 0;
}