Skip to content

Commit

Permalink
(GH-10818) Replace NoRunspaceAffinity docs (#10819)
Browse files Browse the repository at this point in the history
In #10615, the section for the `NoRunspaceAfinnity` attribute was
erroneously deleted as part of the reorganization of the
about_Classes article.

This change:

- Adds back the documentation and examples for the `NoRunspaceAffinity`
  to the 7.4 and 7.5 versions of about_Classes, as well as a note about
  the default behavior  in the general limitations section.
- Adds a section and example showing the behavior for corrupted runspaces
  due to class runspace affinity in the 7.2 and 7.3 versions, and updates
  the limitations section.
- Resolves #10818
- Fixes AB#202807
  • Loading branch information
michaeltlombardi committed Jan 23, 2024
1 parent ad92a6b commit c364721
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 4 deletions.
54 changes: 53 additions & 1 deletion reference/7.2/Microsoft.PowerShell.Core/About/about_Classes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Describes how you can use classes to create your own custom types.
Locale: en-US
ms.date: 01/19/2024
ms.date: 01/23/2024
online version: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.2&WT.mc_id=ps-gethelp
schema: 2.0.0
title: about Classes
Expand Down Expand Up @@ -359,6 +359,37 @@ Line |
| Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
```

### Example 4 - Parallel execution corrupting a runspace

The `ShowRunspaceId()` method of `[UnsafeClass]` reports different thread Ids
but the same runspace ID. Eventually, the session state is corrupted causing
an error, such as `Global scope cannot be removed`.

```powershell
# Class definition with Runspace affinity (default behavior)
class UnsafeClass {
static [object] ShowRunspaceId($val) {
return [PSCustomObject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$unsafe = [UnsafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($using:unsafe)::ShowRunspaceId($_)
}
}
```

> [!NOTE]
> This example runs in an infinite loop. Enter <kbd>Ctrl</kbd>+<kbd>C</kbd> to
> stop the execution.
## Class properties

Properties are variables declared in the class scope. A property can be of any
Expand Down Expand Up @@ -452,6 +483,21 @@ For more information about deriving classes that inherit from a base class or
implement interfaces, see
[about_Classes_Inheritance][11].

## Runspace affinity

A runspace is the operating environment for the commands invoked by PowerShell.
This environment includes the commands and data that are currently present, and
any language restrictions that currently apply.

A PowerShell class is affiliated with the **Runspace** where it's
created. Using a PowerShell class in `ForEach-Object -Parallel` isn't safe.
Method invocations on the class are marshalled back to the **Runspace** where
it was created, which can corrupt the state of the **Runspace** or cause a
deadlock.

For an illustration of a how runspace affinity can cause errors, see
[Example 4](#example-4---parallel-execution-corrupting-a-runspace).

## Exporting classes with type accelerators

By default, PowerShell modules don't automatically export classes and
Expand Down Expand Up @@ -585,6 +631,12 @@ workaround for those limitations, if any.
- The `hidden` and `static` keywords only apply to class members, not a class
definition.

Workaround: None.
- PowerShell classes aren't safe to use in parallel execution across runspaces.
When you Invoke methods on a class, PowerShell marshalls the invocations back
to the **Runspace** where the class was created, which can corrupt the state
of the **Runspace** or cause a deadlock.

Workaround: None.

### Constructor limitations
Expand Down
54 changes: 53 additions & 1 deletion reference/7.3/Microsoft.PowerShell.Core/About/about_Classes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Describes how you can use classes to create your own custom types.
Locale: en-US
ms.date: 01/19/2024
ms.date: 01/23/2024
online version: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.3&WT.mc_id=ps-gethelp
schema: 2.0.0
title: about Classes
Expand Down Expand Up @@ -359,6 +359,37 @@ Line |
| Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
```

### Example 4 - Parallel execution corrupting a runspace

The `ShowRunspaceId()` method of `[UnsafeClass]` reports different thread Ids
but the same runspace ID. Eventually, the session state is corrupted causing
an error, such as `Global scope cannot be removed`.

```powershell
# Class definition with Runspace affinity (default behavior)
class UnsafeClass {
static [object] ShowRunspaceId($val) {
return [PSCustomObject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$unsafe = [UnsafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($using:unsafe)::ShowRunspaceId($_)
}
}
```

> [!NOTE]
> This example runs in an infinite loop. Enter <kbd>Ctrl</kbd>+<kbd>C</kbd> to
> stop the execution.
## Class properties

Properties are variables declared in the class scope. A property can be of any
Expand Down Expand Up @@ -452,6 +483,21 @@ For more information about deriving classes that inherit from a base class or
implement interfaces, see
[about_Classes_Inheritance][11].

## Runspace affinity

A runspace is the operating environment for the commands invoked by PowerShell.
This environment includes the commands and data that are currently present, and
any language restrictions that currently apply.

A PowerShell class is affiliated with the **Runspace** where it's
created. Using a PowerShell class in `ForEach-Object -Parallel` isn't safe.
Method invocations on the class are marshalled back to the **Runspace** where
it was created, which can corrupt the state of the **Runspace** or cause a
deadlock.

For an illustration of a how runspace affinity can cause errors, see
[Example 4](#example-4---parallel-execution-corrupting-a-runspace).

## Exporting classes with type accelerators

By default, PowerShell modules don't automatically export classes and
Expand Down Expand Up @@ -585,6 +631,12 @@ workaround for those limitations, if any.
- The `hidden` and `static` keywords only apply to class members, not a class
definition.

Workaround: None.
- PowerShell classes aren't safe to use in parallel execution across runspaces.
When you Invoke methods on a class, PowerShell marshalls the invocations back
to the **Runspace** where the class was created, which can corrupt the state
of the **Runspace** or cause a deadlock.

Workaround: None.

### Constructor limitations
Expand Down
91 changes: 90 additions & 1 deletion reference/7.4/Microsoft.PowerShell.Core/About/about_Classes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Describes how you can use classes to create your own custom types.
Locale: en-US
ms.date: 01/19/2024
ms.date: 01/23/2024
online version: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.4&WT.mc_id=ps-gethelp
schema: 2.0.0
title: about Classes
Expand Down Expand Up @@ -359,6 +359,66 @@ Line |
| Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
```

### Example 4 - Class definition with and without Runspace affinity

The `ShowRunspaceId()` method of `[UnsafeClass]` reports different thread Ids
but the same runspace ID. Eventually, the session state is corrupted causing
an error, such as `Global scope cannot be removed`.

```powershell
# Class definition with Runspace affinity (default behavior)
class UnsafeClass {
static [object] ShowRunspaceId($val) {
return [PSCustomObject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$unsafe = [UnsafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($using:unsafe)::ShowRunspaceId($_)
}
}
```

> [!NOTE]
> This example runs in an infinite loop. Enter <kbd>Ctrl</kbd>+<kbd>C</kbd> to
> stop the execution.
The `ShowRunspaceId()` method of `[SafeClass]` reports different thread and
Runspace ids.

```powershell
# Class definition with NoRunspaceAffinity attribute
[NoRunspaceAffinity()]
class SafeClass {
static [object] ShowRunspaceId($val) {
return [PSCustomObject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$safe = [SafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($using:safe)::ShowRunspaceId($_)
}
}
```

> [!NOTE]
> This example runs in an infinite loop. Enter <kbd>Ctrl</kbd>+<kbd>C</kbd> to
> stop the execution.
## Class properties

Properties are variables declared in the class scope. A property can be of any
Expand Down Expand Up @@ -452,6 +512,29 @@ For more information about deriving classes that inherit from a base class or
implement interfaces, see
[about_Classes_Inheritance][11].

## NoRunspaceAffinity attribute

A runspace is the operating environment for the commands invoked by PowerShell.
This environment includes the commands and data that are currently present, and
any language restrictions that currently apply.

By default, a PowerShell class is affiliated with the **Runspace** where it's
created. Using a PowerShell class in `ForEach-Object -Parallel` isn't safe.
Method invocations on the class are marshalled back to the **Runspace** where
it was created, which can corrupt the state of the **Runspace** or cause a
deadlock.

Adding the `NoRunspaceAffinity` attribute to the class definition ensures that
the PowerShell class isn't affiliated with a particular runspace. Method
invocations, both instance and static, use the **Runspace** of the running
thread and the thread's current session state.

The attribute was added in PowerShell 7.4.

For an illustration of the difference in behavior for classes with and without
the `NoRunspaceAffinity` attribute, see
[Example 4](#example-4---class-definition-with-and-without-runspace-affinity).

## Exporting classes with type accelerators

By default, PowerShell modules don't automatically export classes and
Expand Down Expand Up @@ -586,6 +669,12 @@ workaround for those limitations, if any.
definition.

Workaround: None.
- By default, PowerShell classes aren't safe to use in parallel execution
across runspaces. When you Invoke methods on a class, PowerShell marshalls
the invocations back to the **Runspace** where the class was created, which
can corrupt the state of the **Runspace** or cause a deadlock.

Workaround: Add the `NoRunspaceAffinity` attribute to the class declaration.

### Constructor limitations

Expand Down
Loading

0 comments on commit c364721

Please sign in to comment.