Envisia.FusionCache.MultiProcess 0.1.0

dotnet add package Envisia.FusionCache.MultiProcess --version 0.1.0
                    
NuGet\Install-Package Envisia.FusionCache.MultiProcess -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Envisia.FusionCache.MultiProcess" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Envisia.FusionCache.MultiProcess" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Envisia.FusionCache.MultiProcess" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Envisia.FusionCache.MultiProcess --version 0.1.0
                    
#r "nuget: Envisia.FusionCache.MultiProcess, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Envisia.FusionCache.MultiProcess@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Envisia.FusionCache.MultiProcess&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Envisia.FusionCache.MultiProcess&version=0.1.0
                    
Install as a Cake Tool

Envisia.FusionCache.MultiProcess

A dependency-free, single-machine, multi-process backplane and distributed locker for FusionCache, built entirely on the OS filesystem.

It targets the scenario in FusionCache #355: several processes on one host (potentially under different user accounts) that need to coordinate cache invalidation and factory execution without standing up Redis, a database, or any other external service.

  • Backplane — cross-process cache-invalidation notifications via a FileSystemWatcher "mailbox" (OS-native push: inotify / ReadDirectoryChangesW / FSEvents; no polling).
  • Distributed locker — cross-process cache-stampede protection via OS file locks.
  • Cross-process & cross-user, on Windows, Linux and macOS.
  • No external dependencies beyond FusionCache itself.
  • ✅ Targets .NET 10.

Despite the original issue (and this repo's folder name) mentioning named pipes, the implementation uses the filesystem instead. Named pipes cannot deliver cross-user IPC uniformly across all three operating systems (on Unix .NET maps them to per-user socket files), whereas a watched directory plus file locks do. The package name — MultiProcess — reflects what it actually does.

Install

dotnet add package Envisia.FusionCache.MultiProcess

Usage

using Microsoft.Extensions.DependencyInjection;

services.AddFusionCache()
    .WithMultiProcessBackplane()           // cross-process invalidation
    .WithMultiProcessDistributedLocker();  // cross-process stampede protection

Customize options inline:

services.AddFusionCache()
    .WithMultiProcessBackplane(options =>
    {
        options.BaseDirectory = "/var/lib/myapp/fusioncache"; // optional
        options.MessageTimeToLive = TimeSpan.FromSeconds(30);
    })
    .WithMultiProcessDistributedLocker(options =>
    {
        options.BaseDirectory = "/var/lib/myapp/fusioncache";
    });

Via the service collection + registered components

services.AddFusionCacheMultiProcessBackplane(o => o.MessageTimeToLive = TimeSpan.FromSeconds(15));
services.AddFusionCacheMultiProcessDistributedLocker();

services.AddFusionCache()
    .WithRegisteredBackplane()
    .WithRegisteredDistributedLocker();

Without DI

var backplane = new MultiProcessBackplane(
    new MultiProcessBackplaneOptions { BaseDirectory = baseDir });

var locker = new MultiProcessDistributedLocker(
    new MultiProcessDistributedLockerOptions { BaseDirectory = baseDir });

var cache = new FusionCache(new FusionCacheOptions());
cache.SetupBackplane(backplane);
cache.SetupDistributedLocker(locker);

How it works

Backplane — a filesystem "mailbox"

Each FusionCache channel maps to a shared directory ({baseDir}/channels/{channel}/). To publish, the backplane serializes the BackplaneMessage (using FusionCache's own binary format), writes it to a temp file, and atomically renames it into the channel directory — so peers never observe a partial file. Every other process has a FileSystemWatcher on that directory and is pushed the new file by the OS (no polling); it reads the file and hands the message to FusionCache.

  • A process ignores files it published itself (and FusionCache also filters by SourceId).
  • Duplicate watcher events for the same file are de-duplicated.
  • There is no startup replay: a process only reacts to messages published after it subscribed.
  • Each process deletes only its own message files once they pass MessageTimeToLive. This avoids cross-user deletion (blocked by Windows CREATOR OWNER and the Unix /tmp sticky bit anyway).

Because there is no startup replay, a peer that hasn't finished subscribing yet when a message is published will simply never see it. FusionCache's own SetupBackplane defaults to WaitForInitialBackplaneSubscribe = false, which fires the first Subscribe as fire-and-forget — so IFusionCache can be resolved from DI (and used) before this backplane's FileSystemWatcher is actually armed. For a network backplane (e.g. Redis) a short connect delay is a minor blip; for this filesystem backplane it can mean a lost invalidation. Set .WithOptions(o => o.WaitForInitialBackplaneSubscribe = true) alongside WithMultiProcessBackplane so the initial subscribe completes before the cache is used, especially when multiple processes/hosts can start up and interact with each other within moments of each other (as tests commonly do).

Distributed locker — OS file locks

AcquireLock opens a lock file (path derived from the cache name + lock name) with FileShare.None. If another process holds it, acquisition retries with jittered backoff until the timeout, then returns null (FusionCache then proceeds without the lock — efficiency, not correctness). The returned lock object is the held handle; ReleaseLock disposes it. If a holder process dies, the OS releases the lock automatically — there are no leases or TTLs to get wrong.

Configuration

MultiProcessBackplaneOptions

Property Default Description
BaseDirectory machine-wide shared dir Where channel folders live. null/tmp/envisia-fusioncache (Unix) or a ProgramData subfolder (Windows). Keep this on a local filesystem — advisory file locks and watch notifications are unreliable on network shares (NFS/SMB).
MessageTimeToLive 30s How long a publisher keeps its own message files before deleting them. Must exceed the time peers need to observe them.
WatcherInternalBufferSize framework default Raise for very high invalidation churn to reduce buffer-overflow event loss.

MultiProcessDistributedLockerOptions

Property Default Description
BaseDirectory machine-wide shared dir Where the locks folder lives. Same default as above.

Cross-user access

To let processes under different accounts share the directories:

  • Unix — the shared directory is created world-accessible with the sticky bit (like /tmp); lock files are made group/other read-write so any user can acquire them; message files are world-readable.
  • Windows — the shared directory gets an ACL granting Authenticated Users Modify, inherited by child files.

If every process runs as the same user, no special permissions are needed and the defaults still work.

Security considerations

Cross-user coordination means any local user within the granted scope can:

  • read cache keys (not values — only keys travel over the backplane), and
  • inject invalidation messages or contend on locks.

The worst case of a spoofed invalidation is an unnecessary cache miss — FusionCache's fail-safe and auto-recovery bound the blast radius. If you don't need cross-user access, point BaseDirectory at a location only your user can access and the exposure goes away.

Limitations

  • Single machine only — this is not a network backplane/locker. Use the Redis components for multi-machine setups.
  • Backplane delivery is best-effort — under extreme churn the OS watch buffer can overflow and drop events (logged via the Error event); macOS's watcher is the weakest of the three. A dropped notification only leaves an entry stale until its normal expiration / fail-safe / auto-recovery. This matches the contract FusionCache already assumes for a backplane.
  • The locker is "efficiency, not correctness" — like FusionCache's other distributed lockers, it reduces duplicate factory runs; it is not a general-purpose mutual-exclusion primitive for data correctness.
  • Local filesystem only — point BaseDirectory at local storage; network shares weaken both the advisory file locks and the watch notifications.
  • Crashed peers can leave small orphan files — each process only deletes its own message files (cross-user deletion is blocked by the Unix sticky bit / Windows CREATOR OWNER), so a process that dies hard may leave a few tiny .msg files behind. They are harmless and get swept by the OS's temp-file cleaning; in-memory de-duplication state is bounded by time regardless.

License

See the repository.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
0.1.0 173 7/2/2026