Как обрабатывать исключение волокна за пределами волокна?

Иногда вам нужно работать с необслуживаемыми, старыми, грязными, огромными библиотеками, которые могут быть опасны для нашей программы.

Есть ли лучшие практики для безопасного выполнения этого кода?

Недавно я обнаружил (вероятно, на уровне моих знаний и опыта) неуловимое исключение. Обычная практика, которую я использовал до сегодняшнего дня, - это оборачивать код в Fiber, захватывать исключение внутри и отправлять через канал. Пока это не работает (я не могу поместить Yield или Proc в Fiber).

Опасная библиотека может выглядеть как общий класс с методом, который инкапсулирует Fiber с Fiber.yield для выполнения замены на другие волокна прямо сейчас. В реальной жизни это волокно может содержать внутреннюю работу с вводом-выводом, это не имеет значения.

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

Оболочка, которая должна обрабатывать исключение, состоит из вложенных методов более begin ... rescue. Я вызываю методы с верхнего уровня, а из последнего метода оболочки возвращаю метод 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

Бум! (этот код на 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 ???

Это может произойти из-за обмена исполняемыми контекстами: мой код и исключение находятся в разных контекстах и ​​не могут взаимодействовать? Если вы удалите волокно, исключение будет обнаружено как обычно.

Можно ли решить эту проблему без изменения исходной библиотеки?


person Sergey Fedorov    schedule 08.06.2020    source источник


Ответы (1)


Нет, вы не можете справиться с этим без исправления исходного ошибочного кода. Однако система открытых классов Crystal делает это возможным полностью с вашей стороны, пока поведение восходящего потока не будет исправлено, вы можете просто переопределить метод в своем коде.

Обратите внимание, что это проблема только с точки зрения того, что операция не удалась. Если вы можете получить эту информацию иначе, например, ожидая результата с таймаутом с использованием select, или вам просто все равно, удалась операция или нет, единственная реальная проблема - это небольшой спам в журналах. Волокно, это не основное волокно, сбой не приводит к остановке вашей программы! (см. https://play.crystal-lang.org/#/r/98da < / а>)

Почему это? Вызов исключения означает переход по текущему стеку до тех пор, пока не будет найден обработчик. Когда вы видите «Необработанное исключение», это просто обработчик по умолчанию, который Crystal помещает в корень каждого стека. Что же такое волокно? Это отдельный стек! Таким образом, подъем внутри волокна не разматывает никакие другие стопки, особенно ваше основное волокно.

person Jonne Haß    schedule 08.06.2020
comment
Мне немного грустно, с песочницей было бы проще. Я хотел выполнить чужой код с завязанными глазами и оставил переопределение напоследок в качестве запасного варианта. Что ж, у всего есть своя цена. Это не катастрофа, тем более что основное волокно не сломалось, и прямо сейчас у меня все еще есть несколько вариантов для хеджирования потенциальных рисков. @Jonne большое спасибо за отличное объяснение! - person Sergey Fedorov; 09.06.2020