concurrent-ruby 0.6.0.pre.1 → 0.6.0.pre.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/lib/concurrent.rb +9 -29
  4. data/lib/concurrent/{actor.rb → actor/actor.rb} +3 -3
  5. data/lib/concurrent/actor/actor_context.rb +77 -0
  6. data/lib/concurrent/actor/actor_ref.rb +67 -0
  7. data/lib/concurrent/{postable.rb → actor/postable.rb} +1 -1
  8. data/lib/concurrent/actor/simple_actor_ref.rb +94 -0
  9. data/lib/concurrent/actors.rb +5 -0
  10. data/lib/concurrent/agent.rb +81 -47
  11. data/lib/concurrent/async.rb +35 -35
  12. data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
  13. data/lib/concurrent/atomic/atomic_fixnum.rb +170 -0
  14. data/lib/concurrent/{condition.rb → atomic/condition.rb} +0 -0
  15. data/lib/concurrent/{copy_on_notify_observer_set.rb → atomic/copy_on_notify_observer_set.rb} +48 -13
  16. data/lib/concurrent/{copy_on_write_observer_set.rb → atomic/copy_on_write_observer_set.rb} +41 -20
  17. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  18. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  19. data/lib/concurrent/atomic/event.rb +103 -0
  20. data/lib/concurrent/{thread_local_var.rb → atomic/thread_local_var.rb} +0 -0
  21. data/lib/concurrent/atomics.rb +9 -0
  22. data/lib/concurrent/channel/buffered_channel.rb +6 -4
  23. data/lib/concurrent/channel/channel.rb +30 -2
  24. data/lib/concurrent/channel/unbuffered_channel.rb +2 -2
  25. data/lib/concurrent/channel/waitable_list.rb +3 -1
  26. data/lib/concurrent/channels.rb +5 -0
  27. data/lib/concurrent/{channel → collection}/blocking_ring_buffer.rb +16 -5
  28. data/lib/concurrent/collection/priority_queue.rb +305 -0
  29. data/lib/concurrent/{channel → collection}/ring_buffer.rb +6 -1
  30. data/lib/concurrent/collections.rb +3 -0
  31. data/lib/concurrent/configuration.rb +68 -19
  32. data/lib/concurrent/dataflow.rb +9 -9
  33. data/lib/concurrent/delay.rb +21 -13
  34. data/lib/concurrent/dereferenceable.rb +40 -33
  35. data/lib/concurrent/exchanger.rb +3 -0
  36. data/lib/concurrent/{cached_thread_pool.rb → executor/cached_thread_pool.rb} +8 -9
  37. data/lib/concurrent/executor/executor.rb +222 -0
  38. data/lib/concurrent/{fixed_thread_pool.rb → executor/fixed_thread_pool.rb} +6 -7
  39. data/lib/concurrent/{immediate_executor.rb → executor/immediate_executor.rb} +5 -5
  40. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  41. data/lib/concurrent/{java_fixed_thread_pool.rb → executor/java_fixed_thread_pool.rb} +7 -11
  42. data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
  43. data/lib/concurrent/{java_thread_pool_executor.rb → executor/java_thread_pool_executor.rb} +66 -77
  44. data/lib/concurrent/executor/one_by_one.rb +65 -0
  45. data/lib/concurrent/{per_thread_executor.rb → executor/per_thread_executor.rb} +4 -4
  46. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  47. data/lib/concurrent/{ruby_fixed_thread_pool.rb → executor/ruby_fixed_thread_pool.rb} +5 -4
  48. data/lib/concurrent/executor/ruby_single_thread_executor.rb +72 -0
  49. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +282 -0
  50. data/lib/concurrent/{ruby_thread_pool_worker.rb → executor/ruby_thread_pool_worker.rb} +6 -6
  51. data/lib/concurrent/{safe_task_executor.rb → executor/safe_task_executor.rb} +20 -13
  52. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  53. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  54. data/lib/concurrent/executor/timer_set.rb +138 -0
  55. data/lib/concurrent/executors.rb +9 -0
  56. data/lib/concurrent/future.rb +39 -40
  57. data/lib/concurrent/ivar.rb +22 -15
  58. data/lib/concurrent/mvar.rb +2 -1
  59. data/lib/concurrent/obligation.rb +9 -3
  60. data/lib/concurrent/observable.rb +33 -0
  61. data/lib/concurrent/options_parser.rb +46 -0
  62. data/lib/concurrent/promise.rb +23 -24
  63. data/lib/concurrent/scheduled_task.rb +21 -45
  64. data/lib/concurrent/timer_task.rb +204 -126
  65. data/lib/concurrent/tvar.rb +1 -1
  66. data/lib/concurrent/utilities.rb +3 -36
  67. data/lib/concurrent/{processor_count.rb → utility/processor_count.rb} +1 -1
  68. data/lib/concurrent/utility/timeout.rb +36 -0
  69. data/lib/concurrent/utility/timer.rb +21 -0
  70. data/lib/concurrent/version.rb +1 -1
  71. data/lib/concurrent_ruby_ext.bundle +0 -0
  72. data/spec/concurrent/{actor_context_spec.rb → actor/actor_context_spec.rb} +0 -8
  73. data/spec/concurrent/{actor_ref_shared.rb → actor/actor_ref_shared.rb} +9 -59
  74. data/spec/concurrent/{actor_spec.rb → actor/actor_spec.rb} +43 -41
  75. data/spec/concurrent/{postable_shared.rb → actor/postable_shared.rb} +0 -0
  76. data/spec/concurrent/actor/simple_actor_ref_spec.rb +135 -0
  77. data/spec/concurrent/agent_spec.rb +160 -71
  78. data/spec/concurrent/atomic/atomic_boolean_spec.rb +172 -0
  79. data/spec/concurrent/atomic/atomic_fixnum_spec.rb +186 -0
  80. data/spec/concurrent/{condition_spec.rb → atomic/condition_spec.rb} +2 -2
  81. data/spec/concurrent/{copy_on_notify_observer_set_spec.rb → atomic/copy_on_notify_observer_set_spec.rb} +0 -0
  82. data/spec/concurrent/{copy_on_write_observer_set_spec.rb → atomic/copy_on_write_observer_set_spec.rb} +0 -0
  83. data/spec/concurrent/atomic/count_down_latch_spec.rb +151 -0
  84. data/spec/concurrent/atomic/cyclic_barrier_spec.rb +248 -0
  85. data/spec/concurrent/{event_spec.rb → atomic/event_spec.rb} +18 -3
  86. data/spec/concurrent/{observer_set_shared.rb → atomic/observer_set_shared.rb} +15 -6
  87. data/spec/concurrent/{thread_local_var_spec.rb → atomic/thread_local_var_spec.rb} +0 -0
  88. data/spec/concurrent/channel/buffered_channel_spec.rb +1 -1
  89. data/spec/concurrent/channel/channel_spec.rb +6 -4
  90. data/spec/concurrent/channel/probe_spec.rb +37 -9
  91. data/spec/concurrent/channel/unbuffered_channel_spec.rb +2 -2
  92. data/spec/concurrent/{channel → collection}/blocking_ring_buffer_spec.rb +0 -0
  93. data/spec/concurrent/collection/priority_queue_spec.rb +317 -0
  94. data/spec/concurrent/{channel → collection}/ring_buffer_spec.rb +0 -0
  95. data/spec/concurrent/configuration_spec.rb +4 -70
  96. data/spec/concurrent/dereferenceable_shared.rb +5 -4
  97. data/spec/concurrent/exchanger_spec.rb +10 -5
  98. data/spec/concurrent/{cached_thread_pool_shared.rb → executor/cached_thread_pool_shared.rb} +15 -37
  99. data/spec/concurrent/{fixed_thread_pool_shared.rb → executor/fixed_thread_pool_shared.rb} +0 -0
  100. data/spec/concurrent/{global_thread_pool_shared.rb → executor/global_thread_pool_shared.rb} +10 -8
  101. data/spec/concurrent/{immediate_executor_spec.rb → executor/immediate_executor_spec.rb} +0 -0
  102. data/spec/concurrent/{java_cached_thread_pool_spec.rb → executor/java_cached_thread_pool_spec.rb} +1 -21
  103. data/spec/concurrent/{java_fixed_thread_pool_spec.rb → executor/java_fixed_thread_pool_spec.rb} +0 -0
  104. data/spec/concurrent/executor/java_single_thread_executor_spec.rb +21 -0
  105. data/spec/concurrent/{java_thread_pool_executor_spec.rb → executor/java_thread_pool_executor_spec.rb} +0 -0
  106. data/spec/concurrent/{per_thread_executor_spec.rb → executor/per_thread_executor_spec.rb} +0 -4
  107. data/spec/concurrent/{ruby_cached_thread_pool_spec.rb → executor/ruby_cached_thread_pool_spec.rb} +1 -1
  108. data/spec/concurrent/{ruby_fixed_thread_pool_spec.rb → executor/ruby_fixed_thread_pool_spec.rb} +0 -0
  109. data/spec/concurrent/executor/ruby_single_thread_executor_spec.rb +18 -0
  110. data/spec/concurrent/{ruby_thread_pool_executor_spec.rb → executor/ruby_thread_pool_executor_spec.rb} +12 -24
  111. data/spec/concurrent/executor/safe_task_executor_spec.rb +103 -0
  112. data/spec/concurrent/{thread_pool_class_cast_spec.rb → executor/thread_pool_class_cast_spec.rb} +12 -0
  113. data/spec/concurrent/{thread_pool_executor_shared.rb → executor/thread_pool_executor_shared.rb} +0 -0
  114. data/spec/concurrent/{thread_pool_shared.rb → executor/thread_pool_shared.rb} +84 -119
  115. data/spec/concurrent/executor/timer_set_spec.rb +183 -0
  116. data/spec/concurrent/future_spec.rb +12 -0
  117. data/spec/concurrent/ivar_spec.rb +11 -1
  118. data/spec/concurrent/observable_shared.rb +173 -0
  119. data/spec/concurrent/observable_spec.rb +51 -0
  120. data/spec/concurrent/options_parser_spec.rb +71 -0
  121. data/spec/concurrent/runnable_shared.rb +6 -0
  122. data/spec/concurrent/scheduled_task_spec.rb +60 -40
  123. data/spec/concurrent/timer_task_spec.rb +130 -144
  124. data/spec/concurrent/{processor_count_spec.rb → utility/processor_count_spec.rb} +0 -0
  125. data/spec/concurrent/{utilities_spec.rb → utility/timeout_spec.rb} +0 -0
  126. data/spec/concurrent/utility/timer_spec.rb +52 -0
  127. metadata +147 -108
  128. data/lib/concurrent/actor_context.rb +0 -31
  129. data/lib/concurrent/actor_ref.rb +0 -39
  130. data/lib/concurrent/atomic.rb +0 -121
  131. data/lib/concurrent/channel/probe.rb +0 -19
  132. data/lib/concurrent/count_down_latch.rb +0 -60
  133. data/lib/concurrent/event.rb +0 -80
  134. data/lib/concurrent/java_cached_thread_pool.rb +0 -45
  135. data/lib/concurrent/ruby_cached_thread_pool.rb +0 -37
  136. data/lib/concurrent/ruby_thread_pool_executor.rb +0 -268
  137. data/lib/concurrent/simple_actor_ref.rb +0 -124
  138. data/lib/concurrent/thread_pool_executor.rb +0 -30
  139. data/spec/concurrent/atomic_spec.rb +0 -201
  140. data/spec/concurrent/count_down_latch_spec.rb +0 -125
  141. data/spec/concurrent/safe_task_executor_spec.rb +0 -58
  142. data/spec/concurrent/simple_actor_ref_spec.rb +0 -219
@@ -12,37 +12,63 @@ module Concurrent
12
12
  end
13
13
 
14
14
  # Adds an observer to this set
15
+ # If a block is passed, the observer will be created by this method and no other params should be passed
15
16
  # @param [Object] observer the observer to add
16
17
  # @param [Symbol] func the function to call on the observer during notification. Default is :update
17
- # @return [Symbol] the added function
18
- def add_observer(observer, func=:update)
19
- @mutex.synchronize { @observers[observer] = func }
18
+ # @return [Object] the added observer
19
+ def add_observer(observer=nil, func=:update, &block)
20
+ if observer.nil? && block.nil?
21
+ raise ArgumentError, 'should pass observer as a first argument or block'
22
+ elsif observer && block
23
+ raise ArgumentError.new('cannot provide both an observer and a block')
24
+ end
25
+
26
+ if block
27
+ observer = block
28
+ func = :call
29
+ end
30
+
31
+ @mutex.lock
32
+ @observers[observer] = func
33
+ @mutex.unlock
34
+
35
+ observer
20
36
  end
21
37
 
22
38
  # @param [Object] observer the observer to remove
23
39
  # @return [Object] the deleted observer
24
40
  def delete_observer(observer)
25
- @mutex.synchronize { @observers.delete(observer) }
41
+ @mutex.lock
42
+ @observers.delete(observer)
43
+ @mutex.unlock
44
+
26
45
  observer
27
46
  end
28
47
 
29
48
  # Deletes all observers
30
49
  # @return [CopyOnWriteObserverSet] self
31
50
  def delete_observers
32
- @mutex.synchronize { @observers.clear }
51
+ @mutex.lock
52
+ @observers.clear
53
+ @mutex.unlock
54
+
33
55
  self
34
56
  end
35
57
 
36
58
  # @return [Integer] the observers count
37
59
  def count_observers
38
- @mutex.synchronize { @observers.count }
60
+ @mutex.lock
61
+ result = @observers.count
62
+ @mutex.unlock
63
+
64
+ result
39
65
  end
40
66
 
41
67
  # Notifies all registered observers with optional args
42
68
  # @param [Object] args arguments to be passed to each observer
43
69
  # @return [CopyOnWriteObserverSet] self
44
70
  def notify_observers(*args, &block)
45
- observers = @mutex.synchronize { @observers.dup }
71
+ observers = duplicate_observers
46
72
  notify_to(observers, *args, &block)
47
73
 
48
74
  self
@@ -62,15 +88,24 @@ module Concurrent
62
88
  private
63
89
 
64
90
  def duplicate_and_clear_observers
65
- @mutex.synchronize do
66
- observers = @observers.dup
67
- @observers.clear
68
- observers
69
- end
91
+ @mutex.lock
92
+ observers = @observers.dup
93
+ @observers.clear
94
+ @mutex.unlock
95
+
96
+ observers
97
+ end
98
+
99
+ def duplicate_observers
100
+ @mutex.lock
101
+ observers = @observers.dup
102
+ @mutex.unlock
103
+
104
+ observers
70
105
  end
71
106
 
72
107
  def notify_to(observers, *args)
73
- raise ArgumentError.new('cannot give arguments and a block') if block_given? && ! args.empty?
108
+ raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
74
109
  observers.each do |observer, function|
75
110
  args = yield if block_given?
76
111
  observer.send(function, *args)
@@ -11,26 +11,40 @@ module Concurrent
11
11
  end
12
12
 
13
13
  # Adds an observer to this set
14
+ # If a block is passed, the observer will be created by this method and no other params should be passed
14
15
  # @param [Object] observer the observer to add
15
16
  # @param [Symbol] func the function to call on the observer during notification. Default is :update
16
- # @return [Symbol] the added function
17
- def add_observer(observer, func=:update)
18
- @mutex.synchronize do
19
- new_observers = @observers.dup
20
- new_observers[observer] = func
21
- @observers = new_observers
17
+ # @return [Object] the added observer
18
+ def add_observer(observer=nil, func=:update, &block)
19
+ if observer.nil? && block.nil?
20
+ raise ArgumentError, 'should pass observer as a first argument or block'
21
+ elsif observer && block
22
+ raise ArgumentError.new('cannot provide both an observer and a block')
22
23
  end
23
- func
24
+
25
+ if block
26
+ observer = block
27
+ func = :call
28
+ end
29
+
30
+ @mutex.lock
31
+ new_observers = @observers.dup
32
+ new_observers[observer] = func
33
+ @observers = new_observers
34
+ @mutex.unlock
35
+
36
+ observer
24
37
  end
25
38
 
26
39
  # @param [Object] observer the observer to remove
27
40
  # @return [Object] the deleted observer
28
41
  def delete_observer(observer)
29
- @mutex.synchronize do
30
- new_observers = @observers.dup
31
- new_observers.delete(observer)
32
- @observers = new_observers
33
- end
42
+ @mutex.lock
43
+ new_observers = @observers.dup
44
+ new_observers.delete(observer)
45
+ @observers = new_observers
46
+ @mutex.unlock
47
+
34
48
  observer
35
49
  end
36
50
 
@@ -68,7 +82,7 @@ module Concurrent
68
82
  private
69
83
 
70
84
  def notify_to(observers, *args)
71
- raise ArgumentError.new('cannot give arguments and a block') if block_given? && ! args.empty?
85
+ raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
72
86
  observers.each do |observer, function|
73
87
  args = yield if block_given?
74
88
  observer.send(function, *args)
@@ -76,19 +90,26 @@ module Concurrent
76
90
  end
77
91
 
78
92
  def observers
79
- @mutex.synchronize { @observers }
93
+ @mutex.lock
94
+ o = @observers
95
+ @mutex.unlock
96
+
97
+ o
80
98
  end
81
99
 
82
100
  def observers=(new_set)
83
- @mutex.synchronize { @observers = new_set}
101
+ @mutex.lock
102
+ @observers = new_set
103
+ @mutex.unlock
84
104
  end
85
105
 
86
106
  def clear_observers_and_return_old
87
- @mutex.synchronize do
88
- old_observers = @observers
89
- @observers = {}
90
- old_observers
91
- end
107
+ @mutex.lock
108
+ old_observers = @observers
109
+ @observers = {}
110
+ @mutex.unlock
111
+
112
+ old_observers
92
113
  end
93
114
  end
94
115
  end
@@ -0,0 +1,116 @@
1
+ require 'concurrent/atomic/condition'
2
+
3
+ module Concurrent
4
+
5
+ # @!macro [attach] count_down_latch
6
+ #
7
+ # A synchronization object that allows one thread to wait on multiple other threads.
8
+ # The thread that will wait creates a `CountDownLatch` and sets the initial value
9
+ # (normally equal to the number of other threads). The initiating thread passes the
10
+ # latch to the other threads then waits for the other threads by calling the `#wait`
11
+ # method. Each of the other threads calls `#count_down` when done with its work.
12
+ # When the latch counter reaches zero the waiting thread is unblocked and continues
13
+ # with its work. A `CountDownLatch` can be used only once. Its value cannot be reset.
14
+ class MutexCountDownLatch
15
+
16
+ # @!macro [attach] count_down_latch_method_initialize
17
+ #
18
+ # Create a new `CountDownLatch` with the initial `count`.
19
+ #
20
+ # @param [Fixnum] count the initial count
21
+ #
22
+ # @raise [ArgumentError] if `count` is not an integer or is less than zero
23
+ def initialize(count)
24
+ unless count.is_a?(Fixnum) && count >= 0
25
+ raise ArgumentError.new('count must be in integer greater than or equal zero')
26
+ end
27
+ @mutex = Mutex.new
28
+ @condition = Condition.new
29
+ @count = count
30
+ end
31
+
32
+ # @!macro [attach] count_down_latch_method_wait
33
+ #
34
+ # Block on the latch until the counter reaches zero or until `timeout` is reached.
35
+ #
36
+ # @param [Fixnum] timeout the number of seconds to wait for the counter or `nil`
37
+ # to block indefinitely
38
+ # @return [Boolean] `true` if the `count` reaches zero else false on `timeout`
39
+ def wait(timeout = nil)
40
+ @mutex.synchronize do
41
+
42
+ remaining = Condition::Result.new(timeout)
43
+ while @count > 0 && remaining.can_wait?
44
+ remaining = @condition.wait(@mutex, remaining.remaining_time)
45
+ end
46
+
47
+ @count == 0
48
+ end
49
+ end
50
+
51
+ # @!macro [attach] count_down_latch_method_count_down
52
+ #
53
+ # Signal the latch to decrement the counter. Will signal all blocked threads when
54
+ # the `count` reaches zero.
55
+ def count_down
56
+ @mutex.synchronize do
57
+ @count -= 1 if @count > 0
58
+ @condition.broadcast if @count == 0
59
+ end
60
+ end
61
+
62
+ # @!macro [attach] count_down_latch_method_count
63
+ #
64
+ # The current value of the counter.
65
+ #
66
+ # @return [Fixnum] the current value of the counter
67
+ def count
68
+ @mutex.synchronize { @count }
69
+ end
70
+ end
71
+
72
+ if RUBY_PLATFORM == 'java'
73
+
74
+ # @!macro count_down_latch
75
+ class JavaCountDownLatch
76
+
77
+ # @!macro count_down_latch_method_initialize
78
+ def initialize(count)
79
+ unless count.is_a?(Fixnum) && count >= 0
80
+ raise ArgumentError.new('count must be in integer greater than or equal zero')
81
+ end
82
+ @latch = java.util.concurrent.CountDownLatch.new(count)
83
+ end
84
+
85
+ # @!macro count_down_latch_method_wait
86
+ def wait(timeout = nil)
87
+ if timeout.nil?
88
+ @latch.await
89
+ true
90
+ else
91
+ @latch.await(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
92
+ end
93
+ end
94
+
95
+ # @!macro count_down_latch_method_count_down
96
+ def count_down
97
+ @latch.countDown
98
+ end
99
+
100
+ # @!macro count_down_latch_method_count
101
+ def count
102
+ @latch.getCount
103
+ end
104
+ end
105
+
106
+ # @!macro count_down_latch
107
+ class CountDownLatch < JavaCountDownLatch
108
+ end
109
+
110
+ else
111
+
112
+ # @!macro count_down_latch
113
+ class CountDownLatch < MutexCountDownLatch
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,106 @@
1
+ module Concurrent
2
+
3
+ class CyclicBarrier
4
+
5
+ Generation = Struct.new(:status)
6
+ private_constant :Generation
7
+
8
+ # Create a new `CyclicBarrier` that waits for `parties` threads
9
+ #
10
+ # @param [Fixnum] parties the number of parties
11
+ # @yield an optional block that will be executed that will be executed after the last thread arrives and before the others are released
12
+ #
13
+ # @raise [ArgumentError] if `parties` is not an integer or is less than zero
14
+ def initialize(parties, &block)
15
+ raise ArgumentError.new('count must be in integer greater than or equal zero') if !parties.is_a?(Fixnum) || parties < 1
16
+ @parties = parties
17
+ @mutex = Mutex.new
18
+ @condition = Condition.new
19
+ @number_waiting = 0
20
+ @action = block
21
+ @generation = Generation.new(:waiting)
22
+ end
23
+
24
+ # @return [Fixnum] the number of threads needed to pass the barrier
25
+ def parties
26
+ @parties
27
+ end
28
+
29
+ # @return [Fixnum] the number of threads currently waiting on the barrier
30
+ def number_waiting
31
+ @number_waiting
32
+ end
33
+
34
+ # Blocks on the barrier until the number of waiting threads is equal to `parties` or until `timeout` is reached or `reset` is called
35
+ # If a block has been passed to the constructor, it will be executed once by the last arrived thread before releasing the others
36
+ # @param [Fixnum] timeout the number of seconds to wait for the counter or `nil` to block indefinitely
37
+ # @return [Boolean] `true` if the `count` reaches zero else false on `timeout` or on `reset` or if the barrier is broken
38
+ def wait(timeout = nil)
39
+ @mutex.synchronize do
40
+
41
+ return false unless @generation.status == :waiting
42
+
43
+ @number_waiting += 1
44
+
45
+ if @number_waiting == @parties
46
+ @action.call if @action
47
+ set_status_and_restore(:fulfilled)
48
+ true
49
+ else
50
+ wait_for_wake_up(@generation, timeout)
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+
57
+ # resets the barrier to its initial state
58
+ # If there is at least one waiting thread, it will be woken up, the `wait` method will return false and the barrier will be broken
59
+ # If the barrier is broken, this method restores it to the original state
60
+ #
61
+ # @return [nil]
62
+ def reset
63
+ @mutex.synchronize do
64
+ set_status_and_restore(:reset)
65
+ end
66
+ end
67
+
68
+ # A barrier can be broken when:
69
+ # - a thread called the `reset` method while at least one other thread was waiting
70
+ # - at least one thread timed out on `wait` method
71
+ #
72
+ # A broken barrier can be restored using `reset` it's safer to create a new one
73
+ # @return [Boolean] true if the barrier is broken otherwise false
74
+ def broken?
75
+ @mutex.synchronize { @generation.status != :waiting }
76
+ end
77
+
78
+ private
79
+
80
+ def set_status_and_restore(new_status)
81
+ @generation.status = new_status
82
+ @condition.broadcast
83
+ @generation = Generation.new(:waiting)
84
+ @number_waiting = 0
85
+ end
86
+
87
+ def wait_for_wake_up(generation, timeout)
88
+ if wait_while_waiting(generation, timeout)
89
+ generation.status == :fulfilled
90
+ else
91
+ generation.status = :broken
92
+ @condition.broadcast
93
+ false
94
+ end
95
+ end
96
+
97
+ def wait_while_waiting(generation, timeout)
98
+ remaining = Condition::Result.new(timeout)
99
+ while generation.status == :waiting && remaining.can_wait?
100
+ remaining = @condition.wait(@mutex, remaining.remaining_time)
101
+ end
102
+ remaining.woken_up?
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,103 @@
1
+ require 'thread'
2
+ require 'concurrent/utilities'
3
+ require 'concurrent/atomic/condition'
4
+
5
+ module Concurrent
6
+
7
+ # Old school kernel-style event reminiscent of Win32 programming in C++.
8
+ #
9
+ # When an `Event` is created it is in the `unset` state. Threads can choose to
10
+ # `#wait` on the event, blocking until released by another thread. When one
11
+ # thread wants to alert all blocking threads it calls the `#set` method which
12
+ # will then wake up all listeners. Once an `Event` has been set it remains set.
13
+ # New threads calling `#wait` will return immediately. An `Event` may be
14
+ # `#reset` at any time once it has been set.
15
+ #
16
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx
17
+ class Event
18
+
19
+ # Creates a new `Event` in the unset state. Threads calling `#wait` on the
20
+ # `Event` will block.
21
+ def initialize
22
+ @set = false
23
+ @mutex = Mutex.new
24
+ @condition = Condition.new
25
+ end
26
+
27
+ # Is the object in the set state?
28
+ #
29
+ # @return [Boolean] indicating whether or not the `Event` has been set
30
+ def set?
31
+ @mutex.lock
32
+ result = @set
33
+ @mutex.unlock
34
+
35
+ result
36
+ end
37
+
38
+ # Trigger the event, setting the state to `set` and releasing all threads
39
+ # waiting on the event. Has no effect if the `Event` has already been set.
40
+ #
41
+ # @return [Boolean] should always return `true`
42
+ def set
43
+ @mutex.lock
44
+ unless @set
45
+ @set = true
46
+ @condition.broadcast
47
+ end
48
+ @mutex.unlock
49
+
50
+ true
51
+ end
52
+
53
+ def try?
54
+ @mutex.lock
55
+
56
+ if @set
57
+ result = false
58
+ else
59
+ @set = true
60
+ @condition.broadcast
61
+ result = true
62
+ end
63
+
64
+ @mutex.unlock
65
+
66
+ result
67
+ end
68
+
69
+ # Reset a previously set event back to the `unset` state.
70
+ # Has no effect if the `Event` has not yet been set.
71
+ #
72
+ # @return [Boolean] should always return `true`
73
+ def reset
74
+ @mutex.lock
75
+ @set = false
76
+ @mutex.unlock
77
+
78
+ true
79
+ end
80
+
81
+ # Wait a given number of seconds for the `Event` to be set by another
82
+ # thread. Will wait forever when no `timeout` value is given. Returns
83
+ # immediately if the `Event` has already been set.
84
+ #
85
+ # @return [Boolean] true if the `Event` was set before timeout else false
86
+ def wait(timeout = nil)
87
+ @mutex.lock
88
+
89
+ unless @set
90
+ remaining = Condition::Result.new(timeout)
91
+ while !@set && remaining.can_wait?
92
+ remaining = @condition.wait(@mutex, remaining.remaining_time)
93
+ end
94
+ end
95
+
96
+ result = @set
97
+
98
+ @mutex.unlock
99
+
100
+ result
101
+ end
102
+ end
103
+ end