/tools/Ruby/lib/ruby/1.8/monitor.rb

http://github.com/agross/netopenspace · Ruby · 356 lines · 177 code · 32 blank · 147 comment · 20 complexity · 1c08cadb6646bbef771c0153e920a5b5 MD5 · raw file

  1. =begin
  2. = monitor.rb
  3. Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
  4. This library is distributed under the terms of the Ruby license.
  5. You can freely distribute/modify this library.
  6. == example
  7. This is a simple example.
  8. require 'monitor.rb'
  9. buf = []
  10. buf.extend(MonitorMixin)
  11. empty_cond = buf.new_cond
  12. # consumer
  13. Thread.start do
  14. loop do
  15. buf.synchronize do
  16. empty_cond.wait_while { buf.empty? }
  17. print buf.shift
  18. end
  19. end
  20. end
  21. # producer
  22. while line = ARGF.gets
  23. buf.synchronize do
  24. buf.push(line)
  25. empty_cond.signal
  26. end
  27. end
  28. The consumer thread waits for the producer thread to push a line
  29. to buf while buf.empty?, and the producer thread (main thread)
  30. reads a line from ARGF and push it to buf, then call
  31. empty_cond.signal.
  32. =end
  33. #
  34. # Adds monitor functionality to an arbitrary object by mixing the module with
  35. # +include+. For example:
  36. #
  37. # require 'monitor.rb'
  38. #
  39. # buf = []
  40. # buf.extend(MonitorMixin)
  41. # empty_cond = buf.new_cond
  42. #
  43. # # consumer
  44. # Thread.start do
  45. # loop do
  46. # buf.synchronize do
  47. # empty_cond.wait_while { buf.empty? }
  48. # print buf.shift
  49. # end
  50. # end
  51. # end
  52. #
  53. # # producer
  54. # while line = ARGF.gets
  55. # buf.synchronize do
  56. # buf.push(line)
  57. # empty_cond.signal
  58. # end
  59. # end
  60. #
  61. # The consumer thread waits for the producer thread to push a line
  62. # to buf while buf.empty?, and the producer thread (main thread)
  63. # reads a line from ARGF and push it to buf, then call
  64. # empty_cond.signal.
  65. #
  66. module MonitorMixin
  67. #
  68. # FIXME: This isn't documented in Nutshell.
  69. #
  70. # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
  71. # above calls while_wait and signal, this class should be documented.
  72. #
  73. class ConditionVariable
  74. class Timeout < Exception; end
  75. # Create a new timer with the argument timeout, and add the
  76. # current thread to the list of waiters. Then the thread is
  77. # stopped. It will be resumed when a corresponding #signal
  78. # occurs.
  79. def wait(timeout = nil)
  80. @monitor.instance_eval {mon_check_owner()}
  81. timer = create_timer(timeout)
  82. Thread.critical = true
  83. count = @monitor.instance_eval {mon_exit_for_cond()}
  84. @waiters.push(Thread.current)
  85. begin
  86. Thread.stop
  87. return true
  88. rescue Timeout
  89. return false
  90. ensure
  91. Thread.critical = true
  92. begin
  93. if timer && timer.alive?
  94. Thread.kill(timer)
  95. end
  96. if @waiters.include?(Thread.current) # interrupted?
  97. @waiters.delete(Thread.current)
  98. end
  99. @monitor.instance_eval {mon_enter_for_cond(count)}
  100. ensure
  101. Thread.critical = false
  102. end
  103. end
  104. end
  105. # call #wait while the supplied block returns +true+.
  106. def wait_while
  107. while yield
  108. wait
  109. end
  110. end
  111. # call #wait until the supplied block returns +true+.
  112. def wait_until
  113. until yield
  114. wait
  115. end
  116. end
  117. # Wake up and run the next waiter
  118. def signal
  119. @monitor.instance_eval {mon_check_owner()}
  120. Thread.critical = true
  121. t = @waiters.shift
  122. t.wakeup if t
  123. Thread.critical = false
  124. Thread.pass
  125. end
  126. # Wake up all the waiters.
  127. def broadcast
  128. @monitor.instance_eval {mon_check_owner()}
  129. Thread.critical = true
  130. for t in @waiters
  131. t.wakeup
  132. end
  133. @waiters.clear
  134. Thread.critical = false
  135. Thread.pass
  136. end
  137. def count_waiters
  138. return @waiters.length
  139. end
  140. private
  141. def initialize(monitor)
  142. @monitor = monitor
  143. @waiters = []
  144. end
  145. def create_timer(timeout)
  146. if timeout
  147. waiter = Thread.current
  148. return Thread.start {
  149. Thread.pass
  150. sleep(timeout)
  151. Thread.critical = true
  152. waiter.raise(Timeout.new)
  153. }
  154. else
  155. return nil
  156. end
  157. end
  158. end
  159. def self.extend_object(obj)
  160. super(obj)
  161. obj.instance_eval {mon_initialize()}
  162. end
  163. #
  164. # Attempts to enter exclusive section. Returns +false+ if lock fails.
  165. #
  166. def mon_try_enter
  167. result = false
  168. Thread.critical = true
  169. if @mon_owner.nil?
  170. @mon_owner = Thread.current
  171. end
  172. if @mon_owner == Thread.current
  173. @mon_count += 1
  174. result = true
  175. end
  176. Thread.critical = false
  177. return result
  178. end
  179. # For backward compatibility
  180. alias try_mon_enter mon_try_enter
  181. #
  182. # Enters exclusive section.
  183. #
  184. def mon_enter
  185. Thread.critical = true
  186. mon_acquire(@mon_entering_queue)
  187. @mon_count += 1
  188. ensure
  189. Thread.critical = false
  190. end
  191. #
  192. # Leaves exclusive section.
  193. #
  194. def mon_exit
  195. mon_check_owner
  196. Thread.critical = true
  197. @mon_count -= 1
  198. if @mon_count == 0
  199. mon_release
  200. end
  201. Thread.critical = false
  202. Thread.pass
  203. end
  204. #
  205. # Enters exclusive section and executes the block. Leaves the exclusive
  206. # section automatically when the block exits. See example under
  207. # +MonitorMixin+.
  208. #
  209. def mon_synchronize
  210. mon_enter
  211. begin
  212. yield
  213. ensure
  214. mon_exit
  215. end
  216. end
  217. alias synchronize mon_synchronize
  218. #
  219. # FIXME: This isn't documented in Nutshell.
  220. #
  221. # Create a new condition variable for this monitor.
  222. # This facilitates control of the monitor with #signal and #wait.
  223. #
  224. def new_cond
  225. return ConditionVariable.new(self)
  226. end
  227. private
  228. def initialize(*args)
  229. super
  230. mon_initialize
  231. end
  232. # called by initialize method to set defaults for instance variables.
  233. def mon_initialize
  234. @mon_owner = nil
  235. @mon_count = 0
  236. @mon_entering_queue = []
  237. @mon_waiting_queue = []
  238. end
  239. # Throw a ThreadError exception if the current thread
  240. # does't own the monitor
  241. def mon_check_owner
  242. if @mon_owner != Thread.current
  243. raise ThreadError, "current thread not owner"
  244. end
  245. end
  246. def mon_acquire(queue)
  247. while @mon_owner && @mon_owner != Thread.current
  248. queue.push(Thread.current)
  249. Thread.stop
  250. Thread.critical = true
  251. end
  252. @mon_owner = Thread.current
  253. end
  254. # mon_release requires Thread.critical == true
  255. def mon_release
  256. @mon_owner = nil
  257. while t = @mon_waiting_queue.shift || @mon_entering_queue.shift
  258. if t.alive?
  259. t.wakeup
  260. return
  261. end
  262. end
  263. end
  264. def mon_enter_for_cond(count)
  265. mon_acquire(@mon_waiting_queue)
  266. @mon_count = count
  267. end
  268. def mon_exit_for_cond
  269. count = @mon_count
  270. @mon_count = 0
  271. return count
  272. ensure
  273. mon_release
  274. end
  275. end
  276. # Monitors provide means of mutual exclusion for Thread programming.
  277. # A critical region is created by means of the synchronize method,
  278. # which takes a block.
  279. # The condition variables (created with #new_cond) may be used
  280. # to control the execution of a monitor with #signal and #wait.
  281. #
  282. # the Monitor class wraps MonitorMixin, and provides aliases
  283. # alias try_enter try_mon_enter
  284. # alias enter mon_enter
  285. # alias exit mon_exit
  286. # to access its methods more concisely.
  287. class Monitor
  288. include MonitorMixin
  289. alias try_enter try_mon_enter
  290. alias enter mon_enter
  291. alias exit mon_exit
  292. end
  293. # Documentation comments:
  294. # - All documentation comes from Nutshell.
  295. # - MonitorMixin.new_cond appears in the example, but is not documented in
  296. # Nutshell.
  297. # - All the internals (internal modules Accessible and Initializable, class
  298. # ConditionVariable) appear in RDoc. It might be good to hide them, by
  299. # making them private, or marking them :nodoc:, etc.
  300. # - The entire example from the RD section at the top is replicated in the RDoc
  301. # comment for MonitorMixin. Does the RD section need to remain?
  302. # - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
  303. # not synchronize.
  304. # - mon_owner is in Nutshell, but appears as an accessor in a separate module
  305. # here, so is hard/impossible to RDoc. Some other useful accessors
  306. # (mon_count and some queue stuff) are also in this module, and don't appear
  307. # directly in the RDoc output.
  308. # - in short, it may be worth changing the code layout in this file to make the
  309. # documentation easier
  310. # Local variables:
  311. # mode: Ruby
  312. # tab-width: 8
  313. # End: