Open slowmna opened 4 months ago
应该是v8的认为后续代码用不上StructA了,于是释放了。 你试试在循环之后加一行代码访问下StructA。
应该是v8的认为后续代码用不上StructA了,于是释放了。 你试试在循环之后加一行代码访问下StructA。
是的,我们工程里最后加了一行打印StructA的Num后就不会出现此问题了,这算不算是v8的BUG?
应该是v8的认为后续代码用不上StructA了,于是释放了。 你试试在循环之后加一行代码访问下StructA。
是的,我们工程里最后加了一行打印StructA的Num后就不会出现此问题了,这算不算是v8的BUG?
这是v8的gc算法先进性/或者是编译优化做的好的体现。 可能你代入了你以往接触到的gc的印象才认为是bug。 puerts的JsEnv有个宏WITH_OUTER_LINK,加了后会增加内层节点对外层节点引用,能解决这种疏忽问题,但会增加内存。
应该是v8的认为后续代码用不上StructA了,于是释放了。 你试试在循环之后加一行代码访问下StructA。
是的,我们工程里最后加了一行打印StructA的Num后就不会出现此问题了,这算不算是v8的BUG?
其实按v8运行的实例个数和时长来说,和操作系统核心模块是一个级别的。有问题动不动怀疑是v8的bug有点盲目自信了。
应该是v8的认为后续代码用不上StructA了,于是释放了。 你试试在循环之后加一行代码访问下StructA。
是的,我们工程里最后加了一行打印StructA的Num后就不会出现此问题了,这算不算是v8的BUG?
其实按v8运行的实例和时长来说,和操作性核心模块是一个级别的。有问题动不动怀疑是v8的bug有点盲目自信了。
我懂你意思了,也就是for of因为是迭代的目标是StructA.MyTestB, 而不是StructA本身, 而JavaScript本身是可以单独引用MyTestB而释放StructA的,这种情况下激进的GC策略也不会导致BUG。但仍然是一个危险的优化,这个GC的行为越过了通常意义上理解的生命周期, 我特地搜索了ECMAScript6规范,let 变量的作用域是块级作用域,v8错误的估计了从JavaScript的代码上没有访问,于是在块结束前就提前GC了。照这么说来。 WITH_OUTER_LINK这个宏就不是可选项,而是必选项。这不是我们的疏忽,是v8的激进策略和宿主语言的不搭配导致的。 不可能,也不应该有人预先假设块级作用域在退出{}之前就释放。如果v8没有提供任何规避此问题的手段的话,我只能说这玩意就是给纯JavaScript环境用的吧,和宿主语言的跨语言调用现在这样子能用?
按照我的理解来说,如果不加这个宏,下面的语句也可能崩溃: 因为从获取到StructA之后就没有直接对StructA的访问了,后续用的都是MyTestB,按照这个情况来看,基本上用到UStruct 的地方,几乎都可以明明白白非常正当的崩溃
我觉得你是把c++的scope的一些对象声明周期管理给拿到其它语言了。 scope只有变量可见性是各语言通用的。es规范也是指这个。 scope结束释放对象是c++这种不带gc语言的特殊设定。 对于带gc的语言,gc何时释放对象显然和scope没关,即不保证scope结束对象必须gc完成,也不保证scope前不得释放对象,或者你在es规范找下这方面的定义?c++的scope结束释放对象是在c++标准里的。
我觉得你是把c++的scope的一些对象声明周期管理给拿到其它语言了。 scope只有变量可见性是各语言通用的。es规范也是指这个。 scope结束释放对象是c++这种不带gc语言的特殊设定。 对于带gc的语言,gc何时释放对象显然和scope没关,即不保证scope结束对象必须gc完成,也不保证scope前不得释放对象,或者你在es规范找下这方面的定义?c++的scope结束释放对象是在c++标准里的。
行吧,一般人都容易想到 GC生命周期 >= 可见性,没见过 GC生命周期 < 可见性 的, 那对于这样的问题怎么办?只有把WITH_OUTER_LINK改成默认打开了吧
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用
let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; }
有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用
let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; }
有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
你说的这个问题我们考虑到了,所以我们才觉得只要我持有最外层的Struct的引用,内层的的值就不会释放,现在看起来并不能,最外层的值在v8看起来没有再用了就可能会释放。我猜其他团队也许是恰好最外层还在用,而且本身这个复现的几率就很小。 但对于我举例的三行代码,除了这个宏和v8不要在可见性内GC两种手段之外,没有什么很好的解决方案。我打算打开这个宏算了。
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用 let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; } 有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
你说的这个问题我们考虑到了,所以我们才觉得只要我持有最外层的Struct的引用,内层的的值就不会释放,现在看起来并不能,最外层的值在v8看起来没有再用了就可能会释放。我猜其他团队也许是恰好最外层还在用,而且本身这个复现的几率就很小。 但对于我举例的三行代码,除了这个宏和v8不要在可见性内GC两种手段之外,没有什么很好的解决方案。我打算打开这个宏算了。
其实你的理解还是有些问题:你的理解是语言语法描述有个变量A,于是真实代码编译运行后,还是得有个变量A对应的某个实体。
但实际上变量,可见性都是语言层面的东西,真正的程序/虚拟机中是不必要有的,甚至编译阶段就优化掉。当然也有虚拟机实现有这么个变量实体,比如lua虚拟机会有个upvalue数组。
你这么说也没错,我是没想到这个脚本语言都能优化到这样,而且for of这里不算是引用StructA也很隐蔽(相当于有个this = StructA.MyTestB ?所以第二次循环就不会在用到StructA了) 要是说C++ 定义了一个变量,没有显式使用,然后通过栈顶指针推算这个变量的地址去访问。结果这个变量其实被优化没了导致访问出错很多人还是能想到的
你这么说也没错,我是没想到这个脚本语言都能优化到这样,而且for of这里不算是引用StructA也很隐蔽(相当于有个this = StructA.MyTestB ?所以第二次循环就不会在用到StructA了) 要是说C++ 定义了一个变量,没有显式使用,然后通过栈顶指针推算这个变量的地址去访问。结果这个变量其实被优化没了导致访问出错很多人还是能想到的
V8的代码优化还是可以做的比较激进的
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用
let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; }
有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
插一句嘴,对于这种逻辑,是否有比较好的实践?我们团队在做改造的时候也遇到了类似的问题。例如业务通过Delegate广播一个UObject参数,原有C++逻辑可能会直接持有里面的某个字段,现在直接挪到TS就会出现类似https://github.com/Tencent/puerts/issues/1531 或者 https://github.com/Tencent/puerts/issues/1518 的崩溃
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用 let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; } 有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
插一句嘴,对于这种逻辑,是否有比较好的实践?我们团队在做改造的时候也遇到了类似的问题。例如业务通过Delegate广播一个UObject参数,原有C++逻辑可能会直接持有里面的某个字段,现在直接挪到TS就会出现类似#1531 或者 #1518 的崩溃
看你持有的是什么字段,如果你C++持有的时UObject上的一个USTUCT字段的指针,访问一样崩会。如果是基本类型或者对结构体值拷贝了不会崩,但这种在js也不会。
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用 let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; } 有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
插一句嘴,对于这种逻辑,是否有比较好的实践?我们团队在做改造的时候也遇到了类似的问题。例如业务通过Delegate广播一个UObject参数,原有C++逻辑可能会直接持有里面的某个字段,现在直接挪到TS就会出现类似#1531 或者 #1518 的崩溃
看你持有的是什么字段,如果你C++持有的时UObject上的一个USTUCT字段的指针,访问一样崩会。如果是基本类型或者对结构体值拷贝了不会崩,但这种在js也不会。
嗯,大概结构是StructA,内部有一个Actor指针,业务将这个指针赋值给了TS内部的变量。然后后续访问这个变量的话会随机崩溃。主要困惑是类似的逻辑,在开发期可能比较难察觉问题。我们这边是C++向TS转,现在写业务的时候还得时刻注意有没有对象嵌套或者数组,感觉心智负担有点重
你这种情况反而比较少见,所以WITH_OUTER_LINK也不是专门解决这种问题。 WITH_OUTER_LINK解决的是这些更容易犯的错误: foo(StrunctA.MyTestB);//然后foo把参数存起来用 let test = StrunctA.MyTestB botton.onClick = () = { test.xxx; } 有个团队(有比较多的外包)如上错误比较多,所以我加了WITH_OUTER_LINK,但我不知道是不是普遍现象。
插一句嘴,对于这种逻辑,是否有比较好的实践?我们团队在做改造的时候也遇到了类似的问题。例如业务通过Delegate广播一个UObject参数,原有C++逻辑可能会直接持有里面的某个字段,现在直接挪到TS就会出现类似#1531 或者 #1518 的崩溃
看你持有的是什么字段,如果你C++持有的时UObject上的一个USTUCT字段的指针,访问一样崩会。如果是基本类型或者对结构体值拷贝了不会崩,但这种在js也不会。
嗯,大概结构是StructA,内部有一个Actor指针,业务将这个指针赋值给了TS内部的变量。然后后续访问这个变量的话会随机崩溃。主要困惑是类似的逻辑,在开发期可能比较难察觉问题。我们这边是C++向TS转,现在写业务的时候还得时刻注意有没有对象嵌套或者数组,感觉心智负担有点重
你说的这种(持有Actor指针)也不会有问题。 有问题的是Actior里头的StructA,持有StructA指针,这种你C++也有问题。简单来说,没发现C++不崩而js崩的情况。你发现的话可以写个demo。
前置阅读 | Pre-reading
Puer的版本 | Puer Version
1.0.6
UE的版本 | UE Version
5.3
发生在哪个平台 | Platform
Editor(win)
错误信息 | Error Message
TS代码里,Obj是一个U对象,TestA,TestB是UStruct
UCLASS() class UDummyObj : public UObject { GENERATED_BODY()
public: int num; };
USTRUCT(BlueprintType) struct FTestB { GENERATED_USTRUCT_BODY() public:
};
USTRUCT(BlueprintType) struct FTestA { GENERATED_USTRUCT_BODY() public: UPROPERTY(BlueprintReadWrite, EditAnywhere) TMap<int, FTestB> MyTestB; };
UCLASS() class UTestOuter : public UObject { GENERATED_BODY()
public:
};
问题重现 | Bug reproduce
https://github.com/slowmna/puerts_unreal_demo 已经提交了复现工程,直接运行放着跑,我这里几分钟之后第一次打印出console.errorr 从图上可以看到TestA的地址被释放了然后才for of遍历的
大概10分钟后就频繁出现,几乎稳定必现: