sponges 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sponges.rb ADDED
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require 'boson/runner'
3
+ require 'logger'
4
+ require 'nest'
5
+ require_relative 'sponges/configuration'
6
+ require_relative 'sponges/cpu_infos'
7
+ require_relative 'sponges/worker_builder'
8
+ require_relative 'sponges/supervisor'
9
+ require_relative 'sponges/runner'
10
+ require_relative 'sponges/cli'
11
+
12
+ module Sponges
13
+ SIGNALS = [:INT, :QUIT, :TERM]
14
+
15
+ def configure(&block)
16
+ Sponges::Configuration.configure &block
17
+ end
18
+ module_function :configure
19
+
20
+ def start
21
+ Sponges::Cli.start
22
+ end
23
+ module_function :start
24
+
25
+ def logger
26
+ return @logger if @logger
27
+ @logger = Sponges::Configuration.logger || Logger.new(STDOUT)
28
+ end
29
+ module_function :logger
30
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class Cli < Boson::Runner
4
+ option :daemonize, type: :boolean
5
+ option :size, type: :numeric
6
+ desc "Start workers"
7
+ def start(options = {})
8
+ Sponges::Runner.new(Sponges::Configuration.worker_name, options).
9
+ work(Sponges::Configuration.worker, Sponges::Configuration.worker_method)
10
+ end
11
+
12
+ option :gracefully, type: :boolean
13
+ desc "Stop workers"
14
+ def stop(options = {})
15
+ Sponges::Runner.new(Sponges::Configuration.worker_name, options).
16
+ rest
17
+ end
18
+
19
+ desc "Show running processes"
20
+ def list
21
+ redis = Nest.new('sponges')
22
+ puts %q{
23
+ ___ _ __ ___ _ __ __ _ ___ ___
24
+ / __| '_ \ / _ \| '_ \ / _` |/ _ \/ __|
25
+ \__ \ |_) | (_) | | | | (_| | __/\__ \
26
+ |___/ .__/ \___/|_| |_|\__, |\___||___/
27
+ | | __/ |
28
+ |_| |___/
29
+ }.gsub(/^\n/, '') + "\n"
30
+ puts "Workers:"
31
+ Array(redis[:workers].smembers).each do |worker|
32
+ puts worker.rjust(6)
33
+ puts "supervisor".rjust(15)
34
+ puts redis[:worker][worker][:supervisor].get.rjust(12)
35
+ puts "children".rjust(13)
36
+ Array(redis[:worker][worker][:pids].smembers).each do |pid|
37
+ puts pid.rjust(12)
38
+ end
39
+ end
40
+ puts "\n"
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class Configuration
4
+ class << self
5
+ ACCESSOR = [:worker_name, :worker, :worker_method, :logger]
6
+ attr_accessor *ACCESSOR
7
+
8
+ def configure
9
+ yield self
10
+ end
11
+
12
+ def configuration
13
+ ACCESSOR.inject({}) do |conf, method|
14
+ conf[method] = send(method)
15
+ conf
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ class CpuInfo
3
+ class << self
4
+ def cores_size
5
+ `grep -c processor /proc/cpuinfo`.to_i
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class Runner
4
+ def initialize(name, options = {})
5
+ @name = name
6
+ @options = default_options.merge options
7
+ @redis = Nest.new('sponges')
8
+ end
9
+
10
+ def work(worker, method, *args, &block)
11
+ Sponges.logger.info "Runner #{@name} start message received."
12
+ @supervisor = fork_supervisor(worker, method, *args, &block)
13
+ @redis[:worker][@name][:supervisor].set @supervisor
14
+ trap_signals
15
+ Sponges.logger.info "Supervisor started with #{@supervisor} pid."
16
+ if daemonize?
17
+ Sponges.logger.info "Supervisor daemonized."
18
+ Process.daemon
19
+ else
20
+ Process.waitpid(@supervisor) unless daemonize?
21
+ end
22
+ end
23
+
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
+ private
38
+
39
+ def trap_signals
40
+ Sponges::SIGNALS.each do |signal|
41
+ trap(signal) {|signal| kill_supervisor(signal) }
42
+ end
43
+ end
44
+
45
+ def kill_supervisor(signal)
46
+ Sponges.logger.info "Supervisor receive a #{signal} signal."
47
+ Process.kill :USR1, @supervisor
48
+ end
49
+
50
+ def default_options
51
+ {
52
+ size: CpuInfo.cores_size
53
+ }
54
+ end
55
+
56
+ def fork_supervisor(worker, method, *args, &block)
57
+ fork do
58
+ $PROGRAM_NAME = "#{@name}_supervisor"
59
+ Supervisor.new(@name, @options, worker, method, *args, &block).start
60
+ end
61
+ end
62
+
63
+ def daemonize?
64
+ !!@options[:daemonize]
65
+ end
66
+
67
+ def gracefully?
68
+ !!@options[:gracefully]
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class Supervisor
4
+ def initialize(name, options, worker, method, *args, &block)
5
+ @name, @options = name, options
6
+ @worker, @method, @args, @block = worker, method, args, block
7
+ @redis = Nest.new('sponges')
8
+ @redis[:workers].sadd name
9
+ @pids = @redis[:worker][name][:pids]
10
+ @children_seen = 0
11
+ end
12
+
13
+ def start
14
+ @options[:size].times do
15
+ fork_children
16
+ end
17
+ trap_signals
18
+ sleep
19
+ end
20
+
21
+ private
22
+
23
+ def fork_children
24
+ name = children_name
25
+ pid = fork do
26
+ $PROGRAM_NAME = name
27
+ Sponges::WorkerBuilder.new(@worker, @method, *@args, &@block).start
28
+ end
29
+ Sponges.logger.info "Supervisor create a child with #{pid} pid."
30
+ @pids.sadd pid
31
+ end
32
+
33
+ def children_name
34
+ "#{@name}_child_#{@children_seen +=1}"
35
+ end
36
+
37
+ def trap_signals
38
+ (Sponges::SIGNALS + [:HUP]).each do |signal|
39
+ trap(signal) do
40
+ handle_signal signal
41
+ end
42
+ end
43
+ trap(:CHLD) do
44
+ pids.each do |pid|
45
+ begin
46
+ dead = Process.waitpid(pid.to_i, Process::WNOHANG)
47
+ if dead
48
+ Sponges.logger.warn "Child #{dead} died. Restarting a new one..."
49
+ @pids.srem dead
50
+ fork_children
51
+ end
52
+ rescue Errno::ECHILD => e
53
+ Sponges.logger.error e
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def handle_signal(signal)
60
+ Sponges.logger.info "Supervisor received #{signal} signal."
61
+ kill_them_all(signal)
62
+ Process.waitall
63
+ Sponges.logger.info "Children shutdown complete."
64
+ Sponges.logger.info "Supervisor shutdown. Exiting..."
65
+ Process.kill :USR1, @redis[:worker][@name][:supervisor].to_i
66
+ end
67
+
68
+ def kill_them_all(signal)
69
+ pids.each do |pid|
70
+ begin
71
+ Process.kill signal, pid.to_i
72
+ @pids.srem pid
73
+ Sponges.logger.info "Child #{pid} receive a #{signal} signal."
74
+ rescue Errno::ESRCH => e
75
+ Sponges.logger.error e
76
+ end
77
+ end
78
+ end
79
+
80
+ def pids
81
+ Array(@pids.smembers)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module Sponges
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class WorkerBuilder
4
+ def initialize(worker, method, *args, &block)
5
+ @worker, @method, @args, @block = worker, method, args, block
6
+ end
7
+
8
+ def start
9
+ trap_signals
10
+ @worker.send(@method, *@args, &@block)
11
+ end
12
+
13
+ private
14
+
15
+ def trap_signals
16
+ Sponges::SIGNALS.each do |signal|
17
+ trap(signal) do
18
+ exit 0
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sponges
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - chatgris
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: boson
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nest
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: When I build some worker, I want them to be like an army of spongebob,
47
+ always stressed and eager to work. sponges helps you to build this army of sponge,
48
+ to control them, and, well, kill them gracefully.
49
+ email:
50
+ - jboyer@af83.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - lib/sponges.rb
56
+ - lib/sponges/cli.rb
57
+ - lib/sponges/configuration.rb
58
+ - lib/sponges/cpu_infos.rb
59
+ - lib/sponges/runner.rb
60
+ - lib/sponges/supervisor.rb
61
+ - lib/sponges/version.rb
62
+ - lib/sponges/worker_builder.rb
63
+ homepage: https://github.com/AF83/sponges
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.24
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Turn any ruby object in a daemons controlling an army of sponges.
87
+ test_files: []