垃圾收集时 NodeJS ref-struct 中的内容损坏
Corrupt contents in NodeJS ref-struct upon garbage collection
将一个 ref-struct
实例嵌套在另一个实例中,嵌套对象的一个属性在手动垃圾回收时被破坏。
查看此最小代码复制: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
虽然ref
、ref-struct
和ref-array
是强大的,但却是脆弱的东西,它们的组合可能表现得非常晦涩。
您的示例有两个细微差别:
调用 makeAccessContainerEntry
两次会覆盖您的全局缓存 - CStrings
cached (global.x0
and global.x1
) during the makeAuthGrantedFfiStruct
call will be overwritten by the second 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指针是一个Buffer,它存储实际数据在内存中所在的内存地址。实际数据通常也表示为缓冲区。
ref-struct
是 ref
的一个插件,它实现了将底层内存块(缓冲区)解释为结构的能力——用户定义类型以及它们在内存中的位置,ref-struct
尝试读取内存块的相应部分并获取值。
ref-array
是 ref
的一个插件,它实现了将底层内存块(缓冲区)解释为数组的能力 - 用户定义类型以及它们在内存中的位置,ref-array
尝试读取内存块的相应部分并获取数组项。
这样,如果你为某物分配了一个 Buffer,然后获得一个 ref
对它的引用(一个新的 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
中引用的 data
的隐藏引用。
ref-struct
和 ref-array
知道这种情况并且通常也正确地持有对引用数据的隐藏引用。但是 ref-struct
和 ref-array
的组合揭示了一个错误或潜在的不兼容性,并且隐藏的引用有时会丢失。解决方法是自己缓存引用 - 这是我建议使用的方法。
将一个 ref-struct
实例嵌套在另一个实例中,嵌套对象的一个属性在手动垃圾回收时被破坏。
查看此最小代码复制: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
虽然ref
、ref-struct
和ref-array
是强大的,但却是脆弱的东西,它们的组合可能表现得非常晦涩。
您的示例有两个细微差别:
调用
makeAccessContainerEntry
两次会覆盖您的全局缓存 -CStrings
cached (global.x0
andglobal.x1
) during themakeAuthGrantedFfiStruct
call will be overwritten by the second 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指针是一个Buffer,它存储实际数据在内存中所在的内存地址。实际数据通常也表示为缓冲区。
ref-struct
是 ref
的一个插件,它实现了将底层内存块(缓冲区)解释为结构的能力——用户定义类型以及它们在内存中的位置,ref-struct
尝试读取内存块的相应部分并获取值。
ref-array
是 ref
的一个插件,它实现了将底层内存块(缓冲区)解释为数组的能力 - 用户定义类型以及它们在内存中的位置,ref-array
尝试读取内存块的相应部分并获取数组项。
这样,如果你为某物分配了一个 Buffer,然后获得一个 ref
对它的引用(一个新的 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
中引用的 data
的隐藏引用。
ref-struct
和 ref-array
知道这种情况并且通常也正确地持有对引用数据的隐藏引用。但是 ref-struct
和 ref-array
的组合揭示了一个错误或潜在的不兼容性,并且隐藏的引用有时会丢失。解决方法是自己缓存引用 - 这是我建议使用的方法。