Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9dbba9a
CS-10889: dual-write realm_registry from mutation handlers
lukemelia Apr 23, 2026
f9db2c1
CS-10889: tighten deletePublishedFromRegistryBySource filter
lukemelia Apr 23, 2026
33d66d8
CS-10890: RealmRegistryReconciler + NOTIFY emission + boot advisory lock
lukemelia Apr 23, 2026
0ad2575
CS-10890: prettier line-wrapping in reconciler test
lukemelia Apr 23, 2026
2a4c854
CS-10890: address Copilot review feedback
lukemelia Apr 23, 2026
36f68e9
CS-10891: per-realm write-path advisory locks
lukemelia Apr 23, 2026
cd28bf1
CS-10891: split assert.ok with && to satisfy qunit lint
lukemelia Apr 23, 2026
289cd16
CS-10891: address Copilot review feedback
lukemelia Apr 23, 2026
8cb1e74
CS-10892: realm_file_changes NOTIFY channel + Realm.invalidateCache(p…
lukemelia Apr 24, 2026
b542d69
CS-10894: gate reconciler eager mount on pinned + add lookupOrMount(url)
lukemelia Apr 27, 2026
8083dad
CS-10894: thread RealmRegistryReconciler through RealmServer + routes
lukemelia Apr 27, 2026
53374be
CS-10894: switch boot + request path to reconciler-driven lazy mount
lukemelia Apr 27, 2026
d4d3ee6
CS-10894: lazy-mount integration tests + structured mount-event logs
lukemelia Apr 27, 2026
8ff8d24
CS-10894: fix boot deadlock — publish realm before awaiting start()
lukemelia Apr 27, 2026
8620d5d
CS-10894: server.start() must also start constructor-supplied realms
lukemelia Apr 27, 2026
0f322ec
CS-10894: parallelize pinned reconcile + update lifecycle test
lukemelia Apr 27, 2026
78e6cd1
CS-10894: split mountFromRow into prepare (sync) + start (async)
lukemelia Apr 28, 2026
84500c4
CS-10894: fix boot crash in main.ts logging loop
lukemelia Apr 28, 2026
0a79f4a
CS-10894: address Copilot review feedback
lukemelia Apr 28, 2026
8fc3c17
CS-10894: prettier — collapse REALM_FILE_CHANGES_CHANNEL import to on…
lukemelia Apr 28, 2026
3fafa83
Merge main into cs-10894
lukemelia Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/postgres/pg-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async function acquireConcurrencyGroupLock(
}

// Tracks a task that should loop with a timeout and an interruptible sleep.
class WorkLoop {
export class WorkLoop {
private internalWaker: Deferred<void> | undefined;
private timeout: NodeJS.Timeout | undefined;
private _shuttingDown = false;
Expand Down
150 changes: 86 additions & 64 deletions packages/realm-server/handlers/handle-delete-realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import {
destroyMountedRealm,
removeRealmDatabaseArtifacts,
} from './realm-destruction-utils';
import {
deleteFromRegistryByUrl,
deletePublishedFromRegistryBySource,
} from '../lib/realm-registry-writes';
import { withRealmWriteLock } from '../lib/realm-advisory-locks';

interface DeleteRealmJSON {
data: {
Expand Down Expand Up @@ -138,77 +143,94 @@ export default function handleDeleteRealm({
return;
}

let publishedRealms = (await query(dbAdapter, [
`SELECT id, published_realm_url FROM published_realms WHERE source_realm_url =`,
param(realmURL),
])) as Pick<PublishedRealmTable, 'id' | 'published_realm_url'>[];
// Serialize concurrent writers for this source realm. Lock is on the
// source URL — a concurrent unpublish of one of the associated
// published realms uses a different lock key (its own published URL),
// so in the rare case of overlap, the handlers interleave at the
// per-URL granularity rather than globally. Pragmatic for this PR;
// multi-instance hardening could tighten this later.
await withRealmWriteLock(dbAdapter, realmURL, async () => {
let publishedRealms = (await query(dbAdapter, [
`SELECT id, published_realm_url FROM published_realms WHERE source_realm_url =`,
param(realmURL),
])) as Pick<PublishedRealmTable, 'id' | 'published_realm_url'>[];

for (let publishedRealm of publishedRealms) {
let mountedPublishedRealm = realms.find(
(realm) =>
ensureTrailingSlash(realm.url) ===
ensureTrailingSlash(publishedRealm.published_realm_url),
);
let publishedRealmPath = join(
realmsRootPath,
PUBLISHED_DIRECTORY_NAME,
publishedRealm.id,
);
if (mountedPublishedRealm) {
destroyMountedRealm({
realm: mountedPublishedRealm,
realmPath: publishedRealmPath,
realms,
virtualNetwork,
});
} else {
try {
if (pathExistsSync(publishedRealmPath)) {
removeSync(publishedRealmPath);
for (let publishedRealm of publishedRealms) {
let mountedPublishedRealm = realms.find(
(realm) =>
ensureTrailingSlash(realm.url) ===
ensureTrailingSlash(publishedRealm.published_realm_url),
);
let publishedRealmPath = join(
realmsRootPath,
PUBLISHED_DIRECTORY_NAME,
publishedRealm.id,
);
if (mountedPublishedRealm) {
destroyMountedRealm({
realm: mountedPublishedRealm,
realmPath: publishedRealmPath,
realms,
virtualNetwork,
});
} else {
try {
if (pathExistsSync(publishedRealmPath)) {
removeSync(publishedRealmPath);
}
} catch (error) {
Sentry.captureException(error);
}
} catch (error) {
Sentry.captureException(error);
}
await removeRealmPermissions(
dbAdapter,
new URL(publishedRealm.published_realm_url),
);
await removeRealmDatabaseArtifacts({
dbAdapter,
realmURL: publishedRealm.published_realm_url,
});
}
await removeRealmPermissions(
dbAdapter,
new URL(publishedRealm.published_realm_url),
);
await removeRealmDatabaseArtifacts({
dbAdapter,
realmURL: publishedRealm.published_realm_url,
});
}

await query(dbAdapter, [
`DELETE FROM published_realms WHERE source_realm_url =`,
param(realmURL),
]);
await query(dbAdapter, [
`DELETE FROM published_realms WHERE source_realm_url =`,
param(realmURL),
]);

let { nameExpressions, valueExpressions } = asExpressions({
removed_at: Math.floor(Date.now() / 1000),
});
await query(dbAdapter, [
...update(
'claimed_domains_for_sites',
nameExpressions,
valueExpressions,
),
` WHERE source_realm_url = `,
param(realmURL),
` AND removed_at IS NULL`,
]);
// Phase 1 dual-write: remove the source realm's registry row plus
// every published row sourced from it. Both helpers log and continue
// on failure; drift self-heals on next boot.
await deletePublishedFromRegistryBySource(dbAdapter, realmURL);
await deleteFromRegistryByUrl(dbAdapter, realmURL);

destroyMountedRealm({
realm: sourceRealm,
realmPath: sourceRealm.dir,
realms,
virtualNetwork,
});
await removeRealmPermissions(dbAdapter, parsedRealmURL);
await removeRealmDatabaseArtifacts({
dbAdapter,
realmURL,
let { nameExpressions, valueExpressions } = asExpressions({
removed_at: Math.floor(Date.now() / 1000),
});
await query(dbAdapter, [
...update(
'claimed_domains_for_sites',
nameExpressions,
valueExpressions,
),
` WHERE source_realm_url = `,
param(realmURL),
` AND removed_at IS NULL`,
]);

destroyMountedRealm({
realm: sourceRealm,
// Non-null assertion: we early-returned above if sourceRealm.dir was
// undefined. TS narrowing doesn't propagate into this async closure,
// so we reassert.
realmPath: sourceRealm.dir!,
realms,
virtualNetwork,
});
await removeRealmPermissions(dbAdapter, parsedRealmURL);
await removeRealmDatabaseArtifacts({
dbAdapter,
realmURL,
});
});

await setContextResponse(
Expand Down
Loading
Loading