Skip to content

Commit c93a283

Browse files
authored
refactor existingBackend for better parallelism (#9240)
* refactor existingBackend for better parallelism * add changelog * clean up code a smidge
1 parent 4725f2e commit c93a283

File tree

4 files changed

+29
-27
lines changed

4 files changed

+29
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
- CloudSQL instances created with `firebase deploy` now default to Postgres 17.
88
- Improved the clarity of the `firebase apptesting:execute` command when you have zero or multiple apps.
99
- Fixed an issue where `firebase deploy --only firestore` would fail with 403 errors on projects that never had a database created.
10+
- Fixed an issue where deploying multiple Hosting sites with Functions could encounter a race condition (#9235).

src/deploy/functions/args.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export interface Context {
4747

4848
// Caching fields for backend.existingBackend()
4949
existingBackend?: backend.Backend;
50-
loadedExistingBackend?: boolean;
50+
existingBackendPromise?: Promise<backend.Backend>;
5151
unreachableRegions?: {
5252
gcfV1: string[];
5353
gcfV2: string[];

src/deploy/functions/backend.ts

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -516,51 +516,52 @@ export function scheduleIdForFunction(cloudFunction: TargetIds): string {
516516
* @param forceRefresh If true, ignores and overwrites the cache. These cases should eventually go away.
517517
* @return The backend
518518
*/
519-
export async function existingBackend(context: Context, forceRefresh?: boolean): Promise<Backend> {
520-
if (!context.loadedExistingBackend || forceRefresh) {
521-
await loadExistingBackend(context);
519+
export function existingBackend(context: Context, forceRefresh?: boolean): Promise<Backend> {
520+
if (!context.existingBackendPromise || forceRefresh) {
521+
context.existingBackendPromise = loadExistingBackend(context);
522522
}
523-
// loadExisting guarantees the validity of existingBackend and unreachableRegions
524-
return context.existingBackend!;
523+
return context.existingBackendPromise;
525524
}
526525

527-
async function loadExistingBackend(ctx: Context): Promise<void> {
528-
ctx.loadedExistingBackend = true;
526+
async function loadExistingBackend(ctx: Context): Promise<Backend> {
529527
// Note: is it worth deducing the APIs that must have been enabled for this backend to work?
530528
// it could reduce redundant API calls for enabling the APIs.
531-
ctx.existingBackend = {
529+
const existingBackend = {
532530
...empty(),
533531
};
534-
ctx.unreachableRegions = {
535-
gcfV1: [],
536-
gcfV2: [],
537-
run: [],
532+
const unreachableRegions = {
533+
gcfV1: [] as string[],
534+
gcfV2: [] as string[],
535+
run: [] as string[],
538536
};
539537
const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
540538
for (const apiFunction of gcfV1Results.functions) {
541539
const endpoint = gcf.endpointFromFunction(apiFunction);
542-
ctx.existingBackend.endpoints[endpoint.region] =
543-
ctx.existingBackend.endpoints[endpoint.region] || {};
544-
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
540+
existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {};
541+
existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
545542
}
546-
ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
543+
unreachableRegions.gcfV1 = gcfV1Results.unreachable;
547544

548545
let gcfV2Results;
549546
try {
550547
gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
551548
for (const apiFunction of gcfV2Results.functions) {
552549
const endpoint = gcfV2.endpointFromFunction(apiFunction);
553-
ctx.existingBackend.endpoints[endpoint.region] =
554-
ctx.existingBackend.endpoints[endpoint.region] || {};
555-
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
550+
existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {};
551+
existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
556552
}
557-
ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
553+
unreachableRegions.gcfV2 = gcfV2Results.unreachable;
558554
} catch (err: any) {
559555
if (err.status === 404 && err.message?.toLowerCase().includes("method not found")) {
560-
return; // customer has preview enabled without allowlist set
556+
// customer has preview enabled without allowlist set
557+
} else {
558+
throw err;
561559
}
562-
throw err;
563560
}
561+
562+
ctx.existingBackend = existingBackend;
563+
ctx.unreachableRegions = unreachableRegions;
564+
return ctx.existingBackend;
564565
}
565566

566567
/**
@@ -572,9 +573,7 @@ async function loadExistingBackend(ctx: Context): Promise<void> {
572573
* @param want The desired backend. Can be backend.empty() to only warn about unavailability.
573574
*/
574575
export async function checkAvailability(context: Context, want: Backend): Promise<void> {
575-
if (!context.loadedExistingBackend) {
576-
await loadExistingBackend(context);
577-
}
576+
await existingBackend(context);
578577
const gcfV1Regions = new Set();
579578
const gcfV2Regions = new Set();
580579
for (const ep of allEndpoints(want)) {

src/deploy/hosting/convertConfig.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,9 @@ describe("convertConfig", () => {
471471
it(name, async () => {
472472
const context: Context = {
473473
projectId: PROJECT_ID,
474-
loadedExistingBackend: true,
474+
existingBackendPromise: new Promise((resolve) =>
475+
resolve(existingBackend || backend.empty()),
476+
),
475477
existingBackend: existingBackend || backend.empty(),
476478
unreachableRegions: {
477479
gcfV1: [],

0 commit comments

Comments
 (0)