A completed module process reaches only the session whose UUID was supplied to findSession. Perspective provides no single module API that broadcasts a UserScopeMessageEvent to every active session. Choose either project-level messaging for UI work or controlled per-session fan-out when the event must originate inside the module.
Available Messaging Approaches
| Approach | Best fit | Addressing | Main constraint |
|---|---|---|---|
| Known-session delivery | One identified session | UUID passed to findSession
|
Does not broadcast |
| Session-monitor fan-out | Module-level event delivery to every active session | Enumerate PerspectiveSessionInfo, recover each id, then resolve the session |
The information wrapper exposes no ID accessor |
| Gateway project messaging | Changing Perspective components or properties | Send work to the Gateway project, then use system.perspective.sendMessage
|
Requires project message-handling logic |
Before anything else, confirm whether the module must post UserScopeMessageEvent directly. If the finished process merely needs to trigger session UI logic, keep that logic in the Perspective project and route a project message through system.util.sendMessage. That function can send to clients under the Gateway or to a project within the Gateway. The project-side utility can then call system.perspective.sendMessage for the required session-scope changes.
Recommended Architecture
Use the project-level route for component and property updates. It separates module processing from Perspective view structure and keeps session behavior in project resources. Define one message contract containing the completion state and the data that the receiving handler needs. Do not pass module objects or mutable Java state across that boundary.
- Define a message type for the process-complete event and a payload with stable, serializable fields.
- Configure a Gateway project message handler to receive the module notification.
- Have that handler call
system.perspective.sendMessagewith the project and session routing required by the application. - Add a Perspective message handler that matches the message type and performs the component or property update.
- Test one active session first. Do not move on until the handler receives the expected payload and changes only its intended targets.
- Open multiple sessions and confirm that the selected routing reaches each intended session once.
Use direct fan-out only when the Java module specifically requires access to each session event manager. There is no separate broadcast primitive; broadcasting means taking a snapshot of the active sessions and submitting one event to each session queue.
Direct Module Fan-Out Procedure
PerspectiveSessionMonitor.getSessionInfos() supplies a list of PerspectiveSessionInfo objects, but the wrapper has no public ID accessor. Two extraction methods are possible: encode each object with PerspectiveSessionInfo.SimplifiedGsonEncoder and read its JSON id member, or access the ID field reflectively. Prefer the encoder approach because the ID representation is already used for Perspective JSON encoding and the compatibility dependency can be isolated in one adapter.
gson = PerspectiveModule.createPerspectiveCompatibleGson(b -> {
b.registerTypeHierarchyAdapter(
PerspectiveSessionInfo.class,
new PerspectiveSessionInfo.SimplifiedGsonEncoder());
});
- Create the compatible Gson instance once during module initialization. Do not rebuild it for every completion event.
- Call
getSessionInfos()when the process completes and treat the returned list as a point-in-time snapshot. - Encode each
PerspectiveSessionInfo, read theidmember, and convert it to the UUID type accepted byfindSession. Reject a missing or malformed ID and log that entry without stopping the remaining deliveries. - Call
findSessionimmediately before delivery. A session can close after enumeration, so an empty result is a normal race rather than a reason to abort the broadcast. - Create the
UserScopeMessageEventand submit itspostoperation through that session's queue. - Record the snapshot count, resolved-session count, queued count, and skipped count. Do not report success until every enumerated entry has reached one of those outcomes.
public void sendPerspectiveSessionMessage(
UUID sessionId, String messageType, PyDictionary payload) {
var session = perspectiveContext.getSessionMonitor().findSession(sessionId);
if (session.isEmpty()) {
log.error(
"No active Perspective session with id {} found. Message of type {} not sent.",
sessionId, messageType);
return;
}
var message = new UserScopeMessageEvent(messageType, payload);
session.get().queue().submit(
() -> session.get().getEventManager().post(message));
}
Delivery Mechanism
findSession resolves one live session from one UUID. The session queue is the execution boundary: submitting the task prevents the module's process-completion thread from directly posting into session state. The event manager then dispatches the event inside that session.
Enumeration and delivery are not atomic. A session may disappear between getSessionInfos() and findSession, while a new session may open after the snapshot and receive nothing. If every future session also needs the completed state, store the process result separately and have sessions read it during startup; a transient broadcast alone cannot provide late-join behavior.
Do not mutate a shared PyDictionary after queue submission. Multiple asynchronous queue tasks may observe the same object. Build the payload completely before fan-out or create a separate payload instance for each queued event when any receiver could modify it.
Verification Checks
| Check | Confirmation | Failure indication |
|---|---|---|
| Enumeration | Snapshot count matches the active sessions visible at test time | Monitor scope or lifecycle timing is wrong |
| ID extraction | Every encoded entry contains a valid id
|
Encoder behavior changed or an unexpected object was returned |
| Resolution |
findSession returns a session for each still-active UUID |
The session closed during fan-out or the extracted value is invalid |
| Queue submission | One task is queued per resolved session | An exception stopped iteration or queue access failed |
| Handler execution | Each target session records one matching message type | Handler scope, message type, or payload contract differs |
- Start with one session and compare the extracted UUID with the UUID already proven by the single-session method.
- Open a second session and run one completion event. Confirm one handler invocation in each session.
- Close one session during repeated testing. Confirm that an empty
findSessionresult is logged and the remaining sessions still receive the event. - Open a session after the event. Confirm that it receives the state through the application's startup-state path if late joiners are required.
Recurring Failure Modes
| Symptom | Cause | Correction |
|---|---|---|
| Only one session updates | The module still calls the original method with one UUID | Iterate the monitor snapshot and invoke the per-session path for every resolved UUID |
| No usable IDs appear |
PerspectiveSessionInfo has no accessor |
Use the compatible Gson adapter and read id; keep the conversion in one module class |
| Some sessions are skipped intermittently | Sessions close between enumeration and lookup | Continue after an empty lookup and account for it as a skipped session |
| Upgrade breaks extraction | The encoder or reflected field is an internal compatibility dependency | Run an upgrade test that enumerates, extracts, resolves, and delivers before deploying the module |
| Late sessions miss completion | The event is transient | Persist the result and load it when a session starts |
Reflection avoids JSON conversion but couples the module directly to a non-public field name and runtime-access rules. If reflection is retained, isolate it behind one function and fail the compatibility test during module startup rather than discovering the problem after a process completes.
FAQ
Why does my module message reach only one Perspective session?
findSession resolves only the UUID supplied to it. To reach all active sessions, take the getSessionInfos() snapshot, recover each id, resolve each session, and queue one event per resolved session.
Why can I not read the session ID from PerspectiveSessionInfo?
The wrapper returned by getSessionInfos() has no ID accessor. Encode it with PerspectiveSessionInfo.SimplifiedGsonEncoder and read the JSON id, or use reflection with the added upgrade risk.
How do I verify a Perspective session broadcast?
Open at least two sessions, trigger one completion, and confirm one matching handler invocation per session. Then close one session during a test and verify that its failed lookup is counted while every remaining session still receives exactly one event.