如何在不退出Erlang shell(erl)的前提下终止receive语句?
问题复现与根因
你执行的如下操作会让Erlang shell进入假死状态:
$ erl Erlang/OTP 24 [erts-12.1] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [jit] [dtrace] Eshell V12.1 (abort with ^G) 1> receive X -> X end.
根本原因是:receive X -> X end. 是运行在当前shell进程上的阻塞表达式,它会无限等待发给当前shell进程的任意消息,只要没有其他进程给shell进程发消息,这个表达式永远不会返回,shell自然无法响应输入。
无需重启Erlang节点的解决方案
你不需要执行(a)bort终止整个节点,用Erlang内置的用户切换功能就能恢复正常shell:
- 按下
Ctrl+G(shell启动提示里已经标注了abort with ^G,这里的^G就是Ctrl+G),进入用户交互模式,会看到如下提示符:
User switch command -->
- 依次执行以下命令:
- 输入
k回车:杀掉当前卡住的shell进程 - 输入
s回车:创建一个全新的shell进程 - 输入
c回车:连接到新创建的shell进程
操作完成后你就会得到新的可用shell提示符,整个节点的其他进程、已加载模块、运行中的任务都不会受到任何影响。
Ctrl+C 菜单无法恢复的原因
你按下Ctrl+C弹出的是虚拟机级别的BREAK控制菜单:
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
它的设计定位不是处理单个shell进程的故障:
- (c)ontinue选项会回到之前的执行状态,也就是继续卡在receive表达式上
- 其余非终止选项都是打印调试信息,不会中断当前卡住的shell进程
- 只有(a)bort相关选项会终止整个节点,因此用这个菜单确实没法回到正常shell提示符。
和Let It Crash理念的契合性
这个处理逻辑完全符合Erlang的Let It Crash设计理念:
- Let It Crash的核心是故障隔离,而非遇到问题就整体重启。单个进程(这里就是卡住的shell进程)出现故障(无限阻塞),不需要影响整个节点的运行,只需要销毁故障进程,创建新的正常进程继续服务即可,这正是你杀掉卡住的shell、开新shell的操作逻辑。
- 反过来说,如果必须终止整个Erlang节点才能恢复shell,反而是违背了Let It Crash的隔离设计目标。
内容的提问来源于stack exchange,提问作者ijt
相关产品推荐
相关产品推荐

