作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
状态
草案
类型
标准跟踪
创建于
2013年2月7日
Erlang 版本
OTP_R16A

EEP 42: Erlang 的 setrlimit(2) 模拟 #

摘要 #

添加了一个新的函数 erlang:set_process_info_limit/2,允许进程设置自身内存使用的限制。

规范 #

erlang:set_process_info_limit(Item, Limit :: integer()) ->
    Old_Limit :: integer()

Old_Limit 为 0 表示未设置限制。Limit 为 0 表示不设置限制。

可以设置的项包括:

  • memory:可用于堆栈、堆和内部结构的字节数。

  • memory_words:与 memory 相同的值,但以字为单位表示,以便与 erlang:process_info/[1,2] 中的 heap_sizetotal_heap_sizestack_size 保持一致。这些函数也应该修改以接受此项。

  • message_queue_len:未处理消息的数量。(旁注)文档中提到了 message_queue_len,但系统生成和识别的是 message_queue_len。(结束旁注)

实际设置的值可能小于 Limit,只要节点中配置的任何物理可实现的系统都不能超过所使用的值。

每次垃圾回收或其他内存重组后都会检查非零内存限制;将内存限制设置为小于当前 process_info(self(), 'memory') 的非零值会强制立即进行垃圾回收。

如果进程所需的内存超过其限制,则该进程将以 memory 为原因退出。

每当消息队列长度即将增加或设置此类限制时,都会检查非零消息队列长度限制。

如果超过了消息队列长度限制,则该进程将以 message_queue_len 为原因退出。

动机 #

目前,Erlang 进程可以无限制地获取内存,并最终导致整个节点崩溃。Erlang 邮件列表中经常报告此问题。

Erlang 进程的消息队列也可能无限制地增长。这个问题也经常被报告,足以引起重视。

这是一个 EEP,而不是库更改请求,因为它需要底层运行时系统更改来支持它。例如,限制必须存储在某个地方,这表明需要更改保存有关进程的信息的数据结构。限制必须被检查,这意味着需要更改垃圾收集器和消息发送核心。限制必须被强制执行,这意味着必须支持两个新的退出原因。并且新的退出原因和新函数必须被记录,这需要更改多个文档。

这种需求由来已久,并且至少此 EEP 的存在可能会引发讨论,从而导致一些解决方案。

基本原理 #

设置限制的函数应该具有基于报告当前使用情况的函数名称的名称。该函数是 process_info,因此我在此处选择的名称是 set_process_info_limit

erlang:process_info/[1,2] 函数可以报告任何 Erlang 进程的信息。但是,让一个进程设置另一个进程的限制显然是危险的。我们实际需要的是一个进程能够在启动阶段设置自己的限制。这可以通过将 {'memory',Size}{'message_queue_len',Count} 选项添加到 spawn_opt/[2,3] 来完成。然而,

spawn_opt(Fun, [{memory,128*1024}])

可以通过以下方式模拟

spawn(fun () ->
    erlang:set_process_info_limit(memory, 128*1024),
    Fun()
end)

并且 set_process_info_limit/2 允许进程在不同时间设置不同的限制。如果您试图保护系统免受恶意攻击,那么在 spawn_opt/[2,3] 中设置限制是可行的方法。如果您试图保护系统免受意外的影响,那么让进程设置自己的限制是可行的方法。

要设置的项的名称显然必须与在其他地方使用的相同事物的名称相同。

引入 memory_words 项是因为在我的某些程序运行 32 位,而某些程序运行 64 位的世界中,计算字节数太令人困惑了,尤其是在堆栈大小等等是以字而不是字节来衡量的。

我考虑过允许 total_heap_sizestack_size 等设置自己的限制,但是在发现 total_heap_size“当前包括进程的堆栈”之后,我决定“当前”这不是一个好主意。

我本来希望允许限制减少次数,以便不打算永远存在的进程可以确保自己最终死亡。但是,文档警告说 erlang:bump_reductions/1“可能会被删除”,因此假设减少次数本身很可能会消失。

允许实际设置的限制值较小的目的是允许实现仅使用适合 size_t 的值,以便底层代码不需要处理大数。在符合 POSIX 的操作系统上,如果内存限制较大,Erlang 实现可以使用 UNIX 进程的 RLIMIT_DATA 值,并且可以使用 RLIMIT_DATA 除以消息的最小大小作为消息队列长度。或者,它可以使用其他适当的系统限制。

最大的问题是“如果超过限制会发生什么?”

对于内存,我们可以以 system_limit 为原因退出,但这不会明确什么限制已被超过。引入新的原因似乎是明智的,并且使原因与限制的名称相同似乎是最不容易混淆的方法。

对于消息队列长度,我希望发送消息的进程出现运行时错误。但是,Erlang 文档保证向 pid 发送消息总是会成功,无论该进程是活动还是已死,并且我不想更改太多。消息队列的累积可能是因为某些进程正在发送垃圾消息;我们更倾向于让发送垃圾消息的进程退出,而不是让接收垃圾消息的进程退出。但是,如果垃圾接收器没有清除垃圾,那么这些垃圾永远不会消失,因此它总是会花费时间跳过它,以及存储它的内存。该论点适用于我考虑的另一个选择:只是丢弃会使队列过长的消息。Erlang 的方式是让陷入困境的子系统崩溃。就这样吧。

向后兼容性 #

默认情况下,新函数未导出,因此不会意外调用。

参考实现 #

本草案中没有。

版权 #

本文档已置于公共领域。