workforce 0.1.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/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/workforce +43 -0
- data/config_sample.rb +4 -0
- data/lib/core_ext.rb +9 -0
- data/lib/workforce/configuration.rb +39 -0
- data/lib/workforce/controller.rb +107 -0
- data/lib/workforce/manager.rb +156 -0
- data/lib/workforce.rb +13 -0
- data/sleep_daemon.rb +5 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +105 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Rodrigo Kochenburger
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Summary
|
2
|
+
=======
|
3
|
+
|
4
|
+
Workforce is an attempt to create a framework to develop background processes and controlling the execution of these processes in a distributed environment.
|
5
|
+
|
6
|
+
Project Description
|
7
|
+
===================
|
8
|
+
|
9
|
+
Coming soon.
|
10
|
+
|
11
|
+
Contributing
|
12
|
+
============
|
13
|
+
|
14
|
+
Note on Patches/Pull Requests
|
15
|
+
-----------------------------
|
16
|
+
|
17
|
+
* Fork the project.
|
18
|
+
* Make your feature addition or bug fix.
|
19
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
20
|
+
* Commit, do not mess with Rakefile, version, or history.
|
21
|
+
* Send me a pull request. Bonus points for topic branches.
|
22
|
+
|
23
|
+
Copyright & Licensing
|
24
|
+
=====================
|
25
|
+
|
26
|
+
Copyright (c) 2010 Rodrigo Kochenburger. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "workforce"
|
8
|
+
gem.summary = "Framework and a distributed runtime manager to creating and running background processes"
|
9
|
+
gem.description = "Workforce is an attempt to create a framework to develop background processes and controlling the execution of these processes in a distributed environment"
|
10
|
+
gem.email = "divoxx@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/divoxx/workforce"
|
12
|
+
gem.authors = ["Rodrigo Kochenburger"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "yard", ">= 0.5.5"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'yard'
|
40
|
+
YARD::Rake::YardocTask.new
|
41
|
+
rescue LoadError
|
42
|
+
task :yardoc do
|
43
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
44
|
+
end
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/workforce
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "ostruct"
|
3
|
+
require "optparse"
|
4
|
+
require "workforce"
|
5
|
+
|
6
|
+
config = Workforce::Config.instance
|
7
|
+
files_to_require = []
|
8
|
+
|
9
|
+
optparser = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: #{$0} [options] path/to/worker.rb #{Workforce::Controller::ACTIONS.join("|")}"
|
11
|
+
|
12
|
+
opts.separator ""
|
13
|
+
opts.separator "Options:"
|
14
|
+
|
15
|
+
opts.on("-c", "--config [CONFIG_PATH]", "Uses configurations in config_file") do |config_path|
|
16
|
+
config.load_file(config_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on("--pid-dir PID_DIR", "Store pids on directory PID_DIR", "Default: #{config.pid_dir}") do |pid_dir|
|
20
|
+
config.pid_dir = pid_dir
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("--log-dir LOG_DIR", "Store logs on directory LOG_DIR", "Default: #{config.log_dir}") do |log_dir|
|
24
|
+
config.log_dir = log_dir
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("--require FILE", "Require FILE before executing action") do |file_path|
|
28
|
+
files_to_require << file_path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
optparser.parse!
|
33
|
+
worker_path, action = ARGV
|
34
|
+
|
35
|
+
unless worker_path && action
|
36
|
+
puts optparser
|
37
|
+
exit(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
files_to_require.each { |path| require(path) }
|
41
|
+
require worker_path
|
42
|
+
worker_class = File.basename(worker_path, ".rb").camelcase
|
43
|
+
Workforce::Controller.instance.dispatch(worker_class, action)
|
data/config_sample.rb
ADDED
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Workforce
|
4
|
+
class Config
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def self.config_attr(attr_name)
|
8
|
+
define_method(attr_name) do |*args|
|
9
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size > 1
|
10
|
+
|
11
|
+
if instance_variable_get("@#{attr_name}").nil? || args[0]
|
12
|
+
instance_variable_set("@#{attr_name}", args[0] || yield)
|
13
|
+
end
|
14
|
+
|
15
|
+
instance_variable_get("@#{attr_name}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get(attr_name)
|
20
|
+
self.instance.send(attr_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
yield self if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_file(file_path)
|
28
|
+
instance_eval(File.read(file_path))
|
29
|
+
end
|
30
|
+
|
31
|
+
config_attr :pid_dir do
|
32
|
+
"./pid"
|
33
|
+
end
|
34
|
+
|
35
|
+
config_attr :log_dir do
|
36
|
+
"./log"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "workforce/manager"
|
3
|
+
|
4
|
+
module Workforce
|
5
|
+
class Controller
|
6
|
+
ACTIONS = %w(run start stop restart status schedule dispose)
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def dispatch(worker_klass, action)
|
10
|
+
unless ACTIONS.include?(action.to_s)
|
11
|
+
puts "Invalid action #{action}"
|
12
|
+
exit(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
send(action, worker_klass)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(worker_klass)
|
19
|
+
if running?(worker_klass)
|
20
|
+
puts "#{worker_klass}'s manager already running"
|
21
|
+
exit(1)
|
22
|
+
end
|
23
|
+
|
24
|
+
manager = Workforce::Manager.new(get_klass(worker_klass))
|
25
|
+
store_pid(worker_klass, Process.pid)
|
26
|
+
manager.run
|
27
|
+
remove_pid_file(worker_klass)
|
28
|
+
end
|
29
|
+
|
30
|
+
def start(worker_klass)
|
31
|
+
if running?(worker_klass)
|
32
|
+
puts "#{worker_klass}'s manager already running"
|
33
|
+
exit(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
manager = Workforce::Manager.new(get_klass(worker_klass))
|
37
|
+
store_pid(worker_klass, manager.launch)
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop(worker_klass)
|
41
|
+
Process.kill(:QUIT, running_pid(worker_klass))
|
42
|
+
remove_pid_file(worker_klass)
|
43
|
+
end
|
44
|
+
|
45
|
+
def restart(worker_klass)
|
46
|
+
stop(worker_klass) if running?(worker_klass)
|
47
|
+
start(worker_klass)
|
48
|
+
end
|
49
|
+
|
50
|
+
def status(worker_klass)
|
51
|
+
if running?(worker_klass)
|
52
|
+
puts "Running (#{running_pid(worker_klass)})"
|
53
|
+
else
|
54
|
+
puts "Not running"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def schedule(worker_klass)
|
59
|
+
unless running?(worker_klass)
|
60
|
+
puts "#{worker_klass}'s manager not running yet"
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
Process.kill(:USR1, running_pid(worker_klass))
|
65
|
+
end
|
66
|
+
|
67
|
+
def dispose(worker_klass)
|
68
|
+
unless running?(worker_klass)
|
69
|
+
puts "#{worker_klass}'s manager not running yet"
|
70
|
+
exit(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
Process.kill(:USR2, running_pid(worker_klass))
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def get_klass(klass)
|
78
|
+
return klass unless klass.is_a?(String) || klass.is_a?(Symbol)
|
79
|
+
|
80
|
+
klass.to_s.split(/::/).inject(Object) do |namespace, const_name|
|
81
|
+
namespace.const_get(const_name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def pid_file(worker_klass)
|
86
|
+
"#{Workforce::Config.get(:pid_dir)}/#{worker_klass.to_s.underscore}.pid"
|
87
|
+
end
|
88
|
+
|
89
|
+
def running?(worker_klass)
|
90
|
+
File.exist?(pid_file(worker_klass))
|
91
|
+
end
|
92
|
+
|
93
|
+
def running_pid(worker_klass)
|
94
|
+
Integer(File.read(pid_file(worker_klass)))
|
95
|
+
end
|
96
|
+
|
97
|
+
def store_pid(worker_klass, pid)
|
98
|
+
File.open(pid_file(worker_klass), "w") do |fp|
|
99
|
+
fp.write(pid)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def remove_pid_file(worker_klass)
|
104
|
+
File.unlink(pid_file(worker_klass))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Workforce
|
2
|
+
# A manager is responisble for managing instance of workers of a certain class.
|
3
|
+
# It allows to dinamically schedule a new worker or dispose an existing one by trapping signals.
|
4
|
+
#
|
5
|
+
# A worker is any class that defines the method _run_.
|
6
|
+
#
|
7
|
+
# For example:
|
8
|
+
#
|
9
|
+
# class ContinuousRun
|
10
|
+
# def initialize
|
11
|
+
# Signal.trap(:QUIT) { @shutdown = true }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def run
|
15
|
+
# loop do
|
16
|
+
# puts "Running worker #{Process.pid}"
|
17
|
+
# sleep 1
|
18
|
+
# Process.exit if @shutdown
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# manager = Workforce::Manager.new(ContinuousRun)
|
24
|
+
# manager.run
|
25
|
+
#
|
26
|
+
# Now the current process will be managing instances of the ContinuousRun task:
|
27
|
+
# - If you send a SIGUSR1 to it, it will schedule a new instance of ContinuousRun to run.
|
28
|
+
# - If you send a SIGUSR2 to it, it will dispose one of the running instance of it.
|
29
|
+
# - If you send a SIGQUIT or SIGINT to it, it will stop all the instances and then exit.
|
30
|
+
#
|
31
|
+
# When a instance is disposed, a SIGQUIT is send to the worker process, so if you might want to
|
32
|
+
# trap the signal and perform a graceful shutdown on the worker.
|
33
|
+
class Manager
|
34
|
+
include Services
|
35
|
+
|
36
|
+
# Creates a new Manager instance to the given worker class.
|
37
|
+
def initialize(worker)
|
38
|
+
@worker = worker
|
39
|
+
@pids = []
|
40
|
+
@old_signals = {}
|
41
|
+
@pipe_in, @pipe_out = IO.pipe
|
42
|
+
end
|
43
|
+
|
44
|
+
# Run the manager main loop, which controls the running instances.
|
45
|
+
def run
|
46
|
+
log_errors do
|
47
|
+
logger.info "Starting manager for #{@worker}"
|
48
|
+
|
49
|
+
@running = true
|
50
|
+
$0 = "Workforce: #{@worker.name} master"
|
51
|
+
|
52
|
+
trap_signal(:INT, :shutdown)
|
53
|
+
trap_signal(:QUIT, :shutdown)
|
54
|
+
trap_signal(:USR1, :schedule)
|
55
|
+
trap_signal(:USR2, :dispose)
|
56
|
+
|
57
|
+
trap_signal(:CLD) do
|
58
|
+
pid = Process.wait
|
59
|
+
logger.info "Removing process (#{pid}) from worker pool"
|
60
|
+
@pids.delete(pid)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Block process until something is written to @pipe_out
|
64
|
+
IO.select([@pipe_in], nil, nil, nil)
|
65
|
+
logger.info "Shutting down manager"
|
66
|
+
end
|
67
|
+
ensure
|
68
|
+
@pipe_in.close
|
69
|
+
@pipe_out.close
|
70
|
+
end
|
71
|
+
|
72
|
+
# Launch a manager as a daemon. Creates a new fork running the manager and returns the
|
73
|
+
# manager pid to the parent process (the one who called this method).
|
74
|
+
def launch
|
75
|
+
logger.info "Launching a new manager for #{@worker.name}"
|
76
|
+
child_id = fork and return child_id
|
77
|
+
Process.setsid
|
78
|
+
Dir.chdir '/'
|
79
|
+
File.umask 0000
|
80
|
+
STDIN.reopen '/dev/null'
|
81
|
+
STDOUT.reopen '/dev/null', 'a'
|
82
|
+
STDERR.reopen STDOUT
|
83
|
+
run
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sends a SIGQUIT to all worker processes and signals the main loop that it is safe to exit.
|
87
|
+
def shutdown
|
88
|
+
log_errors do
|
89
|
+
ensure_running!
|
90
|
+
|
91
|
+
logger.info "Shutting down child processes"
|
92
|
+
@pids.each do |pid|
|
93
|
+
Process.kill(:QUIT, pid)
|
94
|
+
end
|
95
|
+
|
96
|
+
@pipe_out.write(1)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# schedule a new worker instance.
|
101
|
+
# This will fork the master process and start running the worker as a child process.
|
102
|
+
def schedule
|
103
|
+
log_errors do
|
104
|
+
ensure_running!
|
105
|
+
logger.info "Scheduling new worker"
|
106
|
+
|
107
|
+
if pid = fork
|
108
|
+
logger.info "New worker scheduled: #{pid}"
|
109
|
+
@pids << pid
|
110
|
+
else
|
111
|
+
$0 = "Workforce: #{@worker.name} worker"
|
112
|
+
restore_signals
|
113
|
+
@worker.new.run
|
114
|
+
logger.info "Worker finished processing: #{Process.pid}"
|
115
|
+
Process.exit
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Sends a SIGQUIT to one of the workers.
|
121
|
+
def dispose
|
122
|
+
log_errors do
|
123
|
+
ensure_running!
|
124
|
+
logger.info "Disposing one of the workers"
|
125
|
+
|
126
|
+
Process.kill(:QUIT, @pids.shift) unless @pids.empty?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
def ensure_running!
|
132
|
+
raise RuntimeError, "manager is not running" unless @running
|
133
|
+
end
|
134
|
+
|
135
|
+
def log_errors
|
136
|
+
yield
|
137
|
+
rescue => e
|
138
|
+
logger.error("#{e.message}\n#{e.backtrace.join("\n")}")
|
139
|
+
raise(e)
|
140
|
+
end
|
141
|
+
|
142
|
+
def trap_signal(signal, method = nil, &block)
|
143
|
+
current_pid = Process.pid
|
144
|
+
|
145
|
+
@old_signals[signal] = Signal.trap(signal) do
|
146
|
+
if Process.pid == current_pid
|
147
|
+
method ? send(method) : block.call
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def restore_signals
|
153
|
+
@old_signals.each { |sig, cmd| Signal.trap(sig, cmd) }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/workforce.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Workforce
|
2
|
+
module Services
|
3
|
+
def logger
|
4
|
+
@logger ||= Logger.new("#{Config.get(:log_dir)}/#{@worker.name.underscore}.log")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "logger"
|
10
|
+
require "core_ext"
|
11
|
+
require "workforce/configuration"
|
12
|
+
require "workforce/manager"
|
13
|
+
require "workforce/controller"
|
data/sleep_daemon.rb
ADDED
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: workforce
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Rodrigo Kochenburger
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-31 00:00:00 -03:00
|
18
|
+
default_executable: workforce
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 9
|
31
|
+
version: 1.2.9
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: yard
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 5
|
44
|
+
- 5
|
45
|
+
version: 0.5.5
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: Workforce is an attempt to create a framework to develop background processes and controlling the execution of these processes in a distributed environment
|
49
|
+
email: divoxx@gmail.com
|
50
|
+
executables:
|
51
|
+
- workforce
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files:
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
files:
|
58
|
+
- .document
|
59
|
+
- .gitignore
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- VERSION
|
64
|
+
- bin/workforce
|
65
|
+
- config_sample.rb
|
66
|
+
- lib/core_ext.rb
|
67
|
+
- lib/workforce.rb
|
68
|
+
- lib/workforce/configuration.rb
|
69
|
+
- lib/workforce/controller.rb
|
70
|
+
- lib/workforce/manager.rb
|
71
|
+
- sleep_daemon.rb
|
72
|
+
- spec/spec.opts
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: http://github.com/divoxx/workforce
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options:
|
80
|
+
- --charset=UTF-8
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.6
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Framework and a distributed runtime manager to creating and running background processes
|
104
|
+
test_files:
|
105
|
+
- spec/spec_helper.rb
|