KEMBAR78
GitHub Β· Where software is built
Skip to content

Provide a way to assign a returned object to a variable that is explicitly atomic when Stopping PowerShellΒ #24658

@alx9r

Description

@alx9r

As an author of PowerShell jobs that are routinely canceled, I would like to have confidence that cleanup occurs as expected. I very much appreciate how the addition of the clean{} block helps to accomplish that.

Some cleanup scenarios hinge on atomic assignment of a new object to a PowerShell variable. Consider, for example

param($filePath)
end {
    $fs = [System.IO.FileStream]::new($filePath,'Append','Write')
    $fs
}
clean {
    ${fs}?.Dispose()
}

which relies on assignment to $fs to be atomic with the construction of FileStream in order for Dispose() be called reliably. Indeed, that assignment statement seems to be atomic. That is, if the job where this code is running is stopped while the constructor is running, the assignment to $fs succeeds. On the other hand, the assignment

    $fs = New-Object System.IO.FileStream $filePath,'Append','Write'

is not atomic with the construction of FileStream. That is, if the job is stopped while the new FileStream object is being constructed, the construction completes but the assignment does not complete. In that unfortunate condition, this leaves the file at $filePath locked for writing at least until garbage collection occurs. That leads to intermittent errors like

Exception calling ".ctor" with "3" argument(s): "The process cannot access the file 'C:\log.binlog' because it is being used by another process."

when opening the same file is attempted again, for example.

Whether such assignments are atomic to stopping jobs is the subject of this question. There is sample code that tests the atomicity of assignment in this and this answer. There is also a working definition of "atomic" in that question that applies to this proposal (as well as that Q&A).

The reliable cleanup of the FileStream objects in the above examples relies on atomic assignment. Many similar such cleanup scenarios involving the clean{} block also rely on atomic atomic assignment. It appears from this comment, however, that the atomicity of assignment may well be a side-effect of performance considerations rather than intended behavior. The comment includes the following statement:

Stop() is implemented by having the runtime check the Stopping flag in a bunch of places. Any long-running operation should check but we don't check for every operation as that would be too much overhead. I don't believe we check it for assignment statements (but I'd have to check the code).

I like that at least some assignment statements are currently atomic. But I don't like that other idiomatic assignment statements like those involving Set-Variable or New-Object are not. Currently to ensure reliable cleanup I have to carefully consider how each variable used in the clean{} block was assigned and try to reorganize the code so that the assignment happens in a way that is atomic. What's worse is I'm relying on empirical tests rather than intentional design to formulate what are, hopefully, atomic assignment statements.

Proposal

I propose that PowerShell be extended to provide at least one way to construct an object and assign it to a PowerShell variable in a way that is

  1. atomic when PowerShell is stopping,
  2. is unambiguously and easily recognizable as atomic to readers acquainted with its use, and
  3. is intended by the maintainers of PowerShell to be atomic in future releases.

Related Considerations

While not the main concern of this proposal, I note that a similar question of atomicity arises wherever someone is inspired to use the using pattern in PowerShell. Consider for example, an implementation of Use-Object used thus:

Use-Object ($d = [SomeDisposable]::new()) {
   $d # do some useful thing with d
}

I like this usage. I would this to work reliably. Unfortunately for this scenario, PowerShell's parameter binder seems to check the Stopping flag. That means that if a Stop-Job call results in Stopping while [SomeDisposable]::new() is running, $d is constructed, but Use-Object never has its first argument bound, and never calls Dispose() on $d. So $d sometimes remains undisposed until the garbage collector gets to it.

I think clean{} has mostly obviated the need for the using pattern, but it still arises on occasion. However, the same problem of parameter binding atomicity is at play in this formulation of the aforemention FileStream assignment:

    Set-Variable fs ([System.IO.FileStream]::new($filePath,'Append','Write'))

That assignment statement is not atomic for at least the problem of the parameter binding not being atomic. It seems then that achieving the goals of the proposal by way of atomic parameter binding would also allow for reliable Use-Object implementations. But I don't know which would be the most prudent approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Enhancementthe issue is more of a feature request than a bugNeeds-TriageThe issue is new and needs to be triaged by a work group.WG-Enginecore PowerShell engine, interpreter, and runtimeWG-NeedsReviewNeeds a review by the labeled Working Group

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions