【问题标题】:Spawning a supervised process to report a heartbeat periodically生成一个受监督的进程以定期报告心跳
【发布时间】:2016-07-22 18:30:05
【问题描述】:

我正在尝试生成一个进程,该进程将每五秒发布一次 HTTP 请求,以向服务器报告它的心跳。我的代码是:

defmodule MyModule.Heartbeat do
  def start_link do
    spawn_link(fn ->
      :timer.apply_interval(:timer.seconds(5), __MODULE__, :beat, [])
    end)
  end

  defp beat do
    HTTPoison.post "https://myserver/heartbeat
  end
end

defmodule MyModule.Supervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, :ok)
  end

  def init(:ok) do
     children = [
       worker(MyModule.Heartbeat, [])
     ]

    supervise(children, strategy: :one_for_one)
  end
end

但是,当我尝试启动应用程序时,它退出并出现以下错误:

[info] Application my_module exited: MyModule.start(:normal, []) returned an error: shutdown: failed to start child: MyModule.Heartbeat
    ** (EXIT) #PID<0.535.0>

我所需要的只是一些进程,它将作为监督树的一部分运行并在指定的时间间隔内发送它的请求。它本身不需要能够接收任何消息,我也不太在意具体的实现。

谁能建议我在这里做错了什么导致这个过程无法启动,以及是否有更好的方法来实现这一点?

【问题讨论】:

    标签: elixir erlang-supervisor


    【解决方案1】:

    您的代码中有 3 个错误:

    1. :timer.apply_interval/4 立即返回,因此匿名 在MyModule.Heartbeat.start_link/0 中传递给spawn_link 的函数终止 执行该行后不久,导致:timer 删除间隔 它的队列,并且永远不会调用MyModule.Heartbeat.beat/0

      您可以通过添加:timer.sleep(:infinity) 作为最后一个来解决此问题 表达式,使您的进程永远休眠。

    2. MyModule.Heartbeat.beat/0 需要是一个公共函数,以便 传递给spawn_link 的匿名函数可以调用它。

    3. MyModule.Heartbeat.start_link/0 需要在成功时返回 {:ok, pid}spawn_link 只返回一个 pid。

    这 3 次更改后的最终代码:

    defmodule MyModule.Heartbeat do
      def start_link do
        pid = spawn_link(fn ->
          :timer.apply_interval(:timer.seconds(5), __MODULE__, :beat, [])
          :timer.sleep(:infinity)
        end)
        {:ok, pid}
      end
    
      def beat do
        IO.puts "beat"
      end
    end
    
    defmodule MyModule.Supervisor do
      use Supervisor
    
      def start_link do
        Supervisor.start_link(__MODULE__, :ok)
      end
    
      def init(:ok) do
         children = [
           worker(MyModule.Heartbeat, [])
         ]
    
        supervise(children, strategy: :one_for_one)
      end
    end
    

    输出:

    iex(1)> MyModule.Supervisor.start_link
    {:ok, #PID<0.84.0>}
    iex(2)> beat
    beat
    beat
    beat
    

    我不确定您是否需要将所有这些设置作为函数引发的异常 传递给:timer.apply_interval 不影响调用过程。这 以下脚本不断尝试运行该函数,即使它总是引发 例外:

    defmodule Beat do
      def beat do
        raise "hey"
      end
    end
    
    :timer.apply_interval(:timer.seconds(1), Beat, :beat, [])
    :timer.sleep(:infinity)
    

    输出:

    $ elixir a.exs
    
    16:00:43.207 [error] Process #PID<0.53.0> raised an exception
    ** (RuntimeError) hey
        a.exs:3: Beat.beat/0
    
    16:00:44.193 [error] Process #PID<0.54.0> raised an exception
    ** (RuntimeError) hey
        a.exs:3: Beat.beat/0
    
    16:00:45.193 [error] Process #PID<0.55.0> raised an exception
    ** (RuntimeError) hey
        a.exs:3: Beat.beat/0
    

    【讨论】:

    • 为什么使用 :infinity 睡眠后心跳还在继续?整个事情不应该冻结并停止吗?
    • :timer.sleep(:infinity) "冻结" 仅执行该代码的进程(Erlang 进程)。 :timer 应用程序仍在运行,并以指定的时间间隔调用我们要求它调用的函数。
    猜你喜欢
    • 2013-09-23
    • 2023-03-14
    • 2018-05-02
    • 2020-01-25
    • 1970-01-01
    • 2015-06-12
    • 2014-09-12
    • 2018-06-23
    • 1970-01-01
    相关资源
    最近更新 更多