|
@@ -1,6 +1,7 @@
|
|
|
-import puppeteer from "puppeteer";
|
|
|
|
|
|
|
+import { launchBrowserForChartService } from "./browserLauncher.js";
|
|
|
|
|
|
|
|
let browser;
|
|
let browser;
|
|
|
|
|
+
|
|
|
const pagePool = [];
|
|
const pagePool = [];
|
|
|
const MAX_PAGES = Number.parseInt(process.env.CHART_MAX_PAGES || "1", 10);
|
|
const MAX_PAGES = Number.parseInt(process.env.CHART_MAX_PAGES || "1", 10);
|
|
|
const DEFAULT_TIMEOUT_MS = Number.parseInt(
|
|
const DEFAULT_TIMEOUT_MS = Number.parseInt(
|
|
@@ -22,66 +23,40 @@ const clearPagePool = async () => {
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const createPage = async () => {
|
|
|
|
|
- if (!isBrowserAlive()) {
|
|
|
|
|
- await initBrowserPool();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const page = await browser.newPage();
|
|
|
|
|
- page.setDefaultTimeout(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
- page.setDefaultNavigationTimeout(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
- return page;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
const isUsablePage = (page) =>
|
|
const isUsablePage = (page) =>
|
|
|
page && !page.isClosed() && page.browser()?.isConnected();
|
|
page && !page.isClosed() && page.browser()?.isConnected();
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 初始化浏览器 + 预创建 page
|
|
|
|
|
- */
|
|
|
|
|
|
|
+async function openPoolBrowser() {
|
|
|
|
|
+ const { browser: b } = await launchBrowserForChartService(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
+ browser = b;
|
|
|
|
|
+ console.log("✅ Browser pool: Chromium");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export const initBrowserPool = async () => {
|
|
export const initBrowserPool = async () => {
|
|
|
if (!isBrowserAlive()) {
|
|
if (!isBrowserAlive()) {
|
|
|
await clearPagePool();
|
|
await clearPagePool();
|
|
|
browser = null;
|
|
browser = null;
|
|
|
|
|
|
|
|
- browser = await puppeteer.launch({
|
|
|
|
|
- headless: "new",
|
|
|
|
|
- executablePath: process.env.CHROME_PATH || undefined,
|
|
|
|
|
- args: [
|
|
|
|
|
- "--no-sandbox",
|
|
|
|
|
- "--disable-setuid-sandbox",
|
|
|
|
|
- "--disable-dev-shm-usage",
|
|
|
|
|
- "--disable-background-timer-throttling",
|
|
|
|
|
- "--disable-backgrounding-occluded-windows",
|
|
|
|
|
- "--disable-renderer-backgrounding",
|
|
|
|
|
- ],
|
|
|
|
|
- // 避免渲染压力下 CDP 调用超时(对应报错:Runtime.callFunctionOn timed out)
|
|
|
|
|
- protocolTimeout: EFFECTIVE_TIMEOUT_MS,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ await openPoolBrowser();
|
|
|
|
|
|
|
|
browser.on("disconnected", () => {
|
|
browser.on("disconnected", () => {
|
|
|
browser = null;
|
|
browser = null;
|
|
|
pagePool.splice(0, pagePool.length);
|
|
pagePool.splice(0, pagePool.length);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- console.log("✅ Browser initialized");
|
|
|
|
|
-
|
|
|
|
|
- // 预创建 page
|
|
|
|
|
for (let i = 0; i < MAX_PAGES; i++) {
|
|
for (let i = 0; i < MAX_PAGES; i++) {
|
|
|
- pagePool.push(await createPage());
|
|
|
|
|
|
|
+ const page = await browser.newPage();
|
|
|
|
|
+ page.setDefaultTimeout(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
+ page.setDefaultNavigationTimeout(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
+ pagePool.push(page);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
console.log(`✅ Page pool initialized: ${MAX_PAGES}`);
|
|
console.log(`✅ Page pool initialized: ${MAX_PAGES}`);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 获取 page(核心)
|
|
|
|
|
- */
|
|
|
|
|
export const getPage = async () => {
|
|
export const getPage = async () => {
|
|
|
- if (!isBrowserAlive()) {
|
|
|
|
|
- await initBrowserPool();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!isBrowserAlive()) await initBrowserPool();
|
|
|
|
|
|
|
|
if (pagePool.length > 0) {
|
|
if (pagePool.length > 0) {
|
|
|
while (pagePool.length > 0) {
|
|
while (pagePool.length > 0) {
|
|
@@ -91,27 +66,21 @@ export const getPage = async () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // pool 空了就新建(兜底)
|
|
|
|
|
- return createPage();
|
|
|
|
|
|
|
+ const page = await browser.newPage();
|
|
|
|
|
+ page.setDefaultTimeout(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
+ page.setDefaultNavigationTimeout(EFFECTIVE_TIMEOUT_MS);
|
|
|
|
|
+ return page;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 释放 page
|
|
|
|
|
- */
|
|
|
|
|
export const releasePage = async (page) => {
|
|
export const releasePage = async (page) => {
|
|
|
if (!page) return;
|
|
if (!page) return;
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
if (!isUsablePage(page)) {
|
|
if (!isUsablePage(page)) {
|
|
|
await discardPage(page);
|
|
await discardPage(page);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (pagePool.length < MAX_PAGES) {
|
|
|
|
|
- pagePool.push(page);
|
|
|
|
|
- } else {
|
|
|
|
|
- await page.close();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (pagePool.length < MAX_PAGES) pagePool.push(page);
|
|
|
|
|
+ else await page.close();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error("❌ releasePage error:", err);
|
|
console.error("❌ releasePage error:", err);
|
|
|
}
|
|
}
|
|
@@ -119,7 +88,6 @@ export const releasePage = async (page) => {
|
|
|
|
|
|
|
|
export const discardPage = async (page) => {
|
|
export const discardPage = async (page) => {
|
|
|
if (!page) return;
|
|
if (!page) return;
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
if (!page.isClosed()) await page.close();
|
|
if (!page.isClosed()) await page.close();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
@@ -127,18 +95,13 @@ export const discardPage = async (page) => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 关闭
|
|
|
|
|
- */
|
|
|
|
|
export const closeBrowserPool = async () => {
|
|
export const closeBrowserPool = async () => {
|
|
|
try {
|
|
try {
|
|
|
await clearPagePool();
|
|
await clearPagePool();
|
|
|
-
|
|
|
|
|
if (browser) {
|
|
if (browser) {
|
|
|
await browser.close();
|
|
await browser.close();
|
|
|
browser = null;
|
|
browser = null;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
console.log("🛑 Browser pool closed");
|
|
console.log("🛑 Browser pool closed");
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error("❌ closeBrowserPool error:", err);
|
|
console.error("❌ closeBrowserPool error:", err);
|