PageRenderTime 28ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/ruby/stdlib/webrick/utils.rb

http://github.com/jruby/jruby
Ruby | 246 lines | 226 code | 4 blank | 16 comment | 3 complexity | 4e3f19957beccb3c3a25c87e435b917c MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, GPL-2.0, JSON, LGPL-2.1
  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")
  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 "thread"
  85. require "timeout"
  86. require "singleton"
  87. ##
  88. # Class used to manage timeout handlers across multiple threads.
  89. #
  90. # Timeout handlers should be managed by using the class methods which are
  91. # synchronized.
  92. #
  93. # id = TimeoutHandler.register(10, Timeout::Error)
  94. # begin
  95. # sleep 20
  96. # puts 'foo'
  97. # ensure
  98. # TimeoutHandler.cancel(id)
  99. # end
  100. #
  101. # will raise Timeout::Error
  102. #
  103. # id = TimeoutHandler.register(10, Timeout::Error)
  104. # begin
  105. # sleep 5
  106. # puts 'foo'
  107. # ensure
  108. # TimeoutHandler.cancel(id)
  109. # end
  110. #
  111. # will print 'foo'
  112. #
  113. class TimeoutHandler
  114. include Singleton
  115. class Thread < ::Thread; end
  116. ##
  117. # Mutex used to synchronize access across threads
  118. TimeoutMutex = Mutex.new # :nodoc:
  119. ##
  120. # Registers a new timeout handler
  121. #
  122. # +time+:: Timeout in seconds
  123. # +exception+:: Exception to raise when timeout elapsed
  124. def TimeoutHandler.register(seconds, exception)
  125. instance.register(Thread.current, Time.now + seconds, exception)
  126. end
  127. ##
  128. # Cancels the timeout handler +id+
  129. def TimeoutHandler.cancel(id)
  130. instance.cancel(Thread.current, id)
  131. end
  132. ##
  133. # Creates a new TimeoutHandler. You should use ::register and ::cancel
  134. # instead of creating the timeout handler directly.
  135. def initialize
  136. TimeoutMutex.synchronize{
  137. @timeout_info = Hash.new
  138. }
  139. @queue = Queue.new
  140. @watcher = Thread.start{
  141. to_interrupt = []
  142. while true
  143. now = Time.now
  144. wakeup = nil
  145. to_interrupt.clear
  146. TimeoutMutex.synchronize{
  147. @timeout_info.each {|thread, ary|
  148. next unless ary
  149. ary.each{|info|
  150. time, exception = *info
  151. if time < now
  152. to_interrupt.push [thread, info.object_id, exception]
  153. elsif !wakeup || time < wakeup
  154. wakeup = time
  155. end
  156. }
  157. }
  158. }
  159. to_interrupt.each {|arg| interrupt(*arg)}
  160. if !wakeup
  161. @queue.pop
  162. elsif (wakeup -= now) > 0
  163. begin
  164. (th = Thread.start {@queue.pop}).join(wakeup)
  165. ensure
  166. th&.kill&.join
  167. end
  168. end
  169. @queue.clear
  170. end
  171. }
  172. end
  173. ##
  174. # Interrupts the timeout handler +id+ and raises +exception+
  175. def interrupt(thread, id, exception)
  176. if cancel(thread, id) && thread.alive?
  177. thread.raise(exception, "execution timeout")
  178. end
  179. end
  180. ##
  181. # Registers a new timeout handler
  182. #
  183. # +time+:: Timeout in seconds
  184. # +exception+:: Exception to raise when timeout elapsed
  185. def register(thread, time, exception)
  186. info = nil
  187. TimeoutMutex.synchronize{
  188. @timeout_info[thread] ||= Array.new
  189. @timeout_info[thread] << (info = [time, exception])
  190. }
  191. @queue.push nil
  192. return info.object_id
  193. end
  194. ##
  195. # Cancels the timeout handler +id+
  196. def cancel(thread, id)
  197. TimeoutMutex.synchronize{
  198. if ary = @timeout_info[thread]
  199. ary.delete_if{|info| info.object_id == id }
  200. if ary.empty?
  201. @timeout_info.delete(thread)
  202. end
  203. return true
  204. end
  205. return false
  206. }
  207. end
  208. end
  209. ##
  210. # Executes the passed block and raises +exception+ if execution takes more
  211. # than +seconds+.
  212. #
  213. # If +seconds+ is zero or nil, simply executes the block
  214. def timeout(seconds, exception=Timeout::Error)
  215. return yield if seconds.nil? or seconds.zero?
  216. # raise ThreadError, "timeout within critical session" if Thread.critical
  217. id = TimeoutHandler.register(seconds, exception)
  218. begin
  219. yield(seconds)
  220. ensure
  221. TimeoutHandler.cancel(id)
  222. end
  223. end
  224. module_function :timeout
  225. end
  226. end