sponges 0.2.0 → 0.3.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/README.md CHANGED
@@ -22,7 +22,7 @@ Internally, `sponges` strongly relies on Unix signals.
22
22
 
23
23
  ## Installation
24
24
 
25
- Ruby 1.9.2 (or superior) and Redis are required.
25
+ Ruby 1.9.2 (or superior).
26
26
 
27
27
  Install it with rubygems:
28
28
 
@@ -66,15 +66,14 @@ Sponges.configure do |config|
66
66
  config.size = 3
67
67
  config.daemonize = true
68
68
  config.after_fork do
69
- puts "Execute some when a child process is created"
69
+ puts "Execute code when a child process is created"
70
70
  end
71
71
  config.on_chld do
72
72
  puts "Execute code when a child process is killed"
73
73
  end
74
74
 
75
75
  # Register a pool named "worker_name".
76
- # Options are optionnal. Consider it as a default, options given by command have
77
- # the precedence.
76
+ #
78
77
  Sponges.start "worker_name" do
79
78
  Worker.new({some: args}).run
80
79
  end
@@ -99,9 +98,10 @@ Start 8 instances of the worker and daemonize them:
99
98
  ruby example.rb start -d -s 8 # By default, size equals cpu core's size.
100
99
  ```
101
100
 
102
- Retart gracefully 4 instances of the worker and daemonize them:
101
+ Retart gracefully 4 instances of the worker, with a timeout of 3 seconds and
102
+ daemonize them:
103
103
  ``` bash
104
- ruby example.rb restart -g -d -s 4
104
+ ruby example.rb restart -g -d -s 4 -t 3
105
105
  ```
106
106
 
107
107
  Stop workers with a `QUIT` signal :
@@ -116,10 +116,12 @@ ruby example.rb kill
116
116
 
117
117
  Stop workers with a `HUP` signal :
118
118
  ``` bash
119
- ruby example.rb stop -g
119
+ ruby example.rb stop -g -t 5
120
120
  ```
121
121
  In this case, you will have to trap the `HUP` signal, and handle a clean stop
122
- from each workers. The point is to wait for a task to be done before quitting.
122
+ from each workers. The point is to wait for a task to be done before quitting. A
123
+ timeout can be specify with the `-t` option. When this timeout is hited, the
124
+ process is killed.
123
125
 
124
126
  Increment worker's pool size :
125
127
  ``` bash
@@ -136,6 +138,24 @@ Show a list of workers and their children.
136
138
  ruby example.rb list
137
139
  ```
138
140
 
141
+ ## Stores
142
+
143
+ sponges can store pids in memory or in redis. Memory is the default store. The
144
+ `list` command is not available with the memory store.
145
+
146
+ To select the redis store, you need to add `nest` to your application's
147
+ Gemfile, and do the following.
148
+
149
+ ``` ruby
150
+ gem "sponges"
151
+ ```
152
+
153
+ ``` ruby
154
+ Sponges.configure do |config|
155
+ config.store = :redis
156
+ end
157
+ ```
158
+
139
159
  ## Acknowledgements
140
160
 
141
161
  sponges would not have been the same without [Jesse
data/lib/sponges.rb CHANGED
@@ -2,13 +2,15 @@
2
2
  require 'boson/runner'
3
3
  require 'socket'
4
4
  require 'logger'
5
- require 'nest'
6
5
  require 'machine'
7
6
  require_relative 'sponges/configuration'
8
7
  require_relative 'sponges/supervisor'
9
8
  require_relative 'sponges/runner'
10
9
  require_relative 'sponges/commander'
11
10
  require_relative 'sponges/cli'
11
+ require_relative 'sponges/store'
12
+ require_relative 'sponges/store/memory'
13
+ require_relative 'sponges/store/redis'
12
14
 
13
15
  module Sponges
14
16
  SIGNALS = [:INT, :QUIT, :TERM]
data/lib/sponges/cli.rb CHANGED
@@ -17,8 +17,13 @@ module Sponges
17
17
  end
18
18
 
19
19
  option :gracefully, type: :boolean
20
+ option :timeout, type: :numeric
20
21
  desc "Stop workers"
21
22
  def stop(options = {})
23
+ options = {
24
+ timeout: Sponges::Configuration.timeout,
25
+ gracefully: Sponges::Configuration.gracefully
26
+ }.reject{|k, v| v.nil?}.merge(options)
22
27
  Sponges::Commander.new(Sponges::Configuration.worker_name, options).stop
23
28
  end
24
29
 
@@ -30,6 +35,7 @@ module Sponges
30
35
  option :daemonize, type: :boolean
31
36
  option :size, type: :numeric
32
37
  option :gracefully, type: :boolean
38
+ option :timeout, type: :numeric
33
39
  desc "Restart workers"
34
40
  def restart(options = {})
35
41
  stop(options)
@@ -50,6 +56,10 @@ module Sponges
50
56
 
51
57
  desc "Show running processes"
52
58
  def list
59
+ if Sponges::Configuration.store == :memory
60
+ puts "Command not available with the memory store"
61
+ exit
62
+ end
53
63
  redis = Nest.new('sponges')
54
64
  puts %q{
55
65
  ___ _ __ ___ _ __ __ _ ___ ___
@@ -1,12 +1,16 @@
1
1
  # encoding: utf-8
2
+ require 'timeout'
3
+
2
4
  module Sponges
3
5
  # This class concern is to send messages to supervisor. It's used to send
4
6
  # messages like 'stop' or 'restart'
5
7
  #
6
8
  class Commander
9
+ attr_reader :store
10
+
7
11
  def initialize(name, options = {})
8
12
  @name, @options = name, options
9
- @redis = Nest.new('sponges', Configuration.redis || Redis.new)[Socket.gethostname]
13
+ @store = Sponges::Store.new(@name)
10
14
  end
11
15
 
12
16
  # Kills the supervisor, and then all workers.
@@ -14,8 +18,14 @@ module Sponges
14
18
  def kill
15
19
  Sponges.logger.info "Runner #{@name} kill message received."
16
20
  stop :KILL
17
- Array(@redis[:worker][@name][:pids].smembers).each do|pid|
18
- kill_process(:KILL, pid, "Worker")
21
+ children_pids.each do|pid|
22
+ begin
23
+ kill_process(:KILL, pid, "Worker")
24
+ rescue Errno::ESRCH => e
25
+ # Don't panic
26
+ ensure
27
+ store.delete_children pid
28
+ end
19
29
  end
20
30
  end
21
31
 
@@ -24,8 +34,18 @@ module Sponges
24
34
  def stop(signal = nil)
25
35
  signal ||= gracefully? ? :HUP : :QUIT
26
36
  Sponges.logger.info "Runner #{@name} stop message received."
27
- if pid = @redis[:worker][@name][:supervisor].get
28
- kill_process(signal, pid)
37
+ if pid = supervisor_pid
38
+ if @options[:timeout]
39
+ begin
40
+ Timeout::timeout(@options[:timeout]) do
41
+ kill_process(signal, pid)
42
+ end
43
+ rescue Timeout::Error
44
+ kill
45
+ end
46
+ else
47
+ kill_process(signal, pid)
48
+ end
29
49
  else
30
50
  Sponges.logger.info "No supervisor found."
31
51
  end
@@ -35,9 +55,9 @@ module Sponges
35
55
  #
36
56
  def increment
37
57
  Sponges.logger.info "Runner #{@name} increment message received."
38
- if pid = @redis[:worker][@name][:supervisor].get
58
+ if pid = supervisor_pid
39
59
  begin
40
- Process.kill :TTIN, pid.to_i
60
+ Process.kill :TTIN, supervisor.to_i
41
61
  rescue Errno::ESRCH => e
42
62
  Sponges.logger.error e
43
63
  end
@@ -50,9 +70,9 @@ module Sponges
50
70
  #
51
71
  def decrement
52
72
  Sponges.logger.info "Runner #{@name} decrement message received."
53
- if pid = @redis[:worker][@name][:supervisor].get
73
+ if supervisor_pid
54
74
  begin
55
- Process.kill :TTOU, pid.to_i
75
+ Process.kill :TTOU, supervisor_pid.to_i
56
76
  rescue Errno::ESRCH => e
57
77
  Sponges.logger.error e
58
78
  end
@@ -63,6 +83,14 @@ module Sponges
63
83
 
64
84
  private
65
85
 
86
+ def supervisor_pid
87
+ @store.supervisor_pid
88
+ end
89
+
90
+ def children_pids
91
+ @store.children_pids
92
+ end
93
+
66
94
  def kill_process(signal, pid, type = "Supervisor")
67
95
  pid = pid.to_i
68
96
  begin
@@ -73,7 +101,7 @@ module Sponges
73
101
  end
74
102
  Sponges.logger.info "#{type} #{pid} has stopped."
75
103
  rescue Errno::ESRCH => e
76
- Sponges.logger.error e
104
+ # Don't panic
77
105
  end
78
106
  end
79
107
 
@@ -5,7 +5,7 @@ module Sponges
5
5
  class Configuration
6
6
  class << self
7
7
  ACCESSOR = [:worker_name, :worker, :logger, :redis, :size,
8
- :daemonize, :after_fork
8
+ :daemonize, :after_fork, :timeout, :gracefully, :store
9
9
  ]
10
10
  attr_accessor *ACCESSOR
11
11
 
@@ -27,6 +27,14 @@ module Sponges
27
27
  def on_chld(&block)
28
28
  Hook._on_chld = block
29
29
  end
30
+
31
+ def store
32
+ @store || :memory
33
+ end
34
+
35
+ def redis
36
+ @redis ||= Redis.new
37
+ end
30
38
  end
31
39
  end
32
40
 
@@ -4,15 +4,17 @@ module Sponges
4
4
  # watch over the supervisor.
5
5
  #
6
6
  class Runner
7
+ attr_reader :store
8
+
7
9
  def initialize(name, options = {}, block)
8
10
  @name, @block = name, block
9
11
  @options = default_options.merge options
10
- @redis = Nest.new('sponges', Configuration.redis || Redis.new)
11
- if running?
12
+ @store = Sponges::Store.new(@name)
13
+ if store.running?
12
14
  Sponges.logger.error "Runner #{@name} already started."
13
15
  exit
14
16
  end
15
- @redis[:hostnames].sadd Socket.gethostname
17
+ store.register_hostname Socket.gethostname
16
18
  end
17
19
 
18
20
  def start
@@ -29,20 +31,6 @@ module Sponges
29
31
 
30
32
  private
31
33
 
32
- def running?
33
- if pid = @redis[Socket.gethostname][:worker][@name][:supervisor].get
34
- begin
35
- Process.kill 0, pid.to_i
36
- true
37
- rescue Errno::ESRCH => e
38
- @redis[Socket.gethostname][:worker][@name][:supervisor].del
39
- false
40
- end
41
- else
42
- false
43
- end
44
- end
45
-
46
34
  def trap_signals
47
35
  Sponges::SIGNALS.each do |signal|
48
36
  trap(signal) {|signal| kill_supervisor(signal) }
@@ -63,7 +51,7 @@ module Sponges
63
51
  def fork_supervisor
64
52
  fork do
65
53
  $PROGRAM_NAME = "#{@name}_supervisor"
66
- Supervisor.new(@name, @options, @block).start
54
+ Supervisor.new(@name, @options, store, @block).start
67
55
  end
68
56
  end
69
57
 
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class Store
4
+ class << self
5
+ def new(name)
6
+ case Sponges::Configuration.store
7
+ when :memory
8
+ Sponges::Store::Memory.new(name)
9
+ when :redis
10
+ Sponges::Store::Redis.new(name)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ require 'forwardable'
3
+
4
+ module Sponges
5
+ class Store
6
+ class Memory
7
+ extend Forwardable
8
+
9
+ attr_writer :supervisor_pid
10
+ attr_reader :pids, :name
11
+
12
+ def initialize(name)
13
+ @pids, @name = [], name
14
+ end
15
+
16
+ def_delegator :@pids, :<<, :add_children
17
+ def_delegator :@pids, :delete, :delete_children
18
+
19
+ def supervisor_pid
20
+ return @supervisor_pid if @supervisor_pid
21
+ s = find_supervisor
22
+ @supervisor_pid = s.pid if s
23
+ end
24
+
25
+ def children_pids
26
+ @pids.any? ? @pids : find_childs.map(&:pid)
27
+ end
28
+
29
+ def running?
30
+ !!find_supervisor
31
+ end
32
+
33
+ def register(supervisor_pid)
34
+ @supervisor_pid = supervisor_pid
35
+ end
36
+
37
+ def register_hostname(hostname)
38
+ end
39
+
40
+ def on_fork
41
+ end
42
+
43
+ def clear
44
+ end
45
+
46
+ private
47
+
48
+ def supervisor_name
49
+ "#{name}_supervisor"
50
+ end
51
+
52
+ def childs_name
53
+ "#{name}_child"
54
+ end
55
+
56
+ def find_supervisor
57
+ Sys::ProcTable.ps.select {|f| f.cmdline == supervisor_name }.first
58
+ end
59
+
60
+ def find_childs
61
+ Sys::ProcTable.ps.select {|f| f.cmdline =~ /^#{childs_name}/ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+ module Sponges
3
+ class Store
4
+ class Redis
5
+ attr_reader :redis, :hostname_store, :name
6
+ private :redis, :hostname_store
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ @redis ||= Nest.new('sponges', Configuration.redis)
11
+ @hostname_store = @redis[Socket.gethostname]
12
+ end
13
+
14
+ def supervisor_pid
15
+ hostname_store[:worker][name][:supervisor].get
16
+ end
17
+
18
+ def children_pids
19
+ Array(hostname_store[:worker][name][:pids].smembers)
20
+ end
21
+
22
+ def running?
23
+ if pid = hostname_store[:worker][name][:supervisor].get
24
+ begin
25
+ Process.kill 0, pid.to_i
26
+ true
27
+ rescue Errno::ESRCH => e
28
+ hostname_store[:worker][name][:supervisor].del
29
+ false
30
+ end
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def register_hostname(hostname)
37
+ redis[:hostnames].sadd hostname
38
+ end
39
+
40
+ def register(supervisor_pid)
41
+ hostname_store[:workers].sadd name
42
+ hostname_store[:worker][name][:supervisor].set supervisor_pid
43
+ end
44
+
45
+ def add_children(pid)
46
+ hostname_store[:worker][name][:pids].sadd pid
47
+ end
48
+
49
+ def delete_children(pid)
50
+ hostname_store[:worker][name][:pids].srem pid
51
+ end
52
+
53
+ def clear(name)
54
+ hostname_store[:worker][name][:supervisor].del
55
+ hostname_store[:workers].srem name
56
+ end
57
+
58
+ def on_fork
59
+ Sponges::Configuration.redis.client.reconnect
60
+ end
61
+
62
+ private
63
+
64
+ def find_supervisor
65
+ Sys::ProcTable.ps.select {|f| f.cmdline == supervisor_name }.first
66
+ end
67
+
68
+ def find_childs
69
+ Sys::ProcTable.ps.select {|f| f.cmdline =~ /^#{childs_name}/ }
70
+ end
71
+ end
72
+ end
73
+ end
74
+
@@ -1,15 +1,17 @@
1
1
  # encoding: utf-8
2
2
  module Sponges
3
3
  class Supervisor
4
- def initialize(name, options, block)
5
- @name, @options, @block = name, options, block
6
- set_up_redis
7
- @pids = @redis[:worker][name][:pids]
4
+ attr_reader :store, :name, :options
5
+
6
+ def initialize(name, options, store, block)
7
+ @name, @options, @store, @block = name, options, store, block
8
+ store.on_fork
9
+ store.register Process.pid
8
10
  @children_seen = 0
9
11
  end
10
12
 
11
13
  def start
12
- @options[:size].times do
14
+ options[:size].times do
13
15
  fork_children
14
16
  end
15
17
  trap_signals
@@ -22,16 +24,6 @@ module Sponges
22
24
 
23
25
  private
24
26
 
25
- def set_up_redis
26
- if Configuration.redis
27
- redis_client = Configuration.redis
28
- redis_client.client.reconnect
29
- end
30
- @redis = Nest.new('sponges', redis_client || Redis.new)[Socket.gethostname]
31
- @redis[:workers].sadd @name
32
- @redis[:worker][@name][:supervisor].set Process.pid
33
- end
34
-
35
27
  def fork_children
36
28
  name = children_name
37
29
  pid = fork do
@@ -40,11 +32,11 @@ module Sponges
40
32
  @block.call
41
33
  end
42
34
  Sponges.logger.info "Supervisor create a child with #{pid} pid."
43
- @pids.sadd pid
35
+ store.add_children pid
44
36
  end
45
37
 
46
38
  def children_name
47
- "#{@name}_child_#{@children_seen +=1}"
39
+ "#{name}_child_#{@children_seen +=1}"
48
40
  end
49
41
 
50
42
  def trap_signals
@@ -59,19 +51,19 @@ module Sponges
59
51
  end
60
52
  trap(:TTOU) do
61
53
  Sponges.logger.warn "Supervisor decrement child's pool by one."
62
- if pids.first
63
- kill_one(pids.first, :HUP)
54
+ if store.children_pids.first
55
+ kill_one(store.children_pids.first, :HUP)
64
56
  else
65
57
  Sponges.logger.warn "No more child to kill."
66
58
  end
67
59
  end
68
60
  trap(:CHLD) do
69
- pids.each do |pid|
61
+ store.children_pids.each do |pid|
70
62
  begin
71
63
  dead = Process.waitpid(pid.to_i, Process::WNOHANG)
72
64
  if dead
73
65
  Sponges.logger.warn "Child #{dead} died. Restarting a new one..."
74
- @pids.srem dead
66
+ store.delete_children dead
75
67
  Sponges::Hook.on_chld
76
68
  fork_children
77
69
  end
@@ -88,30 +80,26 @@ module Sponges
88
80
  Process.waitall
89
81
  Sponges.logger.info "Children shutdown complete."
90
82
  Sponges.logger.info "Supervisor shutdown. Exiting..."
91
- pid = @redis[:worker][@name][:supervisor]
92
- @redis[:worker][@name][:supervisor].del
93
- @redis[:workers].srem @name
83
+ pid = store.supervisor_pid
84
+ store.clear(name)
94
85
  Process.kill :USR1, pid.to_i
95
86
  end
96
87
 
97
88
  def kill_them_all(signal)
98
- pids.each do |pid|
89
+ store.children_pids.each do |pid|
99
90
  kill_one(pid, signal)
100
91
  end
101
92
  end
102
93
 
103
94
  def kill_one(pid, signal)
104
95
  begin
105
- @pids.srem pid
106
96
  Process.kill signal, pid.to_i
97
+ Process.waitpid pid.to_i
98
+ store.delete_children pid
107
99
  Sponges.logger.info "Child #{pid} receive a #{signal} signal."
108
100
  rescue Errno::ESRCH => e
109
101
  # Don't panic
110
102
  end
111
103
  end
112
-
113
- def pids
114
- Array(@pids.smembers)
115
- end
116
104
  end
117
105
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Sponges
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
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.2.0
4
+ version: 0.3.0
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-11-23 00:00:00.000000000 Z
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: boson
@@ -27,22 +27,6 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
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
30
  - !ruby/object:Gem::Dependency
47
31
  name: machine
48
32
  requirement: !ruby/object:Gem::Requirement
@@ -92,6 +76,9 @@ files:
92
76
  - lib/sponges/commander.rb
93
77
  - lib/sponges/configuration.rb
94
78
  - lib/sponges/runner.rb
79
+ - lib/sponges/store.rb
80
+ - lib/sponges/store/memory.rb
81
+ - lib/sponges/store/redis.rb
95
82
  - lib/sponges/supervisor.rb
96
83
  - lib/sponges/version.rb
97
84
  homepage: https://github.com/AF83/sponges