Skip to content

Commit cc24e1a

Browse files
committed
Merge remote-tracking branch 'origin/master' into cocalc-api-20250927
2 parents fc0d26a + 5d43ee5 commit cc24e1a

File tree

117 files changed

+3530
-4287
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+3530
-4287
lines changed

src/AGENTS.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -292,17 +292,10 @@ The `python/cocalc-api/` directory contains a Python client library for the CoCa
292292
- ✅ Complete project lifecycle management: create → start → test → stop → delete
293293
- ✅ All 14 tests passing with proper resource cleanup
294294

295-
# Important Instruction Reminders
296-
297-
- Do what has been asked; nothing more, nothing less
298-
- NEVER create files unless they're absolutely necessary for achieving your goal
299-
- ALWAYS prefer editing an existing file to creating a new one
300-
- REFUSE to modify files when the git repository is on the `master` or `main` branch
301-
- NEVER proactively create documentation files (`*.md`) or README files. Only create documentation files if explicitly requested by the User
302-
303295
# important-instruction-reminders
304-
Do what has been asked; nothing more, nothing less.
305-
NEVER create files unless they're absolutely necessary for achieving your goal.
306-
ALWAYS prefer editing an existing file to creating a new one.
307-
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
308-
ALWAYS ask questions if something is unclear. Only proceed to the implementation step if you have no questions left.
296+
- Do what has been asked; nothing more, nothing less.
297+
- NEVER create files unless they're absolutely necessary for achieving your goal.
298+
- ALWAYS prefer editing an existing file to creating a new one.
299+
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
300+
- ALWAYS ask questions if something is unclear. Only proceed to the implementation step if you have no questions left.
301+
- When modifying a file with a copyright banner at the top, make sure to fix/add the current year to indicate the copyright year.

src/packages/backend/conat/test/cluster/cluster-sticky-state.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
after,
44
defaultCluster as servers,
55
waitForConsistentState,
6+
wait,
67
addNodeToDefaultCluster,
78
} from "@cocalc/backend/conat/test/setup";
89
import { STICKY_QUEUE_GROUP } from "@cocalc/conat/core/client";
@@ -45,6 +46,9 @@ describe("ensure sticky state sync and use is working properly", () => {
4546
);
4647
// publishing causes a choice to be made and saved on servers[0]
4748
await clients[0].publish(`subject.${i}.foo`, "hello");
49+
expect(servers[0].sticky[`subject.${i}.*`]).not.toBe(undefined);
50+
// but no choice on servers[1]
51+
expect(servers[1].sticky[`subject.${i}.*`]).toBe(undefined);
4852
}
4953
});
5054

@@ -61,12 +65,51 @@ describe("ensure sticky state sync and use is working properly", () => {
6165
chosen = await Promise.race([p0(), p1()]);
6266
});
6367

68+
it(`sticky on servers[0] should have ${count} entries starting in "subject".`, async () => {
69+
const v = Object.keys(servers[0].sticky).filter((s) =>
70+
s.startsWith("subject."),
71+
);
72+
expect(v.length).toBe(count);
73+
});
74+
75+
it(`sticky on servers[1] should have no entries starting in "subject".`, async () => {
76+
const v = Object.keys(servers[1].sticky).filter((s) =>
77+
s.startsWith("subject."),
78+
);
79+
expect(v.length).toBe(0);
80+
});
81+
82+
it(`servers[1]'s link to servers[0] should *eventually* have ${count} entries starting in "subject."`, async () => {
83+
// @ts-ignore
84+
const link = servers[1].clusterLinksByAddress[servers[0].address()];
85+
let v;
86+
await wait({
87+
until: () => {
88+
v = Object.keys(link.sticky).filter((s) => s.startsWith("subject."));
89+
return v.length == count;
90+
},
91+
});
92+
expect(v.length).toBe(count);
93+
});
94+
6495
it("send message from clients[1] to each subject", async () => {
6596
for (let i = 0; i < count; i++) {
6697
await clients[1].publish(`subject.${i}.foo`);
6798
}
6899
});
69100

101+
// Sometimes this fails under very heavy load.
102+
// It's not a good test, because it probably hits some timeouts sometimes, and
103+
// it is testing internal structure/optimizations, not behavior.
104+
// Note also that minimizing sticky state computation is just an optimization so even if this test were failing
105+
// due to a bug, it might just mean things are slightly slower.
106+
// it(`sticky on servers[1] should STILL have no entries starting in "subject", since no choices had to be made`, async () => {
107+
// const v = Object.keys(servers[1].sticky).filter((s) =>
108+
// s.startsWith("subject."),
109+
// );
110+
// expect(v.length).toBe(0);
111+
// });
112+
70113
async function deliveryTest() {
71114
const sub = chosen == 0 ? subs0[0] : subs1[0];
72115

@@ -111,6 +154,17 @@ describe("ensure sticky state sync and use is working properly", () => {
111154
await waitForConsistentState(servers);
112155
});
113156

157+
it("double check the links have the sticky state", () => {
158+
for (const server of servers.slice(1)) {
159+
// @ts-ignore
160+
const link = server.clusterLinksByAddress[servers[0].address()];
161+
const v = Object.keys(link.sticky).filter((s) =>
162+
s.startsWith("subject."),
163+
);
164+
expect(v.length).toBe(count);
165+
}
166+
});
167+
114168
it(
115169
"in bigger, cluster, publish from every node to subject.0.foo",
116170
deliveryTest,
@@ -126,6 +180,14 @@ describe("ensure sticky state sync and use is working properly", () => {
126180
}
127181
sub.close();
128182
});
183+
184+
it("unjoining servers[0] from servers[1] should transfer the sticky state to servers[1]", async () => {
185+
await servers[1].unjoin({ address: servers[0].address() });
186+
const v = Object.keys(servers[1].sticky).filter((s) =>
187+
s.startsWith("subject."),
188+
);
189+
expect(v.length).toBe(count);
190+
});
129191
});
130192

131193
afterAll(after);

src/packages/backend/conat/test/setup.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export async function waitForConsistentState(
206206
// now look at everybody else's view of servers[i].
207207
// @ts-ignore
208208
const a = servers[i].interest.serialize().patterns;
209+
const b = servers[i].sticky;
209210
const hashServer = servers[i].hash();
210211
for (let j = 0; j < servers.length; j++) {
211212
if (i != j) {
@@ -219,8 +220,9 @@ export async function waitForConsistentState(
219220
}
220221
const hashLink = link.hash();
221222
const x = link.interest.serialize().patterns;
223+
const y = link.sticky;
222224
const showInfo = () => {
223-
for (const type of ["interest"]) {
225+
for (const type of ["interest", "sticky"]) {
224226
console.log(
225227
`server stream ${type}: `,
226228
hashServer[type],
@@ -252,6 +254,8 @@ export async function waitForConsistentState(
252254
j,
253255
serverInterest: a,
254256
linkInterest: x,
257+
serverSticky: b,
258+
linkSticky: y,
255259
});
256260
};
257261
if (!isEqual(hashServer, hashLink)) {

src/packages/backend/conat/test/sync/dkv-merge.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pnpm test ./dkv-merge.test.ts
99

1010
import { dkv as createDkv } from "@cocalc/backend/conat/sync";
1111
import { once } from "@cocalc/util/async-utils";
12-
import { diff_match_patch } from "@cocalc/util/dmp";
12+
import { DiffMatchPatch } from "@cocalc/util/dmp";
1313
import { before, after } from "@cocalc/backend/conat/test/setup";
1414

1515
beforeAll(before);
@@ -118,7 +118,7 @@ describe("test a trivial merge conflict resolution function", () => {
118118
});
119119

120120
describe("test a 3-way merge of strings conflict resolution function", () => {
121-
const dmp = new diff_match_patch();
121+
const dmp = new DiffMatchPatch();
122122
const threeWayMerge = (opts: {
123123
prev: string;
124124
local: string;

src/packages/backend/conat/test/sync/open-files.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ to open so they can fulfill their backend responsibilities:
88
99
DEVELOPMENT:
1010
11-
pnpm test `pwd`/open-files.test.ts
11+
pnpm test ./open-files.test.ts
1212
1313
*/
1414

src/packages/backend/data.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,28 @@ export function sslConfigToPsqlEnv(config: SSLConfig): PsqlSSLEnvConfig {
171171

172172
export const root: string = process.env.COCALC_ROOT ?? determineRootFromPath();
173173
export const data: string = process.env.DATA ?? join(root, "data");
174+
175+
// Database Config
174176
export const pguser: string = process.env.PGUSER ?? "smc";
175177
export const pgdata: string = process.env.PGDATA ?? join(data, "postgres");
176178
export const pghost: string = process.env.PGHOST ?? join(pgdata, "socket");
177179
export const pgssl = sslConfigFromCoCalcEnv();
178180
export const pgdatabase: string =
179181
process.env.SMC_DB ?? process.env.PGDATABASE ?? "smc";
182+
183+
// Database healthchecks
184+
export const pgConcurrentWarn: number = parseInt(
185+
process.env.COCALC_HEALTH_PG_CONCURRENT_WARN ?? "300",
186+
);
187+
188+
// Hub server configuration
189+
export const hubHostname: string =
190+
process.env.COCALC_HUB_HOSTNAME ?? "127.0.0.1";
191+
export const agentPort: number = parseInt(process.env.COCALC_AGENT_PORT ?? "0");
192+
180193
export const projects: string =
181194
process.env.PROJECTS ?? join(data, "projects", "[project_id]");
195+
182196
export const secrets: string = process.env.SECRETS ?? join(data, "secrets");
183197

184198
// Where the sqlite database files used for sync are stored.

0 commit comments

Comments
 (0)