Skip to content

Commit 9967535

Browse files
authored
Merge pull request #5129 from cardstack/worktree-flaky-sf-harness-public-port-eaddrinuse
Hold the public realm-server port across the SF harness startup gap
2 parents a42e25f + f1caada commit 9967535

6 files changed

Lines changed: 406 additions & 49 deletions

File tree

packages/realm-test-harness/src/api.ts

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
type FactoryRealmTemplate,
5050
type FactorySupportContext,
5151
type FactoryTestContext,
52+
type PortReservation,
5253
type RealmAction,
5354
type RealmConfig,
5455
type StartedFactoryRealm,
@@ -82,22 +83,33 @@ export { startFactorySupportServices };
8283

8384
export async function startFactoryGlobalContext(
8485
options: FactoryRealmOptions,
86+
internal: { publicPortReservation?: PortReservation } = {},
8587
): Promise<FactoryGlobalContextHandle> {
8688
return await logTimed(harnessLog, 'startFactoryGlobalContext', async () => {
8789
let realms = resolveRealms(options.realms);
88-
let realmServerURL = await resolveFactoryRealmServerURL(
89-
options.realmServerURL,
90-
options.compatRealmServerPort,
91-
);
90+
let { url: realmServerURL, portReservation } =
91+
await resolveFactoryRealmServerURL(
92+
options.realmServerURL,
93+
options.compatRealmServerPort,
94+
);
95+
// Prefer a holder the caller already owns (startFactoryRealmServer
96+
// resolved + held the port before delegating here). Our own
97+
// portReservation is undefined in that case, since options.realmServerURL
98+
// is set.
99+
let publicPortReservation =
100+
internal.publicPortReservation ?? portReservation;
92101
let primaryRealmURL = realmURLWithinServer(realmServerURL, realms[0].path);
93102
let support = await startFactorySupportServices();
94103
try {
95-
let template = await ensureFactoryRealmTemplate({
96-
...options,
97-
realms,
98-
realmServerURL,
99-
context: support.context,
100-
});
104+
let template = await ensureFactoryRealmTemplate(
105+
{
106+
...options,
107+
realms,
108+
realmServerURL,
109+
context: support.context,
110+
},
111+
{ publicPortReservation },
112+
);
101113

102114
let context: FactoryTestContext = {
103115
...support.context,
@@ -116,6 +128,12 @@ export async function startFactoryGlobalContext(
116128
} catch (error) {
117129
await support.stop();
118130
throw error;
131+
} finally {
132+
// The public port is only bound transiently, by the template build's
133+
// compat proxy. The global context never holds it once built, so
134+
// release the holder (idempotent: the build already released it just
135+
// before binding; on a cache hit it was never consumed).
136+
await publicPortReservation?.release().catch(() => undefined);
119137
}
120138
});
121139
}
@@ -132,17 +150,29 @@ function resolveRealms(realms: RealmConfig[]): RealmConfig[] {
132150

133151
export async function ensureFactoryRealmTemplate(
134152
options: FactoryRealmOptions & { forceRebuild?: boolean },
153+
internal: { publicPortReservation?: PortReservation } = {},
135154
): Promise<FactoryRealmTemplate> {
136155
return await logTimed(harnessLog, 'ensureFactoryRealmTemplate', async () => {
137156
let realms = resolveRealms(options.realms);
138157
let contextRealmServerURL =
139158
options.context && hasTemplateDatabaseName(options.context)
140159
? new URL(options.context.realmServerURL)
141160
: undefined;
142-
let realmServerURL = await resolveFactoryRealmServerURL(
143-
options.realmServerURL ?? contextRealmServerURL,
144-
options.compatRealmServerPort,
145-
);
161+
let { url: realmServerURL, portReservation } =
162+
await resolveFactoryRealmServerURL(
163+
options.realmServerURL ?? contextRealmServerURL,
164+
options.compatRealmServerPort,
165+
);
166+
// The effective public-port holder: the caller's delegated reservation
167+
// when present, otherwise the one we freshly allocated above (e.g. the
168+
// cache:prepare path). On the build path startIsolatedRealmStack
169+
// releases it right before the compat proxy binds; we release it again
170+
// at the early (cache-hit) and finally exits as an idempotent backstop.
171+
// Releasing the *effective* holder (not just our own) matters when a
172+
// caller delegates it but the build throws before startIsolatedRealmStack
173+
// takes ownership — otherwise the delegated holder socket leaks.
174+
let publicPortReservation =
175+
internal.publicPortReservation ?? portReservation;
146176
let primaryRealmURL = realmURLWithinServer(realmServerURL, realms[0].path);
147177
let realmsHash = hashRealms(realms);
148178
let cacheKey = hashString(
@@ -163,6 +193,9 @@ export async function ensureFactoryRealmTemplate(
163193
hasTemplateDatabase &&
164194
cachedTemplateMetadata
165195
) {
196+
// No build, so nothing downstream will consume the holder — release
197+
// the effective one (our own or a caller's delegated reservation).
198+
await publicPortReservation?.release().catch(() => undefined);
166199
return {
167200
cacheKey,
168201
templateDatabaseName,
@@ -211,6 +244,7 @@ export async function ensureFactoryRealmTemplate(
211244
context,
212245
cacheKey,
213246
templateDatabaseName,
247+
publicPortReservation,
214248
});
215249
writePreparedTemplateMetadata({
216250
realmDir: realms[0].dir,
@@ -231,6 +265,11 @@ export async function ensureFactoryRealmTemplate(
231265
} finally {
232266
configureLogger(originalLogLevels);
233267
await withSilentConsole(async () => ownedSupport?.stop());
268+
// Backstop for the effective holder (our own or a caller's delegated
269+
// reservation): a no-op once startIsolatedRealmStack has released it
270+
// before the compat-proxy bind, but covers a throw before the build
271+
// reached that point (e.g. a Postgres drop/clone failure).
272+
await publicPortReservation?.release().catch(() => undefined);
234273
}
235274
});
236275
}
@@ -297,10 +336,11 @@ export async function startFactoryRealmServer(
297336
existingContext && hasTemplateDatabaseName(existingContext)
298337
? new URL(existingContext.realmServerURL)
299338
: undefined;
300-
let realmServerURL = await resolveFactoryRealmServerURL(
301-
options.realmServerURL ?? contextRealmServerURL,
302-
options.compatRealmServerPort,
303-
);
339+
let { url: realmServerURL, portReservation: publicPortReservation } =
340+
await resolveFactoryRealmServerURL(
341+
options.realmServerURL ?? contextRealmServerURL,
342+
options.compatRealmServerPort,
343+
);
304344
let primaryRealmURL = realmURLWithinServer(
305345
realmServerURL,
306346
primaryRealm.path,
@@ -311,24 +351,33 @@ export async function startFactoryRealmServer(
311351
let ownedGlobalContext: FactoryGlobalContextHandle | undefined;
312352
let context = existingContext;
313353
if (!context) {
314-
ownedGlobalContext = await startFactoryGlobalContext({
315-
...options,
316-
realms,
317-
realmServerURL,
318-
});
354+
// Hand the public-port holder to the template build so it's held
355+
// across that build's port allocations and released right before its
356+
// compat-proxy binds.
357+
ownedGlobalContext = await startFactoryGlobalContext(
358+
{
359+
...options,
360+
realms,
361+
realmServerURL,
362+
},
363+
{ publicPortReservation },
364+
);
319365
context = ownedGlobalContext.context;
320366
}
321367

322368
if (!templateDatabaseName) {
323369
templateDatabaseName = hasTemplateDatabaseName(context)
324370
? context.templateDatabaseName
325371
: (
326-
await ensureFactoryRealmTemplate({
327-
...options,
328-
realms,
329-
realmServerURL,
330-
context,
331-
})
372+
await ensureFactoryRealmTemplate(
373+
{
374+
...options,
375+
realms,
376+
realmServerURL,
377+
context,
378+
},
379+
{ publicPortReservation },
380+
)
332381
).templateDatabaseName;
333382
}
334383

@@ -380,6 +429,11 @@ export async function startFactoryRealmServer(
380429
}
381430
}
382431

432+
// The template build (above) already released this holder before its
433+
// own compat-proxy bind; release again (idempotent) so the runtime
434+
// stack's compat proxy can re-acquire the same public port via its
435+
// own short-lived hold.
436+
await publicPortReservation?.release().catch(() => undefined);
383437
stack = await startIsolatedRealmStack({
384438
realms,
385439
realmServerURL,
@@ -394,6 +448,10 @@ export async function startFactoryRealmServer(
394448
} catch (error) {
395449
let cleanupError: unknown;
396450

451+
// Backstop: release the public-port holder if an error fired before it
452+
// was handed off (idempotent — a no-op once already released).
453+
await publicPortReservation?.release().catch(() => undefined);
454+
397455
try {
398456
await dropDatabase(databaseName);
399457
} catch (cleanupFailure) {

packages/realm-test-harness/src/database.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
templateLog,
1515
waitUntil,
1616
type FactorySupportContext,
17+
type PortReservation,
1718
type RealmConfig,
1819
type RealmPermissions,
1920
} from './shared';
@@ -707,12 +708,20 @@ export async function buildCombinedTemplateDatabase({
707708
context,
708709
cacheKey,
709710
templateDatabaseName,
711+
publicPortReservation,
710712
}: {
711713
realms: RealmConfig[];
712714
realmServerURL: URL;
713715
context: FactorySupportContext;
714716
cacheKey: string;
715717
templateDatabaseName: string;
718+
/** Holder for the public realm-server port, allocated and held upstream
719+
* in `resolveFactoryRealmServerURL`. Threaded straight into
720+
* `startIsolatedRealmStack`, which releases it right before the compat
721+
* proxy binds. Keeping it held across this build keeps the
722+
* worker-manager hold below (and the support-service ports allocated
723+
* before this call) from being handed the public port number. */
724+
publicPortReservation?: PortReservation;
716725
}): Promise<void> {
717726
if (realms.length === 0) {
718727
throw new Error(
@@ -787,6 +796,7 @@ export async function buildCombinedTemplateDatabase({
787796
migrateDB: !hasMigratedTemplate,
788797
fullIndexOnStartup: true,
789798
workerManagerPort: wmReservation,
799+
publicPortReservation,
790800
});
791801
} catch (error) {
792802
progress.stop();

packages/realm-test-harness/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export {
2222
buildServerToken,
2323
diagnosePortConflict,
2424
findAndHoldAvailablePort,
25+
holdSpecificPort,
2526
isFactorySupportContext,
2627
type PortReservation,
2728
} from './shared';

0 commit comments

Comments
 (0)