Using Shared Variables Across Routes
Share in-memory state (counters, caches, configuration) by defining variables before Enable-KrConfiguration
so route script blocks can reference them at execution time.
Prerequisites: see Introduction. Ensure
Add-KrPowerShellRuntime
is used so PowerShell routes execute.
Full source
File: docs/pwsh/tutorial/examples/4.1-Shared-Variables.ps1
<#
Sample Kestrun Server on how to use shared variables.
This example demonstrates how to use shared variables in a Kestrun server.
FileName: 4.1-Shared-Variables.ps1
#>
# Import the Kestrun module
Install-PSResource -Name Kestrun
# Initialize Kestrun root directory
# the default value is $PWD
# This is recommended in order to use relative paths without issues
Initialize-KrRoot -Path $PSScriptRoot
# Create a new Kestrun server
New-KrServer -Name "Simple Server"
# Add a listener on port 5000 and IP address 127.0.0.1 (localhost)
Add-KrListener -Port 5000 -IPAddress ([IPAddress]::Loopback)
# Add the PowerShell runtime
# !!!!Important!!!! this step is required to process PowerShell routes and middlewares
Add-KrPowerShellRuntime
# Shared variables
$Visits = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
# Shared variable for delay to simulate processing time and force the use of multiple runspaces
$Delay = 1
# Enable Kestrun configuration
Enable-KrConfiguration
# Show the current visit count
Add-KrMapRoute -Server $server -Verbs Get -Pattern '/show' -ScriptBlock {
# $Visits is available
Write-KrTextResponse -InputObject "[Runspace: $([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.Name)] Visits so far: $($Visits.Count)" -StatusCode 200
}
# Route: GET /visit
Add-KrMapRoute -Server $server -Verbs Get -Pattern '/visit' -ScriptBlock {
# Simulate some delay
Start-Sleep -Seconds $Delay
# increment the injected variable
$Visits.AddOrUpdate("Count", 1, { param($k, $v) $v + 1 })
Write-KrTextResponse -InputObject "[Runspace: $([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.Name)] Incremented to $($Visits.Count)" -StatusCode 200
}
# Start the server asynchronously
Start-KrServer
Step-by-step
- Declare shared variables (
$Visits
,$Delay
) before configuration is enabled. - Register routes whose script blocks reference
$Visits
and$Delay
directly. - Call
Enable-KrConfiguration
to freeze the configuration. - Start the server; multiple concurrent requests share the same objects.
How sharing works
Route script blocks capture the parent (script) scope at registration time. Variables created before Enable-KrConfiguration
are available inside each runspace used to handle requests. Mutable reference types remain shared — all routes see the same underlying instance.
Choose thread-safe types
Use ConcurrentDictionary
(or other thread-safe collections) for shared mutations. Avoid plain hashtables for concurrent writes. Primitive value types (like an [int]
variable) are copied into each runspace rather than shared by reference, so incrementing a plain $Counter
won’t reflect across runspaces. Wrap numeric state in a shared mutable object (e.g., store under a key in a ConcurrentDictionary
) if it must be updated concurrently.
Access patterns
# Read current value
$count = $Visits.Count
# Naive increment (ok for demo, not atomic for mixed operations):
$Visits.Count++
# Keyed counter (atomic style)
$null = $Visits.AddOrUpdate('total', 1, { param($k,$v) $v + 1 })
# Atomic increment loop ("clunky" style)
do {
$oldVal = $Visits['total']
$newVal = $oldVal + 1
} while (-not $Visits.TryUpdate('total', $newVal, $oldVal))
Common pitfalls
Pitfall | Symptom | Fix |
---|---|---|
Plain hashtable for writes | Lost / inconsistent updates | Use ConcurrentDictionary or locking |
Variable defined too late | $null inside routes | Move declaration before Enable-KrConfiguration |
Variable name shadowed | Local variable with same name | Use a different local name or explicit scope |
Heavy synchronous logic | Slow responses | Offload to background job / queue |
Concurrency test (optional)
1..10 | ForEach-Object { Start-Job { curl http://127.0.0.1:5000/visit } } | Receive-Job -Wait -AutoRemoveJob
curl http://127.0.0.1:5000/show
Advanced: custom services
You can assign a custom class instance (cache, metrics aggregator, etc.) to a variable pre-configuration and expose its methods through routes. Ensure internal synchronization if it mutates shared state.
Best practices
- Keep state minimal; externalize durable data.
- Prefer thread-safe / immutable structures.
- Validate inputs before mutating shared objects.
- Avoid large synchronized regions; keep per-request critical work small.
Troubleshooting
Symptom | Cause | Resolution |
---|---|---|
Variable is $null | Declared after configuration | Declare earlier |
Inconsistent data | Non-thread-safe structure | Use thread-safe type |
High CPU | Busy loop / heavy work | Throttle or move off request path |
Memory growth | Unbounded keys | Evict or cap size |
Cmdlet references
- Add-KrPowerShellRuntime
- Add-KrMapRoute
- Enable-KrConfiguration
- New-KrServer
- Add-KrListener
- Start-KrServer
Next
Previous: Adding a Favicon
Next: Logging