SkillAgentSearch skills...

DotNetVault

Synchronization Library and Static Analysis Tool for C# 8

Install / Use

/learn @cpsusie/DotNetVault

README

DotNetVault Version 1.0

Synchronization Library and Static Analyzer for C# 8.0+

Version 1.0

DotNetVault takes its inspiration from the synchronization mechanisms provided by Rust language and the Facebook Folly C++ synchronization library. These synchronization mechanisms observe that the mutex should own the data they protect. You literally cannot access the protected data without first obtaining the lock. RAII destroys the lock when it goes out of scope – even if an exception is thrown or early return taken. DotNetVault provides mechanisms for RAII-based thread synchronization and uses Roslyn analyzer to add new rules to the C# language to actively prevent resources protected by its vaults from being used in a thread-unsafe or non-synchronized way. You need not rely on convention or programmer discipline to ensure compliance: DotNetVault enforces compliance at compile-time.

Just using the Software

If you just want to use the software software, it is intended to be used as a nuget package and is available on nuget.org. You can visit its Nuget.org page here.

Version Directory

TLDR: If you want a usable version of the software, for the foreseeable future, refer to the v1.0_master branch and releases with a leading version number of 1. The master branch (where you are now) is for development of future versions of this library and is unlikely to see a production-ready release anytime soon.

The current stable release is version v1.0.0.1 and can be found here and here.

Releases beginning with the number 1 (the latest of which is v1.0.0.1) are the current stable releases and will continue to support .NET Framework v4.8. If you wish the repository for further development of version 1, please use the v1.0_master branch.

By contrast, this branch (i.e. the master branch) will be used for development of version 2 of .NET Vault, which will not support .NET 4.8. While critical fixes from version 1.0+ will be merged into the master branch selectively and as appropriate, development between v1.0_master and master are now deemed separate concerns. Development for versions 1.0+ is now limited to bug and documentation fixes and minor enhancements.

Requirements

  • Environment targeting DotNet Framework 4.8+, DotNet 5.0+, DotNet Standard 2.0+ or DotNet Core 3.1+. (For framework 4.8 and DotNet Standard 2.0, any projects using this library or its analyzer must be manually set to use C# 8.0. This is unnecessary for DotNetCore 3.1+ and DotNet 5.0+).
  • A build environment that supports Roslyn Analyzers and capable of emitting compiler errors as prompted by analyzers. (Both Visual Studio Community 2019+ on Windows and Jetbrains Rider 2019.3.1+ on Amazon Linux have been extensively tested). Visual Studio Code has also been tested but not extensively. From testing, Visual Studio Code, with Roslyn Analyzers enabled, will emit compilation errors at build-time, but Intellisense identification of the errors was markedly inferior to the Intellisense available in Visual Studio and Rider.
  • Installation of this library and its dependencies via NuGet.

Advantages:

Easy to Change Underlying Synchronization Mechanisms

DotNetVault uses a variety of different underlying synchronization mechanisms:

  • Monitor + Sync object This is the most widely used thread synchronization mechanism used in C# code. It is the mechanism used when you use the lock keyword such as

    lock (_syncObj)
    {
        //Access protected resource
    }
    
  • Atomics You can also base your synchronization on lock-free interlocked exchanges using DotNetVault.

  • ReaderWriterLockSlim This synchronization mechanism allows for the possibility of read-only locks which multiple threads can obtain concurrently as well as read-write locks which are exclusive. (Upgradable read-only locks are also available.) DotNetVault provides Vaults that use this as its underlying synchronization mechanism as well.

All of the above synchronization mechanisms use different syntax to obtain and release locks. If you use them directly, and decide to switch to (or try) a different mechanism, it will require extensive refactoring which may be prohibitively difficult to do correctly: and be equally difficult to switch back.

DotNetVault simplifies the required refactoring.
All vaults expose a common compile-time API. Simply change the type of vault protecting your resource (and perhaps update the constructor that instantiates the vault) and the underlying mechanism used changes accordingly. (Of course, read-only locks are only available with vaults that use ReaderWriterLockSlim, but if you are using read-only locks, the other mechanisms are inappropriate.)

Deadlock Avoidance

Deadlocks occur most frequently when you acquire successive locks in differing orders on different threads. In large projects, it can be very difficult to ensure that locks are always obtained in the same order. Errors typically manifest in your application silently freezing up. Moreover, it can be very difficult to reproduce certain deadlocks and the act of attaching a debugger with break points may change the behavior your customer is observing. In short, these are very expensive problems to debug.

The way that DotNetVault helps you avoid deadlocks is by making all lock acquisitions timed by default. You can, of course, do timed acquisition with the underlying primitives directly, but this is syntactically difficult (compare, for example, the untimed, scope-based, release-guaranteed lock statement with its timed alternative using Monitor.TryEnter and a timeout). The syntactic difficulties and lack of RAII, scope based acquisition and release easily leads to errors such as:

  • by forgetting to free it (perhaps due to early return or exception),
  • not realizing it wasn't obtained successfully and, under this delusion, proceed to access the resource without synchronization
  • deciding not to use a timed acquisition because of the foregoing difficulties and thus running into the foregoing dreaded deadlocks.

Using DotNetVault, all access is subject to RAII, scoped-based lock acquisition and release. Failure to obtain a lock throws an exception --- there can be no mistake as to whether it is obtained. When a locks scope ends, it is released. By default, all lock acquisitions are timed -- you must explicitly and clearly use the differently and ominously named untimed acquisition methods if you wish to avoid the slight overhead imposed by timed acquisition. (Typically after using and heavily testing using the standard timed acquisition methods, ensuring there are no deadlocks, profiling and discovering a bottleneck caused by timed acquisition, and then switching to the untimed acquisition method in those identified bottlenecks. It is hard to deadlock accidentally in DotNetVault.

RAII (Scope-based) Lock Acquisition and Release:

Locks are stack-only objects (ref structs) and the integrated Roslyn analyzer forces you to declare the lock inline in a using statement or declaration, or it will cause a compilation error.

  • There is no danger of accidentally holding the lock open longer than its scope even in the presence of an exception or early return.
  • There is no danger of being able to access the protected resource if the lock is not obtained.
  • There is no danger of being able to access the protected resource after release.

Enforcement of Read-Only Access When Lock is Read-Only

None of the synchronization primitives, when used directly, prevents access to the protected resource when it is not obtained. DotNetVault, as mentioned in numerous places, actively prevents such unsynchronized access at compile-time. ReaderWriterLockSlim is unique among the synchronization primitives employed by DotNetVault in allowing for multiple threads to hold read-only locks at the same time. As the primitive cannot prevent access to the resource generally, it also cannot validate that user code does not mutate the protected resource while holding a read-only lock. DotNetVault not only prevents access to the underlying resource while the correct lock is not held, it also enforces that, while a read-only lock is held, the protected object cannot be mutated. This is also enforced statically, at compile-time.

Isolation of Protected Resources

The need for programmer discipline is reduced:

  1. programmers do not need to remember which mutexes protect which resources,
  2. once a protected resource is in a vault, no reference to any mutable state of any object in the resources object graph can be accessed except when a stack-frame limited lock has been obtained: the static analyzer prevents it at compile-time.
  3. programmers cannot access the protected resource before they obtain the lock and cannot access any mutable state from the protected resource after releasing the lock, and
  4. static analysis rules prevent mutable state not protected by the vault from becoming part of the state of the protected resource.

Summary of Advantages

The ubiquity of shared mutable state in Garbage Collected languages like C# can work at cross purposes to thread-safety. One approach to thread-safety in such languages is to eliminate the use of mutable state. Because this is not always possible or even desirable, the synchronization mechanisms employed in C# typically rely on programmer knowledge

View on GitHub
GitHub Stars5
CategoryDevelopment
Updated2mo ago
Forks2

Languages

C#

Security Score

90/100

Audited on Jan 17, 2026

No findings