【问题标题】:Using System.cmd within a Poolboy worker (gen_server) causes silent failure在 Poolboy 工作人员 (gen_server) 中使用 System.cmd 会导致静默失败
【发布时间】:2015-06-18 05:36:37
【问题描述】:

我有一个由 poolboy 工人产生的函数

基本概述:

  • Phoenix Controller 用数据调用 Dispatcher
  • Dispatcher 将数据传递给 Poolboy 工作人员
  • Poolboy 工作人员使用要处理的给定数据生成一个新进程
  • 新进程使用数据调用系统命令(本例中为 wget)

我遇到的问题是,当我运行 ExUnit 测试时,它一直到生成的进程都很好,我可以输出数据(使用 IO.inspect)。

当我运行 System.cmd("wget".... 当 ExUnit 测试运行时,我在终端中看到 wget 输出,因此该命令实际上正在运行,但是我在该命令之后所做的任何事情都没有不跑。

如果我这样做,那么在我的工人中:

IO.puts "hello"
System.cmd("wget", opts)
IO.puts "world"

然后我看到 hello 我看到 wget 的输出但我没有看到 world

如果我做其他类似的事情:

IO.puts "hello"
File.write("/tmp/temp.txt", "test")
IO.puts "world"

然后我看到helloworld 并写入了一个文件。

我是否缺少关于 System.cmd 的特定内容导致出现这种情况?当它不在单独的进程中运行时它工作正常,因此它是进程和 System.cmd 的组合。

有什么想法吗?谢谢!

【问题讨论】:

  • 您是否尝试过运行像ls 这样的更简单的命令,您知道它不会失败?也许wget 失败,而不是System.cmd
  • 另外,您是否看到有任何记录?如果您遇到故障,您应该会在日志中看到条目。
  • “选择”中有什么?听起来 wget 正在等待一些输入或类似的东西,并且不知道您正在通过哪些选项,很难确定发生了什么。
  • @PatrickOscity - 当我运行 "ls", ["-la"] 时一切正常,但我在终端中看不到任何命令输出。但是,如果我尝试对 ls 的结果执行 IO.inspect ,那么它就会再次停止工作。
  • @JoséValim 很抱歉这个愚蠢的问题,但是当我运行 ExUnit 测试时如何检查日志?我以为终端只是输出了状态?

标签: elixir phoenix-framework


【解决方案1】:

您已进入Elixir标记的部分

“这里有龙”

System.cmd 只是Port 的简单包装,Port 在很大程度上是 Erlang port 函数的未记录包装。

http://www.erlang.org/doc/man/erlang.html#open_port-2

底层的 Erlang BEAM 进程调度器是建立在这样的假设之上的: 可以在很短的时间间隔内“交换”进程。如果你只使用 Erlang/Exilir 代码,它们都被设计为在 BEAM VM 中工作。任何可能阻塞或挂起系统调用的代码都需要在驱动程序中运行。这是一个特殊的 连接到 Erlang VM 的接口,将 Erlang 调度程序与任何可能挂在系统调用上的进程隔离开来。

Ports 驱动程序设置为处理对外部程序的调用。

System.cmd 最终调用

 do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun

端口在单独的进程中运行,do_cmd 例程运行接收 循环,直到它从底层 Erlang 端口接收到退出状态。所以 System.cmd 将“阻止”该特定 BEAM 进程,直到 wget unix 进程退出。

但是,Elixir BEAM 的其余流程将继续进行。 我对 PoolBoy 不够熟悉,不知道是否有某种超时 监控或检测您的员工的心跳。但是,如果有并且 wget 命令 超过这个超时时间,工作进程可能会在 wget 命令之前退出 完成。

System.cmd 并没有真正设置为处理可能需要很长时间的命令周围的所有问题。我建议您将 Porcelain 模块作为一个很好的包装器来围绕相当复杂的 Erlang 端口主题。

https://github.com/alco/porcelain

或者因为你正在做一个简单的 wget,使用 Elixir 或 Erlang HTTP 客户端模块可能会在 BEAM 框架中工作得更好。

【讨论】:

  • 感谢详细的回复,很有帮助!我尝试过瓷器,但在使用 .exec 或 .shell 时会产生同样的问题 :( Poolboy 确实有超时但我已将其设置为 :infinity 所以它不应该在长时间运行的进程中超时。wget 只是一个例子我想运行 1 个 shell 程序,所以虽然像 HTTPoison 这样的东西可以解决文件下载,但当我想调用其他 shell 应用程序(例如用于转换目的的 imagemagic 或 openoffice)时,我将再次被卡住
  • 所以要加厚情节......上述问题只有在使用 ExUnit 运行代码时才会出现。如果我通过调用 phoenix 路由来运行它(在运行 mix phoenix.server 之后),那么代码会执行并且我会看到所有预期的输出......但是,执行 results = Porcelain.exec 不会将命令输出保存到 results多变的。 out 元素只是一个空白字符串,而我可以在记录器中看到 wget 的输出,它只是没有保存回变量:/
  • ExUnit 用于执行异步测试的进程超时。所以这与你所看到的一致。
  • 这对于为什么它在 ExUnit 中不起作用是有道理的,尽管设置 async: false 不会阻止这种情况?我仍然对为什么它没有将 System.cmd 的输出设置为变量感到困惑,即使正在调用该命令。
  • 又一次更新!使用github.com/devinus/sh 执行命令确实会将输出保存到results 变量中(尽管在ExUnit 中仍然不起作用)。所以System.cmd("wget"...Porcelain.exec("wget"... 对此不起作用,但Sh.wget 确实起作用
【解决方案2】:

我有同样的问题,可以通过运行带有 -q 选项的 wget 命令来回避它。

System.cmd("wget", ["-q", url])

这似乎可以通过减少 wget 的输出来防止进程卡住。

【讨论】:

    猜你喜欢
    • 2021-08-08
    • 2013-04-26
    • 1970-01-01
    • 1970-01-01
    • 2018-12-08
    • 1970-01-01
    • 2017-04-17
    • 1970-01-01
    • 2020-05-19
    相关资源
    最近更新 更多