PageRenderTime 45ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/timeout.rb

https://github.com/wanabe/ruby
Ruby | 133 lines | 71 code | 10 blank | 52 comment | 11 complexity | dfb0857e9e9521e5a2ca01a21241a53f MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, 0BSD, Unlicense, GPL-2.0, BSD-3-Clause
  1. # frozen_string_literal: false
  2. # Timeout long-running blocks
  3. #
  4. # == Synopsis
  5. #
  6. # require 'timeout'
  7. # status = Timeout::timeout(5) {
  8. # # Something that should be interrupted if it takes more than 5 seconds...
  9. # }
  10. #
  11. # == Description
  12. #
  13. # Timeout provides a way to auto-terminate a potentially long-running
  14. # operation if it hasn't finished in a fixed amount of time.
  15. #
  16. # Previous versions didn't use a module for namespacing, however
  17. # #timeout is provided for backwards compatibility. You
  18. # should prefer Timeout.timeout instead.
  19. #
  20. # == Copyright
  21. #
  22. # Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
  23. # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
  24. module Timeout
  25. VERSION = "0.1.1"
  26. # Raised by Timeout.timeout when the block times out.
  27. class Error < RuntimeError
  28. attr_reader :thread
  29. def self.catch(*args)
  30. exc = new(*args)
  31. exc.instance_variable_set(:@thread, Thread.current)
  32. exc.instance_variable_set(:@catch_value, exc)
  33. ::Kernel.catch(exc) {yield exc}
  34. end
  35. def exception(*)
  36. # TODO: use Fiber.current to see if self can be thrown
  37. if self.thread == Thread.current
  38. bt = caller
  39. begin
  40. throw(@catch_value, bt)
  41. rescue UncaughtThrowError
  42. end
  43. end
  44. super
  45. end
  46. end
  47. # :stopdoc:
  48. THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o
  49. CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0
  50. private_constant :THIS_FILE, :CALLER_OFFSET
  51. # :startdoc:
  52. # Perform an operation in a block, raising an error if it takes longer than
  53. # +sec+ seconds to complete.
  54. #
  55. # +sec+:: Number of seconds to wait for the block to terminate. Any number
  56. # may be used, including Floats to specify fractional seconds. A
  57. # value of 0 or +nil+ will execute the block without any timeout.
  58. # +klass+:: Exception Class to raise if the block fails to terminate
  59. # in +sec+ seconds. Omitting will use the default, Timeout::Error
  60. # +message+:: Error message to raise with Exception Class.
  61. # Omitting will use the default, "execution expired"
  62. #
  63. # Returns the result of the block *if* the block completed before
  64. # +sec+ seconds, otherwise throws an exception, based on the value of +klass+.
  65. #
  66. # The exception thrown to terminate the given block cannot be rescued inside
  67. # the block unless +klass+ is given explicitly. However, the block can use
  68. # ensure to prevent the handling of the exception. For that reason, this
  69. # method cannot be relied on to enforce timeouts for untrusted blocks.
  70. #
  71. # If a scheduler is defined, it will be used to handle the timeout by invoking
  72. # Scheduler#timeout_after.
  73. #
  74. # Note that this is both a method of module Timeout, so you can <tt>include
  75. # Timeout</tt> into your classes so they have a #timeout method, as well as
  76. # a module method, so you can call it directly as Timeout.timeout().
  77. def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
  78. return yield(sec) if sec == nil or sec.zero?
  79. message ||= "execution expired".freeze
  80. if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
  81. return scheduler.timeout_after(sec, klass || Error, message, &block)
  82. end
  83. from = "from #{caller_locations(1, 1)[0]}" if $DEBUG
  84. e = Error
  85. bl = proc do |exception|
  86. begin
  87. x = Thread.current
  88. y = Thread.start {
  89. Thread.current.name = from
  90. begin
  91. sleep sec
  92. rescue => e
  93. x.raise e
  94. else
  95. x.raise exception, message
  96. end
  97. }
  98. return yield(sec)
  99. ensure
  100. if y
  101. y.kill
  102. y.join # make sure y is dead.
  103. end
  104. end
  105. end
  106. if klass
  107. begin
  108. bl.call(klass)
  109. rescue klass => e
  110. message = e.message
  111. bt = e.backtrace
  112. end
  113. else
  114. bt = Error.catch(message, &bl)
  115. end
  116. level = -caller(CALLER_OFFSET).size-2
  117. while THIS_FILE =~ bt[level]
  118. bt.delete_at(level)
  119. end
  120. raise(e, message, bt)
  121. end
  122. module_function :timeout
  123. end