Implement Cleanup { } Function Block #9900
Conversation
|
@vexx32, your last commit had 40 failures in try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[6].Extent.Text Expected strings to be the same, but they were different.
Expected length: 3
Actual length: 5
Strings differ at index 0.
Expected: '{2}'
But was: '[int]'
at <ScriptBlock>, /Users/vsts/agent/2.153.1/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[7].Extent.Text Expected strings to be the same, but they were different.
Expected length: 1
Actual length: 6
Strings differ at index 0.
Expected: '2'
But was: '[char]'
at <ScriptBlock>, /Users/vsts/agent/2.153.1/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[8].Extent.Text Expected strings to be the same, but they were different.
Expected length: 1
Actual length: 3
Strings differ at index 0.
Expected: '2'
But was: '{2}'
at <ScriptBlock>, /Users/vsts/agent/2.153.1/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[10].Extent.Text Expected strings to be the same, but they were different.
Expected length: 5
Actual length: 1
Strings differ at index 0.
Expected: '[int]'
But was: '2'
at <ScriptBlock>, /Users/vsts/agent/2.153.1/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[11].Extent.Text Expected strings to be the same, but they were different.
Expected length: 6
Actual length: 1
Strings differ at index 0.
Expected: '[char]'
But was: '2'
at <ScriptBlock>, /Users/vsts/agent/2.153.1/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] } |
|
@vexx32, your last commit had 40 failures in try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[6].Extent.Text Expected strings to be the same, but they were different.
Expected length: 3
Actual length: 5
Strings differ at index 0.
Expected: '{2}'
But was: '[int]'
at <ScriptBlock>, /home/vsts/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[7].Extent.Text Expected strings to be the same, but they were different.
Expected length: 1
Actual length: 6
Strings differ at index 0.
Expected: '2'
But was: '[char]'
at <ScriptBlock>, /home/vsts/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[8].Extent.Text Expected strings to be the same, but they were different.
Expected length: 1
Actual length: 3
Strings differ at index 0.
Expected: '2'
But was: '{2}'
at <ScriptBlock>, /home/vsts/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[10].Extent.Text Expected strings to be the same, but they were different.
Expected length: 5
Actual length: 1
Strings differ at index 0.
Expected: '[int]'
But was: '2'
at <ScriptBlock>, /home/vsts/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] }try/catch/finally statement parsing.Error Statement expected: <<try {1} catch [int],[char] {2} catch>>.$asts[11].Extent.Text Expected strings to be the same, but they were different.
Expected length: 6
Actual length: 1
Strings differ at index 0.
Expected: '[char]'
But was: '2'
at <ScriptBlock>, /home/vsts/work/1/s/test/tools/Modules/HelpersLanguage/HelpersLanguage.psm1: line 125
125: It "`$asts[$($i + 1)].Extent.Text" { $asts[$i + 1].Extent.Text | Should Be $a[$i] } |
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
5e23ce9
to
8dd86bb
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
...PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs
Show resolved
Hide resolved
680c351
to
330a48e
|
As this is a change to the core language it should go through the RFC process. |
|
@BrucePay please see PowerShell/PowerShell-RFC#207 |
Moving half of the cleanup logic to DlrScriptCommandProcessor lets us avoid the double type check/cast and handle script functions exclusively in DlrScriptCommandProcessor. Remaining branch for PSScriptCmdlet is retained in the base class's `Dispose()` method itself (moved up the stack one method).
- Explicit type for variable declaration - Ensure we mark cleanup as completed if there is no cleanup block. - Check depth before taking the lock
This method could also be utilised in all the Begin/Process/End paths. However, they each have a very slightly different version of the code, do things in different orders, and split up certain parts of the code. We can revisit whether we want to refactor those code paths to use the new method in future, but for now this avoids duplicating the code from here to DlrScriptCommandProcessor, which would introduce complications.
|
Rebased onto #14297 to get CI passing |
|
Thanks, Rob! Hmm, that failure's a new one. I'm pretty sure I haven't touched anything that would affect that test. Retriggering CI to verify failure, I don't have a Linux box to test on atm really. |
With some review changes, it appears we're able to use Monitor methods instead of a semaphore as was previously required. Also added some comments.
Well CodeFactor likes to include changes outside the actual diff (just in the same file). It's not a blocker for me |
|
Yeah, I'm not too worried about CodeFactor here, this PR'd be twice the size if I did There was a transient failure on the Linux build, something about the logging picking up something odd. I'm gonna say it's not something I did given it disappeared when I got it to rebuild. |
|
However, I have run this branch now, with the following script: function Test-Cleanup
{
cleanup
{
Write-Host "Enter message:"
$msg = Read-Host
Write-Host "Cleanup: $msg"
}
}
Test-Cleanup
The When we do that, I've seen a couple of things happen:
I think this issue is solvable, but I want to spend a little bit more time working on the solution to present one to you that I like. In particular some questions are:
I suspect the answer to this might simply be to force the stopper to hold the lock, but that makes stopping harder. I need to think about what the actual scenarios are and whether there's more cleanup state we need to take care of. |
|
Interesting! Okay so... I -thought- that I'd already accounted for that case, but it looks like the case where a user presses Ctrl+C during normal operation of I think stopping the pipeline within
In this specific case where it is explicitly cancelling a specific command because the Ctrl+C is received from this input query directly (which I think is kind of an unusual edge case; as far as I've been able to find, any other attempt to Ctrl+C during operation of the dispose block is successfully basically ignored until the cleanup{} script completes) I think it makes sense to treat it as an exception thrown from the Read-Host command itself and terminate the rest of the block.... maybe? |
I'd say this is most consistent with the general scenario where this will occur, which is where a hosted PowerShell runtime will have So I think it makes sense to block the |
|
Yeah, I'd agree there. I'm not sure of the best way to check that, though... Perhaps setting a value on the pipeline stopper which indicates which thread/runspace "holds" the critical section, and if Stop() is called by that same runspace/thread it either returns (no-op, allowing the Hm. |
|
I guess I could try to check that the same thread or runspace that's currently running cleanup{} isn't calling Stop(), but I'm not sure how I'd get at that. Any ideas? |
This part I'm less sure about. My inclination is that if the cleanup script itself errors out normally, we should just emit that error (and currently, we do). For a straight up PipelineStoppedException as it hits in the case you raised, I'm wondering if perhaps a silent termination of that block is okay... it's an edge case that I'm not sure how else folks might run into, but in the majority of cases it shouldn't be possible to even hit that. Perhaps at most we could rewrap the PipelineStoppedException with another exception that indicates that a stop was explicitly demanded / unavoidable during the cleanup{} operation? I can't think of any other way to handle it at the moment. |
So there's two types of stops for the most part:
During the execution of the cleanup block itself, there should only ever be the latter. It would be pretty hard for the cleanup block to actually get an object to trigger the former. That said, in the event that it does, the lock will already be taken on that thread so it should just abort the cleanup block normally.
So if PSRL tries to stop the pipeline mid cleanup, this series of calls will happen (roughly):
I can't think of a scenario where PSRL will be on the pipeline thread, while a cleanup block is running, and want to stop the pipeline. But if there is one it should work like the my first paragraph. |
|
Open PRs should not be assigned to milestone, so they are not assigned to the wrong milestone after they are merged. For backport consideration, use a |
|
Also, @rjmholt:
FWIW I'm seeing this same behaviour from a dev build on the master branch as well; just call |
|
Is this likely to make it into 7.2? |
|
That's the hope. Depends if we get the final kinks ironed out and reviews approved, I suppose. |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
@daxian-dbw @rjmholt @SteveL-MSFT please let me know if more is needed. I've responded to all concerns raised as best as I can. |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.



PR Summary
Adds a new
cleanupkeyword and named block for both script commands and advanced functions.RFC: PowerShell/PowerShell-RFC#207
Changes
cleanup {}function block, including all necessary AST changes toScriptBlockAst, new token type, tokenizer changes, etc.Dispose()implementation (including the newcleanup {}block type) immediately afterend {}/EndProcessing()is called, so as to dispose of function resources as soon as a function's task in the pipeline is complete.Dispose()method in theCommandProcessorBaseto call theDispose()methods for script cmdlets and functions as well.IDisposableimplementation to call thecleanup {}script block during that code path for script cmdlets. Simple functions (utilisingDlrScriptCommandProcessor) already inherit theDispose()implementation fromCommandProcessorBaseso a method was added to handle their disposal by calling theircleanup{}blocks.Cleanup { } behaviour
cleanup {}step will skip the remainder of that Cleanup block but will not prevent subsequent cmdlets' ownCleanup {}steps from being invoked, nor interrupt any disposal of the overall pipeline.PR Context
For functions and script cmdlets that utilise pipeline input, there is currently no possible way to reliably utilise a resource that should be timely disposed. Resources can be disposed by the pipeline, but typically this does not occur until the completion of all commands in the pipeline.
For scripts and modules that interact with external resources, this can still become problematic. This PR attempts to address this gap and bring script cmdlets closer to parity with compiled cmdlets, which are perfectly capable of simply implementing
IDisposableand taking necessary actions there.Fix #6673
EditorSyntax PR: PowerShell/EditorSyntax#186
Open / Complicated Questions
cleanup{}and another function block (i.e., a terminating error is thrown duringbegin,process, orend, and whencleanupis invoked, it also throws a terminating error).cleanup(this is the same as what would happen in C# if you throw both fromtryandfinally, from what I can tell, in that you can only catch / see the error from thefinally), but both errors still surface in$error.Additional Asks
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.