Skip to content

Commit d41775c

Browse files
1.13.418
1 parent 0722c2d commit d41775c

File tree

9 files changed

+366
-2
lines changed

9 files changed

+366
-2
lines changed

PSFramework/PSFramework.psd1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
RootModule = 'PSFramework.psm1'
55

66
# Version number of this module.
7-
ModuleVersion = '1.13.416'
7+
ModuleVersion = '1.13.418'
88

99
# ID used to uniquely identify this module
1010
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'
@@ -76,6 +76,7 @@
7676
'Get-PSFPipeline'
7777
'Get-PSFResultCache'
7878
'Get-PSFRunspace'
79+
'Get-PSFRunspaceLock'
7980
'Get-PSFRunspaceWorkflow'
8081
'Get-PSFRunspaceWorker'
8182
'Get-PSFScriptblock'

PSFramework/bin/PSFramework.dll

1 KB
Binary file not shown.

PSFramework/bin/PSFramework.pdb

4 KB
Binary file not shown.

PSFramework/bin/PSFramework.xml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8573,6 +8573,98 @@
85738573
Purge all RBVs of datasets from all expired runspaces
85748574
</summary>
85758575
</member>
8576+
<member name="F:PSFramework.Runspace.RunspaceHost.Locks">
8577+
<summary>
8578+
List of runspace locks available across the process
8579+
</summary>
8580+
</member>
8581+
<member name="M:PSFramework.Runspace.RunspaceHost.GetRunspaceLock(System.String)">
8582+
<summary>
8583+
Retrieve a named runspace lock, creating it first if necessary.
8584+
</summary>
8585+
<param name="Name">Name of the lock to create.</param>
8586+
<returns>The lock object</returns>
8587+
</member>
8588+
<member name="T:PSFramework.Runspace.RunspaceLock">
8589+
<summary>
8590+
Offers a cross-runspace locking mechanism to PowerShell
8591+
</summary>
8592+
</member>
8593+
<member name="F:PSFramework.Runspace.RunspaceLock.Name">
8594+
<summary>
8595+
The name of the lock.
8596+
Mostly academic, used to keep track of locks across the process.
8597+
</summary>
8598+
</member>
8599+
<member name="F:PSFramework.Runspace.RunspaceLock._Owner">
8600+
<summary>
8601+
Who the current owner is.
8602+
</summary>
8603+
</member>
8604+
<member name="F:PSFramework.Runspace.RunspaceLock._LockedTime">
8605+
<summary>
8606+
When the lock was last taken.
8607+
</summary>
8608+
</member>
8609+
<member name="P:PSFramework.Runspace.RunspaceLock.Owner">
8610+
<summary>
8611+
The current effective owning runspace.
8612+
</summary>
8613+
</member>
8614+
<member name="P:PSFramework.Runspace.RunspaceLock.CurrentID">
8615+
<summary>
8616+
The current runspace from the perspective of the caller.
8617+
</summary>
8618+
</member>
8619+
<member name="F:PSFramework.Runspace.RunspaceLock.MaxLockTime">
8620+
<summary>
8621+
The maximum time the lock can be held. Infinite if 0 or less.
8622+
</summary>
8623+
</member>
8624+
<member name="F:PSFramework.Runspace.RunspaceLock.Lock">
8625+
<summary>
8626+
The actual lock used to marshal access.
8627+
</summary>
8628+
</member>
8629+
<member name="M:PSFramework.Runspace.RunspaceLock.#ctor">
8630+
<summary>
8631+
Creates an empty runspace lock.
8632+
</summary>
8633+
</member>
8634+
<member name="M:PSFramework.Runspace.RunspaceLock.#ctor(System.String)">
8635+
<summary>
8636+
Creates a named runspace lock.
8637+
Names are used for multiple runspaces to access the same lock, if retrieved through the system.
8638+
</summary>
8639+
<param name="Name">The name to assign to the lock.</param>
8640+
</member>
8641+
<member name="M:PSFramework.Runspace.RunspaceLock.#ctor(System.String,System.Int32)">
8642+
<summary>
8643+
Creates a named runspace lock with a maximum lock time.
8644+
</summary>
8645+
<param name="Name">The name for the runspace lock.</param>
8646+
<param name="MaxLockTime">The maximum time (in ms) that the lock can be held.</param>
8647+
</member>
8648+
<member name="M:PSFramework.Runspace.RunspaceLock.Open">
8649+
<summary>
8650+
Attempt to reserve the lock with a 30 seconds timeout.
8651+
If the timeout expires, an error will be thrown.
8652+
</summary>
8653+
</member>
8654+
<member name="M:PSFramework.Runspace.RunspaceLock.Open(PSFramework.Parameter.TimeSpanParameter)">
8655+
<summary>
8656+
Attempte to reserve the lock with a specified timeout.
8657+
If the timeout expires, an error will be thrown.
8658+
</summary>
8659+
<param name="Timeout">The timeout until we give up achieving the lock</param>
8660+
<exception cref="T:System.TimeoutException">If we have to wait longer than we are willing to wait. Might happen, if some other runspace takes too long to release the lock.</exception>
8661+
</member>
8662+
<member name="M:PSFramework.Runspace.RunspaceLock.Close">
8663+
<summary>
8664+
Release the current runspace's control over this lock.
8665+
No action, if the current runspace does not control the lock.
8666+
</summary>
8667+
</member>
85768668
<member name="T:PSFramework.Runspace.RunspaceResult">
85778669
<summary>
85788670
The result of a runspace task

PSFramework/changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# CHANGELOG
22

3-
## ???
3+
## 1.13.418 (2025-11-17)
44

5+
- New: Get-PSFRunspaceLock - Create or retrieve a lock object for runspace use.
56
- Fix: New-PSFSupportPackage - debug dumps in task mode were not correctly rotated.
67

78
## 1.13.416 (2025-10-22)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
function Get-PSFRunspaceLock {
2+
<#
3+
.SYNOPSIS
4+
Create or retrieve a lock object for runspace use.
5+
6+
.DESCRIPTION
7+
Create or retrieve a lock object for runspace use.
8+
One of the fundamental features in multi-threading is the "lock":
9+
A language feature in many programming languages, that helps marshal access to a resource from multiple threads.
10+
The key goal here: Prevent concurrent access to a resources or process, that cannot be done concurrently.
11+
12+
PowerShell does not have such a feature.
13+
14+
This is where the RunspaceLock feature comes in:
15+
This command generates an object, that can take over the role of the lock-feature in a PowerShell environment!
16+
17+
First create a lock object:
18+
$lock = Get-PSFRunspaceLock -Name 'MyModule.Example'
19+
20+
Then you can obtain the lock calling the Open() method:
21+
$lock.Open()
22+
This will reserve the lock for the current runspace.
23+
If another runspace tries to also call Open(), they will be forced to wait until the current runspace releases the lock.
24+
25+
Finally, to release the lock, call the Close() method:
26+
$lock.Close()
27+
28+
Example implementation:
29+
$lock = Get-PSFRunspaceLock -Name 'MyModule.ExchangeConnect'
30+
$lock.Open()
31+
try { Connect-IPPSSession }
32+
finally { $lock.Close() }
33+
34+
This will guarantee, that only one runspace will call "Connect-IPPSSession" at a time, assuming all run this snippet.
35+
36+
.PARAMETER Name
37+
The name of the runspace-lock.
38+
No matter from which runspace, all instances using the same name utilize the same lock and can block each other.
39+
40+
.PARAMETER Timeout
41+
How long a lock is valid for at most.
42+
By default, a lock is valid for 30 seconds, after which it will expire and be released, in order to prevent permanent lockout / deadlock.
43+
Increase this if more time is needed, setting this to 0 or less will remove the timeout.
44+
45+
.PARAMETER Unmanaged
46+
By default, retrieving a lock with the same name will grant access to the exact same lock.
47+
This makes it easy to use in casual runspace scenarios:
48+
Simply call Get-PSFRunspaceLock in each runspace with the same name and you are good.
49+
By making the lock unmanaged, you remove it from this system - the lock-object will not be tracked by PSFramework,
50+
creating additional instances with the same name will NOT reference the same lock.
51+
In return, you can safely pass in the lock object to whatever runspace you want with a guarantee to not conflict with anything else.
52+
This parameter should generally not be needed for most scenarios.
53+
54+
.EXAMPLE
55+
PS C:\> $lock = Get-PSFRunspaceLock -Name 'MyModule.ExchangeConnect'
56+
57+
Creates a new lock object named 'MyModule.ExchangeConnect'
58+
59+
.EXAMPLE
60+
1..20 | Invoke-PSFRunspace {
61+
if (-not $global:connected) {
62+
$lock = Get-PSFRunspaceLock -Name MyModule.ExchangeConnect
63+
$lock.Open('5m')
64+
try { Connect-IPPSSession }
65+
finally { $lock.Close() }
66+
$global:connected = $true
67+
}
68+
Get-Label
69+
} -ThrottleLimit 4
70+
71+
In four background runspaces, it will savely connect to Purview and retrieve labels 20 times total, without getting into conflict.
72+
#>
73+
[OutputType([PSFramework.Runspace.RunspaceLock])]
74+
[CmdletBinding()]
75+
param (
76+
[Parameter(Mandatory = $true)]
77+
[string]
78+
$Name,
79+
80+
[PsfTimeSpan]
81+
$Timeout,
82+
83+
[switch]
84+
$Unmanaged
85+
)
86+
process {
87+
if ($Unmanaged) {
88+
$lock = [PSFramework.Runspace.RunspaceLock]::new($Name)
89+
}
90+
else {
91+
$lock = [PSFramework.Runspace.RunspaceHost]::GetRunspaceLock($Name)
92+
}
93+
if ($Timeout) {
94+
$lock.MaxLockTime = $Timeout.Value.TotalMilliseconds
95+
}
96+
$lock
97+
}
98+
}

library/PSFramework/PSFramework.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
<Compile Include="Runspace\RunspaceBoundValueGeneric.cs" />
219219
<Compile Include="Runspace\RunspaceContainer.cs" />
220220
<Compile Include="Runspace\RunspaceHost.cs" />
221+
<Compile Include="Runspace\RunspaceLock.cs" />
221222
<Compile Include="Runspace\RunspaceResult.cs" />
222223
<Compile Include="Runspace\RunspaceState.cs" />
223224
<Compile Include="Runspace\RunspaceTask.cs" />

library/PSFramework/Runspace/RunspaceHost.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public static int RbvCleanupInterval
3535
/// </summary>
3636
public static ConcurrentDictionary<string, RunspaceContainer> Runspaces = new ConcurrentDictionary<string, RunspaceContainer>(StringComparer.InvariantCultureIgnoreCase);
3737

38+
#region Runspace Bound Values
3839
/// <summary>
3940
/// List of all runspace bound values in use
4041
/// </summary>
@@ -84,5 +85,29 @@ public static void PurgeAllRunspaceBoundVariables()
8485
foreach (RunspaceBoundValue value in _RunspaceBoundValues)
8586
value.PurgeExpired();
8687
}
88+
#endregion Runspace Bound Values
89+
90+
#region Locks
91+
/// <summary>
92+
/// List of runspace locks available across the process
93+
/// </summary>
94+
private static ConcurrentDictionary<string, RunspaceLock> Locks = new ConcurrentDictionary<string, RunspaceLock>(StringComparer.InvariantCultureIgnoreCase);
95+
96+
private static object _Lock = 42;
97+
/// <summary>
98+
/// Retrieve a named runspace lock, creating it first if necessary.
99+
/// </summary>
100+
/// <param name="Name">Name of the lock to create.</param>
101+
/// <returns>The lock object</returns>
102+
public static RunspaceLock GetRunspaceLock(string Name)
103+
{
104+
lock (_Lock)
105+
{
106+
if (null == Locks[Name])
107+
Locks[Name] = new RunspaceLock(Name);
108+
}
109+
return Locks[Name];
110+
}
111+
#endregion Locks
87112
}
88113
}

0 commit comments

Comments
 (0)