PageRenderTime 26ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/aws-flow/lib/aws/flow/future.rb

https://gitlab.com/CORP-RESELLER/aws-flow-ruby
Ruby | 220 lines | 93 code | 26 blank | 101 comment | 5 complexity | e4d27795960f5a8ef9879bba145b3a22 MD5 | raw file
  1. ##
  2. # Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License").
  5. # You may not use this file except in compliance with the License.
  6. # A copy of the License is located at
  7. #
  8. # http://aws.amazon.com/apache2.0
  9. #
  10. # or in the "license" file accompanying this file. This file is distributed
  11. # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  12. # express or implied. See the License for the specific language governing
  13. # permissions and limitations under the License.
  14. ##
  15. # This file contains our Future implementation, which allows us to have asynchronous, blocking promises
  16. module AWS
  17. module Flow
  18. module Core
  19. class AlreadySetException < Exception; end
  20. # Represents the result of an asynchronous computation. Methods are
  21. # provided to:
  22. #
  23. # * retrieve the result of the computation, once it is complete ({Future#get}).
  24. # * check if the computation is complete ({Future#set?})
  25. # * execute a block when computation is complete ({Future#on_set})
  26. #
  27. # The result of a Future can only be retrieved when the computation has
  28. # completed. {Future#get} blocks execution, if necessary, until the
  29. # Future is ready. This is okay: because it will block that fiber,
  30. # another fiber will start executing.
  31. #
  32. class Future
  33. def initialize
  34. @conditional = FiberConditionVariable.new
  35. @set = false
  36. end
  37. # Sets the value of the {Future}, and notifies all of the fibers that
  38. # tried to call {#get} when this future wasn't ready.
  39. # @api private
  40. def set(result=nil)
  41. raise AlreadySetException if @set
  42. @set = true
  43. @result = result
  44. @listeners.each { |b| b.call(self) } if @listeners
  45. @conditional.broadcast if @conditional
  46. self
  47. end
  48. # Is the object is an AWS Flow future? AWS Flow futures *must* have a
  49. # {#get} method.
  50. # @api private
  51. def is_flow_future?
  52. true
  53. end
  54. # Blocks if future is not set. Returns the result of the future once
  55. # {#set} is true.
  56. #
  57. # @return
  58. # The result of the future.
  59. #
  60. # @raise CancellationError
  61. # when the task is cancelled.
  62. def get
  63. @conditional.wait until @set
  64. @result
  65. end
  66. # @return
  67. # true if the {Future} has been set.
  68. def set?
  69. @set
  70. end
  71. # Unsets the future.
  72. #
  73. # @api private
  74. def unset
  75. @set = false
  76. @result = nil
  77. end
  78. # Adds a callback, passed in as a block, which will fire when the future
  79. # is set.
  80. def on_set(&block)
  81. @listeners ||= []
  82. # TODO probably want to use lambda here
  83. @listeners << block
  84. end
  85. end
  86. # Represents a fiber condition variable.
  87. # Based on the Ruby core source:
  88. # https://github.com/ruby/ruby/blob/trunk/lib/thread.rb
  89. # @api private
  90. class FiberConditionVariable
  91. #
  92. # Creates a new ConditionVariable
  93. #
  94. # @api private
  95. def initialize
  96. @waiters = []
  97. end
  98. # Have the current fiber wait on this condition variable, and wake up
  99. # when the FiberConditionVariable is signaled/broadcasted.
  100. #
  101. # @api private
  102. def wait
  103. fiber = ::Fiber.current
  104. @waiters << fiber
  105. Fiber.yield
  106. self
  107. end
  108. #
  109. # Wakes up the first fiber in line waiting for this lock.
  110. #
  111. # @api private
  112. def signal
  113. t = @waiters.shift
  114. t.schedule if t && t.alive?
  115. self
  116. end
  117. #
  118. # Wakes up all fibers waiting for this lock.
  119. #
  120. # @api private
  121. def broadcast
  122. signal until @waiters.empty?
  123. self
  124. end
  125. end
  126. # Represents the result of an asynchronous computation. Methods are
  127. # provided to:
  128. #
  129. # * retrieve the result of the computation, once it is complete ({ExternalFuture#get}).
  130. # * check if the computation is complete ({ExternalFuture#set?})
  131. # * execute a block when computation is complete ({ExternalFuture#on_set})
  132. #
  133. # The result of a Future can only be retrieved when the computation has
  134. # completed. {ExternalFuture#get} blocks execution, if necessary, until the
  135. # ExternalFuture is ready.
  136. #
  137. # Unlike {Future}, {ExternalFuture#get} doesn't block Fibers. Instead it
  138. # blocks the current thread by waiting on a ruby {ConditionVariable}. The
  139. # condition variable is signalled when the future is set, which allows the
  140. # thread to continue execution when the result is ready. This lets us use
  141. # the future outside of an {AsyncScope}
  142. #
  143. class ExternalFuture < Future
  144. def initialize
  145. @conditional = ConditionVariable.new
  146. @mutex = Mutex.new
  147. @set = false
  148. end
  149. # Blocks if future is not set. Returns the result of the future once
  150. # {#set} is true.
  151. #
  152. # @return
  153. # The result of the future.
  154. #
  155. # @raise CancellationError
  156. # when the task is cancelled.
  157. def get(timeout=nil)
  158. @mutex.synchronize do
  159. unless @set
  160. @conditional.wait(@mutex, timeout)
  161. raise Timeout::Error.new unless @set
  162. end
  163. end
  164. @result
  165. end
  166. def method_missing(method, *args, &block)
  167. @mutex.synchronize do
  168. super(method, *args, &block)
  169. end
  170. end
  171. end
  172. # Wrapper around a ruby {Mutex} and {ConditionVariable} to avoid
  173. # writing the synchronization lines repeatedly.
  174. # {ExternalConditionVariable#wait} will block the thread until
  175. # {ConditionVariable} @cond is signalled
  176. #
  177. class ExternalConditionVariable
  178. attr_reader :mutex, :cond
  179. def initialize
  180. @mutex = Mutex.new
  181. @cond = ConditionVariable.new
  182. end
  183. # Block the thread till @cond is signalled
  184. def wait(timeout=nil)
  185. @mutex.synchronize { @cond.wait(@mutex, timeout) }
  186. end
  187. # Pass all messages to the encapsulated {ConditionVariable}
  188. def method_missing(method, *args)
  189. @cond.send(method, *args)
  190. end
  191. end
  192. end
  193. end
  194. end