-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Make Out-Default -Transcript more robust in how it handle TranscribeOnly state
#3436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| using System.Globalization; | ||
| using System.Management.Automation.Runspaces; | ||
| using Microsoft.PowerShell.Commands; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace System.Management.Automation.Host | ||
|
|
@@ -390,7 +391,29 @@ internal void IgnoreCommand(string commandText, InvocationInfo invocation) | |
| /// so that when content is sent through Out-Default it doesn't | ||
| /// make it to the actual host. | ||
| /// </summary> | ||
| internal bool TranscribeOnly { get; set; } | ||
| internal bool TranscribeOnly => Interlocked.CompareExchange(ref _transcribeOnlyCount, 0, 0) != 0; | ||
| private int _transcribeOnlyCount = 0; | ||
| internal IDisposable SetTranscribeOnly() => new TranscribeOnlyCookie(this); | ||
| private sealed class TranscribeOnlyCookie : IDisposable | ||
| { | ||
| private PSHostUserInterface _ui; | ||
| private bool _disposed = false; | ||
| public TranscribeOnlyCookie(PSHostUserInterface ui) | ||
| { | ||
| _ui=ui; | ||
| Interlocked.Increment(ref _ui._transcribeOnlyCount); | ||
| } | ||
| public void Dispose() | ||
| { | ||
| if (!_disposed) | ||
| { | ||
| Interlocked.Decrement(ref _ui._transcribeOnlyCount); | ||
| _disposed = true; | ||
| GC.SuppressFinalize(this); | ||
| } | ||
| } | ||
| ~TranscribeOnlyCookie() => Dispose(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we want a destructor. They are never deterministic - and I think the semantics of transcription should be deterministic.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not like to use destructor either, but the choice is to deterministic lock in |
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Flag to determine whether the host is transcribing. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| Describe "Out-Default Tests" -tag CI { | ||
| BeforeAll { | ||
| # due to https://github.com/PowerShell/PowerShell/issues/3405, `Out-Default -Transcript` emits output to pipeline | ||
| # as running in Pester effectively wraps everything in parenthesis, workaround is to use another powershell | ||
| # to run the test script passed as a string | ||
| $powershell = "$PSHOME/powershell" | ||
| } | ||
|
|
||
| It "'Out-Default -Transcript' shows up in transcript, but not host" { | ||
| $script = @" | ||
| `$null = Start-Transcript -Path "$testdrive\transcript.txt"; | ||
| 'hello' | Microsoft.PowerShell.Core\Out-Default -Transcript; | ||
| 'bye'; | ||
| `$null = Stop-Transcript | ||
| "@ | ||
|
|
||
| & $powershell -c $script | Should BeExactly 'bye' | ||
| "TestDrive:\transcript.txt" | Should Contain 'hello' | ||
| } | ||
|
|
||
| It "Out-Default reverts transcription state when used more than once in a pipeline" { | ||
| & $powershell -c "Out-Default -Transcript | Out-Default -Transcript; 'Hello'" | Should BeExactly "Hello" | ||
| } | ||
|
|
||
| It "Out-Default reverts transcription state when exception occurs in pipeline" { | ||
| & $powershell -c "try { & { throw } | Out-Default -Transcript } catch {}; 'Hello'" | Should BeExactly "Hello" | ||
| } | ||
|
|
||
| It "Out-Default reverts transcription state even if Dispose() isn't called" { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this just a programmer error to not Dispose? I don't think we're wrapping a managed resource, and the fact that you need to call
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But, how to reliable call New-Item Function:Out-Default -Value ([System.Management.Automation.ProxyCommand]::Create((Get-Command Out-Default)))Then someone throw exception in pipeline with & {throw} | Out-Default -TranscriptAnd
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, but a finally block is guaranteed to be called, so it could go in the end block with an empty try.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I guess that's the real problem - we don't expose the If that was possible, then how do you feel about adding a destructor?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then it would be just a programmer error to not Dispose. |
||
| $script = @" | ||
| `$sp = {Out-Default -Transcript}.GetSteppablePipeline(); | ||
| `$sp.Begin(`$false); | ||
| `$sp = `$null; | ||
| [GC]::Collect(); | ||
| [GC]::WaitForPendingFinalizers(); | ||
| 'hello' | ||
| "@ | ||
| & $powershell -c $script | Should BeExactly 'hello' | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty confusing for a getter. A getter shouldn't normally change the value (which this doesn't, but when I read
CompareExchange, I assume it does and need to read the code more closely).If you're worried about atomic reads - that's a non-issue, integers are always read atomically on any platform we'll target.
If you're worried about needing a memory barrier, I think you need volatile on the field and use
Thread.VolatileRead.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was worried about stale read, so I wanted to read fresh value of variable. But given that stale read can only occur if destructor kick in other thread, which is already nondeterministic, it should be fine with plain read or volatile one.