From 189b1ee7f32a05369510f15b7f2bb1c334db8e49 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 12:41:50 -0800 Subject: [PATCH 1/7] Exploring --- src/emulator/emulatorLogger.ts | 2 +- src/emulator/functionsEmulator.ts | 5 ++-- src/emulator/functionsEmulatorRuntime.ts | 29 ++++++++++++++++++------ src/emulator/functionsRuntimeWorker.ts | 1 + 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/emulator/emulatorLogger.ts b/src/emulator/emulatorLogger.ts index 4fd263c49f4..af1ec0debe8 100644 --- a/src/emulator/emulatorLogger.ts +++ b/src/emulator/emulatorLogger.ts @@ -84,7 +84,7 @@ export class EmulatorLogger { EmulatorLogger.log("USER", `${clc.blackBright("> ")} ${log.text}`); break; case "DEBUG": - if (log.data && log.data !== {}) { + if (log.data && Object.keys(log.data).length > 0) { EmulatorLogger.log("DEBUG", `[${log.type}] ${log.text} ${JSON.stringify(log.data)}`); } else { EmulatorLogger.log("DEBUG", `[${log.type}] ${log.text}`); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 5b6dec17b81..41a7218e847 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -29,7 +29,6 @@ import { EmulatorLogger, Verbosity } from "./emulatorLogger"; import { RuntimeWorkerPool, RuntimeWorker } from "./functionsRuntimeWorker"; import { PubsubEmulator } from "./pubsubEmulator"; import { FirebaseError } from "../error"; -import { pubsub } from "firebase-functions"; const EVENT_INVOKE = "functions:invoke"; @@ -141,14 +140,14 @@ export class FunctionsEmulator implements EmulatorInstance { req: express.Request, res: express.Response ) => { - this.handleBackgroundTrigger(req, res); + await this.handleBackgroundTrigger(req, res); }; const httpsHandler: express.RequestHandler = async ( req: express.Request, res: express.Response ) => { - this.handleHttpsTrigger(req, res); + await this.handleHttpsTrigger(req, res); }; // The ordering here is important. The longer routes (background) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 231b8d391d7..0f324f4daa9 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -9,7 +9,6 @@ import { FunctionsRuntimeBundle, FunctionsRuntimeFeatures, getEmulatedTriggersFromDefinitions, - getTemporarySocketPath, FunctionsRuntimeArgs, } from "./functionsEmulatorShared"; import { parseVersionString, compareVersionStrings } from "./functionsEmulatorUtils"; @@ -19,6 +18,8 @@ import * as admin from "firebase-admin"; import * as bodyParser from "body-parser"; import { URL } from "url"; import * as _ from "lodash"; +import * as fs from "fs"; +import { Socket } from "net"; const DATABASE_APP = "__database__"; @@ -810,16 +811,27 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge await new Promise((resolveEphemeralServer, rejectEphemeralServer) => { const handler = async (req: express.Request, res: express.Response) => { try { - logDebug(`Ephemeral server used!`); + logDebug(`Ephemeral server handling ${req.method} request`); const func = trigger.getRawFunction(); - res.on("finish", () => { - instance.close(resolveEphemeralServer); + instance.close((err) => { + instance.getConnections((err, num) => { + logDebug(`Server has ${num} connections after close.`); + }); + + if (err) { + rejectEphemeralServer(err); + } else { + resolveEphemeralServer(); + } + + logDebug(`Closed ephemeral server at socketPath: ${socketPath}`); + }); }); await runHTTPS([req, res], func); } catch (err) { - rejectEphemeralServer(err); + rejectEphemeralServer(err); } }; @@ -858,9 +870,12 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge functionRouter ); + logDebug(`Attempting to listen to socketPath: ${socketPath}`); const instance = ephemeralServer.listen(socketPath, () => { new EmulatorLog("SYSTEM", "runtime-status", "ready", { state: "ready" }).log(); }); + + instance.on("error", rejectEphemeralServer); }); } @@ -964,7 +979,7 @@ async function moduleResolutionDetective(frb: FunctionsRuntimeBundle, error: Err } function logDebug(msg: string, data?: any): void { - new EmulatorLog("DEBUG", "runtime-status", msg, data).log(); + new EmulatorLog("DEBUG", "runtime-status", `[${process.pid}] ${msg}`, data).log(); } async function invokeTrigger( @@ -980,7 +995,7 @@ async function invokeTrigger( }).log(); const trigger = triggers[frb.triggerId]; - logDebug("", trigger.definition); + logDebug("triggerDefinition", trigger.definition); const mode = trigger.definition.httpsTrigger ? "HTTPS" : "BACKGROUND"; logDebug(`Running ${frb.triggerId} in mode ${mode}`); diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index e094ac0aa7d..6c2afdcbe86 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -237,6 +237,7 @@ export class RuntimeWorkerPool { } addWorker(triggerId: string | undefined, runtime: FunctionsRuntimeInstance): RuntimeWorker { + this.log(`addWorker(${triggerId})`); const key = this.getTriggerKey(triggerId); const worker = new RuntimeWorker(key, runtime); From 2129067c4a0d07518548429e51961a7bc86dd5d7 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 13:30:33 -0800 Subject: [PATCH 2/7] Shorten socket names --- src/emulator/functionsEmulatorRuntime.ts | 4 ++-- src/emulator/functionsEmulatorShared.ts | 6 +++--- src/emulator/functionsRuntimeWorker.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 0f324f4daa9..3f73a75f021 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -831,7 +831,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge await runHTTPS([req, res], func); } catch (err) { - rejectEphemeralServer(err); + rejectEphemeralServer(err); } }; @@ -874,7 +874,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge const instance = ephemeralServer.listen(socketPath, () => { new EmulatorLog("SYSTEM", "runtime-status", "ready", { state: "ready" }).log(); }); - + instance.on("error", rejectEphemeralServer); }); } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index c582f6ab20b..dd0f411d952 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -134,13 +134,13 @@ export function getEmulatedTriggersFromDefinitions( }, {}); } -export function getTemporarySocketPath(id: string, cwd: string): string { +export function getTemporarySocketPath(pid: number, cwd: string): string { // See "net" package docs for information about IPC pipes on Windows // https://nodejs.org/api/net.html#net_identifying_paths_for_ipc_connections if (process.platform === "win32") { - return path.join("\\\\?\\pipe", cwd, id.toString()); + return path.join("\\\\?\\pipe", cwd, pid.toString()); } else { - return path.join(os.tmpdir(), `firebase_emulator_invocation_${id}.sock`); + return path.join(os.tmpdir(), `fire_emu_${pid.toString()}.sock`); } } diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 6c2afdcbe86..0204cf9b75b 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -69,7 +69,7 @@ export class RuntimeWorker { // TODO(samstern): I would like to do this elsewhere... if (!execFrb.socketPath) { - execFrb.socketPath = getTemporarySocketPath(this.id, execFrb.cwd); + execFrb.socketPath = getTemporarySocketPath(this.runtime.pid, execFrb.cwd); this.log(`Assigning socketPath: ${execFrb.socketPath}`); } From 5e7297d311ec885da560dfb1104574cfbee915e3 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 13:35:37 -0800 Subject: [PATCH 3/7] Cleanup --- src/emulator/functionsEmulatorRuntime.ts | 8 -------- src/emulator/functionsRuntimeWorker.ts | 1 - 2 files changed, 9 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 3f73a75f021..9f993ed4b6a 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -18,8 +18,6 @@ import * as admin from "firebase-admin"; import * as bodyParser from "body-parser"; import { URL } from "url"; import * as _ from "lodash"; -import * as fs from "fs"; -import { Socket } from "net"; const DATABASE_APP = "__database__"; @@ -815,17 +813,11 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge const func = trigger.getRawFunction(); res.on("finish", () => { instance.close((err) => { - instance.getConnections((err, num) => { - logDebug(`Server has ${num} connections after close.`); - }); - if (err) { rejectEphemeralServer(err); } else { resolveEphemeralServer(); } - - logDebug(`Closed ephemeral server at socketPath: ${socketPath}`); }); }); diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 0204cf9b75b..480b32b8c75 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -237,7 +237,6 @@ export class RuntimeWorkerPool { } addWorker(triggerId: string | undefined, runtime: FunctionsRuntimeInstance): RuntimeWorker { - this.log(`addWorker(${triggerId})`); const key = this.getTriggerKey(triggerId); const worker = new RuntimeWorker(key, runtime); From 985d6ceba4c82937a63a473590b0bedfe7001aee Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 13:37:04 -0800 Subject: [PATCH 4/7] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..1b4473519b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +* Fixes an issue where repeated invoations cause an `EADDRINUSE` error (#1815) \ No newline at end of file From f08bd46c1091b21f53e4d249e3a031656fc29da8 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 16:19:08 -0800 Subject: [PATCH 5/7] Big comment --- src/emulator/functionsEmulatorShared.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index dd0f411d952..df571ceb4ff 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -137,6 +137,17 @@ export function getEmulatedTriggersFromDefinitions( export function getTemporarySocketPath(pid: number, cwd: string): string { // See "net" package docs for information about IPC pipes on Windows // https://nodejs.org/api/net.html#net_identifying_paths_for_ipc_connections + // + // As noted in the linked documentation the socket path is truncated at a certain + // length: + // > On Unix, the local domain is also known as the Unix domain. The path is a filesystem pathname. + // > It gets truncated to a length of sizeof(sockaddr_un.sun_path) - 1, which varies 91 and 107 bytes + // > depending on the operating system. The typical values are 107 on Linux and 103 on macOS. + // + // On Mac our socket paths will begin with something like this: + // /var/folders/xl/6lkrzp7j07581mw8_4dlt3b000643s/T/{...}.sock + // Since the system prefix is about ~50 chargs we only have about ~50 more to work with + // before we will get truncated socket names and then undefined behavior. if (process.platform === "win32") { return path.join("\\\\?\\pipe", cwd, pid.toString()); } else { From 7ac260dc2b2ede355d1cef8058bebd319a4b9ec4 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 16:20:04 -0800 Subject: [PATCH 6/7] Undo await: --- src/emulator/functionsEmulator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 41a7218e847..7123eee53f3 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -140,14 +140,14 @@ export class FunctionsEmulator implements EmulatorInstance { req: express.Request, res: express.Response ) => { - await this.handleBackgroundTrigger(req, res); + this.handleBackgroundTrigger(req, res); }; const httpsHandler: express.RequestHandler = async ( req: express.Request, res: express.Response ) => { - await this.handleHttpsTrigger(req, res); + this.handleHttpsTrigger(req, res); }; // The ordering here is important. The longer routes (background) From 0c04985016656c813fac922c0720ec64a005b7f1 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Nov 2019 16:26:30 -0800 Subject: [PATCH 7/7] Lint --- src/emulator/functionsEmulatorShared.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index df571ceb4ff..d355ee02a60 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -140,14 +140,14 @@ export function getTemporarySocketPath(pid: number, cwd: string): string { // // As noted in the linked documentation the socket path is truncated at a certain // length: - // > On Unix, the local domain is also known as the Unix domain. The path is a filesystem pathname. - // > It gets truncated to a length of sizeof(sockaddr_un.sun_path) - 1, which varies 91 and 107 bytes + // > On Unix, the local domain is also known as the Unix domain. The path is a filesystem pathname. + // > It gets truncated to a length of sizeof(sockaddr_un.sun_path) - 1, which varies 91 and 107 bytes // > depending on the operating system. The typical values are 107 on Linux and 103 on macOS. // // On Mac our socket paths will begin with something like this: // /var/folders/xl/6lkrzp7j07581mw8_4dlt3b000643s/T/{...}.sock // Since the system prefix is about ~50 chargs we only have about ~50 more to work with - // before we will get truncated socket names and then undefined behavior. + // before we will get truncated socket names and then undefined behavior. if (process.platform === "win32") { return path.join("\\\\?\\pipe", cwd, pid.toString()); } else {