在DeepSeek Harness插件内核-10:Fiber是如何管理插件生命周期的 介绍Fiber的时候,我曾经提到过Cordis一个关于Context隔离的问题,我们今天就来着重聊聊,看看它是否是Deep Harness的Bug。如果它算一个BUG的话,我觉得这是一个大BuG。
1. 问题重现
我们先利用一个简单的例子来重写这个问题。 如下面的代码所示,我们创建了一个作为根的Context,并采用指定的名称foobar注册了一个基于函数的服务,函数输出字符串 foo; 然后我们调用Context的isolate方法针对服务名称foobar创建了一个与根Context隔离的子Context,并在子Context上面使用相同的名称注册了另一个函数,后者输出bar。我们验证隔离性,我们直接利用从两个Context的foobar属性提取并执行注册的函数。
import { Context} from '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis'{
interface Context {
foobar: ()=>void;
}
}
const context = new Context();
context.provide("foobar", ()=>console.log("foo"));
const subContext = context.isolate("foobar");
subContext.provide("foobar", ()=>console.log("bar"));
context.foobar();
subContext.foobar();
console.log();
context.plugin(ctx=>ctx.foobar());
subContext.inject(["foobar"], ctx=>ctx.foobar());
然后我们在根Context调用plugin方法注册了一个插件,对应的插件函数会直接提取并执行当前Context的foobar属性,并作为函数予以执行。针对子Context,我们则调用inject将foobar服务作为注入到注册的插件中,该插件与注册在根Context的插件具有相同的实现。从如下的输出可以看出:直接调用Context提取的服务函数会输出我们希望的结果,但是通过插件执行的输出则不行。
foo
bar
bar
bar
输出为bar的函数注册在子Context上,而且是通过调用isolate针对服务注册名称foobar创建的,但是却没有起到隔离的作用,把注册在根Context上的同名服务给覆盖了。
2. 进一步验证Context上注册的服务是否正确?
我们可以进一步按照DeepSeek Harness插件内核-06:Context全面解析介绍的关于isolate方法执行逻辑看看作为服务标识的Symbol是否正确写入两个Context的symbols.isolate字典中。为此我们利用如下的方式分别从两个Context的symbols.isolate字典中将服务名称foobar对应的Symbol提取出来,然后以此作为Key从RefelectService中提取对应的服务实例并执行。从输出来看毫无问题。
import { Context, symbols} from '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis'{
interface Context {
foobar: ()=>void;
}
}
const context = new Context();
context.provide("foobar", ()=>console.log("foo"));
const subContext = context.isolate("foobar");
subContext.provide("foobar", ()=>console.log("bar"));
context.foobar();
subContext.foobar();
console.log();
let key = context[symbols.isolate]["foobar"] as symbol;
(context.reflect.store[key]?.value as ()=>void)();
key = subContext[symbols.isolate]["foobar"] as symbol;
(context.reflect.store[key]?.value as ()=>void)();
输出:
foo
bar
foo
bar
3. 查看Fiber的服务存储
到目前为止我们可以确认当注册在根Context中的插件在执行的时候,提取foobar服务实例并没有按照我们希望的方式:从根Context的symbols.isolate字典中根据指定的服务注册名称查询出作为服务实例唯一标识的Symbol,再是在从ReflectService的store这个服务全局注册表中提取对应的封装了服务实例的Impl对象。那么插件执行过程中提取的服务实例来源于何处呢?通过DeepSeek Harness插件内核-08:利用RefectService以反射方式扩展Context的介绍,ReflectService的provide方法还会将服务冗余存储与当前Fiber对象的store字典中,如下的演示程序证实了这一点:
import { Context, symbols} from '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis'{
interface Context {
foobar: ()=>void;
}
}
const context = new Context();
context.provide("foobar", ()=>console.log("foo"));
const subContext = context.isolate("foobar");
subContext.provide("foobar", ()=>console.log("bar"));
context.foobar();
subContext.foobar();
(context.fiber.store!["foobar"]?.value as ()=>void)();
输出:
foo
bar
bar
而且这也能充分介绍前面的现象:当我们调用isolate方法创建一个子Context的时候,只会为创建的Context指定一个symbols.isolate字典,但是fiber依然是继承自父Context。所以但供我们调用provide方法在子Context上注册同名服务的时候,虽然可以保证新的服务映射会保存在子Context上,由于两个Context共享同一个Fiber,之前存储在store字典中的服务自然被覆盖了。
4. 是什么导致从Fiber中提取服务
到目前为止,一切都是我的猜测,如何确定注册的插件执行的时候会从Fiber中提取服务呢?由于插件仅仅是调用ctx.foobar()方法提取并执行服务函数,而且我们知道这个插件的ctx只是一个代理而已,针对foobar方法的读取会被代理处理器拦截,既然ctx的foobar属性返回了错误的对象,我们就通过代理处理器的定义来看看它究竟注入了怎样的拦截操作。通过DeepSeek Harness插件内核-07:Context全面解析的介绍,我们知道这个代理处理器定义在ReflectService的静态属性handler上。
export class ReflectService {
static handler: ProxyHandler<Context> = {
get: (target, prop, ctx: Context) => {
…
try {
…
return ctx.events.waterfall('internal/get', ctx, prop, error, () => {
const key = target[symbols.isolate][prop]
let fiber = (ctx[symbols.shadow] as Context ?? ctx).fiber
while (true) {
const impl = fiber.store?.[prop]
if (impl) return getTraceable(ctx, impl.value)
if (prop in fiber.inject) {
error.message = `cannot get required service "${prop}" in inactive context`
throw error
}
if (!fiber.runtime) throw error
if (fiber.parent[symbols.isolate][prop] !== key) throw error
fiber = fiber.parent.fiber
}
})
} catch (e: any) {
throw e === error ? enhanceError(e) : e
}
},
}
}
ReflectService的静态属性handler返回一个ProxyHandler<Context>对象,上面提供了该对象get方法的部分代码,我们上面的假设在这里得到了印证(const impl = fiber.store?.[prop];if (impl) return getTraceable(ctx, impl.value))。




