请参阅此最小代码复制:https://github.com/hunterlester/minimum-ref-struct-corruption
请注意,在日志输出的第3行,name的值未被破坏:
Running garbage collection... authGranted object afte gc: { name: '�_n9a\u0002','ref.buffer': <Buffer@0x00000261396F3910 18 86 6c 39 61 02 00 00> } Unnested access container entry after gc: { name: 'apps/net.maidsafe.examples.mailtutorial','ref.buffer': <Buffer@0x00000261396F3B10 60 68 6e 39 61 02 00 00> } Globally assigned values after gc: apps/net.maidsafe.examples.mailtutorial _publicNames
解决方法
您的样本有两个细微差别:
>调用makeAccessContainerEntry两次覆盖全局缓存 – 在makeAuthGrantedFfiStruct调用期间,CStrings cached(global.x0和global.x1)将被第二个directmakeAccessContainerEntry
call覆盖.
>您似乎也应该缓存每个ContainerInfoArray.
此代码应该可以正常工作:
const ArrayType = require('ref-array'); const ref = require('ref'); const Struct = require('ref-struct'); const CString = ref.types.CString; const ContainerInfo = Struct({ name: CString }); const ContainerInfoArray = new ArrayType(ContainerInfo); const AccessContainerEntry = Struct({ containers: ref.refType(ContainerInfo) }); const AuthGranted = Struct({ access_container_entry: AccessContainerEntry }); const accessContainerEntry = [ { "name": "apps/net.maidsafe.examples.mailtutorial",},{ "name": "_publicNames",} ]; const makeAccessContainerEntry = (accessContainerEntry) => { const accessContainerEntryCache = { containerInfoArrayCache: null,containerInfoCaches: [],}; accessContainerEntryCache.containerInfoArrayCache = new ContainerInfoArray(accessContainerEntry.map((entry,index) => { const name = ref.allocCString(entry.name); accessContainerEntryCache.containerInfoCaches.push(name); return new ContainerInfo({ name }); })); return { accessContainerEntry: new AccessContainerEntry({ containers: accessContainerEntryCache.containerInfoArrayCache.buffer,}),accessContainerEntryCache,}; }; const makeAuthGrantedFfiStruct = () => { const ace = makeAccessContainerEntry(accessContainerEntry); return { authGranted: new AuthGranted({ access_container_entry: ace.accessContainerEntry,authGrantedCache: ace.accessContainerEntryCache,}; } const authGranted = makeAuthGrantedFfiStruct(); const unnestedContainerEntry = makeAccessContainerEntry(accessContainerEntry); if(global.gc) { console.log('Running garbage collection...'); global.gc(); } console.log('authGranted object afte gc: ',authGranted.authGranted.access_container_entry.containers.deref()); console.log('Unnested access container entry after gc: ',unnestedContainerEntry.accessContainerEntry.containers.deref());
正如您所看到的,我向makeAccessContainerEntry输出添加了缓存,只要您需要从垃圾收集中保存数据,就应该将其保留在某个位置.
编辑:一些背景
JS实现了高级别Memory Management,其中对象由引用引用,只要没有对特定对象的引用,就会释放内存.
在C中没有引用和GC,但有些指针只是指向特定结构或内存块所在位置的内存地址.
ref使用以下技术来绑定这两个:C指针是一个缓冲区,它存储实际数据在内存中的内存地址.实际数据通常也表示为缓冲区.
ref-struct是ref的一个插件,它实现了将底层内存块(Buffers)解释为结构的能力 – 用户定义类型及它们在内存中的位置,ref-struct尝试读取内存块的相应部分并获取值.
ref-array是ref的一个插件,它实现了将底层内存块(Buffers)解释为数组的能力 – 用户定义类型及它们在内存中的位置,ref-array尝试读取内存块的相应部分并获取数组项.
这样,如果你为某个东西分配了一个Buffer,那么获得一个对它的引用(一个新的Buffer,只保存原始Buffer的内存地址)并丢失对原始Buffer的JS引用,然后原来的Buffer可以通过GC像这样:
function allocateData() { const someData = Buffer.from('sometext'); return ref.ref(data); } const refReference = allocateData(); // There are no more direct JS references to someData - they are all left in the scope of allocateData() function. console.log(refReference.deref()); global.gc(); // As long as there are no more JS references to someData,GC will release it and use its memory for something else. console.log(refReference.deref());
不要急于测试这段代码 – console.log(refReference.deref());将打印相同的输出,因为ref持有对refReference中引用数据的隐藏引用.
ref-struct和ref-array都知道这种情况,并且通常也正确地保存对引用数据的隐藏引用.但是ref-struct和ref-array的组合揭示了一个bug或潜在的不兼容性,隐藏的引用有时会丢失.解决方法是自己缓存引用 – 这是我建议使用的方法.