如何处理光纤外的光纤异常?

How to handle fiber exception outside of fiber?

有时您需要使用无人维护的、陈旧的、肮脏的、庞大的库,这些库对我们的程序来说可能是危险的。

是否有安全执行此代码的最佳实践?

最近我发现(可能以我的知识和经验水平)非捕获异常。直到今天,我常用的做法是将代码包装到 Fiber 中,在内部捕获异常并通过 Channel 发送出去。现在它不起作用(我不能将 Yield 或 Proc 放入 Fiber 中)。

危险的库看起来像普通的 class,其方法是使用 Fiber.yield 封装 Fiber 以立即交换执行到其他 fiber。现实生活中这个Fiber可能包含内部work with IO,没关系。

class LibDangerous
  def exec_remote
    spawn do
      raise IO::Error.new
    end
    Fiber.yield
  end
end

应该处理异常的包装器由 begin ... rescue 上的嵌套方法组成。我从顶层和最后一个包装器方法调用方法,我 return lib 方法总是炸毁程序,即使使用块 begin ... rescue.

class Wrapper
  def capture
    begin
      yield self
    rescue
      puts "rescued from :capture"
    end
  end

  def guard
    begin
    capture do |this|
      yield this
    end
    rescue
      puts "rescued from :guard"
    end
  end

  def run
    begin
      yield LibDangerous.new
    rescue ex
      puts "rescued from :run"
    end
  end
end

这好像是因为你需要在发生异常的同一层处理异常,但是我由于种种原因不能修改别人库的代码

wrapper = Wrapper.new

result = wrapper.guard do |sandbox|
  begin
    sandbox.run do |library|
      library.exec_remote
    end
  rescue
    puts "rescued from top-level"
  end
end

轰! (this code on play.crystal-lang.org)

Unhandled exception in spawn:  (IO::Error)
  from /eval:4:7 in '->'
  from /usr/lib/crystal/fiber.cr:255:3 in 'run'
  from /usr/lib/crystal/fiber.cr:92:34 in '->'
  from ???

可能是交换可执行上下文导致的:我的代码和异常在不同的上下文中,无法交互?如果您删除 Fiber,那么异常会像往常一样被捕获。

是否可以在不修改原库的情况下解决这个问题?

不,如果不修补原始错误代码,您将无法处理此问题。但是 Crystal 的开放 class 系统使这完全可以从您这边实现,直到上游行为得到修复,您可以在代码中重新定义该方法。

请注意,这只是处理操作失败的问题。如果您可以通过其他方式获得该信息,例如通过使用 select 超时等待结果,或者您根本不关心操作是否成功,唯一真正的问题是有点记录垃圾邮件。一根纤程,那不是主纤程,崩溃不会影响你的程序! (参见 https://play.crystal-lang.org/#/r/98da

这是为什么?引发异常意味着向上遍历当前堆栈,直到找到处理程序。当您看到 "Unhanded exception" 时,这只是默认处理程序 Crystal 放在每个堆栈的根部。现在什么是纤维?这是一个单独的堆栈!因此,在纤维内部提升不会展开任何其他堆叠,尤其是您的主纤维。