Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Make Out-Default -Transcript more robust in how it handle TranscribeOnly state#3436

Merged
lzybkr merged 4 commits into
PowerShell:masterPowerShell/PowerShell:masterfrom
PetSerAl:transcribeonly-patchCopy head branch name to clipboard
Apr 1, 2017
Merged

Make Out-Default -Transcript more robust in how it handle TranscribeOnly state#3436
lzybkr merged 4 commits into
PowerShell:masterPowerShell/PowerShell:masterfrom
PetSerAl:transcribeonly-patchCopy head branch name to clipboard

Conversation

@PetSerAl

Copy link
Copy Markdown
Contributor

Addresses #3390

@msftclas

Copy link
Copy Markdown

@PetSerAl,
Thanks for having already signed the Contribution License Agreement. Your agreement was validated by Microsoft. We will now review your pull request.
Thanks,
Microsoft Pull Request Bot

@PetSerAl PetSerAl changed the title Make Out-Default -Transcript more robust in how it handle TranscriptOnly state Make Out-Default -Transcript more robust in how it handle TranscribeOnly state Mar 27, 2017
@SteveL-MSFT SteveL-MSFT requested a review from lzybkr March 27, 2017 23:46

private ArrayList _outVarResults = null;
private bool _savedTranscribeOnly = false;
private IDisposable transcribeOnlyCookie = null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should follow the convention in this file and call it _transcribeOnlyCookie

/// </summary>
internal bool TranscribeOnly { get; set; }
internal bool TranscribeOnly => Interlocked.CompareExchange(ref transcribeOnlyCount, 0, 0) != 0;
private int transcribeOnlyCount = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_transcribeOnlyCount to follow existing convention

private sealed class TranscribeOnlyCookie : IDisposable
{
private PSHostUserInterface ui;
private bool disposed = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_disposed to follow existing convention

internal IDisposable SetTranscribeOnly() => new TranscribeOnlyCookie(this);
private sealed class TranscribeOnlyCookie : IDisposable
{
private PSHostUserInterface ui;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ui to follow existing convention

/// make it to the actual host.
/// </summary>
internal bool TranscribeOnly { get; set; }
internal bool TranscribeOnly => Interlocked.CompareExchange(ref _transcribeOnlyCount, 0, 0) != 0;

Copy link
Copy Markdown
Contributor

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.

@PetSerAl PetSerAl Mar 29, 2017

Copy link
Copy Markdown
Contributor Author

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.

GC.SuppressFinalize(this);
}
}
~TranscribeOnlyCookie() => Dispose();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 TranscribeOnly mode or use destructor as safe net.

& $powershell -c "try { & { throw } | Out-Default -Transcript } catch {}; 'Hello'" | Should BeExactly "Hello"
}

It "Out-Default reverts transcription state even if Dispose() isn't called" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 WaitForPendingFinalizers to have a reliable test suggests to me that we don't really want this behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this just a programmer error to not Dispose?

But, how to reliable call Dispose from PowerShell? Assume I wrap Out-Default in SteppablePipeline to make a proxy command with customized behavior:

New-Item Function:Out-Default -Value ([System.Management.Automation.ProxyCommand]::Create((Get-Command Out-Default)))

Then someone throw exception in pipeline with Out-Default -Transcript:

& {throw} | Out-Default -Transcript

And Dispose of proxy command is not called, because PowerShell functions does not have a Dispose. As result Dispose of SteppablePipeline is not called, which lead to Dispose of wrapped Out-Default not called as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try { <#...#> } finally { $obj.Dispose() } is reliable in PowerShell, the Dispose call will happen even if you hit Ctrl+c to cancel the script.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try/finally can not span thru begin/process/end blocks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

@PetSerAl PetSerAl Mar 29, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But end block is not called as well. If exception occur before your command position in pipeline, then no block of your command is currently in call stack, thus no your try/finally blocks are in effect when exception occur.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 StopProcessing method in PSCmdlet so there is no way to reliable way to release resources in script.

If that was possible, then how do you feel about adding a destructor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it would be just a programmer error to not Dispose.

@lzybkr lzybkr merged commit f76b2fc into PowerShell:master Apr 1, 2017
@PetSerAl

PetSerAl commented Apr 5, 2017

Copy link
Copy Markdown
Contributor Author

@lzybkr Should associated issue (#3390) be closed as well?

@lzybkr

lzybkr commented Apr 5, 2017

Copy link
Copy Markdown
Contributor

Indeed, I forget to do that pretty much every time I merge, you'd think I could remember by now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.