concurrent-ruby 0.1.0 → 0.1.1.pre.1

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 (51) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +279 -224
  3. data/lib/concurrent.rb +27 -20
  4. data/lib/concurrent/agent.rb +106 -130
  5. data/lib/concurrent/cached_thread_pool.rb +130 -122
  6. data/lib/concurrent/defer.rb +67 -69
  7. data/lib/concurrent/drb_async_demux.rb +72 -0
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +87 -0
  11. data/lib/concurrent/fixed_thread_pool.rb +89 -89
  12. data/lib/concurrent/functions.rb +120 -0
  13. data/lib/concurrent/future.rb +52 -42
  14. data/lib/concurrent/global_thread_pool.rb +3 -3
  15. data/lib/concurrent/goroutine.rb +29 -25
  16. data/lib/concurrent/obligation.rb +67 -121
  17. data/lib/concurrent/promise.rb +172 -194
  18. data/lib/concurrent/reactor.rb +162 -0
  19. data/lib/concurrent/smart_mutex.rb +66 -0
  20. data/lib/concurrent/tcp_sync_demux.rb +96 -0
  21. data/lib/concurrent/thread_pool.rb +65 -61
  22. data/lib/concurrent/utilities.rb +34 -0
  23. data/lib/concurrent/version.rb +3 -3
  24. data/lib/concurrent_ruby.rb +1 -1
  25. data/md/agent.md +123 -123
  26. data/md/defer.md +174 -174
  27. data/md/event.md +32 -32
  28. data/md/executor.md +176 -0
  29. data/md/future.md +83 -83
  30. data/md/goroutine.md +52 -52
  31. data/md/obligation.md +32 -32
  32. data/md/promise.md +225 -225
  33. data/md/thread_pool.md +197 -197
  34. data/spec/concurrent/agent_spec.rb +376 -405
  35. data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
  36. data/spec/concurrent/defer_spec.rb +209 -199
  37. data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
  38. data/spec/concurrent/event_spec.rb +134 -134
  39. data/spec/concurrent/executor_spec.rb +146 -0
  40. data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
  41. data/spec/concurrent/functions_spec.rb +57 -0
  42. data/spec/concurrent/future_spec.rb +125 -115
  43. data/spec/concurrent/goroutine_spec.rb +67 -52
  44. data/spec/concurrent/obligation_shared.rb +121 -121
  45. data/spec/concurrent/promise_spec.rb +299 -310
  46. data/spec/concurrent/smart_mutex_spec.rb +234 -0
  47. data/spec/concurrent/thread_pool_shared.rb +209 -209
  48. data/spec/concurrent/utilities_spec.rb +74 -0
  49. data/spec/spec_helper.rb +21 -19
  50. metadata +38 -14
  51. checksums.yaml +0 -7
@@ -1,69 +1,67 @@
1
- require 'thread'
2
-
3
- require 'concurrent/global_thread_pool'
4
-
5
- module Concurrent
6
-
7
- IllegalMethodCallError = Class.new(StandardError)
8
-
9
- class Defer
10
-
11
- def initialize(operation = nil, callback = nil, errorback = nil, &block)
12
- raise ArgumentError.new('no operation given') if operation.nil? && ! block_given?
13
- raise ArgumentError.new('two operations given') if ! operation.nil? && block_given?
14
-
15
- @operation = operation || block
16
- @callback = callback
17
- @errorback = errorback
18
-
19
- if operation.nil?
20
- @running = false
21
- else
22
- self.go
23
- end
24
- end
25
-
26
- def then(&block)
27
- raise IllegalMethodCallError.new('a callback has already been provided') unless @callback.nil?
28
- raise IllegalMethodCallError.new('the defer is already running') if @running
29
- raise ArgumentError.new('no block given') unless block_given?
30
- @callback = block
31
- return self
32
- end
33
-
34
- def rescue(&block)
35
- raise IllegalMethodCallError.new('a errorback has already been provided') unless @errorback.nil?
36
- raise IllegalMethodCallError.new('the defer is already running') if @running
37
- raise ArgumentError.new('no block given') unless block_given?
38
- @errorback = block
39
- return self
40
- end
41
- alias_method :catch, :rescue
42
- alias_method :on_error, :rescue
43
-
44
- def go
45
- return nil if @running
46
- @running = true
47
- $GLOBAL_THREAD_POOL.post { fulfill }
48
- return nil
49
- end
50
-
51
- private
52
-
53
- # @private
54
- def fulfill # :nodoc:
55
- result = @operation.call
56
- @callback.call(result) unless @callback.nil?
57
- rescue Exception => ex
58
- @errorback.call(ex) unless @errorback.nil?
59
- end
60
- end
61
- end
62
-
63
- module Kernel
64
-
65
- def defer(*args, &block)
66
- return Concurrent::Defer.new(*args, &block)
67
- end
68
- module_function :defer
69
- end
1
+ require 'thread'
2
+
3
+ require 'concurrent/global_thread_pool'
4
+
5
+ module Concurrent
6
+
7
+ IllegalMethodCallError = Class.new(StandardError)
8
+
9
+ class Defer
10
+
11
+ def initialize(opts = {}, &block)
12
+ operation = opts[:op] || opts[:operation]
13
+ @callback = opts[:cback] || opts[:callback]
14
+ @errorback = opts[:eback] || opts[:error] || opts[:errorback]
15
+ thread_pool = opts[:pool] || opts[:thread_pool]
16
+
17
+ raise ArgumentError.new('no operation given') if operation.nil? && ! block_given?
18
+ raise ArgumentError.new('two operations given') if ! operation.nil? && block_given?
19
+
20
+ @operation = operation || block
21
+
22
+ if operation.nil?
23
+ @running = false
24
+ else
25
+ self.go(thread_pool)
26
+ end
27
+ end
28
+
29
+ def then(&block)
30
+ raise IllegalMethodCallError.new('a callback has already been provided') unless @callback.nil?
31
+ raise IllegalMethodCallError.new('the defer is already running') if @running
32
+ raise ArgumentError.new('no block given') unless block_given?
33
+ @callback = block
34
+ return self
35
+ end
36
+
37
+ def rescue(&block)
38
+ raise IllegalMethodCallError.new('a errorback has already been provided') unless @errorback.nil?
39
+ raise IllegalMethodCallError.new('the defer is already running') if @running
40
+ raise ArgumentError.new('no block given') unless block_given?
41
+ @errorback = block
42
+ return self
43
+ end
44
+ alias_method :catch, :rescue
45
+ alias_method :on_error, :rescue
46
+
47
+ def go(thread_pool = nil)
48
+ return nil if @running
49
+ atomic {
50
+ thread_pool ||= $GLOBAL_THREAD_POOL
51
+ @running = true
52
+ thread_pool.post { Thread.pass; fulfill }
53
+ }
54
+ return nil
55
+ end
56
+
57
+ private
58
+
59
+ # @private
60
+ def fulfill # :nodoc:
61
+ result = @operation.call
62
+ @callback.call(result) unless @callback.nil?
63
+ rescue Exception => ex
64
+ @errorback.call(ex) unless @errorback.nil?
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,72 @@
1
+ require 'drb/drb'
2
+ require 'drb/acl'
3
+ require 'functional'
4
+ require 'concurrent/reactor'
5
+
6
+ module Concurrent
7
+
8
+ class DRbAsyncDemux
9
+
10
+ behavior(:async_event_demux)
11
+
12
+ DEFAULT_URI = 'druby://localhost:12345'
13
+ DEFAULT_ACL = %[allow all]
14
+
15
+ def initialize(opts = {})
16
+ @uri = opts[:uri] || DEFAULT_URI
17
+ @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
18
+ end
19
+
20
+ def set_reactor(reactor)
21
+ raise ArgumentError.new('invalid reactor') unless reactor.behaves_as?(:demux_reactor)
22
+ @reactor = reactor
23
+ end
24
+
25
+ def start
26
+ DRb.install_acl(@acl)
27
+ @service = DRb.start_service(@uri, Demultiplexer.new(@reactor))
28
+ end
29
+
30
+ def stop
31
+ @service = DRb.stop_service
32
+ end
33
+
34
+ def stopped?
35
+ return @service.nil?
36
+ end
37
+
38
+ private
39
+
40
+ class Demultiplexer
41
+
42
+ def initialize(reactor)
43
+ @reactor = reactor
44
+ end
45
+
46
+ Concurrent::Reactor::RESERVED_EVENTS.each do |event|
47
+ define_method(event){|*args| false }
48
+ end
49
+
50
+ def method_missing(method, *args, &block)
51
+ (class << self; self; end).class_eval do
52
+ define_method(method) do |*args|
53
+ result = @reactor.handle(method, *args)
54
+ case result.first
55
+ when :ok
56
+ return result.last
57
+ when :ex
58
+ raise result.last
59
+ when :noop
60
+ raise NoMethodError.new("undefined method '#{method}' for #{self}")
61
+ else
62
+ raise DRb::DRbUnknownError.new("unexpected error when calling method '#{method}'")
63
+ end
64
+ end
65
+ end
66
+ self.send(method, *args)
67
+ end
68
+ end
69
+ end
70
+
71
+ DRbAsyncDemultiplexer = DRbAsyncDemux
72
+ end
@@ -1,60 +1,60 @@
1
- require 'thread'
2
- require 'timeout'
3
-
4
- module Concurrent
5
-
6
- class Event
7
-
8
- def initialize
9
- @set = false
10
- @notifier = Queue.new
11
- @mutex = Mutex.new
12
- @waiting = 0
13
- end
14
-
15
- def set?
16
- return @set == true
17
- end
18
-
19
- def set(pulse = false)
20
- return true if set?
21
- @mutex.synchronize {
22
- @set = true
23
- while @waiting > 0
24
- @notifier << :set
25
- @waiting -= 1
26
- end
27
- @set = ! pulse
28
- }
29
- return true
30
- end
31
-
32
- def reset
33
- @mutex.synchronize {
34
- @set = false
35
- }
36
- return true
37
- end
38
-
39
- def pulse
40
- return set(true)
41
- end
42
-
43
- def wait(timeout = nil)
44
- return true if set?
45
-
46
- if timeout.nil?
47
- @waiting += 1
48
- @notifier.pop
49
- else
50
- Timeout::timeout(timeout) do
51
- @waiting += 1
52
- @notifier.pop
53
- end
54
- end
55
- return true
56
- rescue Timeout::Error
57
- return false
58
- end
59
- end
60
- end
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ module Concurrent
5
+
6
+ class Event
7
+
8
+ def initialize
9
+ @set = false
10
+ @notifier = Queue.new
11
+ @mutex = Mutex.new
12
+ @waiting = 0
13
+ end
14
+
15
+ def set?
16
+ return @set == true
17
+ end
18
+
19
+ def set(pulse = false)
20
+ return true if set?
21
+ @mutex.synchronize {
22
+ @set = true
23
+ while @waiting > 0
24
+ @notifier << :set
25
+ @waiting -= 1
26
+ end
27
+ @set = ! pulse
28
+ }
29
+ return true
30
+ end
31
+
32
+ def reset
33
+ @mutex.synchronize {
34
+ @set = false
35
+ }
36
+ return true
37
+ end
38
+
39
+ def pulse
40
+ return set(true)
41
+ end
42
+
43
+ def wait(timeout = nil)
44
+ return true if set?
45
+
46
+ if timeout.nil?
47
+ @waiting += 1
48
+ @notifier.pop
49
+ else
50
+ Timeout::timeout(timeout) do
51
+ @waiting += 1
52
+ @notifier.pop
53
+ end
54
+ end
55
+ return true
56
+ rescue Timeout::Error
57
+ return false
58
+ end
59
+ end
60
+ end
@@ -1,23 +1,23 @@
1
- require 'concurrent/global_thread_pool'
2
-
3
- module Concurrent
4
-
5
- class EventMachineDeferProxy
6
- behavior(:global_thread_pool)
7
-
8
- def post(*args, &block)
9
- if args.empty?
10
- EventMachine.defer(block)
11
- else
12
- new_block = proc{ block.call(*args) }
13
- EventMachine.defer(new_block)
14
- end
15
- return true
16
- end
17
-
18
- def <<(block)
19
- EventMachine.defer(block)
20
- return self
21
- end
22
- end
23
- end
1
+ require 'concurrent/global_thread_pool'
2
+
3
+ module Concurrent
4
+
5
+ class EventMachineDeferProxy
6
+ behavior(:global_thread_pool)
7
+
8
+ def post(*args, &block)
9
+ if args.empty?
10
+ EventMachine.defer(block)
11
+ else
12
+ new_block = proc{ block.call(*args) }
13
+ EventMachine.defer(new_block)
14
+ end
15
+ return true
16
+ end
17
+
18
+ def <<(block)
19
+ EventMachine.defer(block)
20
+ return self
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,87 @@
1
+ require 'thread'
2
+
3
+ module Concurrent
4
+
5
+ module Executor
6
+ extend self
7
+
8
+ class ExecutionContext
9
+ attr_reader :name
10
+ attr_reader :execution_interval
11
+ attr_reader :timeout_interval
12
+
13
+ def initialize(name, execution_interval, timeout_interval, thread)
14
+ @name = name
15
+ @execution_interval = execution_interval
16
+ @timeout_interval = timeout_interval
17
+ @thread = thread
18
+ @thread[:stop] = false
19
+ end
20
+
21
+ def status
22
+ return @thread.status unless @thread.nil?
23
+ end
24
+
25
+ def join(limit = nil)
26
+ if @thread.nil?
27
+ return nil
28
+ elsif limit.nil?
29
+ return @thread.join
30
+ else
31
+ return @thread.join(limit)
32
+ end
33
+ end
34
+
35
+ def stop
36
+ @thread[:stop] = true
37
+ end
38
+
39
+ def kill
40
+ @thread.kill
41
+ @thread = nil
42
+ end
43
+ alias_method :terminate, :kill
44
+ alias_method :stop, :kill
45
+ end
46
+
47
+ EXECUTION_INTERVAL = 60
48
+ TIMEOUT_INTERVAL = 30
49
+
50
+ STDOUT_LOGGER = proc do |name, level, msg|
51
+ print "%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg]
52
+ end
53
+
54
+ def run(name, opts = {})
55
+ raise ArgumentError.new('no block given') unless block_given?
56
+
57
+ execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
58
+ timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
59
+ logger = opts[:logger] || STDOUT_LOGGER
60
+ block_args = opts[:args] || opts [:arguments] || []
61
+
62
+ executor = Thread.new(*block_args) do |*args|
63
+ loop do
64
+ sleep(execution_interval)
65
+ break if Thread.current[:stop]
66
+ begin
67
+ worker = Thread.new{ yield(*args) }
68
+ worker.abort_on_exception = false
69
+ if worker.join(timeout_interval).nil?
70
+ logger.call(name, :warn, "execution timed out after #{timeout_interval} seconds")
71
+ else
72
+ logger.call(name, :info, 'execution completed successfully')
73
+ end
74
+ rescue Exception => ex
75
+ logger.call(name, :error, "execution failed with error '#{ex}'")
76
+ ensure
77
+ Thread.kill(worker)
78
+ worker = nil
79
+ end
80
+ break if Thread.current[:stop]
81
+ end
82
+ end
83
+
84
+ return ExecutionContext.new(name, execution_interval, timeout_interval, executor)
85
+ end
86
+ end
87
+ end