Not sure where else to put this, but @adam , does our license cover implementation assistance for solutions that are particular to the platform? I’m running into some bumper-car-esque scenarios where I’m rubbing against the limitations of the platform and to continue properly I think I’ll need someone to outline a little more about how the platform works under the hood.
We don’t include professional services in the license cost but do bill separately for them. It’s usually hours per project. Email support if you are interested.
If you have specific implementation details of PSU, you’d like me to cover here, let me know.
Thanks!
I’ll keep it brief - as some (who may stumble across this) may be aware, I’m running a TcpListener
under my app to provide event-driven execution of some rendering components of the particular app as it receives data from an event hub.
The issue I’m having is that the finally { ... }
block is not running to stop the TcpListener when the session is discarded (i.e. by a page refresh). This leaves the synchronous listening loop open, as there is a $TcpListener.AcceptTcpClient()
running within that will never receive an event when the session is discarded. This means that every new session requires a restart of the TcpListener
to account for the handler appearing in another place as well that it needs to respond to.
My natural next step was to pursue asynchronous handling, in this case via BeginAcceptTcpClient()
and EndAcceptTcpClient
, using $asyncResult.AsyncWaitHandle.WaitOne()
within the loop to maintain a dynamic wait and allow for support of cancellation tokens I could issue in the background to restart the server across sessions automatically when needed. I had the entire thing recreated, but as soon as I sent an event over, the app (not the entire server, interestingly; just the one app) crashed with a gRPC error that it could not connect to all addresses. My suspicion is that I was interacting with something native to SignalR’s operation that caused a conflict.
A better solution to this would be a TcpListener that I could open in some sort of “management layer” underneath the app that would maintain exactly one instance of itself, manageable within the app and with full access to rerender the components as needed. The goal here would be that the listener would actually start regardless of whether a session was open or not, and allow events to be received without throwing connectivity errors.
Can you point me in the right direction, even if that’s just to give up and move on?
The finally issue is actually a bug. We need to force the PS runspace execution to halt and it’s not doing that so the finally is not being called. I worry a bit that the execution is still just taking place in the background.
In terms of starting something no matter what, I would look into using a scheduled endpoint. This would start up in the background, even if no user is connected.
$Schedule = New-UDEndpointSchedule -Every 1 -Hour
$Endpoint = New-UDEndpoint -Schedule $Schedule -Endpoint {
# Skip if already there
if ($Cache:Listener) { return }
# Use the cache to ensure the object reference is maintained in memory
$Cache:Listener = # Create listener
}
If you run your app in an external environment, like PS7, then the listener will automatically shut down when you stop your app or restart it. I think the schedule endpoint will run once right away and then again in an hour but you might need to play with it. If you check for the existence of the listener, it doesn’t matter too much if you run it frequently since it won’t be doing much.
Okay! I like that idea! Will the endpoint have access to Set-UDElement
within the app? I’m presuming I can import the functions defined under its module without issue. Additionally, where would I store the scheduled endpoint definition? i.e. within the app, .universal\??.ps1
, etc?
Edit: Would you like me to write up a GH issue for the finally
bug?
You need to include this in your app’s main file. It can be before you call New-UDApp. You will have access to Set-UDElement but you will only be able to broadcast since the SessionId and PageId variables are not set in the background endpoint. You could send them in and assign the variables to make it work.
$SessionId = 'xyz'
$PageId = 'xyz'
Set-UDElement -Id 'test123'
Sure!
Broadcasting is the goal; this will be primarily focused on a live-updating display of a large number of datasets where session synchronization is desired between them. Session-specific actions can be handled in a more “traditional” manner. With that in mind, do I need the SessionId
and PageId
for them anyways? Sorry I’d rather be specific and safe than sorry and sorry.
I apologize @adam , I have one further question - I’m setting the endpoint
as a variable outside the New-UDApp
call; if I then declare this endpoint within the app are we back where we started, in which case I need to just explicitly declare the endpoint prior to the app declaration?
To illustrate:
$endpoint = New-UDEndpoint -Schedule $sch -Endpoint $scriptBlock
New-UDApp @params -Content {
$endpoint
<#
... app rendering
#>
}
or
New-UDEndpoint -Schedule $sch -Endpoint $scriptBlock
New-UDApp @params -Content {
<#
... app render
#>
}
The question here boils down to: does the app’s Content
block only load when a session is opened, or does it load as soon as the app is started, at which point any rendering waits until a session starts?
@adam I apologize for the necro, but I’m struggling with this.
My TcpListener still fails after a refresh and requires the listener to be restarted. After restarting the listener (by clearing the cache variable and invoking the endpoint), I get a separate error regarding something calling a register
method with a null key that I also can’t track down.
Using the scheduled endpoint, after a refresh, calling the function confirms the received data but the following happens when trying to call Set-UDElement -Broadcast
:
ud-dashboard.jsx:360 [callback]SocketException: Exception calling "SendWebSocketMessageWithResult" with "3" argument(s): "Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1741706297.635000000","description":"Error received from peer ipv4:127.0.0.1:64168","file":"..\..\..\src\core\lib\surface\call.cc","file_line":953,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")"
at <ScriptBlock>, <No file>: line 144
at <ScriptBlock>, <No file>: line 1
I can’t find any documentation either in the docs or within the module code for managing sessions with the manner you described before, since it seems we’ll have to move forward with that. Can you explain a little more in detail how I am to define the session and then pass it to the endpoint? I’m not sure where I should specify $SessionId
, much less where then to use it, or even if I need to explicitly use it or if it’s implicitly called when available by the relevant cmdlets.
I’m gonna break this out into its own thing
I think it would be best if I made an example of what I’m mentioning because, while in theory, all this should work, I might be missing something or not explaining it well.
I’ve started a new thread for this here to avoid the necro; I’ll respond there.