process-group 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8d207f2d37df9b65b6e179ac9109dd9e3a5a443
4
+ data.tar.gz: 4422d1d9fc5171dc8dc5236b4b9745fbe81dda5b
5
+ SHA512:
6
+ metadata.gz: f2c49a5f6980e9bb4b44c90f5577e5a664611bef83e22da9f3e5bfbc1525870a76a4ff11cffa36340b30f77e594e75e8de3b55f04132537d10b92bce2a85452c
7
+ data.tar.gz: 348213c1cf609dace2d8228a9111b53232cc4bf36cde8c548d62e93bc5ee4e3bb1a5fac5fe5083e2029298201e3b6636e91f8d17319946510bb74e00800aa9d5
data/.gitignore ADDED
@@ -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/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0"
5
+ - "2.1"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in process-pool.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Samuel Williams
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.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Process::Group
2
+
3
+ `Process::Group` allows for multiple fibers to run system processes concurrently with minimal overhead.
4
+
5
+ [![Build Status](https://travis-ci.org/ioquatix/process-group.png?branch=master)](https://travis-ci.org/ioquatix/process-group)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'process-group'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install process-group
20
+
21
+ ## Usage
22
+
23
+ The simplest concurrent usage is as follows:
24
+
25
+ # Create a new process group:
26
+ group = Process::Group.new
27
+
28
+ # Run the command (non-blocking):
29
+ group.run("sleep 1") do |exit_status|
30
+ # Running in a separate fiber, will execute this code once the process completes:
31
+ puts "Command finished with status: #{exit_status}"
32
+ end
33
+
34
+ # Do something else here:
35
+ sleep(1)
36
+
37
+ # Wait for all processes in group to finish:
38
+ group.wait
39
+
40
+ ### Explicit Fibers
41
+
42
+ Items within a single fiber will execute sequentially. Processes (e.g. via `Group#spawn`) will run concurrently in multiple fibers.
43
+
44
+ group = Process::Group.new
45
+
46
+ # Explicity manage concurrency in this fiber:
47
+ Fiber.new do
48
+ # These processes will be run sequentially:
49
+ group.spawn("sleep 1")
50
+ group.spawn("sleep 1")
51
+ end.resume
52
+
53
+ # Implicitly run this task concurrently as the above fiber:
54
+ group.run("sleep 2")
55
+
56
+ # Wait for fiber to complete:
57
+ group.wait
58
+
59
+ `Group#spawn` is theoretically identical to `Process#spawn` except the processes are run concurrently if possible.
60
+
61
+ ### Specify Options
62
+
63
+ You can specify options to `Group#run` and `Group#spawn` just like `Process::spawn`:
64
+
65
+ group = Process::Group.new
66
+
67
+ env = {'FOO' => 'BAR'}
68
+
69
+ # Arguments are essentially the same as Process::spawn.
70
+ group.run(env, "sleep 1", chdir: "/tmp")
71
+
72
+ group.wait
73
+
74
+ ### Process Limit
75
+
76
+ The process group can be used as a way to spawn multiple processes, but sometimes you'd like to limit the number of parallel processes to something relating to the number of processors in the system. A number of options exist.
77
+
78
+ # 'facter' gem - found a bit slow to initialise, but most widely supported.
79
+ require 'facter'
80
+ group = Process::Group.new(limit: Facter.processorcount)
81
+
82
+ # 'system' gem - found very fast, less wide support (but nothing really important).
83
+ require 'system'
84
+ group = Process::Group.new(limit: System::CPU.count)
85
+
86
+ # hardcoded - set to n (8 < n < 32) and let the OS scheduler worry about it.
87
+ group = Process::Group.new(limit: 32)
88
+
89
+ # unlimited - default.
90
+ group = Process::Group.new
91
+
92
+ ### Kill Group
93
+
94
+ It is possible to send a signal (kill) to the entire process group:
95
+
96
+ group.kill(:TERM)
97
+
98
+ If there are no running processes, this is a no-op (rather than an error).
99
+
100
+ ## Contributing
101
+
102
+ 1. Fork it
103
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
104
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
105
+ 4. Push to the branch (`git push origin my-new-feature`)
106
+ 5. Create new Pull Request
107
+
108
+ ## License
109
+
110
+ Released under the MIT license.
111
+
112
+ Copyright, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
113
+
114
+ Permission is hereby granted, free of charge, to any person obtaining a copy
115
+ of this software and associated documentation files (the "Software"), to deal
116
+ in the Software without restriction, including without limitation the rights
117
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
118
+ copies of the Software, and to permit persons to whom the Software is
119
+ furnished to do so, subject to the following conditions:
120
+
121
+ The above copyright notice and this permission notice shall be included in
122
+ all copies or substantial portions of the Software.
123
+
124
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
125
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
126
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
127
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
128
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
129
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
130
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,145 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'fiber'
22
+
23
+ module Process
24
+ # A group of tasks which can be run asynchrnously using fibers. Someone must call Group#wait to ensure that all fibers eventually resume.
25
+ class Group
26
+ class Command
27
+ def initialize(arguments, options, fiber = Fiber.current)
28
+ @arguments = arguments
29
+ @options = options
30
+
31
+ @fiber = fiber
32
+ end
33
+
34
+ attr :arguments
35
+ attr :options
36
+
37
+ def run(options = {})
38
+ Process.spawn(*@arguments, @options.merge(options))
39
+ end
40
+
41
+ def resume(*arguments)
42
+ @fiber.resume(*arguments)
43
+ end
44
+ end
45
+
46
+ # Create a new process group. Can specify `options[:limit]` which limits the maximum number of concurrent processes.
47
+ def initialize(options = {})
48
+ @commands = []
49
+ @limit = options[:limit]
50
+
51
+ @running = {}
52
+ @fiber = nil
53
+
54
+ @pgid = nil
55
+ end
56
+
57
+ # A table of currently running processes.
58
+ attr :running
59
+
60
+ # The maximum number of processes to run concurrently, or zero
61
+ attr_accessor :limit
62
+
63
+ # The id of the process group, only valid if processes are currently running.
64
+ def id
65
+ raise RuntimeError.new("No processes in group, no group id available.") if @running.size == 0
66
+
67
+ -@pgid
68
+ end
69
+
70
+ # Run a process, arguments have same meaning as Process#spawn.
71
+ def run(*arguments)
72
+ Fiber.new do
73
+ exit_status = self.spawn(*arguments)
74
+
75
+ yield exit_status if block_given?
76
+ end.resume
77
+ end
78
+
79
+ def spawn(*arguments)
80
+ # Could be nice to use ** splat, but excludes ruby < 2.0.
81
+ options = Hash === arguments.last ? arguments.pop : {}
82
+
83
+ @commands << Command.new(arguments, options)
84
+
85
+ schedule!
86
+
87
+ Fiber.yield
88
+ end
89
+
90
+ # Whether not not calling run would be scheduled immediately.
91
+ def available?
92
+ if @limit
93
+ @running.size < @limit
94
+ else
95
+ true
96
+ end
97
+ end
98
+
99
+ # Whether or not calling run would block the caller.
100
+ def blocking?
101
+ not available?
102
+ end
103
+
104
+ # Wait for all processes to finish, naturally would schedule any fibers which are currently blocked.
105
+ def wait
106
+ while @running.size > 0
107
+ # Wait for processes in this group:
108
+ pid, status = Process.wait2(-@pgid)
109
+
110
+ command = @running.delete(pid)
111
+
112
+ raise RuntimeError.new("Process #{pid} is not part of group!") unless command
113
+
114
+ schedule!
115
+
116
+ command.resume(status)
117
+ end
118
+ end
119
+
120
+ # Send a signal to all processes.
121
+ def kill(signal)
122
+ if @running.size > 0
123
+ Process.kill(signal, id)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # Run any commands while space is available in the group.
130
+ def schedule!
131
+ while available? and @commands.size > 0
132
+ command = @commands.shift
133
+
134
+ if @running.size == 0
135
+ pid = command.run(:pgroup => true)
136
+ @pgid = Process.getpgid(pid)
137
+ else
138
+ pid = command.run(:pgroup => @pgid)
139
+ end
140
+
141
+ @running[pid] = command
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,5 @@
1
+ module Process
2
+ class Group
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'process/group/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "process-group"
8
+ spec.version = Process::Group::VERSION
9
+ spec.authors = ["Samuel Williams"]
10
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
11
+ spec.description = <<-EOF
12
+ Manages a unix process group for running multiple processes, keeps track of multiple processes and leverages fibers to provide predictable behaviour in complicated process-based scripts.
13
+ EOF
14
+ spec.summary = %q{Run processes concurrently in separate fibers with predictable behaviour.}
15
+ spec.homepage = ""
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,106 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'test/unit'
22
+
23
+ require 'process/group'
24
+
25
+ class TestGroup < Test::Unit::TestCase
26
+ def test_fibers
27
+ group = Process::Group.new
28
+
29
+ start_time = Time.now
30
+
31
+ Fiber.new do
32
+ result = group.spawn("sleep 1")
33
+ assert_equal 0, result
34
+ end.resume
35
+
36
+ Fiber.new do
37
+ result = group.spawn("sleep 2")
38
+ assert_equal 0, result
39
+ end.resume
40
+
41
+ group.wait
42
+
43
+ end_time = Time.now
44
+
45
+ # Check that the execution time was roughly 2 seconds:
46
+ assert (1.9..2.1).include?(end_time - start_time)
47
+ end
48
+
49
+ def test_kill_commands
50
+ group = Process::Group.new
51
+
52
+ start_time = Time.now
53
+
54
+ group.run("sleep 1") do |exit_status|
55
+ assert_not_equal 0, exit_status
56
+ end
57
+
58
+ group.run("sleep 2") do |exit_status|
59
+ assert_not_equal 0, exit_status
60
+ end
61
+
62
+ group.kill(:KILL)
63
+
64
+ group.wait
65
+
66
+ end_time = Time.now
67
+
68
+ # Check that the execution time was roughly 2 seconds:
69
+ assert (end_time - start_time) < 0.1
70
+ end
71
+
72
+ def test_environment_options
73
+ group = Process::Group.new
74
+
75
+ env = {'FOO' => 'BAR'}
76
+
77
+ # Make a pipe to receive output from child process:
78
+ input, output = IO.pipe
79
+
80
+ group.run(env, "echo $FOO", out: output) do |exit_status|
81
+ output.close
82
+ end
83
+
84
+ group.wait
85
+
86
+ assert_equal "BAR\n", input.read
87
+ end
88
+
89
+ def test_yield
90
+ group = Process::Group.new
91
+
92
+ start_time = Time.now
93
+
94
+ group.run("sleep 1")
95
+
96
+ group.run("sleep 1") do |exit_status|
97
+ end
98
+
99
+ group.wait
100
+
101
+ end_time = Time.now
102
+
103
+ # Check that the execution time was roughly 1 second:
104
+ assert (0.9..1.1).include?(end_time - start_time)
105
+ end
106
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: process-group
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: "\tManages a unix process group for running multiple processes, keeps
42
+ track of multiple processes and leverages fibers to provide predictable behaviour
43
+ in complicated process-based scripts.\n"
44
+ email:
45
+ - samuel.williams@oriontransfer.co.nz
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - .gitignore
51
+ - .travis.yml
52
+ - Gemfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - lib/process/group.rb
57
+ - lib/process/group/version.rb
58
+ - process-group.gemspec
59
+ - test/test_group.rb
60
+ homepage: ''
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Run processes concurrently in separate fibers with predictable behaviour.
84
+ test_files:
85
+ - test/test_group.rb