concurrent-ruby 0.7.0.rc0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +166 -0
  4. data/ext/concurrent_ruby_ext/atomic_reference.c +78 -0
  5. data/ext/concurrent_ruby_ext/atomic_reference.h +12 -0
  6. data/ext/concurrent_ruby_ext/extconf.rb +59 -0
  7. data/ext/concurrent_ruby_ext/rb_concurrent.c +28 -0
  8. data/lib/2.0/concurrent_ruby_ext.so +0 -0
  9. data/lib/concurrent.rb +45 -0
  10. data/lib/concurrent/actress.rb +221 -0
  11. data/lib/concurrent/actress/ad_hoc.rb +20 -0
  12. data/lib/concurrent/actress/context.rb +98 -0
  13. data/lib/concurrent/actress/core.rb +228 -0
  14. data/lib/concurrent/actress/core_delegations.rb +42 -0
  15. data/lib/concurrent/actress/envelope.rb +41 -0
  16. data/lib/concurrent/actress/errors.rb +14 -0
  17. data/lib/concurrent/actress/reference.rb +64 -0
  18. data/lib/concurrent/actress/type_check.rb +48 -0
  19. data/lib/concurrent/agent.rb +232 -0
  20. data/lib/concurrent/async.rb +319 -0
  21. data/lib/concurrent/atomic.rb +46 -0
  22. data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
  23. data/lib/concurrent/atomic/atomic_fixnum.rb +162 -0
  24. data/lib/concurrent/atomic/condition.rb +67 -0
  25. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
  26. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
  27. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  28. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  29. data/lib/concurrent/atomic/event.rb +98 -0
  30. data/lib/concurrent/atomic/thread_local_var.rb +117 -0
  31. data/lib/concurrent/atomic_reference/concurrent_update_error.rb +7 -0
  32. data/lib/concurrent/atomic_reference/delegated_update.rb +28 -0
  33. data/lib/concurrent/atomic_reference/direct_update.rb +28 -0
  34. data/lib/concurrent/atomic_reference/jruby.rb +8 -0
  35. data/lib/concurrent/atomic_reference/mutex_atomic.rb +47 -0
  36. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +24 -0
  37. data/lib/concurrent/atomic_reference/rbx.rb +16 -0
  38. data/lib/concurrent/atomic_reference/ruby.rb +16 -0
  39. data/lib/concurrent/atomics.rb +10 -0
  40. data/lib/concurrent/channel/buffered_channel.rb +85 -0
  41. data/lib/concurrent/channel/channel.rb +41 -0
  42. data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
  43. data/lib/concurrent/channel/waitable_list.rb +40 -0
  44. data/lib/concurrent/channels.rb +5 -0
  45. data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
  46. data/lib/concurrent/collection/priority_queue.rb +305 -0
  47. data/lib/concurrent/collection/ring_buffer.rb +59 -0
  48. data/lib/concurrent/collections.rb +3 -0
  49. data/lib/concurrent/configuration.rb +158 -0
  50. data/lib/concurrent/dataflow.rb +91 -0
  51. data/lib/concurrent/delay.rb +112 -0
  52. data/lib/concurrent/dereferenceable.rb +101 -0
  53. data/lib/concurrent/errors.rb +30 -0
  54. data/lib/concurrent/exchanger.rb +34 -0
  55. data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
  56. data/lib/concurrent/executor/executor.rb +229 -0
  57. data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
  58. data/lib/concurrent/executor/immediate_executor.rb +16 -0
  59. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  60. data/lib/concurrent/executor/java_fixed_thread_pool.rb +33 -0
  61. data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
  62. data/lib/concurrent/executor/java_thread_pool_executor.rb +187 -0
  63. data/lib/concurrent/executor/per_thread_executor.rb +24 -0
  64. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  65. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
  66. data/lib/concurrent/executor/ruby_single_thread_executor.rb +73 -0
  67. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +286 -0
  68. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
  69. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  70. data/lib/concurrent/executor/serialized_execution.rb +90 -0
  71. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  72. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  73. data/lib/concurrent/executor/timer_set.rb +143 -0
  74. data/lib/concurrent/executors.rb +9 -0
  75. data/lib/concurrent/future.rb +124 -0
  76. data/lib/concurrent/ivar.rb +111 -0
  77. data/lib/concurrent/logging.rb +17 -0
  78. data/lib/concurrent/mvar.rb +200 -0
  79. data/lib/concurrent/obligation.rb +171 -0
  80. data/lib/concurrent/observable.rb +40 -0
  81. data/lib/concurrent/options_parser.rb +46 -0
  82. data/lib/concurrent/promise.rb +169 -0
  83. data/lib/concurrent/scheduled_task.rb +78 -0
  84. data/lib/concurrent/supervisor.rb +343 -0
  85. data/lib/concurrent/timer_task.rb +341 -0
  86. data/lib/concurrent/tvar.rb +252 -0
  87. data/lib/concurrent/utilities.rb +3 -0
  88. data/lib/concurrent/utility/processor_count.rb +150 -0
  89. data/lib/concurrent/utility/timeout.rb +35 -0
  90. data/lib/concurrent/utility/timer.rb +21 -0
  91. data/lib/concurrent/version.rb +3 -0
  92. data/lib/concurrent_ruby.rb +1 -0
  93. data/lib/concurrent_ruby_ext.so +0 -0
  94. data/lib/extension_helper.rb +9 -0
  95. metadata +141 -0
@@ -0,0 +1,341 @@
1
+ require 'concurrent/dereferenceable'
2
+ require 'concurrent/observable'
3
+ require 'concurrent/atomic/atomic_boolean'
4
+ require 'concurrent/executor/executor'
5
+ require 'concurrent/executor/safe_task_executor'
6
+
7
+ module Concurrent
8
+
9
+ # A very common currency pattern is to run a thread that performs a task at regular
10
+ # intervals. The thread that performs the task sleeps for the given interval then
11
+ # wakes up and performs the task. Lather, rinse, repeat... This pattern causes two
12
+ # problems. First, it is difficult to test the business logic of the task because the
13
+ # task itself is tightly coupled with the concurrency logic. Second, an exception in
14
+ # raised while performing the task can cause the entire thread to abend. In a
15
+ # long-running application where the task thread is intended to run for days/weeks/years
16
+ # a crashed task thread can pose a significant problem. `TimerTask` alleviates both problems.
17
+ #
18
+ # When a `TimerTask` is launched it starts a thread for monitoring the execution interval.
19
+ # The `TimerTask` thread does not perform the task, however. Instead, the TimerTask
20
+ # launches the task on a separate thread. Should the task experience an unrecoverable
21
+ # crash only the task thread will crash. This makes the `TimerTask` very fault tolerant
22
+ # Additionally, the `TimerTask` thread can respond to the success or failure of the task,
23
+ # performing logging or ancillary operations. `TimerTask` can also be configured with a
24
+ # timeout value allowing it to kill a task that runs too long.
25
+ #
26
+ # One other advantage of `TimerTask` is it forces the business logic to be completely decoupled
27
+ # from the concurrency logic. The business logic can be tested separately then passed to the
28
+ # `TimerTask` for scheduling and running.
29
+ #
30
+ # In some cases it may be necessary for a `TimerTask` to affect its own execution cycle.
31
+ # To facilitate this a reference to the task object is passed into the block as a block
32
+ # argument every time the task is executed.
33
+ #
34
+ # The `TimerTask` class includes the `Dereferenceable` mixin module so the result of
35
+ # the last execution is always available via the `#value` method. Derefencing options
36
+ # can be passed to the `TimerTask` during construction or at any later time using the
37
+ # `#set_deref_options` method.
38
+ #
39
+ # `TimerTask` supports notification through the Ruby standard library
40
+ # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html Observable}
41
+ # module. On execution the `TimerTask` will notify the observers
42
+ # with threes arguments: time of execution, the result of the block (or nil on failure),
43
+ # and any raised exceptions (or nil on success). If the timeout interval is exceeded
44
+ # the observer will receive a `Concurrent::TimeoutError` object as the third argument.
45
+ #
46
+ # @example Basic usage
47
+ # task = Concurrent::TimerTask.new{ puts 'Boom!' }
48
+ # task.run!
49
+ #
50
+ # task.execution_interval #=> 60 (default)
51
+ # task.timeout_interval #=> 30 (default)
52
+ #
53
+ # # wait 60 seconds...
54
+ # #=> 'Boom!'
55
+ #
56
+ # task.stop #=> true
57
+ #
58
+ # @example Configuring `:execution_interval` and `:timeout_interval`
59
+ # task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
60
+ # puts 'Boom!'
61
+ # end
62
+ #
63
+ # task.execution_interval #=> 5
64
+ # task.timeout_interval #=> 5
65
+ #
66
+ # @example Immediate execution with `:run_now`
67
+ # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
68
+ # task.run!
69
+ #
70
+ # #=> 'Boom!'
71
+ #
72
+ # @example Last `#value` and `Dereferenceable` mixin
73
+ # task = Concurrent::TimerTask.new(
74
+ # dup_on_deref: true,
75
+ # execution_interval: 5
76
+ # ){ Time.now }
77
+ #
78
+ # task.run!
79
+ # Time.now #=> 2013-11-07 18:06:50 -0500
80
+ # sleep(10)
81
+ # task.value #=> 2013-11-07 18:06:55 -0500
82
+ #
83
+ # @example Controlling execution from within the block
84
+ # timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
85
+ # task.execution_interval.times{ print 'Boom! ' }
86
+ # print "\n"
87
+ # task.execution_interval += 1
88
+ # if task.execution_interval > 5
89
+ # puts 'Stopping...'
90
+ # task.stop
91
+ # end
92
+ # end
93
+ #
94
+ # timer_task.run # blocking call - this task will stop itself
95
+ # #=> Boom!
96
+ # #=> Boom! Boom!
97
+ # #=> Boom! Boom! Boom!
98
+ # #=> Boom! Boom! Boom! Boom!
99
+ # #=> Boom! Boom! Boom! Boom! Boom!
100
+ # #=> Stopping...
101
+ #
102
+ # @example Observation
103
+ # class TaskObserver
104
+ # def update(time, result, ex)
105
+ # if result
106
+ # print "(#{time}) Execution successfully returned #{result}\n"
107
+ # elsif ex.is_a?(Concurrent::TimeoutError)
108
+ # print "(#{time}) Execution timed out\n"
109
+ # else
110
+ # print "(#{time}) Execution failed with error #{ex}\n"
111
+ # end
112
+ # end
113
+ # end
114
+ #
115
+ # task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ 42 }
116
+ # task.add_observer(TaskObserver.new)
117
+ # task.run!
118
+ #
119
+ # #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42
120
+ # #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42
121
+ # #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42
122
+ # task.stop
123
+ #
124
+ # task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ sleep }
125
+ # task.add_observer(TaskObserver.new)
126
+ # task.run!
127
+ #
128
+ # #=> (2013-10-13 19:07:25 -0400) Execution timed out
129
+ # #=> (2013-10-13 19:07:27 -0400) Execution timed out
130
+ # #=> (2013-10-13 19:07:29 -0400) Execution timed out
131
+ # task.stop
132
+ #
133
+ # task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError }
134
+ # task.add_observer(TaskObserver.new)
135
+ # task.run!
136
+ #
137
+ # #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError
138
+ # #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError
139
+ # #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError
140
+ # task.stop
141
+ #
142
+ # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
143
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
144
+ class TimerTask
145
+ include Dereferenceable
146
+ include RubyExecutor
147
+ include Concurrent::Observable
148
+
149
+ # Default `:execution_interval` in seconds.
150
+ EXECUTION_INTERVAL = 60
151
+
152
+ # Default `:timeout_interval` in seconds.
153
+ TIMEOUT_INTERVAL = 30
154
+
155
+ # Create a new TimerTask with the given task and configuration.
156
+ #
157
+ # @!macro [attach] timer_task_initialize
158
+ # @param [Hash] opts the options defining task execution.
159
+ # @option opts [Integer] :execution_interval number of seconds between
160
+ # task executions (default: EXECUTION_INTERVAL)
161
+ # @option opts [Integer] :timeout_interval number of seconds a task can
162
+ # run before it is considered to have failed (default: TIMEOUT_INTERVAL)
163
+ # @option opts [Boolean] :run_now Whether to run the task immediately
164
+ # upon instantiation or to wait until the first # execution_interval
165
+ # has passed (default: false)
166
+ #
167
+ # @raise ArgumentError when no block is given.
168
+ #
169
+ # @yield to the block after :execution_interval seconds have passed since
170
+ # the last yield
171
+ # @yieldparam task a reference to the `TimerTask` instance so that the
172
+ # block can control its own lifecycle. Necessary since `self` will
173
+ # refer to the execution context of the block rather than the running
174
+ # `TimerTask`.
175
+ #
176
+ # @note Calls Concurrent::Dereferenceable# set_deref_options passing `opts`.
177
+ # All options supported by Concurrent::Dereferenceable can be set
178
+ # during object initialization.
179
+ #
180
+ # @return [TimerTask] the new `TimerTask`
181
+ #
182
+ # @see Concurrent::Dereferenceable# set_deref_options
183
+ def initialize(opts = {}, &task)
184
+ raise ArgumentError.new('no block given') unless block_given?
185
+
186
+ init_executor
187
+ set_deref_options(opts)
188
+
189
+ self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
190
+ self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
191
+ @run_now = opts[:now] || opts[:run_now]
192
+ @executor = Concurrent::SafeTaskExecutor.new(task)
193
+ @running = Concurrent::AtomicBoolean.new(false)
194
+
195
+ self.observers = CopyOnNotifyObserverSet.new
196
+ end
197
+
198
+ # Is the executor running?
199
+ #
200
+ # @return [Boolean] `true` when running, `false` when shutting down or shutdown
201
+ def running?
202
+ @running.true?
203
+ end
204
+
205
+ # Execute a previously created `TimerTask`.
206
+ #
207
+ # @return [TimerTask] a reference to `self`
208
+ #
209
+ # @example Instance and execute in separate steps
210
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }
211
+ # task.running? #=> false
212
+ # task.execute
213
+ # task.running? #=> true
214
+ #
215
+ # @example Instance and execute in one line
216
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }.execute
217
+ # task.running? #=> true
218
+ #
219
+ # @since 0.6.0
220
+ def execute
221
+ mutex.synchronize do
222
+ if @running.false?
223
+ @running.make_true
224
+ schedule_next_task(@run_now ? 0 : @execution_interval)
225
+ end
226
+ end
227
+ self
228
+ end
229
+
230
+ # Create and execute a new `TimerTask`.
231
+ #
232
+ # @!macro timer_task_initialize
233
+ #
234
+ # @example
235
+ # task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" }
236
+ # task.running? #=> true
237
+ #
238
+ # @since 0.6.0
239
+ def self.execute(opts = {}, &task)
240
+ TimerTask.new(opts, &task).execute
241
+ end
242
+
243
+ # @!attribute [rw] execution_interval
244
+ # @return [Fixnum] Number of seconds after the task completes before the
245
+ # task is performed again.
246
+ def execution_interval
247
+ mutex.lock
248
+ @execution_interval
249
+ ensure
250
+ mutex.unlock
251
+ end
252
+
253
+ # @!attribute [rw] execution_interval
254
+ # @return [Fixnum] Number of seconds after the task completes before the
255
+ # task is performed again.
256
+ def execution_interval=(value)
257
+ if (value = value.to_f) <= 0.0
258
+ raise ArgumentError.new('must be greater than zero')
259
+ else
260
+ begin
261
+ mutex.lock
262
+ @execution_interval = value
263
+ ensure
264
+ mutex.unlock
265
+ end
266
+ end
267
+ end
268
+
269
+ # @!attribute [rw] timeout_interval
270
+ # @return [Fixnum] Number of seconds the task can run before it is
271
+ # considered to have failed.
272
+ def timeout_interval
273
+ mutex.lock
274
+ @timeout_interval
275
+ ensure
276
+ mutex.unlock
277
+ end
278
+
279
+ # @!attribute [rw] timeout_interval
280
+ # @return [Fixnum] Number of seconds the task can run before it is
281
+ # considered to have failed.
282
+ def timeout_interval=(value)
283
+ if (value = value.to_f) <= 0.0
284
+ raise ArgumentError.new('must be greater than zero')
285
+ else
286
+ begin
287
+ mutex.lock
288
+ @timeout_interval = value
289
+ ensure
290
+ mutex.unlock
291
+ end
292
+ end
293
+ end
294
+
295
+ private :post, :<<
296
+
297
+ protected
298
+
299
+ # @!visibility private
300
+ def shutdown_execution
301
+ @running.make_false
302
+ super
303
+ end
304
+
305
+ # @!visibility private
306
+ def kill_execution
307
+ @running.make_false
308
+ super
309
+ end
310
+
311
+ # @!visibility private
312
+ def schedule_next_task(interval = execution_interval)
313
+ Concurrent::timer(interval, Concurrent::Event.new, &method(:execute_task))
314
+ end
315
+
316
+ # @!visibility private
317
+ def execute_task(completion)
318
+ return unless @running.true?
319
+ Concurrent::timer(timeout_interval, completion, &method(:timeout_task))
320
+ success, value, reason = @executor.execute(self)
321
+ if completion.try?
322
+ self.value = value
323
+ schedule_next_task
324
+ time = Time.now
325
+ observers.notify_observers do
326
+ [time, self.value, reason]
327
+ end
328
+ end
329
+ end
330
+
331
+ # @!visibility private
332
+ def timeout_task(completion)
333
+ return unless @running.true?
334
+ if completion.try?
335
+ self.value = value
336
+ schedule_next_task
337
+ observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
338
+ end
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,252 @@
1
+ require 'set'
2
+
3
+ require 'concurrent/atomic/thread_local_var'
4
+
5
+ module Concurrent
6
+
7
+ # A `TVar` is a transactional variable - a single-element container that
8
+ # is used as part of a transaction - see `Concurrent::atomically`.
9
+ class TVar
10
+
11
+ # Create a new `TVar` with an initial value.
12
+ def initialize(value)
13
+ @value = value
14
+ @version = 0
15
+ @lock = Mutex.new
16
+ end
17
+
18
+ # Get the value of a `TVar`.
19
+ def value
20
+ Concurrent::atomically do
21
+ Transaction::current.read(self)
22
+ end
23
+ end
24
+
25
+ # Set the value of a `TVar`.
26
+ def value=(value)
27
+ Concurrent::atomically do
28
+ Transaction::current.write(self, value)
29
+ end
30
+ end
31
+
32
+ # @!visibility private
33
+ def unsafe_value # :nodoc:
34
+ @value
35
+ end
36
+
37
+ # @!visibility private
38
+ def unsafe_value=(value) # :nodoc:
39
+ @value = value
40
+ end
41
+
42
+ # @!visibility private
43
+ def unsafe_version # :nodoc:
44
+ @version
45
+ end
46
+
47
+ # @!visibility private
48
+ def unsafe_increment_version # :nodoc:
49
+ @version += 1
50
+ end
51
+
52
+ # @!visibility private
53
+ def unsafe_lock # :nodoc:
54
+ @lock
55
+ end
56
+
57
+ end
58
+
59
+ # Run a block that reads and writes `TVar`s as a single atomic transaction.
60
+ # With respect to the value of `TVar` objects, the transaction is atomic,
61
+ # in that it either happens or it does not, consistent, in that the `TVar`
62
+ # objects involved will never enter an illegal state, and isolated, in that
63
+ # transactions never interfere with each other. You may recognise these
64
+ # properties from database transactions.
65
+ #
66
+ # There are some very important and unusual semantics that you must be aware of:
67
+ #
68
+ # * Most importantly, the block that you pass to atomically may be executed more than once. In most cases your code should be free of side-effects, except for via TVar.
69
+ #
70
+ # * If an exception escapes an atomically block it will abort the transaction.
71
+ #
72
+ # * It is undefined behaviour to use callcc or Fiber with atomically.
73
+ #
74
+ # * If you create a new thread within an atomically, it will not be part of the transaction. Creating a thread counts as a side-effect.
75
+ #
76
+ # Transactions within transactions are flattened to a single transaction.
77
+ #
78
+ # @example
79
+ # a = new TVar(100_000)
80
+ # b = new TVar(100)
81
+ #
82
+ # Concurrent::atomically do
83
+ # a.value -= 10
84
+ # b.value += 10
85
+ # end
86
+ def atomically
87
+ raise ArgumentError.new('no block given') unless block_given?
88
+
89
+ # Get the current transaction
90
+
91
+ transaction = Transaction::current
92
+
93
+ # Are we not already in a transaction (not nested)?
94
+
95
+ if transaction.nil?
96
+ # New transaction
97
+
98
+ begin
99
+ # Retry loop
100
+
101
+ loop do
102
+
103
+ # Create a new transaction
104
+
105
+ transaction = Transaction.new
106
+ Transaction::current = transaction
107
+
108
+ # Run the block, aborting on exceptions
109
+
110
+ begin
111
+ result = yield
112
+ rescue Transaction::AbortError => e
113
+ transaction.abort
114
+ result = Transaction::ABORTED
115
+ rescue => e
116
+ transaction.abort
117
+ throw e
118
+ end
119
+ # If we can commit, break out of the loop
120
+
121
+ if result != Transaction::ABORTED
122
+ if transaction.commit
123
+ break result
124
+ end
125
+ end
126
+ end
127
+ ensure
128
+ # Clear the current transaction
129
+
130
+ Transaction::current = nil
131
+ end
132
+ else
133
+ # Nested transaction - flatten it and just run the block
134
+
135
+ yield
136
+ end
137
+ end
138
+
139
+ # Abort a currently running transaction - see `Concurrent::atomically`.
140
+ def abort_transaction
141
+ raise Transaction::AbortError.new
142
+ end
143
+
144
+ module_function :atomically, :abort_transaction
145
+
146
+ private
147
+
148
+ class Transaction
149
+
150
+ ABORTED = Object.new
151
+
152
+ CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
153
+
154
+ ReadLogEntry = Struct.new(:tvar, :version)
155
+ UndoLogEntry = Struct.new(:tvar, :value)
156
+
157
+ AbortError = Class.new(StandardError)
158
+
159
+ def initialize
160
+ @write_set = Set.new
161
+ @read_log = []
162
+ @undo_log = []
163
+ end
164
+
165
+ def read(tvar)
166
+ Concurrent::abort_transaction unless valid?
167
+ @read_log.push(ReadLogEntry.new(tvar, tvar.unsafe_version))
168
+ tvar.unsafe_value
169
+ end
170
+
171
+ def write(tvar, value)
172
+ # Have we already written to this TVar?
173
+
174
+ unless @write_set.include? tvar
175
+ # Try to lock the TVar
176
+
177
+ unless tvar.unsafe_lock.try_lock
178
+ # Someone else is writing to this TVar - abort
179
+ Concurrent::abort_transaction
180
+ end
181
+
182
+ # We've locked it - add it to the write set
183
+
184
+ @write_set.add(tvar)
185
+
186
+ # If we previously wrote to it, check the version hasn't changed
187
+
188
+ @read_log.each do |log_entry|
189
+ if log_entry.tvar == tvar and tvar.unsafe_version > log_entry.version
190
+ Concurrent::abort_transaction
191
+ end
192
+ end
193
+ end
194
+
195
+ # Record the current value of the TVar so we can undo it later
196
+
197
+ @undo_log.push(UndoLogEntry.new(tvar, tvar.unsafe_value))
198
+
199
+ # Write the new value to the TVar
200
+
201
+ tvar.unsafe_value = value
202
+ end
203
+
204
+ def abort
205
+ @undo_log.each do |entry|
206
+ entry.tvar.unsafe_value = entry.value
207
+ end
208
+
209
+ unlock
210
+ end
211
+
212
+ def commit
213
+ return false unless valid?
214
+
215
+ @write_set.each do |tvar|
216
+ tvar.unsafe_increment_version
217
+ end
218
+
219
+ unlock
220
+
221
+ true
222
+ end
223
+
224
+ def valid?
225
+ @read_log.each do |log_entry|
226
+ unless @write_set.include? log_entry.tvar
227
+ if log_entry.tvar.unsafe_version > log_entry.version
228
+ return false
229
+ end
230
+ end
231
+ end
232
+
233
+ true
234
+ end
235
+
236
+ def unlock
237
+ @write_set.each do |tvar|
238
+ tvar.unsafe_lock.unlock
239
+ end
240
+ end
241
+
242
+ def self.current
243
+ CURRENT_TRANSACTION.value
244
+ end
245
+
246
+ def self.current=(transaction)
247
+ CURRENT_TRANSACTION.value = transaction
248
+ end
249
+
250
+ end
251
+
252
+ end