Scala系统进程挂起

时间:2014-02-20 19:38:08

标签: scala process runtime actor hang

我有一个使用ProcessBuilder执行外部进程的actor:

  def act {
    while (true) {
      receive {
        case param: String => {
           val filePaths = Seq("/tmp/file1","/tmp/file2")
           val fileList = new ByteArrayInputStream(filePaths.mkString("\n").getBytes())
           val output = s"myExecutable.sh ${param}" #< fileList !!<

           doSomethingWith(output)
        }
      }
    }
  }

我运行了数百名并行运行的演员。有时,由于未知原因,进程(!!)的执行永远不会返回。它永远挂起。此特定actor无法处理新消息。有没有办法设置此进程的超时返回,如果超过重试?

这些处决永远存在的原因是什么?因为这些命令不应该持续超过几毫秒。

编辑1: 我观察到的两个重要事实:

  1. Max OS X上不会出现此问题,仅在Linux中
  2. 当我不使用ByteArrayInputStream作为执行的输入时,程序不会挂起

4 个答案:

答案 0 :(得分:2)

  

我有一个使用ProcessBuilder来执行外部进程的actor:...我运行了数百个并行运行的actor ......

为了在每种情况下实现几毫秒的工作,这是一些非常繁重的并行处理。并发处理机制排名如下(在资源使用,可伸缩性和性能方面从最差到最佳):

  1. process = heavy-weight
  2. thread =中等权重(可在单个进程空间内执行数十个线程)
  3. actor =轻量级(通过利用单个共享线程或多个共享线程可以执行数十个actor)
  4. 同时产生许多进程需要大量的操作系统资源 - 用于创建和终止进程。在极端情况下,开始的O / S开销&amp;与实际作业执行相比,最终进程可能消耗数百或数千个CPU和内存资源。这就是创建线程模型的原因(以及更高效的actor模型)。将您当前的处理视为在极其可扩展的角色中进行“类似CGI”的非可扩展O / S压力处理 - 这是一种反模式。将一些操作系统强调到破损点并不需要太多:这可能会发生。

    此外,如果正在读取的文件非常大,最好是可扩展性和可靠性来限制同时读取同一磁盘上的文件的进程数。最多可以同时读取10个进程,我怀疑这对100来说是否可行。

      

    Actor应如何调用外部程序?

    当然,如果您将myExecutable.sh中的逻辑转换为Scala,则根本不需要创建进程。实现可扩展性,性能和可靠性会更加直接。

    假设这不可能/不可取,您应该限制创建的进程总数,并且应该在不同的Actors /请求中重复使用它们。

    第一个解决方案选项:(1)创建一个重用的进程池(比如大小为10)(2)创建actor(比如100),如果所有进程都忙于通过ProcessIO(3)与进程进行通信处理,然后Actors阻止直到一个可用。这个选项的问题:复杂性; 100个演员必须努力与流程池互动,演员本身在流程瓶颈时几乎没有增加价值。

    更好的解决方案选项:(1)创建有限数量的actor(比如10)(2)让每个actor创建1个私有长时间运行的进程(即没有这样的池)(3)让每个actor进行通信通过ProcessIO,阻止进程繁忙。问题:仍然不尽可能简单;演员与阻止流程的互动很差。

    最佳解决方案选项:(1)没有actor,来自主线程的简单for循环将实现与actor相同的好处(2)通过for循环创建有限数量的进程(10)(3),顺序使用ProcessIO交互每个进程(如果忙 - 阻止或跳到下一次迭代)

      

    有没有办法为此进程设置超时返回,如果超过重试?

    确实有。演员最强大的功能之一是某些演员能够产生其他演员并充当他们的主管(接收失败或超时消息,他们可以从中恢复/重启)。使用“本地scala actor”,这是通过基本编程完成的,生成自己的检查和超时消息。但我不会介绍,因为Akka方法更强大,更简单。此外,下一个主要的Scala版本(2.11)将使用Akka作为支持的actor模型,使用“本地scala actor”deprecated

    以下是Akka监督演员的示例超时/重启(未编译/测试)。当然,如果你使用第三个解决方案选项,这没用:

    import scala.concurrent.duration._
    import scala.collection.immutable.Set
    
    class Supervisor extends Actor {
      override val supervisorStrategy =
        OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
          case _: ArithmeticException => Resume     // resumes (reuses) all child actors
          case _: NullPointerException => Restart   // restarts all child actors
          case _: IllegalArgumentException => Stop  // terminates this actor & all children
          case _: Exception => Escalate             // supervisor to receive exception
        }
    
      val worker = context.actorOf(Props[Worker])  // creates a supervised child actor
      var pendingRequests = Set.empty[WorkerRequest]
    
      def receive = {
        case req: WorkRequest(sender, jobReq) => 
          pendingRequests = pendingRequests + req
          worker ! req
          system.scheduler.scheduleOnce(10 seconds, self, WorkTimeout(req))
        case resp: WorkResponse(req @ WorkRequest(sender, jobReq), jobResp) => 
          pendingRequests = pendingRequests - req
          sender ! resp
        case timeout: WorkTimeout(req) =>
          if (pendingRequests get req != None) {
            // restart the unresponsive worker
            worker restart
            // resend all pending requests
            pendingRequests foreach{ worker ! _ }
          }
      }
    }
    

    提醒一句:这种对行为者监督的态度不会克服糟糕的架构和障碍。设计。如果您从合适的工艺/线程/演员设计开始,以满足您的要求,那么监督将提高可靠性。但是如果你从糟糕的设计开始,那么就有可能从O / S级故障中使用“强力”恢复可能会加剧你的问题 - 使过程可靠性变差甚至导致机器崩溃。

答案 1 :(得分:1)

我没有足够的信息来重现这个问题,所以我无法准确地诊断它,但是如果我在你的鞋子里,我就会去诊断它。基本方法是鉴别诊断 - 确定可能的原因,以及可以证明或排除它们的测试。

我要做的第一件事就是验证应用程序生成的myExecutable.sh进程是否实际终止。

如果进程不是终止,那么这是问题的一部分,所以我们需要了解原因。我们可以做的一件事是运行myExecutable.sh之外的其他东西。您建议ByteArrayInputStream可能是问题的一部分,这表明myExecutable.shstdin上输入错误。如果是这种情况,那么您可以改为运行一个脚本,只需将其输入记录到一个文件中即可显示。如果输入无效,那么ByteArrayInputStream由于某种原因提供了错误的数据 - 线程安全和unicode是明显的罪魁祸首,但查看实际的错误数据应该会给你一个线索。如果输入有效,那么这是myExecutable.sh中的错误。

如果进程 终止,则问题出在其他地方。我的第一个猜测是它与actor调度有关(actor库通常使用ForkJoin执行,这很好,但不能很好地处理阻塞代码),或scala.sys.process中的错误库(不会是史无前例的 - 我不得不从我正在处理because of a memory leak的项目中删除scala.sys.process

查看挂起线程的堆栈跟踪应该会给你一些线索(VisualVM是你的朋友),因为你应该能够看到正在等待的东西。然后,您可以在OpenJDK或Scala标准库源代码中找到相关代码。你去哪里取决于你找到的东西。

答案 2 :(得分:0)

您是否可以在将来启动此流程及其处理并使用定时等待它?

答案 3 :(得分:0)

我不认为我们可以通过了解myExecutable.sh或doSomethingWith来解决这个问题。

当它挂起时,尝试终止所有myExecutable.sh进程。

  • 如果有帮助,您应该检查myExecutable.sh。
  • 如果没有用,你应该检查doSomethingWith函数。