futuroscope 0.0.5 → 0.0.6
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.
- checksums.yaml +4 -4
- data/README.md +9 -8
- data/lib/futuroscope/pool.rb +59 -17
- data/lib/futuroscope/version.rb +1 -1
- data/lib/futuroscope/worker.rb +32 -0
- data/spec/futuroscope/pool_spec.rb +36 -3
- data/spec/futuroscope/worker_spec.rb +25 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e165b095edd6b4a24e0d696a2de6d19fbbb32b76
|
4
|
+
data.tar.gz: 30b612ac0946a96b7d4016e8f66b307f45d77482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
131
|
+
## Worker Pool
|
132
132
|
|
133
|
-
Futures are scheduled in a
|
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
|
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
|
141
|
-
concurrency like this:
|
141
|
+
The default thread pool can be configured like this:
|
142
142
|
|
143
143
|
```Ruby
|
144
|
-
Futuroscope.default_pool =
|
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
|
|
data/lib/futuroscope/pool.rb
CHANGED
@@ -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 :
|
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
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
-
@
|
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
|
-
@
|
18
|
-
|
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
|
-
@
|
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
|
31
|
-
@
|
32
|
-
@
|
33
|
-
|
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
|
-
@
|
83
|
+
@workers.each(&:stop)
|
42
84
|
end
|
43
85
|
end
|
44
86
|
end
|
data/lib/futuroscope/version.rb
CHANGED
@@ -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
|
6
|
+
it "spins up a number of workers" do
|
7
7
|
pool = Pool.new(2)
|
8
|
-
expect(pool.
|
8
|
+
expect(pool.workers).to have(2).workers
|
9
9
|
|
10
10
|
pool = Pool.new(3)
|
11
|
-
expect(pool.
|
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.
|
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-
|
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
|