Skip to content

Conversation

@PetSerAl
Copy link
Contributor

Addresses #3390

@msftclas
Copy link

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

Copy link
Contributor Author

@PetSerAl PetSerAl Mar 29, 2017

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

Copy link
Contributor Author

@PetSerAl PetSerAl Mar 29, 2017

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
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
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
Copy link
Contributor Author

PetSerAl commented Apr 5, 2017

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

@lzybkr
Copy link
Contributor

lzybkr commented Apr 5, 2017

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