Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README-5.0.0-preview.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ This feature is **opt-in** — all existing single-process behaviour is preserve
```csharp
var config = new PageFileConfig
{
EnableMultiProcessAccess = true,
AllowMultiProcessAccess = true,
};

// Process A (writer)
Expand All @@ -539,7 +539,7 @@ using var db = new AppDb("shared.db", config);
```

> [!IMPORTANT]
> Multi-process WAL requires all cooperating processes to open the database with `EnableMultiProcessAccess = true`. A process that opens with `EnableMultiProcessAccess = false` will hold `FileShare.None` and block other processes.
> Multi-process WAL requires all cooperating processes to open the database with `AllowMultiProcessAccess = true`. A process that opens with `AllowMultiProcessAccess = false` will hold `FileShare.None` and block other processes.

---

Expand Down Expand Up @@ -628,7 +628,7 @@ v5.0.0-preview.0 is **fully backwards-compatible** with v4.4.2 databases. No fil
| Add audit trail | Call `ConfigureAudit(...)` after construction |
| Add GDPR annotations | Annotate properties with `[PersonalData]` and rebuild |
| Enable Strict mode | Add `HasGdprMode(GdprMode.Strict)` + configure encryption + audit |
| Enable multi-process access | Set `PageFileConfig.EnableMultiProcessAccess = true` on all processes |
| Enable multi-process access | Set `PageFileConfig.AllowMultiProcessAccess = true` on all processes |

---

Expand Down Expand Up @@ -706,7 +706,7 @@ v5.0.0-preview.0 is **fully backwards-compatible** with v4.4.2 databases. No fil
## Known limitations in this preview

- `RotateEncryptionKeyAsync` is implemented but not yet stress-tested under concurrent write load. Use it during a maintenance window.
- Multi-process WAL (`EnableMultiProcessAccess`) is not yet supported on WASM/Browser targets (tracked in [WASM_SUPPORT.md](WASM_SUPPORT.md)).
- Multi-process WAL (`AllowMultiProcessAccess`) is not yet supported on WASM/Browser targets (tracked in [WASM_SUPPORT.md](WASM_SUPPORT.md)).
- Argon2id KDF is reserved for a future release. PBKDF2-SHA256 (600 000 iterations) and HKDF-SHA256 are the only supported KDFs in this preview.
- Source-generator GDPR metadata emission (`PersonalDataFields`) requires .NET SDK 9+ (Roslyn 4.x). Fallback reflection path is fully functional on all supported runtimes.

Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ await db.VacuumAsync();
A `.wal-shm` sidecar enables **N-reader / 1-writer** access across OS processes (opt-in):

```csharp
var config = new PageFileConfig { EnableMultiProcessAccess = true };
var config = new PageFileConfig { AllowMultiProcessAccess = true };
using var db = new AppDb("shared.db", config); // open from multiple processes
```

Expand Down Expand Up @@ -1082,7 +1082,7 @@ We are actively building the core. Here is where we stand:
- ✅ **GDPR Compliance Primitives (v5.0.0)**: `[PersonalData]` annotation, `DataSensitivity` levels, Subject Export (`ExportSubjectDataAsync` — Art. 15/20), Database Inspection (`InspectDatabase` — Art. 30), CDC Field Masking (WP2 — `RevealPersonalData`, `IncludeOnlyFields`, `ExcludeFields`), and `GdprMode.Strict` (Art. 25 privacy-by-default orchestration).
- ✅ **Generalized Retention Policy (v5.0.0)**: `HasRetentionPolicy` now applies to any typed collection (not only `TimeSeries`). Supports `maxAge`, `maxDocumentCount`, and configurable `RetentionTrigger` (on-insert or scheduled).
- ✅ **Secure Erase & VACUUM (v5.0.0)**: `HasSecureErase(true)` zeros the storage slot on delete for GDPR Art. 17. `VacuumAsync()` compacts the database and reclaims free space.
- ✅ **Multi-Process WAL (v5.0.0)**: `.wal-shm` sidecar enables N-reader / 1-writer access across OS processes. Opt in via `PageFileConfig.EnableMultiProcessAccess = true`.
- ✅ **Multi-Process WAL (v5.0.0)**: `.wal-shm` sidecar enables N-reader / 1-writer access across OS processes. Opt in via `PageFileConfig.AllowMultiProcessAccess = true`.

## 🔮 Future Vision

Expand Down Expand Up @@ -1127,4 +1127,3 @@ Special thanks to the community members who helped improve BLite:

Licensed under the MIT License. Use it freely in personal and commercial projects.


8 changes: 6 additions & 2 deletions src/BLite.Core/Storage/PageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,14 @@ public void Open()

var fileExists = File.Exists(_filePath);

var isReadOnlyAccess = _config.Access == MemoryMappedFileAccess.Read;
var fileMode = isReadOnlyAccess ? FileMode.Open : FileMode.OpenOrCreate;
var fileAccess = isReadOnlyAccess ? FileAccess.Read : FileAccess.ReadWrite;

Comment on lines 355 to +360
_fileStream = new FileStream(
_filePath,
FileMode.OpenOrCreate,
FileAccess.ReadWrite,
fileMode,
fileAccess,
_config.AllowMultiProcessAccess ? FileShare.ReadWrite : FileShare.None,
bufferSize: 4096,
#if NET6_0_OR_GREATER
Expand Down
47 changes: 47 additions & 0 deletions tests/BLite.Tests/MultiProcessAccessConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,51 @@ public void StorageEngine_ForwardsAllowMultiProcessAccess_ToOwnedWal()
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
}
}

[Fact]
public void StorageEngine_ReadOnlyReader_CanOpenAlongsideWriter_WhenMultiProcessEnabled()
{
var dir = Path.Combine(Path.GetTempPath(), $"blite_mpread_{Guid.NewGuid():N}");
Directory.CreateDirectory(dir);
try
{
var dbPath = Path.Combine(dir, "shared.db");
var writerConfig = PageFileConfig.Default with { AllowMultiProcessAccess = true };
var readerConfig = writerConfig with { Access = System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read };

using var writer = new StorageEngine(dbPath, writerConfig);
using var reader = new StorageEngine(dbPath, readerConfig);

Assert.NotNull(writer.SharedMemory);
Assert.NotNull(reader.SharedMemory);
}
finally
{
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
}
}

[Fact]
public void PageFile_ReadOnlyMode_DoesNotCreateMissingFile()
{
var dir = Path.Combine(Path.GetTempPath(), $"blite_readonly_{Guid.NewGuid():N}");
Directory.CreateDirectory(dir);
try
{
var dbPath = Path.Combine(dir, "missing.db");
var config = PageFileConfig.Default with
{
Access = System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read,
AllowMultiProcessAccess = true
};

using var pageFile = new PageFile(dbPath, config);
Assert.Throws<FileNotFoundException>(() => pageFile.Open());
Assert.False(File.Exists(dbPath));
}
finally
{
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
}
}
}
9 changes: 6 additions & 3 deletions tools/BLite.Studio/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,13 @@ private async Task TryOpen(string path, int presetValue, bool readOnly)
if (string.IsNullOrEmpty(EncryptionPassphrase))
throw new InvalidOperationException("A passphrase is required when encryption is enabled.");

var access = IsReadOnly
? MemoryMappedFileAccess.Read
: MemoryMappedFileAccess.ReadWrite;
var crypto = new CryptoOptions(EncryptionPassphrase);
var baseConfig = new PageFileConfig { AllowMultiProcessAccess = true };
var baseConfig = new PageFileConfig { AllowMultiProcessAccess = true, Access = access };
_engine = new BLiteEngine(path, crypto, baseConfig: baseConfig);
_openedConfig = PageFileConfig.Default with { AllowMultiProcessAccess = true };
_openedConfig = PageFileConfig.Default with { AllowMultiProcessAccess = true, Access = access };
Comment on lines +265 to +271
}
else
{
Expand Down Expand Up @@ -308,7 +311,7 @@ private async Task TryOpen(string path, int presetValue, bool readOnly)
}
catch (Exception ex)
{
StatusMessage = $"Errore: {ex.Message}";
StatusMessage = $"Error: {ex.Message}";
IsDatabaseOpen = false;
}
}
Expand Down
6 changes: 3 additions & 3 deletions tools/BLite.Studio/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ private async void Browse_Click(object? sender, RoutedEventArgs e)

var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Apri database BLite",
Title = "Open BLite database",
AllowMultiple = false,
// Nessun filtro: qualsiasi estensione è accettata
// Keep broad filters so users can open existing files with custom extensions.
FileTypeFilter =
[
new FilePickerFileType("Database BLite") { Patterns = ["*.db", "*.blite", "*.blt"] },
new FilePickerFileType("Tutti i file") { Patterns = ["*.*"] },
new FilePickerFileType("All files") { Patterns = ["*.*"] },
]
});

Expand Down
Loading