xpool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+ group :development do
3
+ gem 'rake'
4
+ gem 'ichannel', path: '../ichannel' if File.exists? '../ichannel'
5
+ end
6
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Rob Gleeson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,77 @@
1
+ __OVERVIEW__
2
+
3
+ | Project | XPool
4
+ |:----------------|:--------------------------------------------------
5
+ | Homepage | https://github.com/robgleeson/xpool
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)
8
+ | Author | Rob Gleeson
9
+
10
+
11
+ __DESCRIPTION__
12
+
13
+ A lightweight UNIX(X) Process Pool implementation. The size of the pool
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.
18
+
19
+ There are also all the other features you might expect, such as an interface to
20
+ shutdown gracefully or to shutdown immediately. Graceful shutdowns can operate
21
+ within a timeout that when passed shuts down the pool immediately.
22
+
23
+
24
+ __EXAMPLES__
25
+
26
+ _1._
27
+
28
+ A demo of how you'd create a pool of 10 subprocesses:
29
+
30
+ #
31
+ # Make sure you define your units of work before
32
+ # you create a process pool or you'll get strange
33
+ # serialization errors.
34
+ #
35
+ class Unit
36
+ def run
37
+ sleep 1
38
+ end
39
+ end
40
+ pool = XPool.new 10
41
+ 5.times { pool.schedule Unit.new }
42
+ pool.shutdown
43
+
44
+ _2._
45
+
46
+ A demo of how you'd resize the pool from 10 to 5 subprocesses at runtime:
47
+
48
+ class Unit
49
+ def run
50
+ sleep 5
51
+ end
52
+ end
53
+ pool = XPool.new 10
54
+ pool.resize! 1..5
55
+ pool.shutdown
56
+
57
+ _3._
58
+
59
+ A demo of how you'd gracefully shutdown but force a hard shutdown if 3 seconds
60
+ pass by & all subprocesses have not exited:
61
+
62
+ class Unit
63
+ def run
64
+ sleep 5
65
+ end
66
+ end
67
+ pool = XPool.new 10
68
+ pool.schedule Unit.new
69
+ pool.shutdown 3
70
+
71
+ __INSTALL__
72
+
73
+ $ gem install xpool
74
+
75
+ __LICENSE__
76
+
77
+ MIT. See `LICENSE.txt`
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ task :test do
4
+ $LOAD_PATH.unshift 'lib'
5
+ Dir["test/*_test.rb"].each do |file|
6
+ require_relative file
7
+ end
8
+ end
9
+ task :default => :test
@@ -0,0 +1,151 @@
1
+ class XPool
2
+ require 'json'
3
+ require 'ichannel'
4
+ require 'timeout'
5
+ require 'logger'
6
+ require_relative "xpool/version"
7
+ require_relative "xpool/process"
8
+
9
+ def self.debug
10
+ if block_given?
11
+ begin
12
+ @debug = true
13
+ yield
14
+ ensure
15
+ @debug = false
16
+ end
17
+ else
18
+ @debug
19
+ end
20
+ end
21
+
22
+ def self.debug=(boolean)
23
+ @debug = boolean
24
+ end
25
+
26
+ def self.log(msg, type = :info)
27
+ @logger = @logger || Logger.new(STDOUT)
28
+ if @debug
29
+ @logger.public_send type, msg
30
+ end
31
+ end
32
+
33
+ #
34
+ # @param [Fixnum] size
35
+ # The number of subprocesses to spawn.
36
+ #
37
+ # @return [XPool]
38
+ #
39
+ def initialize(size=10)
40
+ @channel = IChannel.new Marshal
41
+ @pool = Array.new size do
42
+ spawn
43
+ end
44
+ end
45
+
46
+ #
47
+ # A graceful shutdown of the pool.
48
+ #
49
+ # All busy subprocesses finish up any code they're running & exit normally
50
+ # afterwards.
51
+ #
52
+ # @param [Fixnum] timeout
53
+ # An optional amount of seconds to wait before forcing a shutdown through
54
+ # {#shutdown!}.
55
+ #
56
+ # @see XPool::Process#spawn
57
+ #
58
+ # @return [void]
59
+ #
60
+ def shutdown(timeout=nil)
61
+ if timeout
62
+ begin
63
+ Timeout.timeout(timeout) do
64
+ @pool.each do |process|
65
+ process.shutdown
66
+ end
67
+ end
68
+ rescue Timeout::Error
69
+ XPool.log "'#{timeout}' seconds elapsed, switching to hard shutdown."
70
+ shutdown!
71
+ end
72
+ else
73
+ @pool.each(&:shutdown)
74
+ end
75
+ end
76
+
77
+ #
78
+ # A forceful shutdown of the pool (through SIGKILL).
79
+ #
80
+ # @return [void]
81
+ #
82
+ def shutdown!
83
+ @pool.each(&:shutdown!)
84
+ end
85
+
86
+ #
87
+ # Resize the pool.
88
+ # All subprocesses in the pool are abruptly stopped through {#shutdown!} and
89
+ # a new pool the size of _range_ is created.
90
+ #
91
+ # @example
92
+ # pool = XPool.new 10
93
+ # pool.resiz!e 1..5
94
+ # pool.shutdown
95
+ #
96
+ # @param [Range] range
97
+ # The new size of the pool.
98
+ #
99
+ # @return [void]
100
+ #
101
+ def resize!(range)
102
+ shutdown!
103
+ @pool = range.to_a.map do
104
+ spawn
105
+ end
106
+ end
107
+
108
+ #
109
+ # Dispatch a unit of work in a subprocess.
110
+ #
111
+ # @param
112
+ # (see Process#schedule)
113
+ #
114
+ # @return
115
+ # (see Process#schedule)
116
+ #
117
+ def schedule(unit, *args)
118
+ @channel.put unit: unit, args: args
119
+ end
120
+
121
+ private
122
+ def spawn
123
+ pid = fork do
124
+ trap :SIGUSR1 do
125
+ XPool.log "#{::Process.pid} got request to shutdown."
126
+ @shutdown_requested = true
127
+ end
128
+ loop do
129
+ begin
130
+ #
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
135
+ # understand right now.
136
+ #
137
+ if @channel.readable?
138
+ msg = @channel.get
139
+ msg[:unit].run *msg[:args]
140
+ end
141
+ ensure
142
+ if @shutdown_requested && !@channel.readable?
143
+ XPool.log "#{::Process.pid} is about to exit."
144
+ break
145
+ end
146
+ end
147
+ end
148
+ end
149
+ Process.new pid
150
+ end
151
+ end
@@ -0,0 +1,40 @@
1
+ class XPool::Process
2
+ #
3
+ # @param [Fixnum] id
4
+ # The Process ID.
5
+ #
6
+ def initialize(id)
7
+ @id = id
8
+ end
9
+
10
+ #
11
+ # A graceful shutdown of the process.
12
+ #
13
+ # The signal 'SIGUSR1' is caught in the subprocess and exit is
14
+ # performed through Kernel#exit after the process has finished
15
+ # executing its work.
16
+ #
17
+ # @return [void]
18
+ #
19
+ def shutdown
20
+ _shutdown 'SIGUSR1'
21
+ end
22
+
23
+ #
24
+ # A non-graceful shutdown through SIGKILL.
25
+ #
26
+ # @return [void]
27
+ #
28
+ def shutdown!
29
+ _shutdown 'SIGKILL'
30
+ end
31
+
32
+ private
33
+ def _shutdown(sig)
34
+ begin
35
+ Process.kill sig, @id
36
+ Process.wait @id
37
+ rescue SystemCallError
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ class XPool
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ require 'xpool'
3
+ require 'test/unit'
4
+ require 'fileutils'
5
+ XPool.debug = ENV.has_key? "DEBUG"
@@ -0,0 +1,66 @@
1
+ require_relative 'setup'
2
+ class XPoolTest < Test::Unit::TestCase
3
+ class Unit
4
+ def run
5
+ sleep 1
6
+ end
7
+ end
8
+
9
+ class XUnit
10
+ def initialize
11
+ file = Tempfile.new '__xpool_test'
12
+ @path = file.path
13
+ file.close
14
+ end
15
+
16
+ def run
17
+ File.open @path, 'w' do |f|
18
+ f.write 'true'
19
+ end
20
+ end
21
+
22
+ def run?
23
+ return @run if defined?(@run)
24
+ @run = File.read(@path) == 'true'
25
+ FileUtils.rm_rf @path
26
+ @run
27
+ end
28
+ end
29
+
30
+ def setup
31
+ @pool = XPool.new 5
32
+ end
33
+
34
+ def teardown
35
+ @pool.shutdown
36
+ end
37
+
38
+
39
+ def test_queue
40
+ @pool.resize! 1..1
41
+ units = Array.new(5) { XUnit.new }
42
+ units.each do |unit|
43
+ @pool.schedule unit
44
+ end
45
+ @pool.shutdown
46
+ units.each do |unit|
47
+ assert unit.run?
48
+ end
49
+ end
50
+
51
+ def test_parallelism
52
+ 5.times do
53
+ @pool.schedule Unit.new
54
+ end
55
+ assert_nothing_raised Timeout::Error do
56
+ Timeout.timeout 2 do
57
+ @pool.shutdown
58
+ end
59
+ end
60
+ end
61
+
62
+ def test_resize!
63
+ @pool.resize! 1..1
64
+ assert_equal 1, @pool.instance_variable_get(:@pool).size
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/xpool/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Robert Gleeson"]
6
+ gem.email = ["rob@flowof.info"]
7
+ gem.description = %q{A lightweight UNIX(X) Process Pool implementation}
8
+ gem.summary = gem.description
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "xpool"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = XPool::VERSION
17
+ gem.add_runtime_dependency 'ichannel', '~> 4.1.0'
18
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xpool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robert Gleeson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ichannel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 4.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 4.1.0
30
+ description: A lightweight UNIX(X) Process Pool implementation
31
+ email:
32
+ - rob@flowof.info
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - lib/xpool.rb
43
+ - lib/xpool/process.rb
44
+ - lib/xpool/version.rb
45
+ - test/setup.rb
46
+ - test/xpool_test.rb
47
+ - xpool.gemspec
48
+ homepage: ''
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ segments:
61
+ - 0
62
+ hash: 977068148446885427
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ segments:
70
+ - 0
71
+ hash: 977068148446885427
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 1.8.23
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: A lightweight UNIX(X) Process Pool implementation
78
+ test_files:
79
+ - test/setup.rb
80
+ - test/xpool_test.rb