Skip to content
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

Invoke-PSUScript from App taking a very long time to trigger #4238

Open
bt-ndollimount opened this issue Jan 16, 2025 · 16 comments
Open

Invoke-PSUScript from App taking a very long time to trigger #4238

bt-ndollimount opened this issue Jan 16, 2025 · 16 comments
Labels
bug Something isn't working PowerShell Universal Issue relates to PowerShell Universal. requires triage Issue has not yet been verified by the development team. v5 Version 5 issue.

Comments

@bt-ndollimount
Copy link

Description of Issue

After upgrading to 5.2.0, Invoke-PSUScript in an App takes a very long time to trigger. In the browser dev tools, it actually times out. I have a button that is showing progress spinner while it executes and because of the timeout, that never stops. The data is returned eventually but the button stays busy. Alternatively, using the exact same command to execute the same script from a normal job, this runs much quicker as expected.

Version

5.2.0

Severity

High

Hosting Method

Other

Operating System

Linux

Database

PostgreSQL

Licensed

Yes

Features

Git Sync

Additional Environment data

Windows Server 2022 Standard / Amazon Linux
PowerShell 7.4.6
PostgreSQL 16

Screenshots/Animations

No response

@bt-ndollimount bt-ndollimount added bug Something isn't working PowerShell Universal Issue relates to PowerShell Universal. requires triage Issue has not yet been verified by the development team. v5 Version 5 issue. labels Jan 16, 2025
@adamdriscoll
Copy link
Member

I'll try to reproduce and get a fix in 5.2.1 if I can do so. Can you let me know a few things?

  • Are you using -Integrated for Invoke-PSUScript?
  • Are you returning data from Invoke-PSUScript (e.g. -Wait)?
  • Is your app authenticated with roles?

@adamdriscoll
Copy link
Member

adamdriscoll commented Jan 16, 2025

I'm not seeing it time out but am definitely seeing a delay. The script I'm running executes in under a second.

Get-Service | Select-Object Name

In my app, I made 4 buttons with various configurations.

New-UDApp -Content {
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Wait| Out-Null
        }
        Show-UDToast "Wait: $($Value.TotalMilliseconds)" -Persistent
    } -Text "Wait"
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' | Out-Null
        }
        Show-UDToast "No Wait: $($Value.TotalMilliseconds)" -Persistent
    } -Text "No Wait"
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Wait -Integrated| Out-Null
        }
        Show-UDToast "Wait - Integrated: $($Value.TotalMilliseconds)" -Persistent
    } -Text "Wait - Integrated"
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Integrated| Out-Null
        }
        Show-UDToast "No Wait - Integrated: $($Value.TotalMilliseconds)" -Persistent
    } -Text "No Wait - Integrated"
}

It is pretty slow. It takes about 4 seconds if I'm waiting on the job.

Image

That said, I introduced the -Silent flag, and it runs almost 4x as fast.

New-UDApp -Content {
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Wait -Silent | Out-Null
        }
        Show-UDToast "Wait: $($Value.TotalMilliseconds)" -Persistent
    } -Text "Wait"
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Silent | Out-Null
        }
        Show-UDToast "No Wait: $($Value.TotalMilliseconds)" -Persistent
    } -Text "No Wait"
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Wait -Integrated -Silent | Out-Null
        }
        Show-UDToast "Wait - Integrated: $($Value.TotalMilliseconds)" -Persistent
    } -Text "Wait - Integrated"
    New-UDButton -ShowLoading -OnClick {
        $Value = Measure-Command {
            Invoke-PSUScript -Name 'script1.ps1' -Integrated -Silent | Out-Null
        }
        Show-UDToast "No Wait - Integrated: $($Value.TotalMilliseconds)" -Persistent
    } -Text "No Wait - Integrated"
}

Image

I also setup a parent script to run the child script.

Measure-Command {
    Invoke-PSUScript -Name 'script1.ps1' -Wait -Silent | Out-Null
} | Select TotalMilliseconds
Measure-Command {
    Invoke-PSUScript -Name 'script1.ps1' | Out-Null
} | Select TotalMilliseconds
Measure-Command {
    Invoke-PSUScript -Name 'script1.ps1' -Wait -Integrated -Silent | Out-Null
} | Select TotalMilliseconds
Measure-Command {
    Invoke-PSUScript -Name 'script1.ps1' -Integrated | Out-Null
} | Select TotalMilliseconds

It's actually slower than running in the app.

TotalMilliseconds
-----------------
          2170.12
            79.83
          2131.13
            24.89

I suspect that this has to do with the app logging when not using -Silent. Here's the output from the app log. It streams the job output to the log if -Silent isn't on. It's unclear to me why this would be different in v5.1

Image

@bt-ndollimount
Copy link
Author

I'll try to reproduce and get a fix in 5.2.1 if I can do so. Can you let me know a few things?

  • Are you using -Integrated for Invoke-PSUScript?
  • Are you returning data from Invoke-PSUScript (e.g. -Wait)?
  • Is your app authenticated with roles?

Here's the code line I'm using; I didn't realize there was the -Wait and I'll give that a try. Piping it to Wait-PSUJob might be slowing it down?

$userData = Invoke-PSUScript -ComputerGroup 'Job Nodes (Any)' -ComputerName localhost -AppToken $Secret:PSUToken -Script 'Dashboard Functionality/GetUserPostsRepliesStats.ps1' -UserId $selectedUserId | Wait-PSUJob

I am returning data but once the job executes, it executes quickly and the data is returned quickly as well. It's the pause while it's waiting to actually execute the job.

The app is authenticated with roles and right now this one is just my account as Administrator role.

@adamdriscoll
Copy link
Member

I see. I wonder if there is a heavy SQL query or something running that is causing the delay. How many jobs do you have in your Jobs table?

@bt-ndollimount
Copy link
Author

Running jobs is only 2 and it's still really long. However, the public.job table has 4497 entries in it. The hangfire.job table has 257 entries.

Following up to using -Wait -Silent; this doesn't appear to have made a difference.

@adamdriscoll
Copy link
Member

adamdriscoll commented Jan 16, 2025

Strange. That's not a lot of data. It's also really weird it runs quickly from other jobs and it's only the app triggering that is slow.

Do you have permissive security on? If so, have you tried -Integrated on Invoke-PSUScript? Mostly wondering because without it, the cmdlets effectively go out to the web server URL and communicate via that while integrated will use the backend gRPC port and I want to know if this a DB issue or something else.

Logs from the app and server might be good. Feel free to email those.

@bt-ndollimount
Copy link
Author

I don't think we have permissive set up. With our configuration, would that need to be set on all nodes? Also, our firewalls are extremely locked down so if that requires opening a new port, that could take some time...

Internal
4 job nodes
1 PostgreSQL host

External
Web node (doesn't run jobs due to lack of access to internal resources)

@adamdriscoll
Copy link
Member

The gRPC port is dynamic and already being used to communicate between the jobs and server so no extra ports need to be opened on the firewall. Switching to integrated just changes the communication from cmdlet -> HTTP 443\80 to cmdlet -> Dynamic Port. We did add the ability to configure the cmdlet security behavior in 5.2 in the Settings \ General page.

That said, I'm not totally convinced it's a communication issue and likely something else so it may not be going through the whole configuration of that. Just figured we could try it out if it was already setup. You can always try to include the -Integrated switch and it will just throw an error if it's not enabled.

@bt-ndollimount
Copy link
Author

That makes sense. I just tested with -Integrated with the same results and no error. Also when testing from a script, I made sure that the source script is running on the web node and it invokes it quickly.

I do have a long running job currently going, 16 hours and 40 minutes in, but right now that's just looping through a list, calculating and updating the list in-memory. The CPU usage on the database server is minimal, no more than 20% and on that particular job server, it's just the single core that's floating around 97%; I also don't have any other jobs going to that node as it's the "test" linux node.

@adamdriscoll adamdriscoll added this to the 5.2.1 milestone Jan 16, 2025
@adamdriscoll
Copy link
Member

I don't think the long running job should affect this. It seems something related to the app. I'm going to review our changes to apps over the last month to see if I can see anything that will give us a hint about this. Unfortunately, I don't think this will make it into the 5.2.1 release as we have some pressing issues help address with a release tomorrow.

That said, I realize this is impactful, so I won't hesitate to publish a 5.2.2 release sooner than expected to resolve it once we have it nailed down.

@bt-ndollimount
Copy link
Author

Okay, I have a pretty significant update and seem to have isolated where things are breaking down.

Recalling I said that running it from a script, it executes fine; in that script, the parameter value (userid) is hardcoded. In the app, we load all the users when the page loads and save them into a variable, $Page:communityUsers. I tested hardcoding the parameter value in there and it does execute. So I looked upward in the code.

The setup of the app, we have a dropdown that is populated with the users list emails. The button click, it pipes the $Page:communityUsers to a Where-Object to match the email in the users list to the selected item value (email), then grabs the ID property of the resulting item. This ID is the value used for the parameter on the Invoke-PSUScript. Below is that line where we get that.

$selectedUserId = ($Page:communityUsers | Where-Object { $_.Email -eq (Get-UDElement -Id 'selectedUser').Value }).UserId

When I isolate that line, it hangs. Breaking it apart, the following work:

Show-UDToast -Message $Page:communityUsers[0].Email
Show-UDToast -Message (Get-UDElement -Id 'selectedUser').Value

So it seems to be the part where the list is being piped to the Where-Object cmdlet that is causing it to hang. I tested using .Where instead and that has the same result. This is also isolated where the rest is commented out so it's only trying to do the loop to get the ID and then show a UDToast with the ID value.

Furthermore, the browser log and app log (same by the looks of it?) gets filled with the following and looks like it probably repeats the amount of times matching the amount of objects in $Page:communityUsers.

An error occurred: Cannot bind argument to parameter 'InputObject' because it is null.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Exception calling "SendWebSocketMessageWithResult" with "3" argument(s): "Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1737088373.377684654","description":"Error received from peer ipv4:127.0.0.1:39031","file":"/var/local/git/grpc/src/core/lib/surface/call.cc","file_line":953,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")"
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Cannot bind argument to parameter 'InputObject' because it is null.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Exception calling "SendWebSocketMessageWithResult" with "3" argument(s): "Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1737088373.380277372","description":"Error received from peer ipv4:127.0.0.1:39031","file":"/var/local/git/grpc/src/core/lib/surface/call.cc","file_line":953,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")"
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Cannot bind argument to parameter 'InputObject' because it is null.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Exception calling "SendWebSocketMessageWithResult" with "3" argument(s): "Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1737088373.382406039","description":"Error received from peer ipv4:127.0.0.1:39031","file":"/var/local/git/grpc/src/core/lib/surface/call.cc","file_line":953,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")"
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Cannot bind argument to parameter 'InputObject' because it is null.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Exception calling "SendWebSocketMessageWithResult" with "3" argument(s): "Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1737088373.384588934","description":"Error received from peer ipv4:127.0.0.1:39031","file":"/var/local/git/grpc/src/core/lib/surface/call.cc","file_line":953,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")"
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Cannot bind argument to parameter 'InputObject' because it is null.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Exception calling "SendWebSocketMessageWithResult" with "3" argument(s): "Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1737088373.386835139","description":"Error received from peer ipv4:127.0.0.1:39031","file":"/var/local/git/grpc/src/core/lib/surface/call.cc","file_line":953,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")"
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Cannot bind argument to parameter 'InputObject' because it is null.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

ud-dashboard.jsx:360 An error occurred: Cannot bind argument to parameter 'Message' because it is an empty string.
Endpoint: f81607bb-4b99-414a-b77a-923d877e7da3
Session: fe040b1f-618c-48da-b259-d8a697960269, User: ndollimount@beyondtrust.com

Then at the very end, you can see that it fails to show the toast message because the message is empty, of course 🥲.

@bt-ndollimount
Copy link
Author

Another update: I got it working...

So I made the following changes, breaking out the fetching of the selected user dropdown value into a variable and then used that variable in the Where filter. It seems the nesting of the that is what is breaking it.

$selectedUserEmail = (Get-UDElement -Id 'selectedUser').Value
$selectedUserId = $Page:communityUsers.Where({ $_.Email -eq $selectedUserEmail }).UserId
$userData = Invoke-PSUScript -ComputerGroup 'Job Nodes (Any)' -ComputerName localhost -AppToken $Secret:PSUToken -Wait -Script '*******************/*********************.ps1' -UserId $selectedUserId

@adamdriscoll
Copy link
Member

I wonder what changed in 5.2 to cause this. I don't see any changes for Get-UDElement. That said, the change you made is a big optimization because calling Get-UDElement in the loop was making a web socket call for each item.

I could try to track down the problem but I figure that, since you have it working, this can probably be marked resolved unless you notice anything else.

@bt-ndollimount
Copy link
Author

The only thing I'd be worried about is if there are other instances where having a nested check inside is valid and someone isn't aware of this bug, since theoretically it should work regardless of the performance of what I was using it for. Can you throw it into a "not super important but will get to it at some point" pile? 😁

@adamdriscoll
Copy link
Member

Sounds good. I'll see if I can review the changes between the release and nail it down.

@adamdriscoll adamdriscoll removed this from the 5.2.1 milestone Jan 17, 2025
@adamdriscoll
Copy link
Member

I didn't fix the actual error, but I did narrow down that the slowness is actually related to the logging in the apps. I did some optimization and was able to get my 4 second test down to the same speed as the -Silent test. There was effectively 3 seconds of overhead from logging that is now a couple milliseconds. This should really improve app performance in 5.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working PowerShell Universal Issue relates to PowerShell Universal. requires triage Issue has not yet been verified by the development team. v5 Version 5 issue.
Projects
None yet
Development

No branches or pull requests

2 participants