xpool 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ == v0.3.0
2
+ * Add XPool#size.
3
+ It returns the number of alive subprocesses in the pool.
4
+
5
+ == v0.2.0
6
+ * Minor README & API documentation improvements.
7
+ * Change the default number of subprocesses to spawn in a pool from 10 to 5.
data/README.md CHANGED
@@ -4,17 +4,17 @@ __OVERVIEW__
4
4
  |:----------------|:--------------------------------------------------
5
5
  | Homepage | https://github.com/robgleeson/xpool
6
6
  | Documentation | http://rubydoc.info/gems/xpool/frames
7
- | CI | [![Build Status](https://travis-ci.org/robgleeson/XPool.png)](https://travis-ci.org/robgleeson/XPool)
7
+ | CI | [![Build Status](https://travis-ci.org/robgleeson/xpool.png)](https://travis-ci.org/robgleeson/XPool)
8
8
  | Author | Rob Gleeson
9
9
 
10
10
 
11
11
  __DESCRIPTION__
12
12
 
13
- A lightweight UNIX(X) Process Pool implementation. The size of the pool
13
+ A lightweight UNIX(X) Process Pool written in Ruby. The size of the pool
14
14
  is dynamic and it can be resized at runtime if needs be. 'Units of work' are
15
- what you can schedule and they are dispatched by a subprocess in the pool. If
16
- the pool dries up(all processes are busy) the units of work are queued & the
17
- next available subprocess will pick it up.
15
+ what you can schedule and each unit of work is dispatched by a free subprocess
16
+ in the the pool. If the pool dries up(all subprocesses are busy) the units
17
+ of work are queued & the next available subprocess will pick it up.
18
18
 
19
19
  There are also all the other features you might expect, such as an interface to
20
20
  shutdown gracefully or to shutdown immediately. Graceful shutdowns can operate
@@ -25,7 +25,7 @@ __EXAMPLES__
25
25
 
26
26
  _1._
27
27
 
28
- A demo of how you'd create a pool of 10 subprocesses:
28
+ A demo of how you'd create a pool of 5 subprocesses:
29
29
 
30
30
  ```ruby
31
31
  #
@@ -38,7 +38,7 @@ class Unit
38
38
  sleep 1
39
39
  end
40
40
  end
41
- pool = XPool.new 10
41
+ pool = XPool.new 5
42
42
  5.times { pool.schedule Unit.new }
43
43
  pool.shutdown
44
44
  ```
@@ -68,11 +68,32 @@ class Unit
68
68
  sleep 5
69
69
  end
70
70
  end
71
- pool = XPool.new 10
71
+ pool = XPool.new 5
72
72
  pool.schedule Unit.new
73
73
  pool.shutdown 3
74
74
  ```
75
75
 
76
+ __DEBUGGING OUTPUT__
77
+
78
+ XPool can print helpful debugging information if you set `XPool.debug`
79
+ to true:
80
+
81
+ ```ruby
82
+ XPool.debug = true
83
+ ```
84
+
85
+ Or you can temporarily enable debugging output for the duration of a block:
86
+
87
+ ```ruby
88
+ XPool.debug do
89
+ pool = XPool.new 5
90
+ pool.shutdown
91
+ end
92
+ ```
93
+
94
+ The debugging information you'll see is all about how the pool is operating.
95
+ It can be interesting to look over even if you're not bug hunting.
96
+
76
97
  __INSTALL__
77
98
 
78
99
  $ gem install xpool
@@ -1,8 +1,8 @@
1
1
  class XPool
2
- require 'json'
3
2
  require 'ichannel'
4
3
  require 'timeout'
5
4
  require 'logger'
5
+ require 'rbconfig'
6
6
  require_relative "xpool/version"
7
7
  require_relative "xpool/process"
8
8
 
@@ -23,7 +23,7 @@ class XPool
23
23
  @debug = boolean
24
24
  end
25
25
 
26
- def self.log(msg, type = :info)
26
+ def self.log(msg, type = :info)
27
27
  @logger = @logger || Logger.new(STDOUT)
28
28
  if @debug
29
29
  @logger.public_send type, msg
@@ -36,9 +36,9 @@ class XPool
36
36
  #
37
37
  # @return [XPool]
38
38
  #
39
- def initialize(size=10)
39
+ def initialize(size=number_of_cpu_cores)
40
40
  @channel = IChannel.new Marshal
41
- @pool = Array.new size do
41
+ @pool = Array.new size do
42
42
  spawn
43
43
  end
44
44
  end
@@ -46,24 +46,22 @@ class XPool
46
46
  #
47
47
  # A graceful shutdown of the pool.
48
48
  #
49
- # All busy subprocesses finish up any code they're running & exit normally
49
+ # All busy subprocesses finish up any code they're running & exit normally
50
50
  # afterwards.
51
51
  #
52
- # @param [Fixnum] timeout
52
+ # @param [Fixnum] timeout
53
53
  # An optional amount of seconds to wait before forcing a shutdown through
54
54
  # {#shutdown!}.
55
55
  #
56
56
  # @see XPool::Process#spawn
57
- #
57
+ #
58
58
  # @return [void]
59
59
  #
60
60
  def shutdown(timeout=nil)
61
61
  if timeout
62
62
  begin
63
63
  Timeout.timeout(timeout) do
64
- @pool.each do |process|
65
- process.shutdown
66
- end
64
+ @pool.each(&:shutdown)
67
65
  end
68
66
  rescue Timeout::Error
69
67
  XPool.log "'#{timeout}' seconds elapsed, switching to hard shutdown."
@@ -85,12 +83,12 @@ class XPool
85
83
 
86
84
  #
87
85
  # Resize the pool.
88
- # All subprocesses in the pool are abruptly stopped through {#shutdown!} and
86
+ # All subprocesses in the pool are abruptly stopped through {#shutdown!} and
89
87
  # a new pool the size of _range_ is created.
90
88
  #
91
89
  # @example
92
- # pool = XPool.new 10
93
- # pool.resiz!e 1..5
90
+ # pool = XPool.new 5
91
+ # pool.resize! 1..3
94
92
  # pool.shutdown
95
93
  #
96
94
  # @param [Range] range
@@ -108,30 +106,40 @@ class XPool
108
106
  #
109
107
  # Dispatch a unit of work in a subprocess.
110
108
  #
111
- # @param
109
+ # @param
112
110
  # (see Process#schedule)
113
111
  #
114
- # @return
112
+ # @return
115
113
  # (see Process#schedule)
116
114
  #
117
115
  def schedule(unit, *args)
118
116
  @channel.put unit: unit, args: args
119
117
  end
120
118
 
119
+ #
120
+ # @return [Fixnum]
121
+ # Returns the number of alive subprocesses in the pool.
122
+ #
123
+ def size
124
+ @pool.count do |process|
125
+ process.alive?
126
+ end
127
+ end
128
+
121
129
  private
122
130
  def spawn
123
131
  pid = fork do
124
132
  trap :SIGUSR1 do
125
133
  XPool.log "#{::Process.pid} got request to shutdown."
126
- @shutdown_requested = true
134
+ @shutdown_requested = true
127
135
  end
128
136
  loop do
129
137
  begin
130
138
  #
131
- # I've noticed that select can wait an infinite amount of time for
132
- # a UNIXSocket to become readable. It usually happens on the tenth or
133
- # so iteration. By checking if we have data to read first we elimate
134
- # this problem but it is a band aid for a bigger issue I don't
139
+ # I've noticed that select can wait an infinite amount of time for
140
+ # a UNIXSocket to become readable. It usually happens on the tenth or
141
+ # so iteration. By checking if we have data to read first we elimate
142
+ # this problem but it is a band aid for a bigger issue I don't
135
143
  # understand right now.
136
144
  #
137
145
  if @channel.readable?
@@ -146,6 +154,22 @@ private
146
154
  end
147
155
  end
148
156
  end
149
- Process.new pid
157
+ Process.new pid
158
+ end
159
+
160
+ #
161
+ # Count the number of CPU cores available.
162
+ #
163
+ def number_of_cpu_cores
164
+ case RbConfig::CONFIG['host_os']
165
+ when /linux/
166
+ Dir.glob('/sys/devices/system/cpu/cpu[0-9]*').count
167
+ when /darwin|bsd/
168
+ Integer(`sysctl -n hw.ncpu`)
169
+ when /solaris/
170
+ Integer(`kstat -m cpu_info | grep -w core_id | uniq | wc -l`)
171
+ else
172
+ 5
173
+ end
150
174
  end
151
175
  end
@@ -1,6 +1,6 @@
1
1
  class XPool::Process
2
2
  #
3
- # @param [Fixnum] id
3
+ # @param [Fixnum] id
4
4
  # The Process ID.
5
5
  #
6
6
  def initialize(id)
@@ -10,8 +10,8 @@ class XPool::Process
10
10
  #
11
11
  # A graceful shutdown of the process.
12
12
  #
13
- # The signal 'SIGUSR1' is caught in the subprocess and exit is
14
- # performed through Kernel#exit after the process has finished
13
+ # The signal 'SIGUSR1' is caught in the subprocess and exit is
14
+ # performed through Kernel#exit after the process has finished
15
15
  # executing its work.
16
16
  #
17
17
  # @return [void]
@@ -28,13 +28,31 @@ class XPool::Process
28
28
  def shutdown!
29
29
  _shutdown 'SIGKILL'
30
30
  end
31
-
31
+
32
+ #
33
+ # @return [Boolean]
34
+ # Returns true when the process is alive.
35
+ #
36
+ def alive?
37
+ !dead?
38
+ end
39
+
40
+ #
41
+ # @return [Boolean]
42
+ # Returns true when the process is no longer running.
43
+ #
44
+ def dead?
45
+ @dead
46
+ end
47
+
32
48
  private
33
49
  def _shutdown(sig)
34
50
  begin
35
51
  Process.kill sig, @id
36
52
  Process.wait @id
37
53
  rescue SystemCallError
54
+ ensure
55
+ @dead = true
38
56
  end
39
57
  end
40
58
  end
@@ -1,3 +1,3 @@
1
1
  class XPool
2
- VERSION = "0.1.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -10,7 +10,7 @@ class XPoolTest < Test::Unit::TestCase
10
10
  def initialize
11
11
  file = Tempfile.new '__xpool_test'
12
12
  @path = file.path
13
- file.close
13
+ file.close false
14
14
  end
15
15
 
16
16
  def run
@@ -34,9 +34,21 @@ class XPoolTest < Test::Unit::TestCase
34
34
  def teardown
35
35
  @pool.shutdown
36
36
  end
37
-
38
37
 
39
- def test_queue
38
+
39
+ def test_size_with_graceful_shutdown
40
+ assert_equal 5, @pool.size
41
+ @pool.shutdown
42
+ assert_equal 0, @pool.size
43
+ end
44
+
45
+ def test_size_with_forceful_shutdown
46
+ assert_equal 5, @pool.size
47
+ @pool.shutdown!
48
+ assert_equal 0, @pool.size
49
+ end
50
+
51
+ def test_queue
40
52
  @pool.resize! 1..1
41
53
  units = Array.new(5) { XUnit.new }
42
54
  units.each do |unit|
@@ -48,18 +60,18 @@ class XPoolTest < Test::Unit::TestCase
48
60
  end
49
61
  end
50
62
 
51
- def test_parallelism
52
- 5.times do
63
+ def test_parallelism
64
+ 5.times do
53
65
  @pool.schedule Unit.new
54
66
  end
55
67
  assert_nothing_raised Timeout::Error do
56
- Timeout.timeout 2 do
68
+ Timeout.timeout 2 do
57
69
  @pool.shutdown
58
70
  end
59
71
  end
60
72
  end
61
73
 
62
- def test_resize!
74
+ def test_resize!
63
75
  @pool.resize! 1..1
64
76
  assert_equal 1, @pool.instance_variable_get(:@pool).size
65
77
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xpool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-01 00:00:00.000000000 Z
12
+ date: 2012-12-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ichannel
@@ -35,6 +35,7 @@ extensions: []
35
35
  extra_rdoc_files: []
36
36
  files:
37
37
  - .gitignore
38
+ - ChangeLog.txt
38
39
  - Gemfile
39
40
  - LICENSE.txt
40
41
  - README.md
@@ -59,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
60
  version: '0'
60
61
  segments:
61
62
  - 0
62
- hash: -2070941546545092687
63
+ hash: -2000550100409961336
63
64
  required_rubygems_version: !ruby/object:Gem::Requirement
64
65
  none: false
65
66
  requirements:
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  version: '0'
69
70
  segments:
70
71
  - 0
71
- hash: -2070941546545092687
72
+ hash: -2000550100409961336
72
73
  requirements: []
73
74
  rubyforge_project:
74
75
  rubygems_version: 1.8.23