futuroscope 0.0.5 → 0.0.6

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: 3209580d247ece206a07186449942582e6611740
4
- data.tar.gz: b9b20627ef16e8b590d11d0c5cb1dd636960a76c
3
+ metadata.gz: e165b095edd6b4a24e0d696a2de6d19fbbb32b76
4
+ data.tar.gz: 30b612ac0946a96b7d4016e8f66b307f45d77482
5
5
  SHA512:
6
- metadata.gz: dea9d87bbe54b822f7960a2c412ea1ae3dc94e8c49f2ec8c931d70384aab9fa2ce18a0cb9d3dc2815699f150ef64250865d6b009384e293fe0256841b4d2c661
7
- data.tar.gz: 209d3d2f712fbf64d4ad07faece782db1c1c998590716e1b55d5d901d5561ff20d764a5a5342fbdf0f35e290c0f790b7f3d60ee892f22e09dd7d6219b16abf43
6
+ metadata.gz: bb92a560f8cf1c17eb6c6251d9258bb17832c2aef483337450d08968490d8b508819a3436d108d50f4ac6875223af0a385f732d7b1c743b85fbf3de45677c2c0
7
+ data.tar.gz: 772f23f61ba3576af774239147047e7b0e4a5d15c2eb803ae7b71b10fe9a1b17d27c0990b5b5faa8ec56796b0e5b9b8222af59c556d926e546fd476643431e3c
data/README.md CHANGED
@@ -128,26 +128,27 @@ If you're looking for other ways to improve your code performance via
128
128
  concurrency, you should probably deal directly with [Ruby's
129
129
  threads](http://ruby-doc.org/core-2.0/Thread.html).
130
130
 
131
- ## Thread pool
131
+ ## Worker Pool
132
132
 
133
- Futures are scheduled in a thread pool that helps managing concurrency in a way
133
+ Futures are scheduled in a worker pool that helps managing concurrency in a way
134
134
  that doesn't get out of hands. Also comes with great benefits since their
135
135
  threads are spawned at load time (and not in runtime).
136
136
 
137
- The default thread pool comes with a concurrency of 8 threads, which seems
138
- reasonable for the most use cases.
137
+ The default thread pool comes with a concurrency of 8 Workers, which seems
138
+ reasonable for the most use cases. It will elastically expand to the default of
139
+ 16 threads and will kill them when they're not needed.
139
140
 
140
- The default thread pool can be replaced by a new pool with different
141
- concurrency like this:
141
+ The default thread pool can be configured like this:
142
142
 
143
143
  ```Ruby
144
- Futuroscope.default_pool = Futuroscope::Pool.new(24)
144
+ Futuroscope.default_pool.min_workers = 2
145
+ Futuroscope.default_pool.max_workers = 16
145
146
  ```
146
147
 
147
148
  Also, each future can be scheduled to a different pool like this:
148
149
 
149
150
  ```Ruby
150
- pool = Futuroscope::Pool.new(32)
151
+ pool = Futuroscope::Pool.new(16, 32)
151
152
 
152
153
  future = Future.new(pool){ :edballs }
153
154
 
@@ -1,44 +1,86 @@
1
1
  require 'thread'
2
+ require 'futuroscope/worker'
2
3
 
3
4
  module Futuroscope
4
5
  # Futuroscope's pool is design to control concurency and keep it between some
5
6
  # certain benefits. Moreover, we warm up the threads beforehand so we don't
6
7
  # have to spin them up each time a future is created.
7
8
  class Pool
8
- attr_reader :threads
9
+ attr_reader :workers
10
+ attr_accessor :min_workers, :max_workers
9
11
 
10
- # Initializes a new Pool.
12
+ # Public: Initializes a new Pool.
11
13
  #
12
- # thread_count - The number of threads that this pool is gonna have
13
- def initialize(thread_count = 8)
14
- @thread_count = thread_count
15
- @mutex = Mutex.new
14
+ # thread_count - The number of workers that this pool is gonna have
15
+ def initialize(min_workers = 8, max_workers = 16)
16
+ @min_workers = min_workers
17
+ @max_workers = max_workers
16
18
  @queue = Queue.new
17
- @threads = Array.new
18
- spin_threads
19
+ @workers = Set.new
20
+ @mutex = Mutex.new
21
+ warm_up_workers
19
22
  end
20
23
 
21
- # Enqueues a new Future into the pool.
24
+ # Public: Enqueues a new Future into the pool.
22
25
  #
23
26
  # future - The Future to enqueue.
24
27
  def queue(future)
25
- @queue.push future
28
+ @mutex.synchronize do
29
+ spin_worker if can_spin_extra_workers?
30
+ @queue.push future
31
+ end
32
+ end
33
+
34
+ # Public: Pops a new job from the pool. It will return nil if there's
35
+ # enough workers in the pool to take care of it.
36
+ #
37
+ # Returns a Future
38
+ def pop
39
+ return nil if @queue.empty? && more_workers_than_needed?
40
+ return @queue.pop
41
+ end
42
+
43
+ # Private: Notifies that a worker just died so it can be removed from the
44
+ # pool.
45
+ #
46
+ # worker - A Worker
47
+ def worker_died(worker)
48
+ @mutex.synchronize do
49
+ @workers.delete(worker)
50
+ end
51
+ end
52
+
53
+ def min_workers=(count)
54
+ @min_workers = count
55
+ warm_up_workers
26
56
  end
27
57
 
28
58
  private
29
59
 
30
- def spin_threads
31
- @thread_count.times do |i|
32
- @threads << Thread.new do
33
- loop do
34
- @queue.pop.run_future
35
- end
60
+ def warm_up_workers
61
+ @mutex.synchronize do
62
+ while(@workers.length < @min_workers) do
63
+ spin_worker
36
64
  end
37
65
  end
38
66
  end
39
67
 
68
+ def can_spin_extra_workers?
69
+ @workers.length < @max_workers
70
+ end
71
+
72
+ def more_workers_than_needed?
73
+ @workers.length > @min_workers
74
+ end
75
+
76
+ def spin_worker
77
+ worker = Worker.new(self)
78
+ @workers << worker
79
+ worker.run
80
+ end
81
+
40
82
  def finalize
41
- @threads.each(&:kill)
83
+ @workers.each(&:stop)
42
84
  end
43
85
  end
44
86
  end
@@ -1,3 +1,3 @@
1
1
  module Futuroscope
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -0,0 +1,32 @@
1
+ module Futuroscope
2
+ # A futuroscope worker takes care of resolving a future's value. It works
3
+ # together with a Pool.
4
+ class Worker
5
+ # Public: Initializes a new Worker.
6
+ #
7
+ # pool - The worker Pool it belongs to.
8
+ def initialize(pool)
9
+ @pool = pool
10
+ end
11
+
12
+ # Runs the worker. It keeps asking the Pool for a new job. If the pool
13
+ # decides there's no job use it now or in the future, it will die and the
14
+ # Pool will be notified. Otherwise, it will be given a new job or blocked
15
+ # until there's a new future available to process.
16
+ #
17
+ def run
18
+ @thread = Thread.new do
19
+ while(future = @pool.pop) do
20
+ future.run_future
21
+ end
22
+ stop
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def stop
29
+ @pool.worker_died(self)
30
+ end
31
+ end
32
+ end
@@ -3,12 +3,12 @@ require 'futuroscope/pool'
3
3
 
4
4
  module Futuroscope
5
5
  describe Pool do
6
- it "spins up a number of threads" do
6
+ it "spins up a number of workers" do
7
7
  pool = Pool.new(2)
8
- expect(pool.threads).to have(2).threads
8
+ expect(pool.workers).to have(2).workers
9
9
 
10
10
  pool = Pool.new(3)
11
- expect(pool.threads).to have(3).threads
11
+ expect(pool.workers).to have(3).workers
12
12
  end
13
13
 
14
14
  describe "queue" do
@@ -21,5 +21,38 @@ module Futuroscope
21
21
  sleep(0.1)
22
22
  end
23
23
  end
24
+
25
+ describe "worker control" do
26
+ it "adds more workers when needed and returns to the default amount" do
27
+ pool = Pool.new(2, 8)
28
+ 10.times do |future|
29
+ Future.new(pool){ sleep(1) }
30
+ end
31
+
32
+ sleep(0.5)
33
+ expect(pool.workers).to have(8).workers
34
+
35
+ sleep(1.5)
36
+ expect(pool.workers).to have(2).workers
37
+ end
38
+
39
+ it "allows overriding min workers real time" do
40
+ pool = Pool.new(2, 8)
41
+ pool.min_workers = 3
42
+ expect(pool.workers).to have(3).workers
43
+ end
44
+
45
+ it "allows overriding max workers real time" do
46
+ pool = Pool.new(2, 8)
47
+ pool.max_workers = 4
48
+
49
+ 10.times do |future|
50
+ Future.new(pool){ sleep(1) }
51
+ end
52
+
53
+ sleep(0.5)
54
+ expect(pool.workers).to have(4).workers
55
+ end
56
+ end
24
57
  end
25
58
  end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'futuroscope/worker'
3
+ require 'futuroscope/pool'
4
+
5
+ module Futuroscope
6
+ describe Worker do
7
+ it "asks the pool for a new job and runs the future" do
8
+ future = double(:future)
9
+ pool = [future]
10
+ future.should_receive :run_future
11
+
12
+ Worker.new(pool).run
13
+ sleep(1)
14
+ end
15
+
16
+ it "notifies the pool when the worker died because there's no job" do
17
+ pool = []
18
+ worker = Worker.new(pool)
19
+
20
+ pool.should_receive(:worker_died).with(worker)
21
+ worker.run
22
+ sleep(1)
23
+ end
24
+ end
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: futuroscope
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep Jaume Rey Peroy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-04 00:00:00.000000000 Z
11
+ date: 2013-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -91,10 +91,12 @@ files:
91
91
  - lib/futuroscope/map.rb
92
92
  - lib/futuroscope/pool.rb
93
93
  - lib/futuroscope/version.rb
94
+ - lib/futuroscope/worker.rb
94
95
  - spec/futuroscope/convenience_spec.rb
95
96
  - spec/futuroscope/future_spec.rb
96
97
  - spec/futuroscope/map_spec.rb
97
98
  - spec/futuroscope/pool_spec.rb
99
+ - spec/futuroscope/worker_spec.rb
98
100
  - spec/futuroscope_spec.rb
99
101
  - spec/spec_helper.rb
100
102
  homepage: ''
@@ -127,5 +129,6 @@ test_files:
127
129
  - spec/futuroscope/future_spec.rb
128
130
  - spec/futuroscope/map_spec.rb
129
131
  - spec/futuroscope/pool_spec.rb
132
+ - spec/futuroscope/worker_spec.rb
130
133
  - spec/futuroscope_spec.rb
131
134
  - spec/spec_helper.rb