【问题标题】:Corrupt contents in NodeJS ref-struct upon garbage collection垃圾收集时 NodeJS ref-struct 中的损坏内容
【发布时间】:2019-01-18 11:11:51
【问题描述】:

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

【问题讨论】:

  • 如果您能提供一小段可重现的代码来演示该问题,那就太好了。
  • @vsenko 感谢您的建议,我在原始问题中添加了一个最小示例。

标签: node.js garbage-collection ref ref-struct


【解决方案1】:

虽然refref-structref-array 是强大但脆弱的东西,但它们的组合可能表现得非常晦涩。

您的样本有两个细微差别:

  1. makeAuthGrantedFfiStruct 调用期间两次调用makeAccessContainerEntry 会覆盖您的全局缓存-CStrings cachedglobal.x0global.x1)将被第二个直接makeAccessContainerEntry call 覆盖。

  2. 看来你也应该缓存每个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,它存储实际数据在内存中所在的内存地址。实际数据通常也表示为 Buffer。

ref-structref 的一个插件,它实现了将底层内存块(缓冲区)解释为结构的能力 - 用户定义类型以及它们在内存中的位置,ref-struct 尝试读取 a 的相应部分内存块并获取值。

ref-arrayref 的一个插件,它实现了将底层内存块(缓冲区)解释为数组的能力 - 用户定义类型以及它们在内存中的位置,ref-array 尝试读取 a 的相应部分内存块并获取数组项。

这样如果你为某个东西分配了一个Buffer,那么就获得一个ref对它的引用(一个新的Buffer,它只是保存了原始Buffer的内存地址)并失去了对原始Buffer的JS引用,然后是原始的缓冲区可以像这样被 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()); 将打印相同的输出,因为 refrefReference 中包含对引用的 data 的隐藏引用。

ref-structref-array 知道这种情况,并且通常也正确地保存对引用数据的隐藏引用。但是ref-structref-array 的组合揭示了一个错误或潜在的不兼容,并且隐藏的引用有时会丢失。一种解决方法是自己缓存引用 - 这是我建议使用的方法。

【讨论】:

  • 感谢@vsenko 抽出宝贵时间帮助我。此解决方案确实可以防止垃圾收集时损坏的值。我仍然希望我能更深入地理解这个错误,但现在这很好。谢谢。
  • @HunterLester,我添加了一些背景。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 1970-01-01
  • 2012-05-15
  • 2012-06-28
  • 2011-01-21
  • 1970-01-01
相关资源
最近更新 更多