concurrent-ruby 0.1.0 → 0.1.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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