sponges 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.md +119 -0
- data/lib/sponges/cli.rb +25 -10
- data/lib/sponges/commander.rb +45 -0
- data/lib/sponges/configuration.rb +3 -1
- data/lib/sponges/cpu_infos.rb +2 -0
- data/lib/sponges/runner.rb +5 -19
- data/lib/sponges/supervisor.rb +11 -3
- data/lib/sponges/version.rb +2 -1
- data/lib/sponges/worker_builder.rb +6 -0
- data/lib/sponges.rb +4 -2
- metadata +37 -2
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) chatgris <jboyer@af83.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# sponges
|
2
|
+
|
3
|
+
When I build workers, I want them to be like an army of spongebobs, always
|
4
|
+
stressed and eager to work. `sponges` helps you build this army of sponges, to
|
5
|
+
control them, and, well, to kill them gracefully. Making them stressed and eager
|
6
|
+
to work is your job. :)
|
7
|
+
|
8
|
+
Internally, `sponges` strongly relies on Unix signals.
|
9
|
+
|
10
|
+
## Is it any good?
|
11
|
+
|
12
|
+
[Yes.](http://news.ycombinator.com/item?id=3067434)
|
13
|
+
|
14
|
+
## Production ready ?
|
15
|
+
|
16
|
+
Not yet, but soon.
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Ruby 1.9.2 (or superior) and Redis are required.
|
21
|
+
|
22
|
+
Install it with rubygems:
|
23
|
+
|
24
|
+
gem install sponges
|
25
|
+
|
26
|
+
With bundler, add it to your `Gemfile`:
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
gem "sponges"
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
In a file called `example.rb`:
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
# The worker class is the one you want to daemonize.
|
37
|
+
#
|
38
|
+
require 'sponges'
|
39
|
+
|
40
|
+
class Worker
|
41
|
+
def run
|
42
|
+
# Trap the HUP signal, set a boolean to true.
|
43
|
+
trap(:HUP) {
|
44
|
+
Sponges.logger.info "HUP signal trapped, clean stop."
|
45
|
+
@hup = true
|
46
|
+
}
|
47
|
+
Sponges.logger.info Process.pid
|
48
|
+
if @hup # is true, we need to shutdown this worker
|
49
|
+
Sponges.logger.info "HUP signal trapped, shutdown..."
|
50
|
+
exit 0 # everything's fine, we can exit
|
51
|
+
else # this worker can continue its work
|
52
|
+
sleep rand(20)
|
53
|
+
run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Sponges.configure do |config|
|
59
|
+
config.worker = Worker.new # mandatory
|
60
|
+
config.worker_name = "bob" # mandatory
|
61
|
+
config.worker_method = :run # mandatory
|
62
|
+
config.worker_args = {first: true} # mandatory
|
63
|
+
config.logger = MyCustomLogger.new # optionnal
|
64
|
+
config.redis = Redis.new # optionnal
|
65
|
+
end
|
66
|
+
|
67
|
+
Sponges.start
|
68
|
+
```
|
69
|
+
See the help message :
|
70
|
+
``` bash
|
71
|
+
ruby example.rb
|
72
|
+
```
|
73
|
+
|
74
|
+
Start workers :
|
75
|
+
``` bash
|
76
|
+
ruby example.rb start
|
77
|
+
```
|
78
|
+
|
79
|
+
Start workers and daemonize them:
|
80
|
+
``` bash
|
81
|
+
ruby example.rb start -d
|
82
|
+
```
|
83
|
+
|
84
|
+
Start 8 instances of the worker and daemonize them:
|
85
|
+
``` bash
|
86
|
+
ruby example.rb start -d -s 8
|
87
|
+
```
|
88
|
+
|
89
|
+
Retart gracefully 4 instances of the worker and daemonize them:
|
90
|
+
``` bash
|
91
|
+
ruby example.rb restart -g -d -s 4
|
92
|
+
```
|
93
|
+
|
94
|
+
Stop workers with a `QUIT` signal :
|
95
|
+
``` bash
|
96
|
+
ruby example.rb stop
|
97
|
+
```
|
98
|
+
|
99
|
+
Stop workers with a `HUP` signal :
|
100
|
+
``` bash
|
101
|
+
ruby example.rb stop -g
|
102
|
+
```
|
103
|
+
In this case, you will have to trap the `HUP` signal, and handle a clean stop
|
104
|
+
from each workers. The point is to wait for a task to be done before quitting.
|
105
|
+
|
106
|
+
Show a list of workers and their children.
|
107
|
+
``` bash
|
108
|
+
ruby example.rb list
|
109
|
+
```
|
110
|
+
|
111
|
+
## TODO
|
112
|
+
|
113
|
+
* Specing.
|
114
|
+
* Increment / decrement workers pool size with `TTIN` and `TTOUT`.
|
115
|
+
* Check on OSX.
|
116
|
+
|
117
|
+
## Copyright
|
118
|
+
|
119
|
+
MIT. See LICENSE for further details.
|
data/lib/sponges/cli.rb
CHANGED
@@ -1,19 +1,31 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sponges
|
3
|
+
# This class concern is to expose a nice CLI interface.
|
4
|
+
#
|
3
5
|
class Cli < Boson::Runner
|
4
6
|
option :daemonize, type: :boolean
|
5
7
|
option :size, type: :numeric
|
6
8
|
desc "Start workers"
|
7
9
|
def start(options = {})
|
8
10
|
Sponges::Runner.new(Sponges::Configuration.worker_name, options).
|
9
|
-
work(Sponges::Configuration.worker, Sponges::Configuration.worker_method
|
11
|
+
work(Sponges::Configuration.worker, Sponges::Configuration.worker_method,
|
12
|
+
Sponges::Configuration.worker_args)
|
10
13
|
end
|
11
14
|
|
12
15
|
option :gracefully, type: :boolean
|
13
16
|
desc "Stop workers"
|
14
17
|
def stop(options = {})
|
15
|
-
Sponges::
|
16
|
-
|
18
|
+
Sponges::Commander.new(Sponges::Configuration.worker_name, options).
|
19
|
+
stop
|
20
|
+
end
|
21
|
+
|
22
|
+
option :daemonize, type: :boolean
|
23
|
+
option :size, type: :numeric
|
24
|
+
option :gracefully, type: :boolean
|
25
|
+
desc "Restart workers"
|
26
|
+
def restart(options = {})
|
27
|
+
stop(options)
|
28
|
+
start(options)
|
17
29
|
end
|
18
30
|
|
19
31
|
desc "Show running processes"
|
@@ -28,13 +40,16 @@ module Sponges
|
|
28
40
|
|_| |___/
|
29
41
|
}.gsub(/^\n/, '') + "\n"
|
30
42
|
puts "Workers:"
|
31
|
-
Array(redis[:
|
32
|
-
puts
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
puts
|
43
|
+
Array(redis[:hostnames].smembers).each do |hostname|
|
44
|
+
puts hostname.rjust(6)
|
45
|
+
Array(redis[hostname][:workers].smembers).each do |worker|
|
46
|
+
puts worker.rjust(6)
|
47
|
+
puts "supervisor".rjust(15)
|
48
|
+
puts redis[hostname][:worker][worker][:supervisor].get.rjust(12)
|
49
|
+
puts "children".rjust(13)
|
50
|
+
Array(redis[hostname][:worker][worker][:pids].smembers).each do |pid|
|
51
|
+
puts pid.rjust(12)
|
52
|
+
end
|
38
53
|
end
|
39
54
|
end
|
40
55
|
puts "\n"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Sponges
|
3
|
+
# This class concern is to send messages to supervisor. It's used to send
|
4
|
+
# messages like 'stop' or 'restart'
|
5
|
+
#
|
6
|
+
class Commander
|
7
|
+
def initialize(name, options = {})
|
8
|
+
@name, @options = name, options
|
9
|
+
@redis = Nest.new('sponges', Configuration.redis || Redis.new)[Socket.gethostname]
|
10
|
+
end
|
11
|
+
|
12
|
+
def stop
|
13
|
+
Sponges.logger.info "Runner #{@name} stop message received."
|
14
|
+
if pid = @redis[:worker][@name][:supervisor].get
|
15
|
+
begin
|
16
|
+
Process.kill gracefully? ? :HUP : :QUIT, pid.to_i
|
17
|
+
while alive?(pid.to_i) do
|
18
|
+
Sponges.logger.info "Supervisor #{pid} still alive"
|
19
|
+
sleep 0.5
|
20
|
+
end
|
21
|
+
Sponges.logger.info "Supervisor #{pid} has stopped."
|
22
|
+
rescue Errno::ESRCH => e
|
23
|
+
Sponges.logger.error e
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Sponges.logger.info "No supervisor found."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def alive?(pid)
|
33
|
+
begin
|
34
|
+
Process.kill 0, pid
|
35
|
+
true
|
36
|
+
rescue Errno::ESRCH => e
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def gracefully?
|
42
|
+
!!@options[:gracefully]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sponges
|
3
|
+
# This class concern is to provide a global object for configuration needs.
|
4
|
+
#
|
3
5
|
class Configuration
|
4
6
|
class << self
|
5
|
-
ACCESSOR = [:worker_name, :worker, :worker_method, :logger]
|
7
|
+
ACCESSOR = [:worker_name, :worker, :worker_method, :worker_args, :logger, :redis]
|
6
8
|
attr_accessor *ACCESSOR
|
7
9
|
|
8
10
|
def configure
|
data/lib/sponges/cpu_infos.rb
CHANGED
data/lib/sponges/runner.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sponges
|
3
|
+
# This class concern is to create a Supervisor, set some signals handlers and
|
4
|
+
# watch over the supervisor.
|
5
|
+
#
|
3
6
|
class Runner
|
4
7
|
def initialize(name, options = {})
|
5
8
|
@name = name
|
6
9
|
@options = default_options.merge options
|
7
|
-
@redis = Nest.new('sponges')
|
10
|
+
@redis = Nest.new('sponges', Configuration.redis || Redis.new)
|
11
|
+
@redis[:hostnames].sadd Socket.gethostname
|
8
12
|
end
|
9
13
|
|
10
14
|
def work(worker, method, *args, &block)
|
11
15
|
Sponges.logger.info "Runner #{@name} start message received."
|
12
16
|
@supervisor = fork_supervisor(worker, method, *args, &block)
|
13
|
-
@redis[:worker][@name][:supervisor].set @supervisor
|
14
17
|
trap_signals
|
15
18
|
Sponges.logger.info "Supervisor started with #{@supervisor} pid."
|
16
19
|
if daemonize?
|
@@ -21,19 +24,6 @@ module Sponges
|
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
|
-
def rest
|
25
|
-
Sponges.logger.info "Runner #{@name} stop message received."
|
26
|
-
if pid = @redis[:worker][@name][:supervisor].get
|
27
|
-
begin
|
28
|
-
Process.kill gracefully? ? :HUP : :QUIT, pid.to_i
|
29
|
-
rescue Errno::ESRCH => e
|
30
|
-
Sponges.logger.error e
|
31
|
-
end
|
32
|
-
else
|
33
|
-
Sponges.logger.info "No supervisor found."
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
27
|
private
|
38
28
|
|
39
29
|
def trap_signals
|
@@ -63,9 +53,5 @@ module Sponges
|
|
63
53
|
def daemonize?
|
64
54
|
!!@options[:daemonize]
|
65
55
|
end
|
66
|
-
|
67
|
-
def gracefully?
|
68
|
-
!!@options[:gracefully]
|
69
|
-
end
|
70
56
|
end
|
71
57
|
end
|
data/lib/sponges/supervisor.rb
CHANGED
@@ -4,8 +4,9 @@ module Sponges
|
|
4
4
|
def initialize(name, options, worker, method, *args, &block)
|
5
5
|
@name, @options = name, options
|
6
6
|
@worker, @method, @args, @block = worker, method, args, block
|
7
|
-
@redis = Nest.new('sponges')
|
7
|
+
@redis = Nest.new('sponges', Configuration.redis || Redis.new)[Socket.gethostname]
|
8
8
|
@redis[:workers].sadd name
|
9
|
+
@redis[:worker][@name][:supervisor].set Process.pid
|
9
10
|
@pids = @redis[:worker][name][:pids]
|
10
11
|
@children_seen = 0
|
11
12
|
end
|
@@ -15,6 +16,10 @@ module Sponges
|
|
15
16
|
fork_children
|
16
17
|
end
|
17
18
|
trap_signals
|
19
|
+
at_exit do
|
20
|
+
Sponges.logger.info "Supervisor exits."
|
21
|
+
end
|
22
|
+
Sponges.logger.info "Supervisor started, waiting for messages."
|
18
23
|
sleep
|
19
24
|
end
|
20
25
|
|
@@ -50,7 +55,7 @@ module Sponges
|
|
50
55
|
fork_children
|
51
56
|
end
|
52
57
|
rescue Errno::ECHILD => e
|
53
|
-
|
58
|
+
# Don't panic
|
54
59
|
end
|
55
60
|
end
|
56
61
|
end
|
@@ -62,7 +67,10 @@ module Sponges
|
|
62
67
|
Process.waitall
|
63
68
|
Sponges.logger.info "Children shutdown complete."
|
64
69
|
Sponges.logger.info "Supervisor shutdown. Exiting..."
|
65
|
-
|
70
|
+
pid = @redis[:worker][@name][:supervisor]
|
71
|
+
@redis[:worker][@name][:supervisor].del
|
72
|
+
@redis[:workers].srem @name
|
73
|
+
Process.kill :USR1, pid.to_i
|
66
74
|
end
|
67
75
|
|
68
76
|
def kill_them_all(signal)
|
data/lib/sponges/version.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module Sponges
|
3
|
+
# This class concern is to build a worker instance, set some signals handlers
|
4
|
+
# and make it start its job.
|
5
|
+
#
|
3
6
|
class WorkerBuilder
|
4
7
|
def initialize(worker, method, *args, &block)
|
5
8
|
@worker, @method, @args, @block = worker, method, args, block
|
@@ -7,6 +10,9 @@ module Sponges
|
|
7
10
|
|
8
11
|
def start
|
9
12
|
trap_signals
|
13
|
+
at_exit do
|
14
|
+
Sponges.logger.info "Child exits."
|
15
|
+
end
|
10
16
|
@worker.send(@method, *@args, &@block)
|
11
17
|
end
|
12
18
|
|
data/lib/sponges.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'boson/runner'
|
3
|
+
require 'socket'
|
3
4
|
require 'logger'
|
4
5
|
require 'nest'
|
5
6
|
require_relative 'sponges/configuration'
|
@@ -7,6 +8,7 @@ require_relative 'sponges/cpu_infos'
|
|
7
8
|
require_relative 'sponges/worker_builder'
|
8
9
|
require_relative 'sponges/supervisor'
|
9
10
|
require_relative 'sponges/runner'
|
11
|
+
require_relative 'sponges/commander'
|
10
12
|
require_relative 'sponges/cli'
|
11
13
|
|
12
14
|
module Sponges
|
@@ -17,8 +19,8 @@ module Sponges
|
|
17
19
|
end
|
18
20
|
module_function :configure
|
19
21
|
|
20
|
-
def start
|
21
|
-
Sponges::Cli.start
|
22
|
+
def start(options = ARGV)
|
23
|
+
Sponges::Cli.start(options)
|
22
24
|
end
|
23
25
|
module_function :start
|
24
26
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sponges
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: boson
|
@@ -43,6 +43,38 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.10.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.10.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sys-proctable
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
46
78
|
description: When I build some worker, I want them to be like an army of spongebob,
|
47
79
|
always stressed and eager to work. sponges helps you to build this army of sponge,
|
48
80
|
to control them, and, well, kill them gracefully.
|
@@ -52,8 +84,11 @@ executables: []
|
|
52
84
|
extensions: []
|
53
85
|
extra_rdoc_files: []
|
54
86
|
files:
|
87
|
+
- LICENSE
|
88
|
+
- README.md
|
55
89
|
- lib/sponges.rb
|
56
90
|
- lib/sponges/cli.rb
|
91
|
+
- lib/sponges/commander.rb
|
57
92
|
- lib/sponges/configuration.rb
|
58
93
|
- lib/sponges/cpu_infos.rb
|
59
94
|
- lib/sponges/runner.rb
|