Skip to content

Add async::exclusive_access for Resource Coordination #22

Description

@kammce

Motivation

When implementing async drivers for shared hardware resources (I2C, SPI, UART, etc.), we need a way to:

  1. Ensure only one coroutine can access the resource at a time (mutual exclusion)
  2. Block waiting coroutines with appropriate state information for scheduler optimization
  3. Allow schedulers to implement advanced features like priority inheritance and efficient wake-up queues
  4. Provide a safe, RAII-based API that prevents resource leaks

Currently, driver implementers must manually manage ownership tracking and blocking coordination, which is error-prone and leads to boilerplate code.

Proposed Solution

Introduce async::exclusive_access - a lightweight primitive that:

  • Tracks which async::context currently owns a resource (as std::uintptr_t to avoid reference leaks)
  • Provides RAII guard for automatic cleanup
  • Exposes ownership information for scheduler coordination
  • Uses standard blocking states (blocked_by::external)

API Design

namespace async {

class exclusive_access {
public:
  class guard {
  public:
    explicit guard(exclusive_access& parent);
    ~guard();  // Automatically releases access
    
    // Non-copyable, non-movable (must live on stack)
    guard(guard const&) = delete;
    guard& operator=(guard const&) = delete;
    guard(guard&&) = delete;
    guard& operator=(guard&&) = delete;
  };
  
  /// Grant exclusive access to a context, returns RAII guard
  [[nodiscard]] guard grant(context& ctx);
  
  /// Check if currently held by any context
  bool is_held() const;
  
  /// Convenience operator for checking if held
  explicit operator bool() const;
  
  /// Get the address of the owning context (for scheduler use)
  /// Returns 0 if not held
  std::uintptr_t owner() const;

private:
  friend class guard;
  void release();
  
  std::uintptr_t m_owner = 0;
};

} // namespace async

Usage Example

class my_i2c : public hal::async_i2c {
public:
  async::future<void> driver_transaction(
      async::context& p_context,
      hal::byte p_address,
      std::span<hal::byte const> p_data_out,
      std::span<hal::byte> p_data_in)
  {
    // Wait until resource is available
    while (m_bus.is_held()) {  // or just: while (m_bus)
      co_await block_by_sync(m_bus);
    }
    
    // Acquire exclusive access (RAII guard)
    auto access = m_bus.grant(p_context);
    
    // Perform I2C transaction
    perform_i2c_setup(p_address);
    setup_interrupt();
    i2c_start();
    
    while (i2c_bus_busy()) {
      co_await block_by_io();  // Different blocking reason
    }
    
    // access automatically released when scope exits
  }

private:
  async::exclusive_access m_bus;
};

Implementation Notes

  1. std::uintptr_t for ownership: Stores the context's address as an integer to avoid creating reference leaks (which would violate your clang-tidy checks)

  2. RAII guard: The guard type should be non-movable to ensure it stays on the stack and gets destroyed at scope exit. This prevents accidental transfer of ownership or early release.

  3. [[nodiscard]] attribute: Forces users to capture the guard, preventing the common mistake of calling grant() without storing the result (which would immediately release).

  4. blocked_by::sync: Uses the existing blocking state, with the exclusive_access object's address stored in the block info for scheduler coordination.

  5. Simple scheduler compatibility: Schedulers that don't need advanced coordination can simply ignore the ownership information and use round-robin scheduling.

Benefits

  • Safety: RAII prevents resource leaks
  • Clarity: Clear API makes driver code readable
  • Flexibility: Simple schedulers can ignore it, sophisticated schedulers leverage it
  • Performance: Enables efficient scheduler wake-up queues and priority inheritance
  • No overhead: Zero-cost abstraction for basic use cases

Activity

  1. kammce commented on Jan 6, 2026

    @kammce
    MemberAuthor

    This should replace context_token. I think context token is actually a bad name with very little descriptive properties.

  2. kammce commented on Jan 17, 2026

    @kammce
    MemberAuthor

    In 0.0.3, exlcusive_access exists but its lacking the guard object.

  3. assigned and unassigned on Apr 19, 2026
  4. kammce commented on Apr 19, 2026

    @kammce
    MemberAuthor

    I took over this and resolved it in #92

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions