mob_spawner 1.0.0
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.
- data/README.md +46 -0
- data/lib/mob_spawner.rb +162 -0
- metadata +55 -0
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# MobSpawner
|
2
|
+
|
3
|
+
MobSpawner manages worker threads that can run arbitrary commands and report
|
4
|
+
results. Unlike distributed queues, MobSpawner is self-contained and perfect
|
5
|
+
for small batch scripts that need to run multiple independent jobs.
|
6
|
+
|
7
|
+
Documentation is on [rubydoc.org](http://rubydoc.org/gems/mob_spawner/frames).
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
The simplest usage of MobSpawner is:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
commands = ["rvm install 1.8.6", "rvm install 1.9.2", "rvm install rbx"]
|
15
|
+
MobSpawner.new(commands).run
|
16
|
+
```
|
17
|
+
|
18
|
+
The above will attempt to run the 3 commands concurrently across the default of
|
19
|
+
3 worker threads. By default commands do not report output; to get command
|
20
|
+
output, use callbacks discussed in the next section.
|
21
|
+
|
22
|
+
For more information on how to initialize a spawner, see the {MobSpawner}
|
23
|
+
documentation.
|
24
|
+
|
25
|
+
## Callbacks
|
26
|
+
|
27
|
+
In addition to simply running worker threads, you can also receive reports
|
28
|
+
about each worker's execution results using callbacks. To setup a spawner
|
29
|
+
with callbacks, use {MobSpawner#before_worker} and {MobSpawner#after_worker}:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
spawner = MobSpawner.new("command1", "command2", "command3")
|
33
|
+
spawner.before_worker do |data|
|
34
|
+
puts "Worker #{data[:worker]} about to run #{data[:command].command}"
|
35
|
+
end
|
36
|
+
spawner.after_worker do |data|
|
37
|
+
puts "Worker #{data[:worker]} exited with status #{data[:status]}"
|
38
|
+
puts "Output:"
|
39
|
+
puts data[:output]
|
40
|
+
end
|
41
|
+
spawner.run
|
42
|
+
```
|
43
|
+
|
44
|
+
## License & Copyright
|
45
|
+
|
46
|
+
MobSpawner is licensed under the MIT license, © 2012 Loren Segal
|
data/lib/mob_spawner.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
# MobSpawner manages worker threads that can run arbitrary commands and report
|
4
|
+
# results. Unlike distributed queues, MobSpawner is self-contained and perfect
|
5
|
+
# for small batch scripts that need to run multiple independent jobs.
|
6
|
+
class MobSpawner
|
7
|
+
VERSION = '1.0.0'
|
8
|
+
|
9
|
+
# Represents a command to be called by the spawner. Can also hold environment
|
10
|
+
# variables and arbitrary client data to identify the object.
|
11
|
+
class Command
|
12
|
+
# @return [String] the command to be executed by the spawner
|
13
|
+
attr_accessor :command
|
14
|
+
|
15
|
+
# @return [Hash{String=>String}] any environment variables to be set
|
16
|
+
# when running the command.
|
17
|
+
attr_accessor :env
|
18
|
+
|
19
|
+
# @return [Object] arbitrary client data used to identify the command
|
20
|
+
# object.
|
21
|
+
attr_accessor :data
|
22
|
+
|
23
|
+
# Creates a new command.
|
24
|
+
#
|
25
|
+
# @overload initialize(opts = {})
|
26
|
+
# @param [Hash{Symbol=>Object}] opts option data to be passed during
|
27
|
+
# initialization. Keys can be any attribute defined on this class,
|
28
|
+
# such as {#command}, {#env} or {#data}.
|
29
|
+
# @overload initialize(cmd, env = {}, data = nil)
|
30
|
+
# @param [String] cmd the command to execute
|
31
|
+
# @param [Hash{String=>String}] env environment variables to be set
|
32
|
+
# when running the command
|
33
|
+
# @param [Object] data any client data to be set on the command object
|
34
|
+
def initialize(cmd, env = {}, data = nil)
|
35
|
+
self.env = {}
|
36
|
+
if cmd.is_a?(Hash)
|
37
|
+
cmd.each do |k, v|
|
38
|
+
meth = "#{k}="
|
39
|
+
send(meth, v) if respond_to?(meth)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
self.command = cmd
|
43
|
+
self.env = env
|
44
|
+
self.data = data
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Fixnum] the number of workers to run, defaults to 3.
|
50
|
+
attr_accessor :num_workers
|
51
|
+
|
52
|
+
# @return [Array<Command,String>] a list of commands to be executed. Note that
|
53
|
+
# if a command is a String, it will eventually be converted into a {Command}
|
54
|
+
# object.
|
55
|
+
attr_accessor :commands
|
56
|
+
|
57
|
+
# @return [Array<Proc>] a list of callbacks to be called before each worker.
|
58
|
+
# Use {#before_worker} instead of setting the callbacks list directly.
|
59
|
+
# @see #before_worker
|
60
|
+
attr_accessor :before_callbacks
|
61
|
+
|
62
|
+
# @return [Array<Proc>] a list of callbacks to be called after each worker.
|
63
|
+
# Use {#after_worker} instead of setting the callbacks list directly.
|
64
|
+
# @see #after_worker
|
65
|
+
attr_accessor :after_callbacks
|
66
|
+
|
67
|
+
# Creates a new spawner, use {#run} to run it.
|
68
|
+
#
|
69
|
+
# @overload initialize(opts = {})
|
70
|
+
# @param [Hash{Symbol=>Object}] opts option data to be passed during
|
71
|
+
# initialization. Keys can be any attribute defined on this class,
|
72
|
+
# such as {#num_workers}, {#commands}, etc.
|
73
|
+
# @overload initialize(*commands)
|
74
|
+
# @param [Array<String>] commands a list of commands to be run using
|
75
|
+
# default settings.
|
76
|
+
def initialize(*commands)
|
77
|
+
super()
|
78
|
+
self.num_workers = 3
|
79
|
+
self.commands = []
|
80
|
+
self.before_callbacks = []
|
81
|
+
self.after_callbacks = []
|
82
|
+
if commands.size == 1 && commands.first.is_a?(Hash)
|
83
|
+
setup_options(commands.first)
|
84
|
+
else
|
85
|
+
self.commands = commands.flatten
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Runs the spawner, initializing all workers and running the commands.
|
90
|
+
def run
|
91
|
+
self.commands = commands.map {|c| c.is_a?(Command) ? c : Command.new(c) }
|
92
|
+
workers = []
|
93
|
+
num_workers.times { workers << [] }
|
94
|
+
divide_to_workers(workers, commands)
|
95
|
+
threads = []
|
96
|
+
workers.each_with_index do |worker, i|
|
97
|
+
next if worker.size == 0
|
98
|
+
threads << Thread.new do
|
99
|
+
worker.each do |cmd|
|
100
|
+
data = {:worker => i+1, :command => cmd}
|
101
|
+
before_callbacks.each {|cb| cb.call(data) }
|
102
|
+
begin
|
103
|
+
output, status = Open3.capture2e(cmd.env, cmd.command)
|
104
|
+
data.update(:output => output, :status => status)
|
105
|
+
rescue => exc
|
106
|
+
data.update(:exception => exc, :status => 256)
|
107
|
+
end
|
108
|
+
after_callbacks.each {|cb| cb.call(data) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
while threads.size > 0
|
113
|
+
threads.dup.each do |thr|
|
114
|
+
thr.join(0.1)
|
115
|
+
threads.delete(thr) unless thr.alive?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Creates a callback that is executed before each worker is run.
|
121
|
+
#
|
122
|
+
# @yield [data] worker information
|
123
|
+
# @yieldparam [Hash{Symbol=>Object}] data information about the worker
|
124
|
+
# thread. Valid keys are:
|
125
|
+
#
|
126
|
+
# * +:worker+ - the worker number (starting from 1)
|
127
|
+
# * +:command+ - the {Command} object about to be run
|
128
|
+
def before_worker(&block)
|
129
|
+
before_callbacks << block
|
130
|
+
end
|
131
|
+
|
132
|
+
# Creates a callback that is executed after each worker is run.
|
133
|
+
#
|
134
|
+
# @yield [data] worker information
|
135
|
+
# @yieldparam [Hash{Symbol=>Object}] data information about the worker
|
136
|
+
# thread. Valid keys are:
|
137
|
+
#
|
138
|
+
# * +:worker+ - the worker number (starting from 1)
|
139
|
+
# * +:command+ - the {Command} object about to be run
|
140
|
+
# * +:output+ - all stdout and stderr output from the command
|
141
|
+
# * +:status+ - the status code from the exited command
|
142
|
+
# * +:exception+ - if a Ruby exception occurred during execution
|
143
|
+
def after_worker(&block)
|
144
|
+
after_callbacks << block
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def setup_options(opts)
|
150
|
+
opts.each do |k, v|
|
151
|
+
meth = "#{k}="
|
152
|
+
send(meth, v) if respond_to?(meth)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def divide_to_workers(workers, commands)
|
157
|
+
num_workers = workers.size
|
158
|
+
commands.each_with_index do |command, i|
|
159
|
+
workers[i % num_workers] << command
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mob_spawner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Loren Segal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-09 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
description: ! 'MobSpawner manages worker threads that can run arbitrary commands
|
16
|
+
and report
|
17
|
+
|
18
|
+
results. Unlike distributed queues, MobSpawner is self-contained and perfect
|
19
|
+
|
20
|
+
for small batch scripts that need to run multiple independent jobs.
|
21
|
+
|
22
|
+
'
|
23
|
+
email: lsegal@soen.ca
|
24
|
+
executables: []
|
25
|
+
extensions: []
|
26
|
+
extra_rdoc_files: []
|
27
|
+
files:
|
28
|
+
- lib/mob_spawner.rb
|
29
|
+
- README.md
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://github.com/lsegal/mob_spawner
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.3.9.5
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Manages and spawns worker threads to run arbitrary shell commands.
|
55
|
+
test_files: []
|