docs(runtime): explain asset display and image decode lifecycles
This commit is contained in:
@@ -16,12 +16,15 @@ type Entry = { readonly key: string; readonly owner: object; readonly controller
|
||||
* durable bytes before acquiring. Scope objects are never serialized or reused
|
||||
* across project engines/auth epochs. There is no idle URL retention.
|
||||
*/
|
||||
/** 创建纯显示生命周期容器;owner 应绑定 engine/auth epoch,不能序列化或跨账号复用。 */
|
||||
/** 创建纯显示生命周期容器,owners 两层 Map 用对象身份+字符串 key 索引共享条目。
|
||||
* owner 应绑定 engine/auth epoch;本模块不解释账号、资源 ID、URL 权限或实际文件 pin。
|
||||
* 没有容量上限/空闲保留,最后 lease 释放即清理;永久撤销 owner 则另记到 WeakSet。 */
|
||||
export function createDisplayCache(options: { onError?: (error: unknown) => void } = {}) {
|
||||
// owners 仅保留活条目;retired WeakSet 禁止退役 owner 再 acquire,但不强持有其内存。
|
||||
const owners = new Map<object, Map<string, Entry>>(), retired = new WeakSet<object>();
|
||||
let closed = false;
|
||||
/** 统一调用来源 release 并隔离错误,清理/日志失败不阻止释放其它 URL。 */
|
||||
/** 同步调用来源 release,隔离其抛错和 onError 的抛错;onError 只报告清理失败。
|
||||
* load 的失败通过各 lease.ready 传出,不在这里自动记录或重试;release 合同也不是异步清理任务。 */
|
||||
function dispose(source: DisplaySource) { try { source.release?.(); } catch (error) { try { options.onError?.(error); } catch { /* Reporting cannot interrupt cleanup. */ } } }
|
||||
/** 幂等退休共享条目,仅删除仍指向自己的表项,避免旧任务删掉同 key 的新 entry。 */
|
||||
function retire(entry: Entry) {
|
||||
@@ -41,7 +44,9 @@ export function createDisplayCache(options: { onError?: (error: unknown) => void
|
||||
}
|
||||
}
|
||||
return Object.freeze({
|
||||
/** 借用显示来源:同 owner/key 合并加载,但每个调用者可独立取消自己的等待。 */
|
||||
/** 同 owner/key 复用进行中或已加载来源,命中时忽略本次传入的 load,不逐次重验权限。
|
||||
* 每个调用者独立 ready/release;释放已兑现的 ready 不会改变 Promise 的成功结果,但 URL 可能随最后释放失效。
|
||||
* 不自动接收 AbortSignal,取消通过 lease.release;单个释放不取消仍有其它消费者的共享加载。 */
|
||||
acquire(owner: object, key: string, load: (signal: AbortSignal) => Promise<DisplaySource>): DisplayLease {
|
||||
if (closed || retired.has(owner)) throw fault('display_scope_closed');
|
||||
if (!owner || typeof owner !== 'object' || typeof key !== 'string' || !key) throw fault('invalid_display_key');
|
||||
@@ -55,6 +60,7 @@ export function createDisplayCache(options: { onError?: (error: unknown) => void
|
||||
if (created.retired) throw fault('display_released');
|
||||
return load(created.controller.signal);
|
||||
}).then(source => {
|
||||
// 这里只检查 URL 为非空字符串及可选 release 为函数,不验证 URL 协议/可访问性或图片内容。
|
||||
if (!source || typeof source.url !== 'string' || !source.url || source.release !== undefined && typeof source.release !== 'function') { if (typeof source?.release === 'function') dispose(source); throw fault('invalid_display_source'); }
|
||||
// 即使 load 不响应 abort,迟到返回也只能 dispose,不允许挂回已无消费者的缓存。
|
||||
if (created.retired || !created.leases.size) { dispose(source); throw fault('display_released'); }
|
||||
@@ -70,7 +76,8 @@ export function createDisplayCache(options: { onError?: (error: unknown) => void
|
||||
if (!shared.leases.size) retire(shared);
|
||||
};
|
||||
shared.leases.add(release);
|
||||
// 将共享 promise 映射成每租约的独立 ready;来源失败时本租约也自动归还,不泄漏计数。
|
||||
// 映射共享 promise 到各 lease.ready,失败逐个归还;校验 source 时抛错也沿这条失败路径清理。
|
||||
// 缓存不吞 ready 的拒绝,消费者需接住取消/加载错误;释放后迟到成功不能再次 resolve 本租约。
|
||||
const ready = new Promise<string>((resolve, reject) => {
|
||||
rejectReady = reject;
|
||||
shared.promise.then(source => { if (!released) resolve(source.url); }, error => { if (!released) { reject(error); release(); } });
|
||||
|
||||
Reference in New Issue
Block a user