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
<PackageReference Include="Envisia.FusionCache.MultiProcess" Version="0.1.0" />
<PackageVersion Include="Envisia.FusionCache.MultiProcess" Version="0.1.0" />
<PackageReference Include="Envisia.FusionCache.MultiProcess" />
paket add Envisia.FusionCache.MultiProcess --version 0.1.0
#r "nuget: Envisia.FusionCache.MultiProcess, 0.1.0"
#:package Envisia.FusionCache.MultiProcess@0.1.0
#addin nuget:?package=Envisia.FusionCache.MultiProcess&version=0.1.0
#tool nuget:?package=Envisia.FusionCache.MultiProcess&version=0.1.0
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
With the fluent builder (recommended)
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 WindowsCREATOR OWNERand the Unix/tmpsticky 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
Errorevent); 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
BaseDirectoryat 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.msgfiles 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 | Versions 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. |
-
net10.0
- ZiggyCreatures.FusionCache (>= 2.6.0)
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 |