Skip to content

Instantly share code, notes, and snippets.

@ruurdadema
Created November 24, 2025 21:58
Show Gist options
  • Select an option

  • Save ruurdadema/0c0efe42634244ee14e24e8e05724413 to your computer and use it in GitHub Desktop.

Select an option

Save ruurdadema/0c0efe42634244ee14e24e8e05724413 to your computer and use it in GitHub Desktop.
ExclusiveAccessGuard
/*
* Owllab License Agreement
*
* This software is provided by Owllab and may not be used, copied, modified,
* merged, published, distributed, sublicensed, or sold without a valid and
* explicit agreement with Owllab.
*
* Copyright (c) 2024 Owllab. All rights reserved.
*/
#pragma once
#include "ravennakit/core/assert.hpp"
#include "ravennakit/core/util/defer.hpp"
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define CONCAT_INNER(a, b) a##b
/**
* Helper macro which asserts exclusive access to scope. Whenever 2 different threads access the scope, an assertion
* will be triggered.
*/
#define RAV_ASSERT_EXCLUSIVE_ACCESS(guard) \
rav::ExclusiveAccessGuard::Lock CONCAT(lock, __LINE__)(guard); \
RAV_ASSERT(!CONCAT(lock, __LINE__).violated(), "exclusive access violation");
#include <atomic>
#include <stdexcept>
namespace rav {
/**
* Guards exclusive access to a resource. Throws if the resource is already accessed.
*/
class ExclusiveAccessGuard {
public:
/**
* Locks and exclusive access guard.
*/
class Lock {
public:
/**
* Constructs a new lock.
* @throws std::runtime_error if exclusive access is violated.
*/
explicit Lock(ExclusiveAccessGuard& access_guard) : exclusive_access_guard_(access_guard) {
const auto prev = exclusive_access_guard_.counter_.fetch_add(1, std::memory_order_relaxed);
if (prev != 0) {
violated_ = true;
}
}
~Lock() {
exclusive_access_guard_.counter_.fetch_sub(1, std::memory_order_relaxed);
}
/**
* @return True if exclusive access is violated.
*/
[[nodiscard]] bool violated() const {
return violated_;
}
private:
ExclusiveAccessGuard& exclusive_access_guard_;
bool violated_ = false;
};
/**
* Constructs a new exclusive access guard.
*/
explicit ExclusiveAccessGuard() = default;
ExclusiveAccessGuard(const ExclusiveAccessGuard&) = delete;
ExclusiveAccessGuard& operator=(const ExclusiveAccessGuard&) = delete;
ExclusiveAccessGuard(ExclusiveAccessGuard&&) = delete;
ExclusiveAccessGuard& operator=(ExclusiveAccessGuard&&) = delete;
private:
std::atomic<int8_t> counter_ {};
};
} // namespace rav
/*
* Owllab License Agreement
*
* This software is provided by Owllab and may not be used, copied, modified,
* merged, published, distributed, sublicensed, or sold without a valid and
* explicit agreement with Owllab.
*
* Copyright (c) 2024 Owllab. All rights reserved.
*/
#include "ravennakit/core/util/exclusive_access_guard.hpp"
#include <future>
#include <thread>
#include <catch2/catch_all.hpp>
TEST_CASE("rav::ExclusiveAccessGuard") {
SECTION("Exclusive access violation") {
rav::ExclusiveAccessGuard guard;
const rav::ExclusiveAccessGuard::Lock lock1(guard);
const rav::ExclusiveAccessGuard::Lock lock2(guard);
REQUIRE_FALSE(lock1.violated());
REQUIRE(lock2.violated());
}
SECTION("Trigger exclusive access violation by running two threads") {
std::atomic keep_going {true};
rav::ExclusiveAccessGuard guard;
auto function = [&keep_going, &guard]() {
while (keep_going) {
const rav::ExclusiveAccessGuard::Lock lock(guard);
if (lock.violated()) {
keep_going = false;
return true;
}
// Introduce a delay to increase the chance of violation
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return false;
};
auto f1 = std::async(std::launch::async, function);
auto f2 = std::async(std::launch::async, function);
f1.wait_for(std::chrono::seconds(5));
f2.wait_for(std::chrono::seconds(5));
REQUIRE((f1.get() || f2.get()));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment