-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Occasional NullReferenceException in Microsoft.PowerShell.ProgresPane.Hide() when updating multiple progress bars on PS 7.4.0 #21021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I see this every time that Invoke-Command is executed on a -Session if the connection is broken during the command. For example, "Invoke-Command -Session $Session -ScriptBlock { shutdown -t 0 -r }". It does waits for the timeout and then throws the null object exception. This may make it easier to reproduce... |
Pinging to keep this issue open |
@SteveL-MSFT Unfortunately this is still happening on |
@kborowinski I see you use Start-ThreadJob so the #17497 issue is the same. Can you share a simple script to reproduce the issue? |
@iSazonov Tomorrow I'll try to come up with sensible reproduction script and will let you know. |
I was able to create a reproducible PowerShell script that consistently triggers the
Note: The NREs are happening also when using Save the following script as
|
Here is a version of the same script that uses #Requires -Version 7.5 -Modules 7Zip4Powershell
$Global:ProgressPreference = 'Continue'
# $PSStyle.Progress.MaxWidth = 240
$numberOfSubfolders = 128
$maximumNumberOfFilesPerSubfolder = 1KB
# Define the main directory path
$rootFolder = 'C:\_ProgressTest'
$sourceFolders = Join-Path -Path $rootFolder -ChildPath 'SourceFolders'
$compressedFolders = Join-Path -Path $rootFolder -ChildPath 'CompressedFolders'
# Create folder structure
$null = New-Item -Path $rootFolder -ItemType Directory -Force
$null = New-Item -Path $sourceFolders -ItemType Directory -Force
$null = New-Item -Path $compressedFolders -ItemType Directory -Force
$splatForEachParallel = @{
ThrottleLimit = [Environment]::ProcessorCount
}
function Test-Setup {
[CmdletBinding()]
param()
# Remove pre-generated folders if any
Remove-Item -Path "$sourceFolders\*" -Recurse -Force -ErrorAction Stop
# Remove archives
Remove-Item -Path "$compressedFolders\*" -Recurse -Force -ErrorAction Stop
# Create subdirectories and files in parallel
1..$numberOfSubfolders | ForEach-Object -Parallel {
function Get-RandomNumber {Get-Random -Minimum 128 -Maximum $Using:maximumNumberOfFilesPerSubfolder}
# Subfolder path
$subfolderPath = Join-Path -Path $Using:sourceFolders -ChildPath "Subfolder$_"
$fileCount = Get-RandomNumber
Write-Host ('Creating "{0}" with {1} files' -f $subfolderPath, $fileCount)
# Create the subfolder
$null = New-Item -Path $subfolderPath -ItemType Directory -Force
# Create files with random content
1..$fileCount | ForEach-Object {
$filePath = Join-Path -Path $subfolderPath -ChildPath "file$_.txt"
$fileContent = [Guid]::NewGuid().Guid * (Get-RandomNumber)
Set-Content -Path $filePath -Value $fileContent -Force
}
} @splatForEachParallel
}
function Test-Compress {
[CmdletBinding()]
param()
$isVerbose = $PSBoundParameters['Verbose'] -eq $true
Get-ChildItem -Path $sourceFolders -Directory | ForEach-Object -Parallel {
try {
$VerbosePreference = if ($Using:isVerbose) {'Continue'} else {'SilentlyContinue'}
$subfolderPath = $_
$archivePath = Join-Path -Path $Using:compressedFolders -ChildPath ('{0}.7z' -f $subfolderPath.BaseName)
#Check if target archive exists and remove it
if (Test-Path -Path $archivePath -PathType Leaf) {
Remove-Item -Path $archivePath -Force
}
# Define the parameters for Compress-7Zip cmdlet
$splatCompress = @{
ArchiveFileName = $archivePath
Path = $subfolderPath
PreserveDirectoryRoot = $true
Verbose = $false
ErrorAction = 'Stop'
}
Compress-7Zip @splatCompress
Write-Verbose -Message (
'Compression completted: "{0}"' -f
$archivePath
)
} catch {
# Write-Warning ('[PID: "{0}" | RID: "{1}"]' -f
# $PID,
# [Management.Automation.Runspaces.Runspace]::DefaultRunspace.Id,
# $_.Exception.Message
# )
# Wait-Debugger
throw $_
}
} @splatForEachParallel
}
function Test-Expand {
[CmdletBinding()]
param()
$isVerbose = $PSBoundParameters['Verbose'] -eq $true
Get-ChildItem -Path $compressedFolders -File | ForEach-Object -Parallel {
try {
$VerbosePreference = if ($Using:isVerbose) {'Continue'} else {'SilentlyContinue'}
$subfolder = $_
$subfolderPath = Join-Path -Path $Using:sourceFolders -ChildPath $subfolder.BaseName
# Check if the target directory already exists and remove it.
if (Test-Path -Path $subfolderPath -PathType Container) {
Remove-Item -Path $subfolderPath -Recurse -Force
}
# Expand the archive to the target directory.
$splatExpand = @{
ArchiveFileName = $subfolder
TargetPath = $Using:sourceFolders
Verbose = $false
ErrorAction = 'Stop'
}
Expand-7Zip @splatExpand
Write-Verbose -Message (
'Expansion completted: "{0}"' -f
$subfolder
)
} catch {
# Write-Warning ('[PID: "{0}" | RID: "{1}"]' -f
# $PID,
# [Management.Automation.Runspaces.Runspace]::DefaultRunspace.Id,
# $_.Exception.Message
# )
# Wait-Debugger
throw $_
}
} @splatForEachParallel
} |
@kborowinski Thanks for great repro!
I don't think so. Root of the issue is most likely in conhost. From the demo I see NREs throw after verboses push progress bars out of the window. Perhaps this will allow us to make even simpler repro based on two "threads" or even one. |
@iSazonov I came up with much simpler version that reproduces the same behavior. No need for initial test setup, however it still requires ~50 threads to reproduce reliably. I will also post it at the top.
#Requires -Version 7.5 -Modules Microsoft.PowerShell.ThreadJob
function Test-ProgressBar {
[CmdletBinding()]
param()
$Global:ProgressPreference = 'Continue'
$PSStyle.Progress.MaxWidth = $Host.UI.RawUI.WindowSize.Width
$splatThreadJob = @{
ThrottleLimit = [Environment]::ProcessorCount
StreamingHost = $Host
}
$isVerbose = $PSBoundParameters['Verbose'] -eq $true
$threadJobs = 1..50 | ForEach-Object {
Start-ThreadJob -Name $_ -ScriptBlock {
try {
$VerbosePreference = if ($Using:isVerbose) {'Continue'} else {'SilentlyContinue'}
$progressBarId = $Using:_
$progressBar = 'ProgressBar-{0}' -f $progressBarId
foreach ($n in 0..100) {
Write-Progress -Activity $progressBar -Status ('{0}%' -f $n) -PercentComplete $n -Id $progressBarId
Start-Sleep -Milliseconds (Get-Random -Minimum 15 -Maximum 45)
}
Write-Progress -Activity $progressBar -Completed -Id $progressBarId
Write-Verbose -Message (
'Progress bar completted: "{0}"' -f
$progressBar
)
} catch {
# Write-Warning ('[PID: "{0}" | RID: "{1}"]' -f
# $PID,
# [Management.Automation.Runspaces.Runspace]::DefaultRunspace.Id,
# $_.Exception.Message
# )
# Wait-Debugger
throw $_
}
} @splatThreadJob
}
$null = Wait-Job -Job $threadJobs | Receive-Job
Remove-Job -Job $threadJobs -Force
} Visuals: |
@kborowinski Thanks! We get the error more fast if console windows height will be 4-5 line. Could you please suppress virtual terminal support (set env variable TERM=dumb ) and try run your test again? |
@iSazonov Yeah, suppressing virtual terminal support helps: |
@kborowinski Thanks for confirmation! PSAnsiRendering was introduced in #13758 (#15864) PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs Lines 1273 to 1283 in 6c216bf
New WriteLine() method directly call WriteToConsole() method (WriteImpl()->ConsoleTextWriter.Write()->WriteToConsole()), PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs Lines 710 to 713 in 6c216bf
but old one calls the method using wrapper with global lock PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs Lines 805 to 824 in 6c216bf
No global lock |
@iSazonov This is a great find. I will try to re-introduce the locks on my local fork to see if it solves the problem. |
Perhaps @SteveL-MSFT remembers whether the lack of the lock is intentional or accidental. |
@iSazonov Yep, Thanks for solving it! 🚀 I will make a draft pull request to address this issue, but since I do not know the impact on the performance, I will wait for your review and guidance if that's OK. |
Old code (coming from Windows PowerShell) has the lock for years. So we don't lost performance more than before. Early we merge some PRs to improve progress bar performance and I think we can not do more in current code design. In general, it is bad idea to write to console from many threads - script writers should understand that writing to console slow down their scripts. |
I know that it's better to avoid progress bars, as they can impact performance. The funny thing is that I had a check in my profile that on PS 7.5.0 preview was disabling it globally. The bug reappeared when I moved to the stable release and my check was no longer valid. Then I was hit by a progress bar bug coming from However, I find it particularly useful to have a method for visually indicating that a long-running thread task has been completed, such as using verbose output. |
WG-Engine reviewed this and agrees that it's a bug, and are glad to see a PR in progress. |
📣 Hey @@kborowinski, how did we do? We would love to hear your feedback with the link below! 🗣️ 🔗 https://aka.ms/PSRepoFeedback |
Prerequisites
Steps to reproduce
This is not happening on PS 5.1
Issue was first identified when expanding multiple 7z archives with
Start-ThreadJob
andExpand-7Zip
cmdlet from 7Zip4PowerShell module. It might be also related to this issue with corresponding PRIssue description:
When using a cmdlet that opens multiple progress panes on PS 7.4.0, a NullReferenceException is thrown occasionally on progress pan completion. Please refer to above links for more details.
Workaround:
Disable progress before calling cmdlet that opens multiple progress panes with
$ProgressPreference = 'SilentlyContinue'
Expected behavior
NullReferenceException on ProgressPane completion should not happen
Actual behavior
Occasionally an exception is thrown that "Object reference not set to an instance of an object" at Microsoft.PowerShell.ProgressPane.Hide()
Error details
Environment data
Visuals
Edit: 2025-01-31
Script to reproduce the NRE's:
Test-ProgressBar.ps1
Test-ProgressBar -Verbose
to trigger NREsTest-ProgressBar
no errorsVisuals:
The text was updated successfully, but these errors were encountered: