Last active
March 6, 2018 23:15
-
-
Save alx9r/f81cf64f50a016edda4e92968bdb9c3b to your computer and use it in GitHub Desktop.
Attempt at ensuring Dispose() is called on an advanced function's local variable on stop signal.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Attempt at answering https://stackoverflow.com/questions/46714132 | |
# "How can I ensure Dispose() is called on an advanced function's local variable on stop signal?" | |
# an IDisposable stub object for testing | |
class f : System.IDisposable { | |
Dispose() { Write-Host 'disposed' } | |
} | |
function g { | |
param | |
( | |
# The most complex case is when the function is in a pipeline, | |
# so we accept pipeline input. | |
[Parameter(ValueFromPipeline)] | |
$InputObject | |
) | |
begin { $f = [f]::new() } # The local IDisposable is created when the pipeline is established. | |
process { | |
try | |
{ | |
# flags to keep track of why finally was run | |
$success = $false | |
$caught = $false | |
$InputObject # output an object to exercise the pipeline downstream | |
# if we get here, nothing unusual happened downstream | |
$success = $true | |
} | |
catch | |
{ | |
# we get here if an exception was thrown | |
$caught = $true | |
# !!! | |
# This is bad news. It's possible the exception will be | |
# handled by an upstream process{} block. The pipeline would | |
# survive and the next invocation of process{} would occur | |
# after $f is disposed. | |
# !!! | |
$f.Dispose() | |
# rethrow the exception | |
throw | |
} | |
finally | |
{ | |
# !!! | |
# This finally block is not invoked when the PowerShell instance receives | |
# a stop signal while executing code upstream in the pipeline. In that | |
# situation cleanup $f.Dispose() is not invoked. | |
# !!! | |
if ( -not $success -and -not $caught ) | |
{ | |
# dispose only if finally{} is the only block remaining to run | |
$f.Dispose() | |
} | |
} | |
} | |
end {$f.Dispose()} | |
} | |
function throws { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { throw 'something' } | |
} | |
function throwsAfter { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { $InputObject; throw 'something' } | |
} | |
function breaks { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { break } | |
} | |
function breaksAfter { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { $InputObject; break } | |
} | |
function continues { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { continue } | |
} | |
function continuesAfter { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { $InputObject; continue } | |
} | |
function TerminateStatement { | |
[CmdletBinding()] | |
param | |
( | |
[Parameter(ValueFromPipeline)]$x | |
) | |
process | |
{ | |
$PSCmdlet.ThrowTerminatingError( | |
[System.Management.Automation.ErrorRecord]::new( | |
'Statement-Terminating Error', | |
'id', | |
0, | |
$null | |
) | |
) | |
} | |
} | |
function TerminateStatementAfter { | |
[CmdletBinding()] | |
param | |
( | |
[Parameter(ValueFromPipeline)]$x | |
) | |
process | |
{ | |
$InputObject | |
$PSCmdlet.ThrowTerminatingError( | |
[System.Management.Automation.ErrorRecord]::new( | |
'Statement-Terminating Error', | |
'id', | |
0, | |
$null | |
) | |
) | |
} | |
} | |
function blocks { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { Wait-Event 'bogus' } | |
} | |
function blocksAfter { | |
param ( [Parameter(ValueFromPipeline)] $InputObject ) | |
process { $InputObject; Wait-Event 'bogus' } | |
} | |
'===multiple inputs' | |
1,2 | g | |
'===throws' | |
try | |
{ | |
1 | g | throws | |
} | |
catch{} | |
'===throwsAfter' | |
try | |
{ | |
1 | throwsAfter | g # !!! Dispose never runs | |
} | |
catch{} | |
'===breaks' | |
do { | |
1 | g | breaks | |
} while (1) | |
'===breakAfter' | |
do { | |
1 | breaksAfter | g # !!! Dispose never runs | |
} while (1) | |
'===continues' | |
foreach ( $i in 1) | |
{ | |
1 | g | continues | |
Write-Host 'this should get skipped' | |
} | |
'===continuesAfter' | |
foreach ( $i in 1) | |
{ | |
1 | continuesAfter | g # !!! Dispose never runs | |
Write-Host 'this should get skipped' | |
} | |
'===TerminateStatement' | |
1 | g | TerminateStatement | |
'===TerminateStatementAfter' | |
1 | g | TerminateStatementAfter | |
'===Ctrl-C' | |
#1 | g | blocks | |
'===Ctrl-C after' | |
1 | blocksAfter | g # !!! Dispose never runs |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment