WIP: feat(realtime): print on several application nodes (shared wakeups, durable connection record, claim gate on any node) #36
Draft
james.maes
wants to merge 2 commits from
feat/multi-node-realtime-20261110 into main
pull from: feat/multi-node-realtime-20261110
merge into: QRun-IO:main
QRun-IO:main
QRun-IO:fix/delete-refusal-under-lock
QRun-IO:feat/delete-stations-printers-20261111
QRun-IO:fix/print-job-created-date-time
QRun-IO:feat/station-identity-20261110
QRun-IO:wip/native-auth-pins-20261107
QRun-IO:docs/release-1.1.2-20261106
QRun-IO:ci/gitea-registry-rc22-20261106
QRun-IO:docs/release-1.1.1-20261106
QRun-IO:fix/realtime-header-case-20261106
QRun-IO:docs/release-1.1.0-20261105
QRun-IO:feat/remove-printers-devices-main-20261105
QRun-IO:feat/printer-approval-20261105
QRun-IO:feat/remove-printers-devices-20261105
QRun-IO:docs/printing-release-closeout-20261104
QRun-IO:release/printing-1.0.0-20261103
QRun-IO:release/printing-cohort-20261101
QRun-IO:dependabot/maven/org.codehaus.mojo-exec-maven-plugin-3.6.4
QRun-IO:dependabot/maven/org.apache.maven.plugins-maven-surefire-plugin-3.6.1
QRun-IO:dependabot/maven/org.apache.maven.plugins-maven-checkstyle-plugin-3.6.0
QRun-IO:migration/cutoff-cf06815f30e1-b9a717
QRun-IO:migration/cutoff-7bedb1016fa9-a854c1
QRun-IO:migration/cutoff-5f74c0c69a55-38f8f8
QRun-IO:dependabot/maven/com.puppycrawl.tools-checkstyle-14.3.0
QRun-IO:dependabot/maven/com.kingsrook-qbit-build-parent-2.1.2
QRun-IO:dependabot/github_actions/github/codeql-action/upload-sarif-4.39.1
QRun-IO:dependabot/github_actions/github/codeql-action/init-4.39.1
QRun-IO:dependabot/github_actions/astral-sh/setup-uv-10.3.2
QRun-IO:dependabot/github_actions/KofTwentyTwo/standards/dot-github/workflows/pr.yml-1.2.1
QRun-IO:dependabot/github_actions/actions/setup-java-6.1.0
QRun-IO:chore/printing-quality-integration-20261101
QRun-IO:feat/small-label-tests-20261101
QRun-IO:archive/qrun-github-cutover-20261101/e08b3ef9c817/dependabot/github_actions/github/codeql-action/init-4.39.1
QRun-IO:migration/cutoff-e08b3ef9c817-b21637
QRun-IO:archive/qrun-github-cutover-20261101/de286e7eb67b/dependabot/github_actions/github/codeql-action/upload-sarif-4.39.1
QRun-IO:migration/cutoff-de286e7eb67b-06d241
QRun-IO:archive/qrun-github-cutover-20261101/cc4dc3dc2700/dependabot/github_actions/astral-sh/setup-uv-10.3.2
QRun-IO:migration/cutoff-cc4dc3dc2700-4e3528
QRun-IO:archive/qrun-github-cutover-20261101/2cec82bf4aa9/dependabot/github_actions/actions/setup-java-6.1.0
QRun-IO:migration/cutoff-2cec82bf4aa9-68d7f8
QRun-IO:archive/qrun-github-cutover-20261101/2a2eedc89f64/dependabot/github_actions/KofTwentyTwo/standards/dot-github/workflows/pr.yml-1.2.1
QRun-IO:migration/cutoff-2a2eedc89f64-2894f0
QRun-IO:archive/qrun-github-cutover-20261101/088692e3aefa/main
QRun-IO:archive/qrun-github-cutover-20261101/7bedb1016fa9/dependabot/maven/org.apache.maven.plugins-maven-surefire-plugin-3.6.1
QRun-IO:archive/qrun-github-cutover-20261101/5f74c0c69a55/dependabot/maven/org.apache.maven.plugins-maven-checkstyle-plugin-3.6.0
QRun-IO:archive/qrun-github-cutover-20261101/1db6a1163d5e/dependabot/github_actions/actions/setup-java-6.1.0
QRun-IO:archive/qrun-github-cutover-20261101/c8f8e016cf29/dependabot/maven/com.kingsrook-qbit-build-parent-2.1.2
QRun-IO:migration/cutoff-c8f8e016cf29-8a552b
QRun-IO:archive/qrun-github-cutover-20261101/cf06815f30e1/dependabot/maven/org.codehaus.mojo-exec-maven-plugin-3.6.4
QRun-IO:archive/qrun-github-cutover-20261101/985acc06d7e9/dependabot/maven/com.puppycrawl.tools-checkstyle-14.3.0
QRun-IO:migration/cutoff-985acc06d7e9-7f35cb
QRun-IO:archive/qrun-github-cutover-20261101/1a0964ef233d/main
QRun-IO:chore/server-source-standards-20261101
QRun-IO:archive/qrun-github-cutover-20261101/02b922aa11fa/main
QRun-IO:archive/qrun-github-cutover-20261101/4f962f7fbfd8/dependabot/maven/com.kingsrook-qbit-build-parent-2.1.2
QRun-IO:archive/qrun-github-cutover-20261101/8460edb60d58/dependabot/maven/org.apache.maven.plugins-maven-surefire-plugin-3.6.1
QRun-IO:archive/qrun-github-cutover-20261101/05c83067e182/dependabot/maven/com.puppycrawl.tools-checkstyle-14.3.0
QRun-IO:archive/qrun-github-cutover-20261101/b1b8f3b065b8/dependabot/maven/org.apache.maven.plugins-maven-checkstyle-plugin-3.6.0
QRun-IO:archive/qrun-github-cutover-20261101/e0ff82392398/dependabot/maven/org.codehaus.mojo-exec-maven-plugin-3.6.4
QRun-IO:feat/print-anything
QRun-IO:feat/record-printing
QRun-IO:archive/qrun-github-cutover-20261101/9128133f5cdd/dependabot/github_actions/actions/setup-java-6.1.0
QRun-IO:docs/installed-native-020-20261031
QRun-IO:dependabot/github_actions/KofTwentyTwo/standards/dot-github/workflows/codeql.yml-1.2.1
QRun-IO:dependabot/github_actions/actions/download-artifact-8.0.2
No Reviewers
Labels
Clear labels
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
java
question
secuurity
wontfix
Something isn't working
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
Pull requests that update GitHub Actions code
Good for newcomers
Extra attention is needed
This doesn't seem right
Pull requests that update java code
Further information is requested
This will not be worked on
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: QRun-IO/qbit-printing#36
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Draft. Do not merge, tag or release from this pull request without the owner's review.
Why
An application that runs as several nodes (pods) over one database could not print reliably. A device holds one realtime connection, on one node, and three things about it lived only in that JVM:
PrintingRealtimeBus.java:32(listeners),publish:77-91PrintingRealtimeBus.java:33(owners),PrintingRealtimeRoutes.java:66(channels)PrintingRealtimeRoutes.java:181-188(requireReadyreadschannels), per-connectionappliedState/appliedSettings:326-329Already shared and unchanged: job rows, events and the claim itself (
PrintingService.claimForJob, device rowFOR UPDATE; events are idempotent by event id), device control and settings revisions.What changes
PrintingRealtimeBus.join(instance)subscribes toRealtimeBus.get(instance): in memory on one node, the host's shared bus (Redis through qbit-redis) on several.publishdelivers to this JVM directly, then hands the wakeup to the shared bus through a bounded background worker, so the committing request never waits for the transport. The route provider joins when it starts; a host adds no wiring. Channelqbit-printingis not one a browser stream can subscribe to, and the message carries a tenant and a device identifier only.PrintingRealtimeConnections; four additive columns). Every statement is one guarded update: the newest connection takes the row, and only the connection named on the row can renew it, mark it ready or clear it.control_applied_revision = control_revision) and settings (realtime_applied_revision= the settings revision); otherwise 409, as before.PING, everyheartbeatIntervalSeconds: 20 by default, 5 at the least) the connection renews its lease and asks the database whether anything was committed that it has not been told (newer desired state or settings, a pending command, a job a claim would hand out now). If so it runs the ordinary wakeup. A wakeup the bus lost is late by at most one heartbeat interval. An idle heartbeat costs a few indexed reads and sends no frame. When the transport reports a gap (resync), every connection reconciles at once.Delivery guarantee
At least once to the device, idempotent acknowledgement, never two spooler submissions for one job:
OUTCOME_UNKNOWN, not reprinted.Proof:
PrintingClaimPostgresTeston PostgreSQL 17 (eight claims racing through two service instances, twelve rounds: one transition, one job, the next job untouched; six concurrent deliveries of one event: one row) andPrintingMultiNodeTest(claim and events over HTTP on the node without the socket).Tests
PrintingMultiNodeTest: two services, each with its own bus and route provider (its own sockets), one H2 database, one application realtime bus the test can switch to "drop" or "throw". A job accepted on A wakes the device connected to B; the claim is made over HTTP on A (409 before this change); events through either node; lease expiry and renewal; settings and pause changes close the gate on both nodes until applied; bus down, then found on the next heartbeat; a transport gap; replacement through another node with and without the bus; upgrade status mapping.PrintingRealtimeBusTest: exactly-once delivery across joined buses including the transport's echo, foreign and malformed messages ignored, join counting, a blocked or failing transport never delays or fails the publisher.PrintingClaimPostgresTest(Testcontainerspostgres:17-alpine; the suite now needs Docker, which the sharedcontainer-buildrunner provides): racing claims, concurrent acknowledgements, the connection record's guards, the reconciliation predicate, andinitialize()itself on PostgreSQL.Gates (seen)
4aa63ae,bash ./mvnw -B -Pui-demo verify(no skip flags; QRun artifacts from the Gitea registry): 215 tests, 0 failures, 0 errors, 1 skipped (the opt-in 10,000-job workload), Checkstyle 0 violations, Error Prone / NullAway-Werrorclean, guardrails pass.4aa63ae: Gitea run 7392 (maven / build (push)), success.42dff1fadds documentation only; its hosted run is recorded in a comment below.Release and adoption
mainby tag (docs/RELEASES.md; sharedci-workflowsMaven workflow).mainalready carries tagv1.2.0-RC.1with the same source as 1.1.1 / 1.1.2. After merge, tagv1.2.0-RC.2on the qualifiedmaincommit: the workflow publishescom.kingsrook.qbits:qbit-printing:1.2.0-RC.2and commits nothing back. The POM version is not changed here.docs/REALTIME.md; additive, the previous release keeps working on the new schema). Hosts that callinitialize()get them from it.Not verified
Two real processes behind a load balancer; a node dying mid-claim or mid-download; Redis as the transport (the test transport stands in for it;
RedisRealtimeBusis exercised only by its own repository's tests); clock skew between nodes beyond ordinary synchronization; a physical printer.🤖 Generated with Claude Code
Gates seen on head
42dff1f(documentation added on top of4aa63ae; no source or test change):bash ./mvnw -B -Pui-demo verify, no skip flags, QRun artifacts resolved from the QRun-IO Gitea registry: 215 tests, 0 failures, 0 errors, 1 skipped (the opt-in 10,000-job workload), Checkstyle 0 violations, BUILD SUCCESS. The same result was seen on4aa63ae.maven / build (push),-Pui-demo verifyon thecontainer-buildrunner, PostgreSQL 17 through Testcontainers): 215 tests, 0 failures, 0 errors, 1 skipped, success in 4m20s. Run 7392 on4aa63ae: success in 3m37s.🤖 Generated with Claude Code
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.