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

  1. Declare shared variables ($Visits, $Delay) before configuration is enabled.
  2. Register routes whose script blocks reference $Visits and $Delay directly.
  3. Call Enable-KrConfiguration to freeze the configuration.
  4. 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


Next

Previous: Adding a Favicon
Next: Logging