Push the real per-project sequence number instead of hardcoded event tags - #47
Merged
Conversation
Viewers can now open GET /data/projects/{projectId}/events as a
server-sent-events stream. A registry keeps the open streams per project
and fans incoming payloads out on an executor, evicting dead emitters on
send failure; a scheduled comment heartbeat keeps idle streams alive
through nginx's 60-second proxy read timeout, and the path rides the
existing /data/ route so no proxy changes are needed (response buffering
is disabled per-response with X-Accel-Buffering).
Authorization at stream-open mirrors the STOMP subscribe check: the
caller must hold ViewProject on the project, with identity resolution
isolated in one seam that #305 replaces with a short-lived redeemable
ticket. The Last-Event-ID header (and a lastEventId query fallback for
fresh loads, which never send the header) is plumbed to a catch-up seam
whose real replay implementation arrives with the sequence work; streams
also get a finite lifetime so every reconnect re-authorizes.
Nothing publishes into the registry yet — the broker bridge lands
separately once events carry real sequence numbers (#296).
Part of protegeproject/webprotege-gwt-ui#304
(epic protegeproject/webprotege-gwt-ui#303).
…tags Every pushed event window carried the hardcoded tags 0 to 1, so after the first window advanced a client's bookmark past 1, the client's stale- window guard dropped every subsequent push — the websocket went silent after one event and delivery quietly fell back to the 10-second poll. That is the direct cause of the ~9.5s median UI latency measured in the epic's baseline. The emitter now consumes the sequenced event that the event-history service publishes after archiving a change bundle, and pushes it with truthful tags: startTag is the previous ordinal, endTag is the bundle's ordinal. Because that event is emitted strictly after persistence, anything pushed is already fetchable through the pull path. The wire payload shape is unchanged, so the client deserializes it exactly as before. The error log now includes the exception so failures are diagnosable. The gateway half of protegeproject/webprotege-gwt-ui#296 (part of protegeproject/webprotege-gwt-ui#303).
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gateway half of protegeproject/webprotege-gwt-ui#296 (part of protegeproject/webprotege-gwt-ui#303). Stacked on #46; retarget to
epic/303-sseonce that merges. Producer side: protegeproject/webprotege-event-history-service#33.Every pushed window carried hardcoded tags
0→1, so the client's stale-window guard dropped every push after the first — the socket went silent and delivery fell back to the 10s poll (the direct cause of the ~9.5s median UI latency in the epic's baseline). The emitter now consumes the post-persistenceSequencedPackagedProjectChangeEventand pushes truthful tags (startTag = seq-1, endTag = seq). Wire payload shape unchanged — the client deserializes exactly as before (pinned by a JSON field-name test, since there is no shared DTO artifact). Also includes the exception in the error log (previously dropped).Tests: 4/4 (handler test migrated to JUnit 5 so it actually executes — this repo skips JUnit 4 silently).