Shared State
Most things in Pode run in isolated runspaces: Routes, Middleware, Schedules - to name a few. This means you can't create a variable in a Timer, and then access that variable in a Route. To overcome this limitation you can use the Shared State feature within Pode, which allows you to set/get variables on a state shared between all runspaces. This lets you can create a variable in a Timer and store it within the shared state; then you can retrieve the variable from the state in a Route.
You also have the option of saving the current state to a file, and then restoring the state back on server start. This way you won't lose state between server restarts.
You can also use the State in combination with Lock-PodeObject to ensure thread safety - if needed.
Tip
From Pode v2.14.0 the State is now a thread-safe Concurrent Dictionary, which means in the vast majority of cases you won't need to use Lock-PodeObject when setting and retrieving State values.
Usage
Where possible use the same casing for the -Name of state keys. When using Restore-PodeState the state will become case-sensitive due to the nature of how ConvertFrom-Json works.
Set
The Set-PodeState function will create/update a variable in the state. You need to supply a name and a value to set on the state, there's also an optional scope that can be supplied - which lets you save specific state objects with a certain scope.
An example of setting a hashtable variable in the state is as follows:
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
$value = Set-PodeState -Name 'data' -Value (New-PodeStateDictionary -Data @{ Name = 'Rick Sanchez' })
}
}
Alternatively you could use the $state: variable scope to set a variable in state. This variable will be scope-less, so if you need scope then use Set-PodeState. $state: can be used anywhere, but keep in mind that like $session: Pode can only remap the this in scriptblocks it's aware of; so using it in a function of a custom module won't work. Similar to the example above:
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
$state:data = New-PodeStateDictionary -Data @{ Name = 'Rick Sanchez' }
}
}
Get
The Get-PodeState function will return the value currently stored in the state for a variable. If the variable doesn't exist then $null is returned.
An example of retrieving a value from the state is as follows:
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
$value = Get-PodeState -Name 'data'
}
}
Alternatively you could use the $state: variable scope to get a variable in state. $state: can be used anywhere, but keep in mind that like $session: Pode can only remap the this in scriptblocks it's aware of; so using it in a function of a custom module won't work. Similar to the example above:
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
$value = $state:data
}
}
Remove
The Remove-PodeState function will remove a variable from the state. It will also return the value stored in the state before removing the variable.
An example of removing a variable from the state is as follows:
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
Remove-PodeState -Name 'data' -NoPassThru
}
}
Save
The Save-PodeState function will save the current state, as JSON, to the specified file. The file path can either be relative, or literal. When saving the state, it's recommended to wrap the function within Lock-PodeObject.
An example of saving the current state every hour is as follows:
Start-PodeServer {
Add-PodeSchedule -Name 'save-state' -Cron '@hourly' -ScriptBlock {
Lock-PodeObject -ScriptBlock {
Save-PodeState -Path './state.json'
}
}
}
When saving the state, you can also use the -Exclude or -Include parameters to exclude/include certain state objects from being saved. Saving also has a -Scope parameter, which allows you so save only state objects created with the specified scope(s).
You can use all the above 3 parameter in conjunction, with -Exclude having the highest precedence and -Scope having the lowest.
By default the JSON will be saved expanded, but you can saved the JSON as compressed by supplying the -Compress switch.
Restore
The Restore-PodeState function will restore the current state from the specified file. The file path can either be relative, or a literal path. if you're restoring the state immediately on server start, you don't need to use Lock-PodeObject.
An example of restore the current state on server start is as follows:
Start-PodeServer {
Restore-PodeState './state.json'
}
By default, restoring from a state file will overwrite the current state. You can change this so the restored state is merged instead by using the -Merge switch. (Note: if you restore a key that already exists in state, this will still overwrite that key).
Thread Safety
From Pode v2.14.0 State will be a Concurrent Dictionary for improved thread-safety, reducing the need to use Lock-PodeObject. Before this version you will need to use Lock-PodeObject when setting/retrieving State values.
Additionally, there are also 5 helper functions for creating thread-safe collections for use with State:
| Function | Type |
|---|---|
New-PodeStateDictionary |
ConcurrentDictionary[string, object] |
New-PodeStateOrderedDictionary |
PodeOrderedConcurrentDictionary[string, object] |
New-PodeStateBag |
ConcurrentBag[object] |
New-PodeStateSet |
PodeConcurrentSet[object] |
New-PodeStateList |
PodeConcurrentList[object] |
Each of the above classes has the following methods:
TryAdd(<item>)- returns a boolean of whether the item was addedTryRemove(<item>, [ref])- returns a boolean of whether the item was removed, and a [ref] of the removed item
Full Example
The following is a full example of using the State functions. It is a simple Timer that creates and updates a hashtable variable, and then a Route is used to retrieve that variable. There is also another route that will remove the variable from the state. The state is also saved on every iteration of the timer, and restored on server start:
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
# create the shared variable
Set-PodeState -Name 'hash' -Value @{ 'values' = @(); } | Out-Null
# attempt to re-initialise the state (will do nothing if the file doesn't exist)
Restore-PodeState -Path './state.json'
# timer to add a random number to the shared state
Add-PodeTimer -Name 'forever' -Interval 2 -ScriptBlock {
# ensure we're thread safe
Lock-PodeObject -ScriptBlock {
# attempt to get the hashtable from the state
$hash = (Get-PodeState -Name 'hash')
# add a random number
$hash.values += (Get-Random -Minimum 0 -Maximum 10)
# save the state to file
Save-PodeState -Path './state.json'
}
}
# route to return the value of the hashtable from shared state
Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
# again, ensure we're thread safe
Lock-PodeObject -ScriptBlock {
# get the hashtable from the state and return it
$hash = (Get-PodeState -Name 'hash')
Write-PodeJsonResponse -Value $hash
}
}
# route to remove the hashtable from shared state
Add-PodeRoute -Method Delete -Path '/' -ScriptBlock {
# ensure we're thread safe
Lock-PodeObject -ScriptBlock {
# remove the hashtable from the state
Remove-PodeState -Name 'hash' | Out-Null
}
}
}