SiddiqSoft.RWLEnvelope
1.4.3
dotnet add package SiddiqSoft.RWLEnvelope --version 1.4.3
NuGet\Install-Package SiddiqSoft.RWLEnvelope -Version 1.4.3
<PackageReference Include="SiddiqSoft.RWLEnvelope" Version="1.4.3" />
<PackageVersion Include="SiddiqSoft.RWLEnvelope" Version="1.4.3" />
<PackageReference Include="SiddiqSoft.RWLEnvelope" />
paket add SiddiqSoft.RWLEnvelope --version 1.4.3
#r "nuget: SiddiqSoft.RWLEnvelope, 1.4.3"
#:package SiddiqSoft.RWLEnvelope@1.4.3
#addin nuget:?package=SiddiqSoft.RWLEnvelope&version=1.4.3
#tool nuget:?package=SiddiqSoft.RWLEnvelope&version=1.4.3
RWLEnvelope : A simple read-writer lock envelope
Objective
RWLEnvelope is a header-only C++ template library that provides a simple, convenient envelope-access model for thread-safe access to objects using reader-writer locks. Our goal is to:
Avoid re-implementing the rw-lock: The standard C++ library (since C++14) provides excellent reader-writer lock implementations via
std::shared_mutex,std::unique_lock, andstd::shared_lock.Simplify thread-safe access patterns: We provide a convenient layer that makes it easy to work with shared data in multi-threaded applications without exposing the complexity of manual lock management.
Enable safe concurrent access: Support multiple concurrent readers while ensuring exclusive access for writers, with automatic lock management and RAII semantics.
Minimize boilerplate code: Reduce the amount of locking code needed to safely access shared objects through intuitive APIs.
<p align="right" width="50%"> <b>WE DO NOT IMPLEMENT</b> a read-writer lock; the standard C++ library has one.<br/>We provide a header-only package simplifying the locking code around thread-safe access to your underlying type. <br/> <i>NOT a wrapper; an envelope.</i> </p>
Why RWLEnvelope?
The Problem with Manual Lock Management
Writing thread-safe code is hard. Without RWLEnvelope, you'd need to:
// Without RWLEnvelope - verbose and error-prone
std::shared_mutex mutex;
std::map<std::string, int> data;
// Reading
{
std::shared_lock lock(mutex);
auto it = data.find("key");
if (it != data.end()) {
std::cout << it->second << std::endl;
}
} // Lock released here
// Writing
{
std::unique_lock lock(mutex);
data["key"] = 42;
} // Lock released here
The RWLEnvelope Solution
With RWLEnvelope, the same code becomes cleaner and safer:
// With RWLEnvelope - clean and safe
siddiqsoft::RWLEnvelope<std::map<std::string, int>> data;
// Reading
data.observe<void>([](const auto& m) {
auto it = m.find("key");
if (it != m.end()) {
std::cout << it->second << std::endl;
}
});
// Writing
data.mutate<void>([](auto& m) {
m["key"] = 42;
});
Key Benefits
Automatic Lock Management: Locks are acquired and released automatically via RAII. No risk of forgetting to unlock.
Clear Intent:
observe()for reads andmutate()for writes makes your code's intent explicit and self-documenting.Reduced Boilerplate: No need to manually create lock objects or manage scopes. The library handles it.
Type Safety: The template enforces that you're working with the correct type. No accidental type mismatches.
Exception Safe: If your callback throws, the lock is still released properly. No deadlocks or resource leaks.
Flexible Access Patterns: Choose between callback-based access (for simple operations) or direct lock access (for complex operations).
Zero Overhead: Header-only implementation with no runtime overhead beyond the standard library's mutex.
Works with Any Type: Not limited to JSON or maps. Works with any type that supports move semantics.
Real-World Scenarios
Configuration Management:
siddiqsoft::RWLEnvelope<AppConfig> config;
// Multiple threads reading config
config.observe<std::string>([](const auto& cfg) {
return cfg.getDatabaseUrl();
});
// Single thread updating config
config.mutate<void>([](auto& cfg) {
cfg.setDatabaseUrl("new_url");
});
Cache Implementation:
siddiqsoft::RWLEnvelope<std::unordered_map<std::string, CacheEntry>> cache;
// Fast concurrent reads
cache.observe<CacheEntry>([](const auto& c) {
return c.at("key");
});
// Exclusive writes
cache.mutate<void>([](auto& c) {
c["key"] = computeValue();
});
Shared State in Services:
siddiqsoft::RWLEnvelope<ServiceState> state;
// Multiple reader threads
state.observe<bool>([](const auto& s) {
return s.isHealthy();
});
// Single writer thread
state.mutate<void>([](auto& s) {
s.updateMetrics();
});
API Documentation
For comprehensive API documentation, including detailed descriptions of all methods, usage patterns, and examples, see API.md.
Requirements
- You must be able to use
<shared_mutex>and<mutex>. - Minimal target is
C++17. - The build and tests are for Visual Studio 2019 under x64.
- We use
nlohmann::jsononly in our tests and the library is aware to provide a conversion operator if library is detected.
Usage
- Use the nuget SiddiqSoft.RWLEnvelope
- Copy paste..whatever works.
- The idea is to not "wrap" the underlying type forcing you to either inherit or re-implement the types but to take advantage of the underlying type's interface whilst ensuring that we have the necessary locks.
- Two methods:
- Observer/mutator model with callback and custom return to limit access and to focus the where and how to access the underlying type.
- Take advantage of init-statement in if-statement to get the contained object within a lock and have the compiler auto-release once we leave scope.
- A sample implementation (say you want a std::map with reader-writer lock)
using RWLMap = siddiqsoft::RWLEnvelope<std::map>;
#include "gtest/gtest.h"
#include "nlohmann/json.hpp"
#include "siddiqsoft/RWLEnvelope.hpp"
TEST(examples, AssignWithCallbacks)
{
siddiqsoft::RWLEnvelope<nlohmann::json> docl; // we will assign later
nlohmann::json doc2 {{"baa", 0x0baa}, {"fee", 0x0fee}, {"bee", 0x0bee}};
// Move assign here post init
docl.reassign(std::move(doc2));
// Must be empty since we moved it into the envelope
EXPECT_TRUE(doc2.empty());
// Check we have pre-change value.. Note that here we return a boolean to avoid data copy
EXPECT_TRUE(docl.observe<bool>([](const auto& doc) -> bool {
return (doc.value("fee", 0xfa17) == 0x0fee) && (doc.value("baa", 0xfa17) == 0x0baa) && (doc.value("bee", 0xfa17) == 0x0bee);
}));
EXPECT_EQ(3, docl.observe<size_t>([](const auto& doc) { return doc.size(); }));
}
TEST(examples, AssignWithDirectLocks)
{
siddiqsoft::RWLEnvelope<nlohmann::json> docl({{"foo", "bar"}, {"few", "lar"}});
nlohmann::json doc2 {{"baa", 0x0baa}, {"fee", 0x0fee}, {"bee", 0x0bee}};
// Previous document has two items..
if (auto const& [doc, rl] = docl.readLock(); rl) { EXPECT_EQ(2, doc.size()); }
// Modify the item (replace the initial with new)
if (auto [doc, wl] = docl.writeLock(); wl) { doc = std::move(doc2); };
//doc2 -> Must be empty since we moved it into the envelope
EXPECT_TRUE(doc2.empty());
// Check we have post-change value..
if (const auto& [doc, rl] = docl.readLock(); rl) { EXPECT_EQ(3, doc.size()); }
}
Additional examples.
Test Coverage
The library includes comprehensive test coverage across multiple categories:
Basic Functionality Tests
- Simple Operations: Basic envelope creation and mutation
- Callback-Based Access: Testing
observe()andmutate()methods with various return types - Direct Lock Access: Testing
readLock()andwriteLock()with structured bindings - Reassignment: Testing
reassign()method for replacing envelope contents - Snapshot Operations: Testing
snapshot()for independent copies - Move Semantics: Testing move constructors and move assignment
Edge Case Tests
- Default Construction: Envelopes with default-constructed objects
- Return Value Forwarding: Callbacks returning various types (void, int, string, bool, size_t)
- Non-JSON Types: Testing with
std::vector<int>,std::string, and other types - Move Constructor Behavior: Verifying source envelope state after move operations
- RWA Counter Tracking: Validating the read-write-action counter accuracy
- Exception Safety: Testing behavior when callbacks throw exceptions
- Independent Snapshots: Verifying snapshots are truly independent copies
- Multiple Reassignments: Testing repeated reassignment operations
Concurrency & Stress Tests
Reader-Writer Contention
- Two-Thread Tests: Concurrent readers and writers with callbacks and direct locks
- Monotonic Counter Integrity: Verifying counter never goes backwards under concurrent access
- Snapshot Consistency: Ensuring snapshots return internally consistent state
- Concurrent Reassign: Testing reassign racing with observe and snapshot operations
High-Contention Scenarios
- Zero-Sleep Maximum Contention: All threads hammer the lock without delays
- Mixed API Concurrency: All 5 API methods used concurrently on the same envelope
- Shared Read Lock Concurrency: Multiple readers accessing simultaneously without blocking
- Concurrent Observe with Return: Readers returning values under write contention
Data Integrity Verification
- RWA Counter Accuracy: Verifying mutation counter matches exact mutate() count
- Paired Field Consistency: Ensuring related fields remain synchronized
- Version-Data Pairing: Validating version and data fields stay in sync during reassignment
Test Statistics
- Total Test Cases: 20+ comprehensive test cases
- Concurrency Levels: Tests with up to 16 concurrent reader threads and 8 writer threads
- Iteration Counts: Stress tests with 5,000-10,000 iterations per thread
- Coverage Areas:
- ✅ All public API methods
- ✅ Thread safety guarantees
- ✅ Lock semantics (shared vs. exclusive)
- ✅ Exception safety
- ✅ Move semantics
- ✅ Data consistency under contention
- ✅ Return value forwarding
- ✅ JSON serialization (when available)
Running Tests
Tests are built using Google Test (gtest) and can be run via the CMake build system:
cmake --preset Apple-Debug
cmake --build --preset Apple-Debug
ctest --preset Apple-Debug
Coverage reports are generated and tracked via Azure Pipelines CI/CD.
For detailed test results and analysis, see TEST_RESULTS.md.
Test Quality & Reliability
Comprehensive Test Suite
We take testing seriously. The library includes 38 comprehensive tests covering:
- ✅ 4 Example tests - Real-world usage patterns
- ✅ 4 Critical race condition tests - Writer-writer contention, deadlock prevention, reassign-mutate racing
- ✅ 3 High-priority race condition tests - Exception safety, API equivalence verification
- ✅ 2 Medium-priority race condition tests - Snapshot isolation, RWA counter accuracy
- ✅ 3 Low-priority race condition tests - Memory visibility, variable lock durations
- ✅ 4 Basic concurrency tests - Multi-threaded reader-writer scenarios
- ✅ 10 Edge case tests - Exception handling, move semantics, type flexibility
- ✅ 8 Stress tests - High-contention scenarios with 5,000-10,000 iterations
Test Execution
All 38 tests pass successfully in ~7.6 seconds:
- Zero failures - 100% pass rate
- No race conditions detected - Verified with up to 16 concurrent threads
- Exception safe - Callbacks throwing exceptions don't corrupt state
- Deadlock-free - Explicit timeout-based deadlock detection
- Memory safe - All writes visible to readers, no torn reads
Stress Testing
The test suite includes aggressive stress tests:
- Maximum contention: All threads hammer the lock without delays
- Mixed API usage: All 5 API methods used concurrently
- Variable lock durations: Realistic lock hold times
- Concurrent exceptions: Exception safety under contention
- Rapid reassignments: Multiple threads reassigning simultaneously
Feedback & Contributions
Report Issues or Suggest Improvements
We want to hear about your usage scenarios! If you encounter:
- Edge cases not covered by our tests
- Performance issues in your specific use case
- Compatibility problems with your type or compiler
- Feature requests for additional functionality
- Documentation gaps or unclear explanations
Please open an issue on GitHub with:
- Your use case: How are you using RWLEnvelope?
- The scenario: What specific situation triggered the issue?
- Expected behavior: What should happen?
- Actual behavior: What actually happened?
- Reproduction steps: How can we reproduce it?
- Environment: Compiler, OS, C++ version
Help Us Improve
Your feedback helps us:
- Identify edge cases we haven't tested
- Optimize for real-world usage patterns
- Improve documentation and examples
- Ensure the library works reliably in your scenarios
Even if you don't have issues, we'd love to hear about:
- How you're using RWLEnvelope
- Performance characteristics in your application
- Types you're enveloping
- Patterns that work well for you
Contributing
Contributions are welcome! Whether it's:
- Additional test cases for your scenarios
- Performance optimizations
- Documentation improvements
- Bug fixes
Please submit a pull request or open an issue to discuss your ideas.
<small align="right">
© 2021 Siddiq Software LLC. All rights reserved.
</small>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| native | native is compatible. |
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.4.3 | 113 | 4/29/2026 |
| 1.2.0 | 521 | 12/7/2024 |
| 1.1.1 | 874 | 12/2/2021 |
| 1.1.0 | 9,663 | 7/22/2021 |
| 1.1.0-main0001 | 663 | 7/22/2021 |
| 1.0.0 | 1,893 | 7/22/2021 |
| 0.10.0-main0001 | 657 | 7/22/2021 |
## Features
- Header-only C++ template library (no compilation needed)
- Simple, intuitive API for thread-safe access patterns
- Automatic lock management with RAII semantics
- Support for both callback-based and direct lock access
- Exception-safe with proper lock release on errors
- Works with any type supporting move semantics
- Zero runtime overhead beyond standard library mutex
- Comprehensive test coverage (38 tests, 100% pass rate)
- Verified deadlock-free and race-condition-free
- Supports C++17 and later
## What's New
- Comprehensive API documentation (API.md)
- 12 new race condition tests for critical scenarios
- Test quality and reliability section in README
- Real-world usage examples and patterns
- Community feedback invitation for edge cases