concurrent-ruby 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 23428b19ba9bf0dbfb21cc1262c8607bd5b40b8d
4
- data.tar.gz: 7757f144dfef3b8823d5dacc6e3b3db6a220a9aa
3
+ metadata.gz: 3f58685d3bea0c67b29987579c607417ba905790
4
+ data.tar.gz: 82ab333727880726e30369350eaca336a81c5f2c
5
5
  SHA512:
6
- metadata.gz: 445c8a14988adb216f4314453381dcc56d27f399444d8cc15f4086151699208a15c7fc14a54abeeccf69bd83440ff3a7d8a7ccf240b1c845d2612d567889ebe4
7
- data.tar.gz: f6ddc0ec2cf80080fafc79be80d7c6d5f7e220685266ff0ccc6964a06b70f54ca3f0ac4b721266510c356c580089dc892efd6a27698b39a3921fd92169a835ee
6
+ metadata.gz: 34d8085f8ca406fd76ecb34d78c84b8faff82f49deb52095c00093f54300beb2a024e5b3dc9bd376aaa317cb7f126bcae2a7a2b87012ef975fa9c4760b23a206
7
+ data.tar.gz: f707b25361f41d930383a4e12381fbd511966a991cb68c5ae97db9fe9494278b78e165bf9f3aadbd3341c9a728c1478d74ae5a8db1482248ce0ed58d34468e33
data/README.md CHANGED
@@ -1,4 +1,224 @@
1
- # Concurrent Ruby
1
+ # Concurrent Ruby [![Build Status](https://secure.travis-ci.org/jdantonio/concurrent-ruby.png)](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/concurrent-ruby.png)](https://gemnasium.com/jdantonio/concurrent-ruby)
2
2
 
3
- This gem does nothing. It's a placeholder into which I plan to move the concurrency
4
- features of my [functional-ruby](https://github.com/jdantonio/functional-ruby) gem.
3
+ Erlang, Clojure, and Go inspired concurrent programming tools for Ruby.
4
+
5
+ ## Introduction
6
+
7
+ The old-school "lock and synchronize" approach to concurrency is dead. The future of concurrency
8
+ is asynchronous. Send out a bunch of independent [actors](http://en.wikipedia.org/wiki/Actor_model)
9
+ to do your bidding and process the results when you are ready. Although the idea of the concurrent
10
+ actor originated in the early 1970's it has only recently started catching on. Although there is
11
+ no one "true" actor implementation (what *exactly* is "object oriented," what *exactly* is
12
+ "concurrent programming"), many modern programming languages implement variations on the actor
13
+ theme. This library implements a few of the most interesting and useful of those variations.
14
+
15
+ Remember, *there is no silver bullet in concurrent programming.* Concurrency is hard. Very hard.
16
+ These tools will help ease the burden, but at the end of the day it is essential that you
17
+ *know what you are doing.*
18
+
19
+ The project is hosted on the following sites:
20
+
21
+ * [RubyGems project page](https://rubygems.org/gems/concurrent-ruby)
22
+ * [Source code on GitHub](https://github.com/jdantonio/concurrent-ruby)
23
+ * [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/concurrent-ruby/frames)
24
+ * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/concurrent-ruby)
25
+ * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/concurrent-ruby)
26
+ * [Follow me on Twitter](https://twitter.com/jerrydantonio)
27
+
28
+ ### Goals
29
+
30
+ My history with high-performance, highly-concurrent programming goes back to my days with C/C++.
31
+ I have the same scars as everyone else doing that kind of work with those languages.
32
+ I'm fascinated by modern concurrency patterns like [Actors](http://en.wikipedia.org/wiki/Actor_model),
33
+ [Agents](http://doc.akka.io/docs/akka/snapshot/java/agents.html), and
34
+ [Promises](http://promises-aplus.github.io/promises-spec/). I'm equally fascinated by languages
35
+ with strong concurrency support like [Erlang](http://www.erlang.org/doc/getting_started/conc_prog.html),
36
+ [Go](http://golang.org/doc/articles/concurrency_patterns.html), and
37
+ [Clojure](http://clojure.org/concurrent_programming). My goal is to implement those patterns in Ruby.
38
+ Specifically:
39
+
40
+ * Stay true to the spirit of the languages providing inspiration
41
+ * But implement in a way that makes sense for Ruby
42
+ * Keep the semantics as idiomatic Ruby as possible
43
+ * Support features that make sense in Ruby
44
+ * Exclude features that don't make sense in Ruby
45
+ * Keep everything small
46
+ * Be as fast as reasonably possible
47
+
48
+ ## Features (and Documentation)
49
+
50
+ Several features from Erlang, Go, Clojure, Java, and JavaScript have been implemented thus far:
51
+
52
+ * Clojure inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
53
+ * EventMachine inspired [Defer](https://github.com/jdantonio/concurrent-ruby/blob/master/md/defer.md)
54
+ * Clojure inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
55
+ * Go inspired [Goroutine](https://github.com/jdantonio/concurrent-ruby/blob/master/md/goroutine.md)
56
+ * JavaScript inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
57
+ * Java inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
58
+
59
+ ### Is it any good?
60
+
61
+ [Yes](http://news.ycombinator.com/item?id=3067434)
62
+
63
+ ### Supported Ruby versions
64
+
65
+ MRI 1.9.2, 1.9.3, and 2.0. This library is pure Ruby and has no gem dependencies. It should be
66
+ fully compatible with any Ruby interpreter that is 1.9.x compliant. I simply don't know enough
67
+ about JRuby, Rubinius, or the others to fully support them. I can promise good karma and
68
+ attribution on this page to anyone wishing to take responsibility for verifying compaitibility
69
+ with any Ruby other than MRI.
70
+
71
+ ### Install
72
+
73
+ ```shell
74
+ gem install concurrent-ruby
75
+ ```
76
+
77
+ or add the following line to Gemfile:
78
+
79
+ ```ruby
80
+ gem 'concurrent-ruby'
81
+ ```
82
+
83
+ and run `bundle install` from your shell.
84
+
85
+ Once you've installed the gem you must `require` it in your project:
86
+
87
+ ```ruby
88
+ require 'concurrent'
89
+ ```
90
+
91
+ ### Examples
92
+
93
+ For complete examples, see the specific documentation linked above. Below are a few examples to whet your appetite.
94
+
95
+ #### Goroutine (Go)
96
+
97
+ ```ruby
98
+ require 'concurrent'
99
+
100
+ @expected = nil
101
+ go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
102
+ sleep(0.1)
103
+ @expected #=> [3, 2, 1]
104
+ ```
105
+
106
+ #### Agent (Clojure)
107
+
108
+ ```ruby
109
+ require 'concurrent'
110
+
111
+ score = agent(10)
112
+ score.value #=> 10
113
+
114
+ score << proc{|current| current + 100 }
115
+ sleep(0.1)
116
+ score.value #=> 110
117
+
118
+ score << proc{|current| current * 2 }
119
+ sleep(0.1)
120
+ deref score #=> 220
121
+
122
+ score << proc{|current| current - 50 }
123
+ sleep(0.1)
124
+ score.value #=> 170
125
+ ```
126
+
127
+ #### Defer (EventMachine)
128
+
129
+ ```ruby
130
+ require 'concurrent'
131
+
132
+ Concurrent::Defer.new{ "Jerry D'Antonio" }.
133
+ then{|result| puts "Hello, #{result}!" }.
134
+ rescue{|ex| puts ex.message }.
135
+ go
136
+
137
+ #=> Hello, Jerry D'Antonio!
138
+
139
+ operation = proc{ raise StandardError.new('Boom!') }
140
+ callback = proc{|result| puts result }
141
+ errorback = proc{|ex| puts ex.message }
142
+ defer(operation, callback, errorback)
143
+ sleep(0.1)
144
+
145
+ #=> "Boom!"
146
+ ```
147
+
148
+ #### Future (Clojure)
149
+
150
+ ```ruby
151
+ require 'concurrent'
152
+
153
+ count = future{ sleep(1); 10 }
154
+ count.state #=> :pending
155
+ # do stuff...
156
+ count.value #=> 10 (after blocking)
157
+ deref count #=> 10
158
+ ```
159
+
160
+ #### Promise (JavaScript)
161
+
162
+ ```ruby
163
+ require 'concurrent'
164
+
165
+ p = promise("Jerry", "D'Antonio"){|a, b| "#{a} #{b}" }.
166
+ then{|result| "Hello #{result}." }.
167
+ rescue(StandardError){|ex| puts "Boom!" }.
168
+ then{|result| "#{result} Would you like to play a game?"}
169
+ sleep(1)
170
+ p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
171
+ ```
172
+
173
+ #### Thread Pools
174
+
175
+ ```ruby
176
+ require 'concurrent'
177
+
178
+ pool = Concurrent::FixedThreadPool.new(10)
179
+ @expected = 0
180
+ pool.post{ sleep(0.5); @expected += 100 }
181
+ pool.post{ sleep(0.5); @expected += 100 }
182
+ pool.post{ sleep(0.5); @expected += 100 }
183
+ @expected #=> nil
184
+ sleep(1)
185
+ @expected #=> 300
186
+
187
+ pool = Concurrent::CachedThreadPool.new
188
+ @expected = 0
189
+ pool << proc{ sleep(0.5); @expected += 10 }
190
+ pool << proc{ sleep(0.5); @expected += 10 }
191
+ pool << proc{ sleep(0.5); @expected += 10 }
192
+ @expected #=> 0
193
+ sleep(1)
194
+ @expected #=> 30
195
+ ```
196
+
197
+ ## Copyright
198
+
199
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
200
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
201
+
202
+ ## License
203
+
204
+ Released under the MIT license.
205
+
206
+ http://www.opensource.org/licenses/mit-license.php
207
+
208
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
209
+ > of this software and associated documentation files (the "Software"), to deal
210
+ > in the Software without restriction, including without limitation the rights
211
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
212
+ > copies of the Software, and to permit persons to whom the Software is
213
+ > furnished to do so, subject to the following conditions:
214
+ >
215
+ > The above copyright notice and this permission notice shall be included in
216
+ > all copies or substantial portions of the Software.
217
+ >
218
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
219
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
220
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
221
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
222
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
223
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
224
+ > THE SOFTWARE.
@@ -1 +1,20 @@
1
+ require 'thread'
2
+
1
3
  require 'concurrent/version'
4
+
5
+ require 'concurrent/event'
6
+
7
+ require 'concurrent/agent'
8
+ require 'concurrent/defer'
9
+ require 'concurrent/future'
10
+ require 'concurrent/goroutine'
11
+ require 'concurrent/promise'
12
+ require 'concurrent/obligation'
13
+
14
+ require 'concurrent/thread_pool'
15
+ require 'concurrent/cached_thread_pool'
16
+ require 'concurrent/fixed_thread_pool'
17
+
18
+ require 'concurrent/global_thread_pool'
19
+
20
+ require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
@@ -0,0 +1,130 @@
1
+ require 'observer'
2
+ require 'thread'
3
+
4
+ require 'concurrent/global_thread_pool'
5
+
6
+ module Concurrent
7
+
8
+ # An agent is a single atomic value that represents an identity. The current value
9
+ # of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
10
+ # the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
11
+ # will receive the current value of the agent as its sole parameter. The return value of the block
12
+ # will become the new value of the agent. Agents support two error handling modes: fail and continue.
13
+ # A good example of an agent is a shared incrementing counter, such as the score in a video game.
14
+ class Agent
15
+ include Observable
16
+
17
+ TIMEOUT = 5
18
+
19
+ attr_reader :initial
20
+ attr_reader :timeout
21
+
22
+ def initialize(initial, timeout = TIMEOUT)
23
+ @value = initial
24
+ @timeout = timeout
25
+ @rescuers = []
26
+ @validator = nil
27
+ @queue = Queue.new
28
+
29
+ $GLOBAL_THREAD_POOL << proc{ work }
30
+ end
31
+
32
+ def value(timeout = 0) return @value; end
33
+ alias_method :deref, :value
34
+
35
+ def rescue(clazz = Exception, &block)
36
+ @rescuers << Rescuer.new(clazz, block) if block_given?
37
+ return self
38
+ end
39
+ alias_method :catch, :rescue
40
+ alias_method :on_error, :rescue
41
+
42
+ def validate(&block)
43
+ @validator = block if block_given?
44
+ return self
45
+ end
46
+ alias_method :validates, :validate
47
+ alias_method :validate_with, :validate
48
+ alias_method :validates_with, :validate
49
+
50
+ def post(&block)
51
+ return @queue.length unless block_given?
52
+ @queue << block
53
+ return @queue.length
54
+ end
55
+
56
+ def <<(block)
57
+ self.post(&block)
58
+ return self
59
+ end
60
+
61
+ def length
62
+ @queue.length
63
+ end
64
+ alias_method :size, :length
65
+ alias_method :count, :length
66
+
67
+ alias_method :add_watch, :add_observer
68
+
69
+ private
70
+
71
+ # @private
72
+ Rescuer = Struct.new(:clazz, :block)
73
+
74
+ # @private
75
+ def try_rescue(ex) # :nodoc:
76
+ rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
77
+ rescuer.block.call(ex) if rescuer
78
+ rescue Exception => e
79
+ # supress
80
+ end
81
+
82
+ # @private
83
+ def work # :nodoc:
84
+ loop do
85
+ Thread.pass
86
+ handler = @queue.pop
87
+ begin
88
+ result = Timeout.timeout(@timeout){
89
+ handler.call(@value)
90
+ }
91
+ if @validator.nil? || @validator.call(result)
92
+ @value = result
93
+ changed
94
+ notify_observers(Time.now, @value)
95
+ end
96
+ rescue Exception => ex
97
+ try_rescue(ex)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ module Kernel
105
+
106
+ def agent(initial, timeout = Concurrent::Agent::TIMEOUT)
107
+ return Concurrent::Agent.new(initial, timeout)
108
+ end
109
+ module_function :agent
110
+
111
+ def deref(agent, timeout = nil)
112
+ if agent.respond_to?(:deref)
113
+ return agent.deref(timeout)
114
+ elsif agent.respond_to?(:value)
115
+ return agent.deref(timeout)
116
+ else
117
+ return nil
118
+ end
119
+ end
120
+ module_function :deref
121
+
122
+ def post(agent, &block)
123
+ if agent.respond_to?(:post)
124
+ return agent.post(&block)
125
+ else
126
+ return nil
127
+ end
128
+ end
129
+ module_function :deref
130
+ end
@@ -0,0 +1,122 @@
1
+ require 'thread'
2
+
3
+ require 'concurrent/thread_pool'
4
+ require 'functional/utilities'
5
+
6
+ module Concurrent
7
+
8
+ def self.new_cached_thread_pool
9
+ return CachedThreadPool.new
10
+ end
11
+
12
+ class CachedThreadPool < ThreadPool
13
+ behavior(:thread_pool)
14
+
15
+ DEFAULT_GC_INTERVAL = 60
16
+ DEFAULT_THREAD_IDLETIME = 60
17
+
18
+ attr_reader :working
19
+
20
+ def initialize(opts = {})
21
+ @gc_interval = opts[:gc_interval] || DEFAULT_GC_INTERVAL
22
+ @thread_idletime = opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME
23
+ super()
24
+ @working = 0
25
+ @mutex = Mutex.new
26
+ end
27
+
28
+ def kill
29
+ @status = :killed
30
+ @mutex.synchronize do
31
+ @pool.each{|t| Thread.kill(t.thread) }
32
+ end
33
+ end
34
+
35
+ def size
36
+ return @pool.length
37
+ end
38
+
39
+ def post(*args, &block)
40
+ raise ArgumentError.new('no block given') unless block_given?
41
+ if running?
42
+ collect_garbage if @pool.empty?
43
+ @mutex.synchronize do
44
+ if @working >= @pool.length
45
+ create_worker_thread
46
+ end
47
+ @queue << [args, block]
48
+ end
49
+ return true
50
+ else
51
+ return false
52
+ end
53
+ end
54
+
55
+ # @private
56
+ def status # :nodoc:
57
+ @mutex.synchronize do
58
+ @pool.collect do |worker|
59
+ [
60
+ worker.status,
61
+ worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
62
+ worker.thread.status
63
+ ]
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ Worker = Struct.new(:status, :idletime, :thread)
71
+
72
+ # @private
73
+ def create_worker_thread # :nodoc:
74
+ worker = Worker.new(:idle, timestamp, nil)
75
+
76
+ worker.thread = Thread.new(worker) do |me|
77
+
78
+ loop do
79
+ task = @queue.pop
80
+
81
+ @working += 1
82
+ me.status = :working
83
+
84
+ if task == :stop
85
+ me.status = :stopping
86
+ break
87
+ else
88
+ task.last.call(*task.first)
89
+ @working -= 1
90
+ me.status = :idle
91
+ me.idletime = timestamp
92
+ end
93
+ end
94
+
95
+ @pool.delete(me)
96
+ if @pool.empty?
97
+ @termination.set
98
+ @status = :shutdown unless killed?
99
+ end
100
+ end
101
+
102
+ @pool << worker
103
+ end
104
+
105
+ # @private
106
+ def collect_garbage # :nodoc:
107
+ @collector = Thread.new do
108
+ loop do
109
+ sleep(@gc_interval)
110
+ @mutex.synchronize do
111
+ @pool.reject! do |worker|
112
+ worker.thread.status.nil? ||
113
+ (worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
114
+ end
115
+ end
116
+ @working = @pool.count{|worker| worker.status == :working}
117
+ break if @pool.empty?
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end