【问题标题】:Erlang gen_server with long-running tasks具有长时间运行任务的 Erlang gen_server
【发布时间】:2012-02-11 08:16:51
【问题描述】:

早安,

我有一个gen_server 进程,它定期执行一些长时间运行的状态更新任务 handle_info:

handle_info(trigger, State) ->
    NewState = some_long_running_task(),
    erlang:send_after(?LOOP_TIME, self(), trigger),
    {noreply, NewState}.

但是当这样的任务运行时,整个服务器会变得无响应,并且对它的任何调用都会导致整个服务器崩溃:

my_gen_server:status().
** exception exit: {timeout,{gen_server,call,[my_gen_server,status]}}
     in function  gen_server:call/2

如何避免阻塞 gen_server ? 当任何时候拨打my_gen_server:status() 时,结果应该是这样的: {ok, task_active}

【问题讨论】:

    标签: erlang erlang-otp gen-server


    【解决方案1】:

    在单独的进程中执行长时间运行的任务。让这个进程通知 gen_server 它的任务进度(即是否可以跟踪任务的进度)或者让进程完成任务或失败,但至少通知 gen_server 任务的结果。

    让 gen_server 与执行这个长时间运行任务的进程链接,并让 gen_server 知道 PID 或注册名称,以便在退出信号的情况下,它可以将那个重要进程的死亡与 Rest 隔离。

    handle_info(触发器,状态)->
        Pid = spawn_link(?MODULE,some_long_running_task,[State]),
        NewState = save_pid(Pid,State),
        {noreply,新州};
    handle_info({'EXIT',SomePid,_},State)->
        case lookup_pid(State) == SomePid of
            false -> %% 其他进程
                {noreply,州};
            真->
                %% 我们的进程已经死亡
                %% 我们现在干什么 ?
                %% 生成另一个?
                %% 那是你的决定
                ……
                ……
                {noreply,州}
        结尾;
    handle_info({finished,TaskResult},State)->
        .....%% 更新状态等
        erlang:send_after(?LOOP_TIME, self(), trigger),
        {noreply,新州}。
    
    some_long_running_task(ServerState)->
        ....做工作
        ....返回结果
    

    【讨论】:

    • 如果我想返回扩展状态(不仅仅是{ok, task_active}),这看起来是一个有趣的解决方案,但代码最终变得更难阅读和理解
    • 请记住,您可以始终将数据保存在服务器状态。最好的方法是将服务器状态设置为 erlang record ,使您的代码可读且易于使用 pattern matcheable 例如如果您的状态是:-record(state,{field1,field2,...,long_running_pid}).,您可以轻松地匹配其退出信号,例如:handle_info({'EXIT',Pid,_},#state{long_running_pid = Pid} = State) ->
    【解决方案2】:

    此调用不会导致崩溃,而只是导致可以捕获的异常:

    status() ->
      try gen_server:call(my_gen_server, status)
      catch
        exit:{timeout,_} -> {ok, task_active}
      end.
    

    但是,该调用将保留在服务器的队列中,并且在它处理完当前消息后,它会发送回复消息:{ServerRef, Reply},应该被调用进程丢弃。

    避免阻塞 Erlang 中任何进程的唯一方法(无论gen_server 与否)是不在其上运行阻塞任务。因此,另一种选择可能是在仅与您的服务器对话的不同进程上运行您的长任务,因此没有人关心它是否被阻止。

    【讨论】:

    • 谢谢!这对我处理特定的“exit:{timeout,_}”案例真的很有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-20
    • 2015-11-29
    • 2012-08-13
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多