sponges 0.0.1

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/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: []