Shared State Snapshots
Capture and restore a shared in-memory object with Export-KrSharedState, Import-KrSharedState, and Use-KrLock.
Prerequisites: see Introduction.
Full source
File: pwsh/tutorial/examples/4.3-Shared-State-Snapshots.ps1
<#
Sample Kestrun server demonstrating shared-state snapshot export/import.
This example shows how to snapshot and restore a thread-safe shared
state object while using a named lock for grouped updates.
FileName: 4.3-Shared-State-Snapshots.ps1
#>
param(
[int]$Port = $env:PORT ?? 5000
)
Initialize-KrRoot -Path $PSScriptRoot
New-KrServer -Name 'Shared State Snapshot Server'
Add-KrEndpoint -Port $Port
$snapshotLockKey = 'tutorial:shared-state-snapshot'
Set-KrSharedState -Name 'AppState' -Value @{
VisitCount = 0
Notes = @()
LastUpdated = (Get-Date).ToUniversalTime()
} -ThreadSafe
Enable-KrConfiguration
Add-KrMapRoute -Verbs Get -Pattern '/state' -ScriptBlock {
$response = Use-KrLock -Key $snapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
@{
visitCount = [int]$state.VisitCount
notes = @($state.Notes)
lastUpdated = [datetime]$state.LastUpdated
snapshotLockKey = $snapshotLockKey
}
}
Write-KrJsonResponse -InputObject $response -StatusCode 200
}
Add-KrMapRoute -Verbs Post -Pattern '/visit' -ScriptBlock {
$response = Use-KrLock -Key $snapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
$null = ($state.VisitCount = [int]$state.VisitCount + 1)
$null = ($state.LastUpdated = (Get-Date).ToUniversalTime())
return @{
message = 'Visit recorded'
visitCount = [int]$state.VisitCount
notes = @($state.Notes)
}
}
Write-KrJsonResponse -InputObject $response -StatusCode 200
}
Add-KrMapRoute -Verbs Post -Pattern '/note' -ScriptBlock {
$body = Get-KrRequestBody
$note = [string]$body.note
if ([string]::IsNullOrWhiteSpace($note)) {
Write-KrJsonResponse -InputObject @{ error = 'note is required' } -StatusCode 400
return
}
$response = Use-KrLock -Key $snapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
$null = ($state.Notes = @($state.Notes) + $note)
$null = ($state.LastUpdated = (Get-Date).ToUniversalTime())
return @{
message = 'Note added'
visitCount = [int]$state.VisitCount
notes = @($state.Notes)
}
}
Write-KrJsonResponse -InputObject $response -StatusCode 200
}
Add-KrMapRoute -Verbs Post -Pattern '/snapshot/export' -ScriptBlock {
$snapshotData = Use-KrLock -Key $snapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
$snapshotState = [pscustomobject]@{
VisitCount = [int]$state.VisitCount
Notes = @($state.Notes)
LastUpdated = [datetime]$state.LastUpdated
}
@{
snapshot = Export-KrSharedState -InputObject $snapshotState
visitCount = $snapshotState.VisitCount
noteCount = @($snapshotState.Notes).Count
}
}
Write-KrJsonResponse -InputObject @{
snapshot = $snapshotData.snapshot
visitCount = $snapshotData.visitCount
noteCount = $snapshotData.noteCount
exportedAt = (Get-Date).ToUniversalTime()
} -StatusCode 200
}
Add-KrMapRoute -Verbs Post -Pattern '/snapshot/reset' -ScriptBlock {
$response = Use-KrLock -Key $snapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
$null = ($state.VisitCount = 0)
$null = ($state.Notes = @())
$null = ($state.LastUpdated = (Get-Date).ToUniversalTime())
return @{
message = 'State reset'
visitCount = 0
notes = @()
}
}
Write-KrJsonResponse -InputObject $response -StatusCode 200
}
Add-KrMapRoute -Verbs Post -Pattern '/snapshot/import' -ScriptBlock {
$body = Get-KrRequestBody
$snapshot = [string]$body.snapshot
if ([string]::IsNullOrWhiteSpace($snapshot)) {
Write-KrJsonResponse -InputObject @{ error = 'snapshot is required' } -StatusCode 400
return
}
$restored = Import-KrSharedState -InputString $snapshot
$response = Use-KrLock -Key $snapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
$null = ($state.VisitCount = [int]$restored.VisitCount)
$null = ($state.Notes = if ($null -eq $restored.Notes) { @() } else { @($restored.Notes) })
$null = ($state.LastUpdated = [datetime]$restored.LastUpdated)
return @{
message = 'Snapshot restored'
visitCount = [int]$state.VisitCount
notes = @($state.Notes)
}
}
Write-KrJsonResponse -InputObject $response -StatusCode 200
}
Start-KrServer
Step-by-step
- Initialize root:
Initialize-KrRoot -Path $PSScriptRootkeeps file resolution and generated content predictable. - Build the host:
New-KrServerandAdd-KrEndpointexpose a simple API on the selected port. - Define one lock key:
$snapshotLockKey = 'tutorial:shared-state-snapshot'gives every grouped update the same synchronization boundary. - Seed the shared object:
Set-KrSharedState -Name 'AppState' -Value @{ ... } -ThreadSafecreates a mutable document withVisitCount,Notes, andLastUpdated. - Keep critical sections small:
/state,/visit,/note, and/snapshot/resetuseUse-KrLockonly while reading or mutating the shared object, then write the HTTP response after the lock is released. - Export a snapshot:
/snapshot/exporttakes a copy of the current state inside the lock, then callsExport-KrSharedStateto return the serialized XML snapshot. - Restore a snapshot:
/snapshot/importvalidates the request body, deserializes the snapshot withImport-KrSharedState, then applies the restored values inside the same named lock. - Verify the round trip: modify the state, export it, reset it, then import the snapshot and confirm the original values return.
Behavior contract
- Inputs:
POST /visit,POST /note,POST /snapshot/export,POST /snapshot/reset, andPOST /snapshot/import. - Outputs: JSON responses with the current visit count, note list, and serialized snapshot payload.
- Synchronization: the same named lock key protects consistent reads and grouped updates to the shared
AppStateobject.
Why this stays simple
This tutorial follows the same style as the rest of the PowerShell samples: shared state is created with Set-KrSharedState, routes use public cmdlets, and the locking story stays at the PowerShell level.
- Use
Set-KrSharedStatefor the shared document. - Use
Use-KrLockwhen a route needs to read or update multiple properties together. - Use
Export-KrSharedStateandImport-KrSharedStatefor snapshot serialization.
That matches the guidance in the shared-state guide: atomic helpers are great for single counters, but named locks are clearer when a route updates several related fields like VisitCount, Notes, and LastUpdated together.
Why not increment directly?
This sample intentionally uses a named lock instead of a raw increment because each write updates more than one property.
Use-KrLock -Key $SnapshotLockKey -ScriptBlock {
$state = Get-KrSharedState -Name 'AppState'
$state.VisitCount = [int]$state.VisitCount + 1
$state.LastUpdated = (Get-Date).ToUniversalTime()
}
That gives you one clear critical section instead of mixing manual locking, direct registry calls, and custom helper functions.
Try it
Run the sample:
pwsh .\docs\pwsh\tutorial\examples\4.3-Shared-State-Snapshots.ps1
Then exercise the snapshot flow.
1. Seed some state
PowerShell:
Invoke-RestMethod http://127.0.0.1:5000/visit -Method Post
Invoke-RestMethod http://127.0.0.1:5000/visit -Method Post
Invoke-RestMethod http://127.0.0.1:5000/note -Method Post -ContentType 'application/json' -Body (@{ note = 'remember helmet' } | ConvertTo-Json)
Invoke-RestMethod http://127.0.0.1:5000/state -Method Get
curl:
curl -X POST http://127.0.0.1:5000/visit
curl -X POST http://127.0.0.1:5000/visit
curl -X POST http://127.0.0.1:5000/note -H 'Content-Type: application/json' -d '{"note":"remember helmet"}'
curl http://127.0.0.1:5000/state
Expected state:
{
"visitCount": 2,
"notes": [
"remember helmet"
]
}
2. Export a snapshot, reset, and restore it
$export = Invoke-RestMethod http://127.0.0.1:5000/snapshot/export -Method Post
Invoke-RestMethod http://127.0.0.1:5000/snapshot/reset -Method Post
Invoke-RestMethod http://127.0.0.1:5000/state -Method Get
$body = @{ snapshot = $export.snapshot } | ConvertTo-Json -Depth 5
Invoke-RestMethod http://127.0.0.1:5000/snapshot/import -Method Post -ContentType 'application/json' -Body $body
Invoke-RestMethod http://127.0.0.1:5000/state -Method Get
The first /state call after reset should show zero visits and no notes. The final /state call should restore the exported values.
When to snapshot shared state
- Persist a small in-memory cache before recycling a host.
- Capture a support bundle that reproduces a test scenario.
- Reset a demo environment and then quickly restore it to a known baseline.
If the data must survive process restarts permanently, a database or file-backed store is still the right long-term solution. Snapshot export/import is best for operational handoff and controlled recovery flows.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Import returns 400 | Request body does not contain snapshot | Send JSON like @{ snapshot = $export.snapshot } \| ConvertTo-Json |
| Restored state is stale | Snapshot was exported before the latest mutation | Export a fresh snapshot after the writes you care about |
| Requests appear serialized | The sample uses a single named lock on purpose | Keep the critical section small or split state across multiple lock keys |
References
- Set-KrSharedState
- Get-KrSharedState
- Export-KrSharedState
- Import-KrSharedState
- Use-KrLock
- Enable-KrConfiguration
- Add-KrMapRoute
- New-KrServer
- Start-KrServer
- Shared State & Runspaces Guide
Previous / Next
Previous: Shared State Next: Logging