diff --git a/apps/admin/package.json b/apps/admin/package.json index 37517bfa..d369d060 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,6 +1,6 @@ { "name": "admin", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "Admin UI for Pace", "license": "AGPL-3.0", diff --git a/apps/api/internal/live/relay_test.go b/apps/api/internal/live/relay_test.go index be1e9901..0555e15e 100644 --- a/apps/api/internal/live/relay_test.go +++ b/apps/api/internal/live/relay_test.go @@ -54,25 +54,31 @@ func twoServers(t *testing.T) (*fakeAPI, *Server, string, *Server, string) { return api, first, firstAddress, second, secondAddress } -// waitForSubscription blocks until a server's relay is carrying a document, which is what makes a publish from the other one reach it. -func waitForSubscription(t *testing.T, server *Server, name string) { +// waitForSubscribers blocks until Redis itself reports that `want` relays are carrying a document, which is what makes a publish from one of them reach the others. +// +// Redis is asked rather than the relay because the relay registers a subscription in its own map before Redis has confirmed it: Subscribe only queues the command, and the confirmation it waits for comes several statements later. Polling that map therefore returns while a publish can still be dropped -- pub/sub keeps no backlog, so a message published before the far server is confirmed is not late, it is gone. That window is narrow enough to never open on an idle machine and wide enough to open on a loaded CI runner, which is what made this test flake with zero frames received in ten seconds. +func waitForSubscribers(t *testing.T, server *Server, name string, want int64) { t.Helper() + channel := documentChannel(name) deadline := time.Now().Add(5 * time.Second) + var seen int64 for time.Now().Before(deadline) { - server.relay.mu.Lock() - _, subscribed := server.relay.subscriptions[name] - server.relay.mu.Unlock() - if subscribed { + counts, err := server.relay.client.PubSubNumSub(context.Background(), channel).Result() + if err != nil { + t.Fatalf("ask redis who is subscribed to %s: %v", channel, err) + } + seen = counts[channel] + if seen >= want { return } time.Sleep(5 * time.Millisecond) } - t.Fatalf("the relay never subscribed to %s", name) + t.Fatalf("redis reports %d subscriber(s) on %s after five seconds, want %d", seen, channel, want) } // TestAChangeOnOneServerReachesTheOther is the whole reason the relay exists: two people editing one page do not necessarily reach the same server. func TestAChangeOnOneServerReachesTheOther(t *testing.T) { - _, first, firstAddress, second, secondAddress := twoServers(t) + _, first, firstAddress, _, secondAddress := twoServers(t) here := dial(t, firstAddress) here.authenticate(testPageID, "a-user") @@ -82,9 +88,8 @@ func TestAChangeOnOneServerReachesTheOther(t *testing.T) { there.authenticate(testPageID, "a-user") there.readUntil(hocuspocus.MessageAuth) - // Being authenticated is not the same as being subscribed. The document is taken up after the handshake, on the server's own schedule, and Redis pub/sub keeps no backlog -- so a change published before the far server has confirmed its subscription is not delayed, it is discarded. On a loaded machine that is the whole test: it saw zero frames in ten seconds while both servers were working correctly. - waitForSubscription(t, first, testPageID) - waitForSubscription(t, second, testPageID) + // Being authenticated is not the same as being subscribed. The document is taken up after the handshake, on the server's own schedule, and Redis pub/sub keeps no backlog -- so a change published before the far server has confirmed its subscription is not delayed, it is discarded. On a loaded machine that is the whole test: it saw zero frames in ten seconds while both servers were working correctly. Both servers share one Redis, so one question covers the pair. + waitForSubscribers(t, first, testPageID, 2) document, err := ydoc.ParseHTML("
across the cluster
") if err != nil { diff --git a/apps/space/package.json b/apps/space/package.json index 8ed7a530..2b0da047 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -1,6 +1,6 @@ { "name": "space", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/apps/web/package.json b/apps/web/package.json index bfe5be69..fd904fb4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/package.json b/package.json index 4a398755..8096822e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pace", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "Project management that keeps pace with the work", "license": "AGPL-3.0", diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 9395c8ea..00108156 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@pace/codemods", - "version": "1.4.44", + "version": "1.4.45", "private": true, "scripts": { "check:format": "oxfmt --check .", diff --git a/packages/constants/package.json b/packages/constants/package.json index 9bf4c1e5..a4932270 100644 --- a/packages/constants/package.json +++ b/packages/constants/package.json @@ -1,6 +1,6 @@ { "name": "@pace/constants", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/editor/package.json b/packages/editor/package.json index baf4e0ba..af9dfb71 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@pace/editor", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "Core Editor that powers Pace", "keywords": [ diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 2db8c109..b866842f 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@pace/hooks", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "React hooks that are shared across multiple apps internally", "license": "AGPL-3.0", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 224127e9..63e887e7 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@pace/i18n", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "I18n shared across multiple apps internally", "license": "AGPL-3.0", diff --git a/packages/propel/package.json b/packages/propel/package.json index 3abd01a9..d9e0a5c3 100644 --- a/packages/propel/package.json +++ b/packages/propel/package.json @@ -1,6 +1,6 @@ { "name": "@pace/propel", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/services/package.json b/packages/services/package.json index 1fad9afc..037e1283 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -1,6 +1,6 @@ { "name": "@pace/services", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/shared-state/package.json b/packages/shared-state/package.json index 00733efe..c85b3a00 100644 --- a/packages/shared-state/package.json +++ b/packages/shared-state/package.json @@ -1,6 +1,6 @@ { "name": "@pace/shared-state", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "Shared state shared across multiple apps internally", "license": "AGPL-3.0", diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index 6bc9b245..eb830140 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@pace/tailwind-config", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "common tailwind configuration across monorepo", "license": "AGPL-3.0", diff --git a/packages/types/package.json b/packages/types/package.json index 1f8d58f5..70ffe4e6 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@pace/types", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index fc2d195f..04a92042 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@pace/typescript-config", - "version": "1.4.44", + "version": "1.4.45", "private": true, "license": "AGPL-3.0", "files": [ diff --git a/packages/ui/package.json b/packages/ui/package.json index fa4f9895..6c81f4ab 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@pace/ui", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "UI components shared across multiple apps internally", "license": "AGPL-3.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 049b10dc..ec720be3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@pace/utils", - "version": "1.4.44", + "version": "1.4.45", "private": true, "description": "Helper functions shared across multiple apps internally", "license": "AGPL-3.0",