mirror of
https://github.com/2234839/web-font.git
synced 2026-09-04 14:53:32 +08:00
feat: font-meta 并发限制 + metaVersion 磁盘缓存失效 + 清理废弃参数
- font-meta 首次计算复用 withMemoryGate 并发闸门(与子集化共享内存预算) - FontMeta 加 metaVersion 版本指纹,算法变更后 .meta.json 自动失效 - 删除 withMemoryGate/initMemoryGate 废弃的 _softLimitMB 参数 - 清理旧 .meta.json 缓存文件
This commit is contained in:
parent
98c3c5a6e2
commit
fa3fa15bf7
@ -210,7 +210,7 @@ async function main() {
|
||||
console.log("[config] admin upload:", !!adminApiKey);
|
||||
|
||||
/** 初始化子集化并发队列(含字体分组调度) */
|
||||
initMemoryGate(0, subsetConcurrency);
|
||||
initMemoryGate(subsetConcurrency);
|
||||
|
||||
/** 启动临时字体定时清理器 */
|
||||
startTempCleaner();
|
||||
|
||||
@ -460,6 +460,14 @@ export interface FontMeta {
|
||||
config?: FontUserConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 元数据版本指纹
|
||||
*
|
||||
* extractCodePoints / calcCoverage 的算法变更后手动 bump,
|
||||
* 让路由层的 .meta.json 磁盘缓存自动失效(旧文件的 metaVersion 不匹配即重算)。
|
||||
*/
|
||||
export const META_VERSION = 3;
|
||||
|
||||
/**
|
||||
* 提取字体元数据:codepoint 总数、覆盖率、支持的区间、字体基本信息。
|
||||
* 注意:config 字段由路由层从 font-config.json 填充,此处不包含。
|
||||
@ -467,6 +475,7 @@ export interface FontMeta {
|
||||
export function extractFontMeta(fontBuffer: ArrayBuffer | Uint8Array): FontMeta {
|
||||
const cps = extractCodePoints(fontBuffer);
|
||||
return {
|
||||
metaVersion: META_VERSION,
|
||||
totalCodePoints: cps.size,
|
||||
coverage: calcCoverage(cps),
|
||||
ranges: codePointsToRanges(cps),
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { extractFontMeta, type FontUserConfig } from "../font_util/font_meta.js";
|
||||
import { extractFontMeta, META_VERSION, type FontUserConfig } from "../font_util/font_meta.js";
|
||||
import { parseUrl, jsonResponse, findFontPath, readFontBuffer } from "../shared";
|
||||
import { readFile, writeFile, stat } from "../interface";
|
||||
import { withMemoryGate } from "../subset_queue";
|
||||
import { subsetQueueTimeoutSeconds } from "../config";
|
||||
|
||||
/**
|
||||
* 字体元数据路由 —— 分为两层:
|
||||
@ -99,17 +101,41 @@ export async function handleFontMeta(req: Request, _res: Response) {
|
||||
/** 1. 进程内存命中 */
|
||||
let meta = metaCache.get(fontPath);
|
||||
if (!meta) {
|
||||
/** 2. 磁盘 .meta.json 命中 */
|
||||
/** 2. 磁盘 .meta.json 命中(版本指纹不匹配则视为 miss,重新计算) */
|
||||
meta = await loadMetaFromDisk(fontPath);
|
||||
if (meta) {
|
||||
if (meta && meta.metaVersion === META_VERSION) {
|
||||
metaCache.set(fontPath, meta);
|
||||
} else {
|
||||
meta = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/** 3. 首次请求 —— 解析 cmap + name 表计算元数据 */
|
||||
/** 3. 首次请求 —— 解析 cmap + name 表计算元数据(CPU/内存密集,复用子集化并发闸门) */
|
||||
if (!meta) {
|
||||
const fontBuffer = await readFontBuffer(fontPath);
|
||||
meta = extractFontMeta(fontBuffer);
|
||||
/** 排队超时,返回 503 让客户端重试 */
|
||||
const result = await withMemoryGate(
|
||||
async () => {
|
||||
const fontBuffer = await readFontBuffer(fontPath);
|
||||
return extractFontMeta(fontBuffer);
|
||||
},
|
||||
subsetQueueTimeoutSeconds * 1000,
|
||||
fontPath,
|
||||
);
|
||||
|
||||
if (result === null) {
|
||||
return {
|
||||
req,
|
||||
res: new Response("Server busy, please retry", {
|
||||
status: 503,
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Retry-After": "1",
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
meta = result;
|
||||
metaCache.set(fontPath, meta);
|
||||
/** 异步写盘,不阻塞响应 */
|
||||
saveMetaToDisk(fontPath, meta);
|
||||
|
||||
@ -116,7 +116,7 @@ export async function handleFontSubset(req: Request, res: Response) {
|
||||
* 缓存未命中的请求才进入闸门;RSS 超 softLimit 时排队等待,
|
||||
* 前面请求完成 + GC 释放内存后 RSS 回落才执行。避免 OOM 崩溃。
|
||||
*/
|
||||
const subsetResult = await withMemoryGate(subsetConcurrency, async () => {
|
||||
const subsetResult = await withMemoryGate(async () => {
|
||||
return fontSubset(oldFontBuffer, text, {
|
||||
outType: outType,
|
||||
sourceType: fontType,
|
||||
|
||||
@ -37,16 +37,12 @@ let lastGroupKey = "";
|
||||
* 分组优化:同 groupKey(同一字体)的排队请求优先连续唤醒,
|
||||
* 使字体 buffer / 解析对象在缓存窗口内被下一个请求复用,降低内存峰值。
|
||||
*
|
||||
* 函数名保留 withMemoryGate 以减少调用方改动(subset.ts 等)。
|
||||
*
|
||||
* @param _softLimitMB 废弃保留(兼容签名),不再使用
|
||||
* @param task 实际的子集化异步任务
|
||||
* @param task 实际的异步任务
|
||||
* @param queueTimeoutMs 排队超时(毫秒)
|
||||
* @param groupKey 分组键(通常为 fontPath),同组连续处理以复用缓存
|
||||
* @returns 任务结果,或 null 表示排队超时
|
||||
*/
|
||||
export async function withMemoryGate<T>(
|
||||
_softLimitMB: number,
|
||||
task: () => Promise<T>,
|
||||
queueTimeoutMs: number,
|
||||
groupKey = "",
|
||||
@ -121,10 +117,9 @@ function release(): void {
|
||||
* 初始化并发参数(由 config 设置)
|
||||
*
|
||||
* 必须在第一次 withMemoryGate 调用前执行。
|
||||
* @param _softLimitMB 废弃(兼容签名)
|
||||
* @param concurrency 最大并发数
|
||||
*/
|
||||
export function initMemoryGate(_softLimitMB: number, concurrency?: number): void {
|
||||
export function initMemoryGate(concurrency?: number): void {
|
||||
if (concurrency && concurrency > 0) {
|
||||
maxConcurrency = concurrency;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user