PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/webrick/utils.rb

http://github.com/ruby/ruby
Ruby | 270 lines | 250 code | 4 blank | 16 comment | 3 complexity | b85348445c5866ececc5d48728f9e4a7 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0
  1. # frozen_string_literal: false
  2. #
  3. # utils.rb -- Miscellaneous utilities
  4. #
  5. # Author: IPR -- Internet Programming with Ruby -- writers
  6. # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
  7. # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
  8. # reserved.
  9. #
  10. # $IPR: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $
  11. require 'socket'
  12. require 'io/nonblock'
  13. require 'etc'
  14. module WEBrick
  15. module Utils
  16. ##
  17. # Sets IO operations on +io+ to be non-blocking
  18. def set_non_blocking(io)
  19. io.nonblock = true if io.respond_to?(:nonblock=)
  20. end
  21. module_function :set_non_blocking
  22. ##
  23. # Sets the close on exec flag for +io+
  24. def set_close_on_exec(io)
  25. io.close_on_exec = true if io.respond_to?(:close_on_exec=)
  26. end
  27. module_function :set_close_on_exec
  28. ##
  29. # Changes the process's uid and gid to the ones of +user+
  30. def su(user)
  31. if pw = Etc.getpwnam(user)
  32. Process::initgroups(user, pw.gid)
  33. Process::Sys::setgid(pw.gid)
  34. Process::Sys::setuid(pw.uid)
  35. else
  36. warn("WEBrick::Utils::su doesn't work on this platform", uplevel: 1)
  37. end
  38. end
  39. module_function :su
  40. ##
  41. # The server hostname
  42. def getservername
  43. host = Socket::gethostname
  44. begin
  45. Socket::gethostbyname(host)[0]
  46. rescue
  47. host
  48. end
  49. end
  50. module_function :getservername
  51. ##
  52. # Creates TCP server sockets bound to +address+:+port+ and returns them.
  53. #
  54. # It will create IPV4 and IPV6 sockets on all interfaces.
  55. def create_listeners(address, port)
  56. unless port
  57. raise ArgumentError, "must specify port"
  58. end
  59. sockets = Socket.tcp_server_sockets(address, port)
  60. sockets = sockets.map {|s|
  61. s.autoclose = false
  62. ts = TCPServer.for_fd(s.fileno)
  63. s.close
  64. ts
  65. }
  66. return sockets
  67. end
  68. module_function :create_listeners
  69. ##
  70. # Characters used to generate random strings
  71. RAND_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
  72. "0123456789" +
  73. "abcdefghijklmnopqrstuvwxyz"
  74. ##
  75. # Generates a random string of length +len+
  76. def random_string(len)
  77. rand_max = RAND_CHARS.bytesize
  78. ret = ""
  79. len.times{ ret << RAND_CHARS[rand(rand_max)] }
  80. ret
  81. end
  82. module_function :random_string
  83. ###########
  84. require "timeout"
  85. require "singleton"
  86. ##
  87. # Class used to manage timeout handlers across multiple threads.
  88. #
  89. # Timeout handlers should be managed by using the class methods which are
  90. # synchronized.
  91. #
  92. # id = TimeoutHandler.register(10, Timeout::Error)
  93. # begin
  94. # sleep 20
  95. # puts 'foo'
  96. # ensure
  97. # TimeoutHandler.cancel(id)
  98. # end
  99. #
  100. # will raise Timeout::Error
  101. #
  102. # id = TimeoutHandler.register(10, Timeout::Error)
  103. # begin
  104. # sleep 5
  105. # puts 'foo'
  106. # ensure
  107. # TimeoutHandler.cancel(id)
  108. # end
  109. #
  110. # will print 'foo'
  111. #
  112. class TimeoutHandler
  113. include Singleton
  114. ##
  115. # Mutex used to synchronize access across threads
  116. TimeoutMutex = Thread::Mutex.new # :nodoc:
  117. ##
  118. # Registers a new timeout handler
  119. #
  120. # +time+:: Timeout in seconds
  121. # +exception+:: Exception to raise when timeout elapsed
  122. def TimeoutHandler.register(seconds, exception)
  123. at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + seconds
  124. instance.register(Thread.current, at, exception)
  125. end
  126. ##
  127. # Cancels the timeout handler +id+
  128. def TimeoutHandler.cancel(id)
  129. instance.cancel(Thread.current, id)
  130. end
  131. def self.terminate
  132. instance.terminate
  133. end
  134. ##
  135. # Creates a new TimeoutHandler. You should use ::register and ::cancel
  136. # instead of creating the timeout handler directly.
  137. def initialize
  138. TimeoutMutex.synchronize{
  139. @timeout_info = Hash.new
  140. }
  141. @queue = Thread::Queue.new
  142. @watcher = nil
  143. end
  144. # :nodoc:
  145. private \
  146. def watch
  147. to_interrupt = []
  148. while true
  149. now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  150. wakeup = nil
  151. to_interrupt.clear
  152. TimeoutMutex.synchronize{
  153. @timeout_info.each {|thread, ary|
  154. next unless ary
  155. ary.each{|info|
  156. time, exception = *info
  157. if time < now
  158. to_interrupt.push [thread, info.object_id, exception]
  159. elsif !wakeup || time < wakeup
  160. wakeup = time
  161. end
  162. }
  163. }
  164. }
  165. to_interrupt.each {|arg| interrupt(*arg)}
  166. if !wakeup
  167. @queue.pop
  168. elsif (wakeup -= now) > 0
  169. begin
  170. (th = Thread.start {@queue.pop}).join(wakeup)
  171. ensure
  172. th&.kill&.join
  173. end
  174. end
  175. @queue.clear
  176. end
  177. end
  178. # :nodoc:
  179. private \
  180. def watcher
  181. (w = @watcher)&.alive? and return w # usual case
  182. TimeoutMutex.synchronize{
  183. (w = @watcher)&.alive? and next w # pathological check
  184. @watcher = Thread.start(&method(:watch))
  185. }
  186. end
  187. ##
  188. # Interrupts the timeout handler +id+ and raises +exception+
  189. def interrupt(thread, id, exception)
  190. if cancel(thread, id) && thread.alive?
  191. thread.raise(exception, "execution timeout")
  192. end
  193. end
  194. ##
  195. # Registers a new timeout handler
  196. #
  197. # +time+:: Timeout in seconds
  198. # +exception+:: Exception to raise when timeout elapsed
  199. def register(thread, time, exception)
  200. info = nil
  201. TimeoutMutex.synchronize{
  202. (@timeout_info[thread] ||= []) << (info = [time, exception])
  203. }
  204. @queue.push nil
  205. watcher
  206. return info.object_id
  207. end
  208. ##
  209. # Cancels the timeout handler +id+
  210. def cancel(thread, id)
  211. TimeoutMutex.synchronize{
  212. if ary = @timeout_info[thread]
  213. ary.delete_if{|info| info.object_id == id }
  214. if ary.empty?
  215. @timeout_info.delete(thread)
  216. end
  217. return true
  218. end
  219. return false
  220. }
  221. end
  222. ##
  223. def terminate
  224. TimeoutMutex.synchronize{
  225. @timeout_info.clear
  226. @watcher&.kill&.join
  227. }
  228. end
  229. end
  230. ##
  231. # Executes the passed block and raises +exception+ if execution takes more
  232. # than +seconds+.
  233. #
  234. # If +seconds+ is zero or nil, simply executes the block
  235. def timeout(seconds, exception=Timeout::Error)
  236. return yield if seconds.nil? or seconds.zero?
  237. # raise ThreadError, "timeout within critical session" if Thread.critical
  238. id = TimeoutHandler.register(seconds, exception)
  239. begin
  240. yield(seconds)
  241. ensure
  242. TimeoutHandler.cancel(id)
  243. end
  244. end
  245. module_function :timeout
  246. end
  247. end