sponges 0.0.1 → 0.0.2
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/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
|