resque-pool 0.5.0 → 0.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,35 +3,76 @@ module Resque
3
3
  module Logging
4
4
  extend self
5
5
 
6
- # more than a little bit complicated...
7
- # copied this from Unicorn.
6
+ # This reopens ALL logfiles in the process that have been rotated
7
+ # using logrotate(8) (without copytruncate) or similar tools.
8
+ # A +File+ object is considered for reopening if it is:
9
+ # 1) opened with the O_APPEND and O_WRONLY flags
10
+ # 2) the current open file handle does not match its original open path
11
+ # 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
12
+ # Returns the number of files reopened
13
+ #
14
+ # This was mostly copied from Unicorn 4.8.2 to simplify reopening
15
+ # logs in the same way that Unicorn does. Original comments and
16
+ # explanations are left intact.
8
17
  def self.reopen_logs!
9
- log "Flushing logs"
10
- [$stdout, $stderr].each do |fd|
11
- if fd.instance_of? File
12
- # skip if the file is the exact same inode and device
13
- orig_st = fd.stat
14
- begin
15
- cur_st = File.stat(fd.path)
16
- next if orig_st.ino == cur_st.ino && orig_st.dev == cur_st.dev
17
- rescue Errno::ENOENT
18
+ to_reopen = [ ]
19
+ reopened_count = 0
20
+
21
+ ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
22
+ log "Flushing #{to_reopen.length} logs"
23
+
24
+ to_reopen.each do |fp|
25
+ orig_st = begin
26
+ fp.stat
27
+ rescue IOError, Errno::EBADF # race
28
+ next
29
+ end
30
+
31
+ begin
32
+ b = File.stat(fp.path)
33
+ # Skip if reopening wouldn't do anything
34
+ next if orig_st.ino == b.ino && orig_st.dev == b.dev
35
+ rescue Errno::ENOENT
36
+ end
37
+
38
+ begin
39
+ # stdin, stdout, stderr are special. The following dance should
40
+ # guarantee there is no window where `fp' is unwritable in MRI
41
+ # (or any correct Ruby implementation).
42
+ #
43
+ # Fwiw, GVL has zero bearing here. This is tricky because of
44
+ # the unavoidable existence of stdio FILE * pointers for
45
+ # std{in,out,err} in all programs which may use the standard C library
46
+ if fp.fileno <= 2
47
+ # We do not want to hit fclose(3)->dup(2) window for std{in,out,err}
48
+ # MRI will use freopen(3) here internally on std{in,out,err}
49
+ fp.reopen(fp.path, "a")
50
+ else
51
+ # We should not need this workaround, Ruby can be fixed:
52
+ # http://bugs.ruby-lang.org/issues/9036
53
+ # MRI will not call call fclose(3) or freopen(3) here
54
+ # since there's no associated std{in,out,err} FILE * pointer
55
+ # This should atomically use dup3(2) (or dup2(2)) syscall
56
+ File.open(fp.path, "a") { |tmpfp| fp.reopen(tmpfp) }
18
57
  end
19
- # match up the encoding
20
- open_arg = 'a'
21
- if fd.respond_to?(:external_encoding) && enc = fd.external_encoding
22
- open_arg << ":#{enc.to_s}"
23
- enc = fd.internal_encoding and open_arg << ":#{enc.to_s}"
58
+
59
+ fp.sync = true
60
+ fp.flush # IO#sync=true may not implicitly flush
61
+ new_st = fp.stat
62
+
63
+ # this should only happen in the master:
64
+ if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
65
+ fp.chown(orig_st.uid, orig_st.gid)
24
66
  end
25
- # match up buffering (does reopen reset this?)
26
- sync = fd.sync
27
- # sync to disk
28
- fd.fsync
29
- # reopen, and set ruby buffering appropriately
30
- fd.reopen fd.path, open_arg
31
- fd.sync = sync
32
- log "Reopened logfile: #{fd.path}"
67
+
68
+ log "Reopened logfile: #{fp.path}"
69
+ reopened_count += 1
70
+ rescue IOError, Errno::EBADF
71
+ # not much we can do...
33
72
  end
34
73
  end
74
+
75
+ reopened_count
35
76
  end
36
77
 
37
78
  # Given a string, sets the procline ($0)
@@ -43,12 +84,14 @@ module Resque
43
84
 
44
85
  # TODO: make this use an actual logger
45
86
  def log(message)
87
+ return if $skip_logging
46
88
  puts "resque-pool-manager#{app}[#{Process.pid}]: #{message}"
47
89
  #$stdout.fsync
48
90
  end
49
91
 
50
92
  # TODO: make this use an actual logger
51
93
  def log_worker(message)
94
+ return if $skip_logging
52
95
  puts "resque-pool-worker#{app}[#{Process.pid}]: #{message}"
53
96
  #$stdout.fsync
54
97
  end
@@ -60,6 +103,20 @@ module Resque
60
103
  app_name ? "[#{app_name}]" : ""
61
104
  end
62
105
 
106
+ private
107
+
108
+ # Used by reopen_logs, borrowed from Unicorn...
109
+ def self.is_log?(fp)
110
+ append_flags = File::WRONLY | File::APPEND
111
+
112
+ ! fp.closed? &&
113
+ fp.stat.file? &&
114
+ fp.sync &&
115
+ (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
116
+ rescue IOError, Errno::EBADF
117
+ false
118
+ end
119
+
63
120
  end
64
121
  end
65
122
  end
@@ -5,13 +5,17 @@ class Resque::Pool
5
5
  attr_accessor :pool_master_pid
6
6
  attr_accessor :worker_parent_pid
7
7
 
8
+ # We will return false if there are no potential_parent_pids, because that
9
+ # means we aren't even running inside resque-pool.
10
+ #
8
11
  # We can't just check if we've been re-parented to PID 1 (init) because we
9
12
  # want to support docker (which will make the pool master PID 1).
10
13
  #
11
14
  # We also check the worker_parent_pid, because resque-multi-jobs-fork calls
12
15
  # Worker#shutdown? from inside the worker child process.
13
16
  def pool_master_has_gone_away?
14
- not potential_parent_pids.include?(Process.ppid)
17
+ pids = potential_parent_pids
18
+ pids.any? && !pids.include?(Process.ppid)
15
19
  end
16
20
 
17
21
  def potential_parent_pids
@@ -1,5 +1,5 @@
1
1
  module Resque
2
2
  class Pool
3
- VERSION = "0.5.0"
3
+ VERSION = "0.6.0.rc1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nicholas a. evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-25 00:00:00.000000000 Z
11
+ date: 2015-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: resque
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.22'
27
- - !ruby/object:Gem::Dependency
28
- name: trollop
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ~>
32
- - !ruby/object:Gem::Version
33
- version: '2.0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ~>
39
- - !ruby/object:Gem::Version
40
- version: '2.0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -136,27 +122,18 @@ files:
136
122
  - Rakefile
137
123
  - LICENSE.txt
138
124
  - Changelog.md
139
- - lib/resque/pool/logging.rb
140
- - lib/resque/pool/cli.rb
141
- - lib/resque/pool/version.rb
125
+ - lib/resque/pool.rb
142
126
  - lib/resque/pool/tasks.rb
127
+ - lib/resque/pool/cli.rb
143
128
  - lib/resque/pool/pooled_worker.rb
144
- - lib/resque/pool.rb
129
+ - lib/resque/pool/version.rb
130
+ - lib/resque/pool/logging.rb
131
+ - lib/resque/pool/file_or_hash_loader.rb
145
132
  - bin/resque-pool
146
133
  - man/resque-pool.1.ronn
147
134
  - man/resque-pool.1
148
135
  - man/resque-pool.yml.5.ronn
149
136
  - man/resque-pool.yml.5
150
- - features/basic_daemon_config.feature
151
- - features/support/aruba_daemon_support.rb
152
- - features/support/env.rb
153
- - features/step_definitions/daemon_steps.rb
154
- - features/step_definitions/resque-pool_steps.rb
155
- - spec/resque-pool.yml
156
- - spec/mock_config.rb
157
- - spec/resque_pool_spec.rb
158
- - spec/resque-pool-custom.yml.erb
159
- - spec/spec_helper.rb
160
137
  homepage: http://github.com/nevans/resque-pool
161
138
  licenses:
162
139
  - MIT
@@ -169,26 +146,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
169
146
  requirements:
170
147
  - - '>='
171
148
  - !ruby/object:Gem::Version
172
- version: '0'
149
+ version: 1.9.3
173
150
  required_rubygems_version: !ruby/object:Gem::Requirement
174
151
  requirements:
175
- - - '>='
152
+ - - '>'
176
153
  - !ruby/object:Gem::Version
177
- version: '0'
154
+ version: 1.3.1
178
155
  requirements: []
179
156
  rubyforge_project:
180
157
  rubygems_version: 2.0.14
181
158
  signing_key:
182
159
  specification_version: 4
183
160
  summary: quickly and easily fork a pool of resque workers
184
- test_files:
185
- - spec/mock_config.rb
186
- - spec/resque_pool_spec.rb
187
- - spec/spec_helper.rb
188
- - spec/resque-pool.yml
189
- - features/support/aruba_daemon_support.rb
190
- - features/support/env.rb
191
- - features/step_definitions/daemon_steps.rb
192
- - features/step_definitions/resque-pool_steps.rb
193
- - features/basic_daemon_config.feature
194
- has_rdoc:
161
+ test_files: []
@@ -1,68 +0,0 @@
1
- Feature: Basic resque-pool daemon configuration and operation
2
- To easily manage a pool of resque workers, resque-pool provides a daemon with
3
- simple configuration. Static configuration is handled in the
4
- config/config.yml file and dynamic configuration is handled in the Rakefile.
5
-
6
- Background:
7
- Given a file named "Rakefile" with:
8
- """
9
- require 'resque/pool/tasks'
10
- """
11
-
12
- Scenario: no config file
13
- When I run the pool manager as "resque-pool"
14
- Then the pool manager should report that it has started up
15
- And the pool manager should report that the pool is empty
16
- And the pool manager should have no child processes
17
- When I send the pool manager the "QUIT" signal
18
- Then the pool manager should finish
19
- And the pool manager should report that it is finished
20
-
21
- @slow_exit
22
- Scenario: basic config file
23
- Given a file named "config/resque-pool.yml" with:
24
- """
25
- foo: 1
26
- bar: 2
27
- "bar,baz": 3
28
- """
29
- When I run the pool manager as "resque-pool"
30
- Then the pool manager should report that it has started up
31
- And the pool manager should report that 6 workers are in the pool
32
- And the pool manager should have 1 "foo" worker child processes
33
- And the pool manager should have 2 "bar" worker child processes
34
- And the pool manager should have 3 "bar,baz" worker child processes
35
- When I send the pool manager the "QUIT" signal
36
- Then the resque workers should all shutdown
37
- And the pool manager should finish
38
- And the pool manager should report that a "foo" worker has been reaped
39
- And the pool manager should report that a "bar" worker has been reaped
40
- And the pool manager should report that a "bar,baz" worker has been reaped
41
- And the pool manager should report that it is finished
42
-
43
- Scenario: daemonized
44
- Given a directory named "log"
45
- And a directory named "tmp/pids"
46
- And a file named "config/resque-pool.yml" with:
47
- """
48
- foo: 2
49
- bar: 4
50
- "baz,quux": 4
51
- """
52
- When I run the pool manager as "resque-pool -d"
53
- Then the pool manager should record its pid in "tmp/pids/resque-pool.pid"
54
- And the pool manager should daemonize
55
- And a file named "log/resque-pool.stdout.log" should exist
56
- And a file named "log/resque-pool.stderr.log" should exist
57
- And the pool manager should log that it has started up
58
- And the pool manager should log that 10 workers are in the pool
59
- And the pool manager should have 2 "foo" worker child processes
60
- And the pool manager should have 4 "bar" worker child processes
61
- And the pool manager should have 4 "baz,quux" worker child processes
62
- When I send the pool manager the "QUIT" signal
63
- Then the resque workers should all shutdown
64
- And the pool manager daemon should finish
65
- And the pool manager should log that a "foo" worker has been reaped
66
- And the pool manager should log that a "bar" worker has been reaped
67
- And the pool manager should log that a "baz,quux" worker has been reaped
68
- And the pool manager should log that it is finished
@@ -1,33 +0,0 @@
1
- # syntactic sugar, and separate ivar. daemons aren't interactive
2
- When /^I run "([^"]*)" in the background$/ do |cmd|
3
- run_background(unescape(cmd))
4
- end
5
-
6
- Then /^the (output|logfiles) should contain the following lines \(with interpolated \$PID\):$/ do |output_logfiles, partial_output|
7
- interpolate_background_pid(partial_output).split("\n").each do |line|
8
- output_or_log(output_logfiles).should include(line)
9
- end
10
- end
11
-
12
- When /^I send "([^"]*)" the "([^"]*)" signal$/ do |cmd, signal|
13
- send_signal(cmd, signal)
14
- end
15
-
16
- Then /^the "([^"]*)" process should finish$/ do |cmd|
17
- # doesn't actually stop... just polls for exit
18
- processes[cmd].stop
19
- end
20
-
21
- Before("@slow_exit") do
22
- @aruba_timeout_seconds = 10
23
- end
24
-
25
- After do
26
- kill_all_processes!
27
- # now kill the daemon!
28
- begin
29
- Process.kill(9, @pid_from_pidfile) if @pid_from_pidfile
30
- rescue Errno::ESRCH
31
- end
32
- #`pkill -9 resque-pool`
33
- end
@@ -1,159 +0,0 @@
1
- def process_should_exist(pid)
2
- lambda { Process.kill(0, pid) }.should_not raise_error(Errno::ESRCH)
3
- end
4
-
5
- def process_should_not_exist(pid)
6
- lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
7
- end
8
-
9
- def grab_worker_pids(count, str)
10
- puts "TODO: check output_or_log for #{count} worker started messages"
11
- pid_regex = (1..count).map { '(\d+)' }.join ', '
12
- full_regex = /resque-pool-manager\[aruba\]\[\d+\]: Pool contains worker PIDs: \[#{pid_regex}\]/m
13
- str.should =~ full_regex
14
- @worker_pids = full_regex.match(str).captures.map {|pid| pid.to_i }
15
- end
16
-
17
- def output_or_logfiles_string(report_log)
18
- case report_log
19
- when "report", "output"
20
- "output"
21
- when "log", "logfiles"
22
- "logfiles"
23
- else
24
- raise ArgumentError
25
- end
26
- end
27
-
28
- def output_or_log(report_log)
29
- case report_log
30
- when "report", "output"
31
- interactive_output
32
- when "log", "logfiles"
33
- in_current_dir do
34
- File.read("log/resque-pool.stdout.log") << File.read("log/resque-pool.stderr.log")
35
- end
36
- else
37
- raise ArgumentError
38
- end
39
- end
40
-
41
- class NotFinishedStarting < StandardError; end
42
- def worker_processes_for(queues)
43
- children_of(background_pid).select do |pid, cmd|
44
- raise NotFinishedStarting if cmd =~ /Starting$/
45
- cmd =~ /^resque-\d+.\d+.\d+: Waiting for #{queues}$/
46
- end
47
- rescue NotFinishedStarting
48
- retry
49
- end
50
-
51
- def children_of(ppid)
52
- if RUBY_PLATFORM =~ /darwin/i
53
- ps = `ps -eo ppid,pid,comm | grep '^ *#{ppid} '`
54
- else
55
- ps = `ps -eo ppid,pid,cmd | grep '^ *#{ppid} '`
56
- end
57
- ps.split(/\s*\n/).map do |line|
58
- _, pid, cmd = line.strip.split(/\s+/, 3)
59
- [pid, cmd]
60
- end
61
- end
62
-
63
- When /^I run the pool manager as "([^"]*)"$/ do |cmd|
64
- @pool_manager_process = run_background(unescape(cmd))
65
- end
66
-
67
- When /^I send the pool manager the "([^"]*)" signal$/ do |signal|
68
- Process.kill signal, background_pid
69
- output_logfiles = @pid_from_pidfile ? "logfiles" : "output"
70
- case signal
71
- when "QUIT"
72
- keep_trying do
73
- step "the #{output_logfiles} should contain the following lines (with interpolated $PID):", <<-EOF
74
- resque-pool-manager[aruba][$PID]: QUIT: graceful shutdown, waiting for children
75
- EOF
76
- end
77
- else
78
- raise ArgumentError
79
- end
80
- end
81
-
82
- Then /^the pool manager should record its pid in "([^"]*)"$/ do |pidfile|
83
- in_current_dir do
84
- keep_trying do
85
- File.should be_file(pidfile)
86
- @pid_from_pidfile = File.read(pidfile).to_i
87
- @pid_from_pidfile.should_not == 0
88
- process_should_exist(@pid_from_pidfile)
89
- end
90
- end
91
- end
92
-
93
- Then /^the pool manager should daemonize$/ do
94
- stop_processes!
95
- end
96
-
97
- Then /^the pool manager daemon should finish$/ do
98
- keep_trying do
99
- process_should_not_exist(@pid_from_pidfile)
100
- end
101
- end
102
-
103
- # nomenclature: "report" => output to stdout/stderr
104
- # "log" => output to default logfile
105
-
106
- Then /^the pool manager should (report|log) that it has started up$/ do |report_log|
107
- keep_trying do
108
- step "the #{output_or_logfiles_string(report_log)} should contain the following lines (with interpolated $PID):", <<-EOF
109
- resque-pool-manager[aruba][$PID]: Resque Pool running in test environment
110
- resque-pool-manager[aruba][$PID]: started manager
111
- EOF
112
- end
113
- end
114
-
115
- Then /^the pool manager should (report|log) that the pool is empty$/ do |report_log|
116
- step "the #{output_or_logfiles_string(report_log)} should contain the following lines (with interpolated $PID):", <<-EOF
117
- resque-pool-manager[aruba][$PID]: Pool is empty
118
- EOF
119
- end
120
-
121
- Then /^the pool manager should (report|log) that (\d+) workers are in the pool$/ do |report_log, count|
122
- grab_worker_pids Integer(count), output_or_log(report_log)
123
- end
124
-
125
- Then /^the resque workers should all shutdown$/ do
126
- @worker_pids.each do |pid|
127
- keep_trying do
128
- process_should_not_exist(pid)
129
- end
130
- end
131
- end
132
-
133
- Then "the pool manager should have no child processes" do
134
- children_of(background_pid).should have(:no).keys
135
- end
136
-
137
- Then /^the pool manager should have (\d+) "([^"]*)" worker child processes$/ do |count, queues|
138
- worker_processes_for(queues).should have(Integer(count)).members
139
- end
140
-
141
- Then "the pool manager should finish" do
142
- # assuming there will not be multiple processes running
143
- stop_processes!
144
- end
145
-
146
- Then /^the pool manager should (report|log) that it is finished$/ do |report_log|
147
- step "the #{output_or_logfiles_string(report_log)} should contain the following lines (with interpolated $PID):", <<-EOF
148
- resque-pool-manager[aruba][$PID]: manager finished
149
- EOF
150
- end
151
-
152
- Then /^the pool manager should (report|log) that a "([^"]*)" worker has been reaped$/ do |report_log, worker_type|
153
- step 'the '+ output_or_logfiles_string(report_log) +' should match /Reaped resque worker\[\d+\] \(status: 0\) queues: '+ worker_type + '/'
154
- end
155
-
156
- Then /^the logfiles should match \/([^\/]*)\/$/ do |partial_output|
157
- output_or_log("log").should =~ /#{partial_output}/
158
- end
159
-