[v1.4] updateResource 相关代码调整#1193
[v1.4] updateResource 相关代码调整#1193cyfung1031 wants to merge 38 commits intoscriptscat:release/v1.4from
Conversation
|
UI 部份另行处理 |
| await this.scriptCodeDAO.save({ | ||
| uuid: script.uuid, | ||
| code: param.code, | ||
| }); | ||
| logger.info("install success"); | ||
|
|
||
| // Cache更新 & 下载资源 | ||
| await Promise.all([ | ||
| compiledResourceUpdatePromise, | ||
| this.resourceService.updateResourceByType(script, "require"), | ||
| this.resourceService.updateResourceByType(script, "require-css"), | ||
| this.resourceService.updateResourceByType(script, "resource"), | ||
| this.resourceService.updateResourceByTypes(script, ["require", "require-css", "resource"]), | ||
| ]); | ||
| // 如果资源不完整,还是要接受安装吗??? | ||
|
|
||
| // 广播一下 | ||
| // Runtime 會負責更新 CompiledResource | ||
| this.publishInstallScript(script, { update, upsertBy }); | ||
|
|
||
| return { update }; | ||
| }) | ||
| .catch((e: any) => { | ||
| logger.error("install error", Logger.E(e)); | ||
| throw e; | ||
| }); |
There was a problem hiding this comment.
@CodFrm scriptDAO 先 save 了显示成功。 如果资源下载失败,忽略掉是故意的吗?
是的话我就不改这做法
否则就改成下载不了不安装不更新
There was a problem hiding this comment.
那我这个 PR 先不处理这个,先维持
| const updateTime = oldResources?.updatetime; | ||
| // 资源最后更新是24小时内则不更新 | ||
| // 这里是假设 resources 都是 static. 使用者应该加 ?d=xxxx 之类的方式提示SC要更新资源 | ||
| if (updateTime && updateTime > Date.now() - 86400_000) return; |
There was a problem hiding this comment.
@CodFrm 原代码有这个 updateTime 检查操作。但脚本更新可以是每6小时做一次。如果脚本更新了但资源不更新,好像有点奇怪
There was a problem hiding this comment.
之前考虑过不做这个检查,感觉也可以,资源加载下来就不再失效,除非修改url
There was a problem hiding this comment.
Pull request overview
该 PR 主要围绕 Service Worker 侧的资源拉取/更新(updateResource)做重构:移除原先的 loadNow 分支,改为统一的资源获取路径,并新增一个“每次最多 5 个 fetch”的并发控制机制,以降低集中爆发请求对单一站点(如 GreasyFork)的压力。
Changes:
- 新增通用并发控制工具(Semaphore + withTimeoutNotify),并用于资源 fetch 的并发限制
- ResourceService:合并资源获取入口、增加按类型批量处理接口(
getResourceByTypes/updateResourceByTypes),并替换原有getScriptResources的内部实现 - parseUrlSRI 返回结构调整(补充 originalUrl),并联动更新 runtime/script/synchronize 的资源读取逻辑
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pkg/utils/concurrency-control.ts | 新增信号量与超时通知工具,用于资源请求并发控制 |
| src/app/service/service_worker/utils.ts | 调整 parseUrlSRI 返回类型(新增 originalUrl,hash 类型变化) |
| src/app/service/service_worker/synchronize.ts | 备份生成时改为一次性按多类型拉取资源 |
| src/app/service/service_worker/script.ts | 安装流程改为批量触发资源更新;运行资源构建改用新资源读取接口 |
| src/app/service/service_worker/runtime.ts | 资源读取接口替换;file 资源更新逻辑接入新的 parseUrlSRI / updateResource 签名 |
| src/app/service/service_worker/resource.ts | 核心重构:移除 loadNow 路径,新增批量接口与 fetch 并发控制实现 |
| src/app/repo/resource.ts | 补充 contentType 字段语义注释 |
| async createResourceByUrlFetch(u: TUrlSRIInfo, type: ResourceType): Promise<Resource> { | ||
| const url = u.url; // 无 URI Integrity Hash | ||
|
|
||
| let released = false; | ||
| await fetchSemaphore.acquire(); | ||
| // Semaphore 锁 - 同期只有五个 fetch 一起执行 | ||
| const delay = randNum(100, 150); // 100~150ms delay before starting fetch | ||
| await sleep(delay); | ||
| // 执行 fetch, 若超过 800ms, 不会中止 fetch 但会启动下一个网络连接任务 | ||
| // 这只为了避免等候时间过长,同时又不会有过多网络任务同时发生,使Web伺服器返回错误 | ||
| const { result, err } = await withTimeoutNotify(fetch(url), 800, ({ done, timeouted, err }) => { | ||
| if (timeouted || done || err) { | ||
| // fetch 成功 或 发生错误 或 timeout 时解锁 | ||
| if (!released) { | ||
| released = true; | ||
| fetchSemaphore.release(); | ||
| } | ||
| } | ||
| }); | ||
| // Semaphore 锁已解锁。继续处理 fetch Response 的结果 |
There was a problem hiding this comment.
createResourceByUrlFetch 里在 800ms timeout 时提前 release Semaphore,但实际 fetch 仍在进行;在网络慢/服务端卡顿时会导致“in-flight fetch”数量不受 5 的限制,反而可能越跑越多,失去并发控制意义并增加对目标站点的压力。建议把 release 放在 fetch settle(then/catch/finally)时,或改用 AbortController 真正中止超时请求;如果想做的是限速/分批启动,请单独实现 rate limiter 而不是提前释放并发锁。
| if (oldResources && !resourcePath.startsWith("file:///")) { | ||
| // 读取过但失败的资源加载也会被放在缓存,避免再加载资源 | ||
| // 因此 getResource 时不会再加载资源,直接返回 undefined 表示没有资源 | ||
| if (!oldResources.contentType) { | ||
| freshResource = undefined; | ||
| } else { | ||
| freshResource = oldResources; | ||
| } | ||
| } else { |
There was a problem hiding this comment.
getResourceByTypes 在 oldResources 已存在且是非 file:/// 时,如果 oldResources.contentType 为空(之前下载失败的空 Resource),会直接返回 undefined 且不会再次尝试更新,导致该资源失败后“永久缺失”(除非手动清缓存/删除记录)。建议对空 Resource 也加入重试逻辑(例如按 updatetime 做退避/过期重试),或在这里直接走 updateResource(uuid, u, type, oldResources) 以便恢复。
| export class Semaphore { | ||
| private active = 0; | ||
| private readonly queue: Array<() => void> = []; | ||
|
|
||
| constructor(readonly limit: number) { | ||
| if (limit < 1) throw new Error("limit must be >= 1"); | ||
| } | ||
|
|
||
| async acquire() { | ||
| if (this.active >= this.limit) { | ||
| await new Promise<void>((resolve) => this.queue.push(resolve)); | ||
| } | ||
| this.active++; | ||
| } | ||
|
|
||
| release() { | ||
| if (this.active > 0) { | ||
| this.active--; | ||
| this.queue.shift()?.(); | ||
| } else { | ||
| console.warn("Semaphore double release detected"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| type TWithTimeoutNotifyResult<T> = { | ||
| timeouted: boolean; | ||
| result: T | undefined; | ||
| done: boolean; | ||
| err: undefined | Error; | ||
| }; | ||
| export const withTimeoutNotify = <T>( | ||
| promise: Promise<T>, | ||
| time: number, | ||
| fn: (res: TWithTimeoutNotifyResult<T>) => any | ||
| ) => { | ||
| const res: TWithTimeoutNotifyResult<T> = { timeouted: false, result: undefined, done: false, err: undefined }; | ||
| const cid = setTimeout(() => { | ||
| res.timeouted = true; | ||
| fn(res); | ||
| }, time); | ||
| return promise | ||
| .then((result: T) => { | ||
| clearTimeout(cid); | ||
| res.result = result; | ||
| res.done = true; | ||
| fn(res); | ||
| return res; | ||
| }) | ||
| .catch((e) => { | ||
| clearTimeout(cid); | ||
| res.err = e; | ||
| res.done = true; | ||
| fn(res); | ||
| return res; | ||
| }); | ||
| }; |
There was a problem hiding this comment.
新增的 concurrency-control 工具(Semaphore/withTimeoutNotify)目前缺少单元测试。src/pkg/utils 里已有大量 Vitest 测试文件,建议补充:1) Semaphore 的 acquire/release FIFO 行为与 limit 限制;2) withTimeoutNotify 在 timeout/resolve/reject 三种情况下的回调与返回值状态,避免后续并发控制改动引入回归。
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
|
||
| const scriptCodes = {} as Record<string, string>; | ||
| // 更新资源使用了file协议的脚本 | ||
| // 更新资源使用了file协议的脚本 ( 不能在其他地方更新吗?? 见 Issue #918 ) |
There was a problem hiding this comment.
无法监听到file的变更,也不好处理吧
对。。
当时可能头脑不太清醒吧
- 提取魔法数字为命名常量(MAX_CONCURRENT_FETCHES, FETCH_DELAY, FETCH_SEMAPHORE_TIMEOUT, RESOURCE_CACHE_TTL) - 清理疑问注释,改为明确的设计决策说明 - 为 Semaphore 和 withTimeoutNotify 添加单元测试
概述 Descriptions
我都不知道要怎么说明了
反正就是有问题的部份改了
有些觉得奇怪的,我没有改,只加了 comment
变更内容 Changes
截图 Screenshots