Skip to content

Fixed websocket related tasks in InvokeAzContainerInstanceCommand_ExecuteExpanded.cs#22515

Merged
BethanyZhou merged 2 commits intoAzure:generationfrom
andreygoran:generation-fix-invoke-azcontainerinstancecommand
Aug 10, 2023
Merged

Fixed websocket related tasks in InvokeAzContainerInstanceCommand_ExecuteExpanded.cs#22515
BethanyZhou merged 2 commits intoAzure:generationfrom
andreygoran:generation-fix-invoke-azcontainerinstancecommand

Conversation

@andreygoran
Copy link
Contributor

Description

This PR fixes the problem with Invoke-AzContainerInstanceCommand not returning any result when it's run under certain conditions, and closes #22453

The problem occurres when powershell is launched with redirection of input stream (and apparently this is how it's being run in Azure ARM Powershell Deployment Scripts and in Azure Powershell Functions).

Test.ps1 code used to reproduce the problem:

$HostName = Invoke-AzContainerInstanceCommand -ContainerGroupName mypowershellapp -ContainerName mypowershellapp -ResourceGroupName MyResourceGroup -PassThru -Command 'hostname'
("Host: " + $HostName) | Out-Host

Results before the fix:

$ pwsh Test.ps1
SandboxHost-638266427706169802
Host: SandboxHost-638266427706169802
$ echo | pwsh Test.ps1
Host: 

Results after the fix:

$ pwsh Test.ps1
SandboxHost-638266427706169802
Host: SandboxHost-638266427706169802
$ echo | pwsh Test.ps1
SandboxHost-638266427706169802
Host: SandboxHost-638266427706169802

Related Issues

Fixed: #22453
Probably related: #19247
Probably related: #15754 (comment)

Root Cause Analysis

The root cause of the bug was using a return Task.Factory.StartNew(async () => {...}) construction to handle working with websockets doing actual communication with container instance. This code pattern is dangerous and should not be used, see for details https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

Another problem was using Console.KeyAvailable which throws an exception when input redirection is used. This was fixed by adding Console.IsInputRedirected validation.

Checklist

  • SHOULD select appropriate branch. Cmdlets from Autorest.PowerShell should go to generation branch.
  • SHOULD make the title of PR clear and informative, and in the present imperative tense.
  • SHOULD update ChangeLog.md file(s) appropriately
    • For any service, the ChangeLog.md file can be found at src/{{SERVICE}}/{{SERVICE}}/ChangeLog.md
    • A snippet outlining the change(s) made in the PR should be written under the ## Upcoming Release header in the past tense. Add changelog in description section if PR goes into generation branch.
    • Should not change ChangeLog.md if no new release is required, such as fixing test case only.
  • SHOULD have approved design review for the changes in this repository (Microsoft internal only) with following situations
    • Create new module from scratch
    • Create new resource types which are not easy to conform to Azure PowerShell Design Guidelines
    • Create new resource type which name doesn't use module name as prefix
    • Have design question before implementation
  • SHOULD regenerate markdown help files if there is cmdlet API change. Instruction
  • SHOULD have proper test coverage for changes in pull request.
  • SHOULD NOT introduce breaking changes in Az minor release except preview version.
  • SHOULD NOT adjust version of module manually in pull request

@azure-client-tools-bot-prd
Copy link

azure-client-tools-bot-prd bot commented Aug 7, 2023

️✔️Az.Accounts
️✔️Build
️✔️PowerShell Core - Windows
⚠️Az.ContainerInstance
️✔️Build
️✔️PowerShell Core - Windows
️✔️Breaking Change Check
️✔️PowerShell Core - Windows
⚠️Signature Check
⚠️PowerShell Core - Windows
Type Cmdlet Description Remediation
⚠️ Get-AzContainerGroup Get-AzContainerGroup Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ Get-AzContainerGroup Get-AzContainerGroup changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ Get-AzContainerInstanceCachedImage Get-AzContainerInstanceCachedImage Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ Get-AzContainerInstanceCachedImage Get-AzContainerInstanceCachedImage changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ Get-AzContainerInstanceCapability Get-AzContainerInstanceCapability Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ Get-AzContainerInstanceCapability Get-AzContainerInstanceCapability changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ Get-AzContainerInstanceContainerGroupOutboundNetworkDependencyEndpoint Get-AzContainerInstanceContainerGroupOutboundNetworkDependencyEndpoint Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ Get-AzContainerInstanceContainerGroupOutboundNetworkDependencyEndpoint Get-AzContainerInstanceContainerGroupOutboundNetworkDependencyEndpoint changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ Get-AzContainerInstanceLog Get-AzContainerInstanceLog Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ Get-AzContainerInstanceLog Get-AzContainerInstanceLog changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ Get-AzContainerInstanceUsage Get-AzContainerInstanceUsage Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ Get-AzContainerInstanceUsage Get-AzContainerInstanceUsage changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerGroupImageRegistryCredentialObject New-AzContainerGroupImageRegistryCredentialObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerGroupImageRegistryCredentialObject New-AzContainerGroupImageRegistryCredentialObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerGroupPortObject New-AzContainerGroupPortObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerGroupPortObject New-AzContainerGroupPortObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerGroupVolumeObject New-AzContainerGroupVolumeObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerGroupVolumeObject New-AzContainerGroupVolumeObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerInstanceEnvironmentVariableObject New-AzContainerInstanceEnvironmentVariableObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerInstanceEnvironmentVariableObject New-AzContainerInstanceEnvironmentVariableObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerInstanceHttpHeaderObject New-AzContainerInstanceHttpHeaderObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerInstanceHttpHeaderObject New-AzContainerInstanceHttpHeaderObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerInstanceInitDefinitionObject New-AzContainerInstanceInitDefinitionObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerInstanceInitDefinitionObject New-AzContainerInstanceInitDefinitionObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerInstanceObject New-AzContainerInstanceObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerInstanceObject New-AzContainerInstanceObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerInstancePortObject New-AzContainerInstancePortObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerInstancePortObject New-AzContainerInstancePortObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
⚠️ New-AzContainerInstanceVolumeMountObject New-AzContainerInstanceVolumeMountObject Changes the ConfirmImpact but does not set the SupportsShouldProcess property to true in the cmdlet attribute. Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue
⚠️ New-AzContainerInstanceVolumeMountObject New-AzContainerInstanceVolumeMountObject changes the confirm impact. Please ensure that the change in ConfirmImpact is justified Verify that ConfirmImpact is changed appropriately by the cmdlet. It is very rare for a cmdlet to change the ConfirmImpact.
️✔️Help File Existence Check
️✔️PowerShell Core - Windows
⚠️File Change Check
⚠️PowerShell Core - Windows
Type Cmdlet Description Remediation
⚠️ It is required to update ChangeLog.md if you want to release a new version for Az.ContainerInstance. Add a changelog record under Upcoming Release section with past tense.
️✔️UX Metadata Check
️✔️PowerShell Core - Windows
️✔️Test
️✔️PowerShell Core - Linux
️✔️PowerShell Core - MacOS
️✔️PowerShell Core - Windows

@ghost ghost added the customer-reported label Aug 7, 2023
@ghost
Copy link

ghost commented Aug 7, 2023

Thank you for your contribution andreygoran! We will review the pull request and get back to you soon.

@BethanyZhou
Copy link
Contributor

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

private Task PullResponse()
{
return Task.Factory.StartNew(async () =>
return Task.Run(async () =>
Copy link
Contributor

@BethanyZhou BethanyZhou Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://code-maze.com/csharp-task-run-vs-task-factory-startnew/
Task.Factory.StartNew() vs Task.Run()

  1. TaskCreationOptions.AttachedToParent vs TaskCreationOptions.DenyChildAttach? TaskCreationOptions.AttachedToParent as WriteObject() in PullResponse() depends on parent task.
  2. Current vs Default TaskScheduler? Default TaskScheduler. No sure is current TaskScheduler same with default scheduler in ARM deployment environment?
  3. Task<Task<void>> vs Task<void>? No difference in this case due to no return.
  4. Object State? No difference in this case as no shared variable is scoped outside of the task block except socket.

Copy link
Contributor

@BethanyZhou BethanyZhou Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe should use Task.Factory.StartNew/Task.Run(action, this._cancellationTokenSource.Token, TaskCreationOptions.None, TaskScheduler.Default);? @andreygoran what's your thought?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BethanyZhou,

  1. Task<Task<void>> vs Task<void> I think it's actually crucial in this case. We use Task.WaitAll later in code https://github.com/Azure/azure-powershell/blob/d6e1dc5e5759ae025a6509886ac5d7580113f5a6/src/ContainerInstance/custom/InvokeAzContainerInstanceCommand_ExecuteExpanded.cs#L45C13-L45C25 and Task<Task<void>> means that the waiting is over when the inner Task is returned, but this does not mean that the inner Task is actually completed by that time. However Task.Run does understand the semantics of Task<Task<void>> and actually waits until the inner task is completed.
    Another solution would be to use .Unwrap() instead of switching Task.Factory.StartNew to Task.Run:
            return Task.Factory.StartNew(async () =>
            {
...
            }, this._cancellationTokenSource.Token).Unwrap();

Maybe should use Task.Factory.StartNew/Task.Run(action, this._cancellationTokenSource.Token, TaskCreationOptions.None, TaskScheduler.Default);?

I randomly checked a few usages of Task.Run in the project, and I do not see any additional parameters used anywhere. So probably it's fine the way it is.

Copy link
Contributor

@BethanyZhou BethanyZhou Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your opinions.

I prefer

return Task.Factory.StartNew(async () =>
            {
...
            }, this._cancellationTokenSource.Token).Unwrap();

Because we need TaskCreationOptions.AttachedToParent but Task.Run() uses TaskCreationOptions.DenyChildAttach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BethanyZhou I added new commit that restores Task.Factory.StartNew and adds Unwrap()

@vidai-msft
Copy link
Contributor

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Contributor

@BethanyZhou BethanyZhou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@BethanyZhou
Copy link
Contributor

/azp run azure-powershell - powershell-core

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invoke-AzContainerInstanceCommand -PassThru returns nothing when called from ARM deployment script

3 participants