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.
Files changed (3) hide show
  1. data/README.md +46 -0
  2. data/lib/mob_spawner.rb +162 -0
  3. metadata +55 -0
@@ -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
@@ -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: []