uninterruptible 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d15b649edbc7f8a8813c07a7f64c7981fe2d17a
4
- data.tar.gz: bc001b551986276f80f42e4005aa6337f4900d96
3
+ metadata.gz: 72a22a5770c97eab497ced8af3b4ef1eb1969954
4
+ data.tar.gz: 697a74b90a5f7f1e9fa0e4bd0c099e4565de8c4d
5
5
  SHA512:
6
- metadata.gz: 1186dee9b6712bdc2ba842f63d1219f8f753cf7ae58a7cccd011388f0d259cd7cc88a761748979292ba0c191aa649726163051381315d270118e334134e11405
7
- data.tar.gz: 4c0ab3085c21379a2969e0618e4dd8146a061c3302d1542c0adad4767ff5f4ca3cccbd4a787d568d788fa8ef546ee09f4a6f8fe3c87316e6b4595b70d5f5a7ae
6
+ metadata.gz: 35f6725ea1ed919a3ea884840bbd9462d26c451d1efa380a3c7b707427dae2be667b1eb2ffd006915b27371bcbfa897e04e2971b029fce0d72f7f7f32288ecc0
7
+ data.tar.gz: 2a0e883d4ebad0dab294743349d01d851f69e00d279746bdb6e4a35444893706b1ceb66223174685834ea14a380f8f7e436015c85a1415845760c3e1f099f2cc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0
4
+ * Rewrite restart code to avoid potentially nasty hangs.
5
+
3
6
  ## 2.4.1
4
7
  * Handle an error raised (`Errno::EINVAL`) when a client would connect and immediately disconnect before any processing occurs.
5
8
 
@@ -16,7 +16,7 @@ module Uninterruptible
16
16
 
17
17
  # @return [String] Location on disk where socket server is listening
18
18
  def socket_path
19
- @socket_path ||= File.join(socket_directory, 'file_descriptor_server.sock')
19
+ @socket_path ||= File.join(socket_directory, 'fd.sock')
20
20
  end
21
21
 
22
22
  # Accept the next client connection and send it the file descriptor
@@ -41,7 +41,7 @@ module Uninterruptible
41
41
  private
42
42
 
43
43
  def socket_directory
44
- @socket_directory ||= Dir.mktmpdir('uninterruptible-')
44
+ @socket_directory ||= Dir.mktmpdir('u-')
45
45
  end
46
46
 
47
47
  def start_socket_server
@@ -30,7 +30,7 @@ module Uninterruptible
30
30
  module Server
31
31
  def self.included(base)
32
32
  base.class_eval do
33
- attr_reader :active_connections, :socket_server, :signal_pipe_r, :signal_pipe_w, :mutex
33
+ attr_reader :active_connections, :socket_server, :signal_pipe_r, :signal_pipe_w, :mutex, :file_descriptor_server
34
34
  end
35
35
  end
36
36
 
@@ -52,9 +52,15 @@ module Uninterruptible
52
52
 
53
53
  logger.info "Starting server on #{server_configuration.bind}"
54
54
 
55
- establish_socket_server
55
+ # Set up each listener and add it to an array ready for the event loop
56
+ @active_descriptors = []
57
+ @active_descriptors << establish_socket_server
58
+ @active_descriptors << establish_file_descriptor_server
59
+ @active_descriptors << setup_signal_traps
60
+
56
61
  write_pidfile
57
- setup_signal_traps
62
+
63
+ # Enter the main loop
58
64
  select_loop
59
65
  end
60
66
 
@@ -72,15 +78,31 @@ module Uninterruptible
72
78
  # signal_pipe_r for processing any signals sent to the process.
73
79
  def select_loop
74
80
  loop do
75
- readable, = IO.select([socket_server, signal_pipe_r])
76
- readable.each do |reader|
77
- if reader == socket_server
78
- accept_client_connection
79
- elsif reader == signal_pipe_r
80
- signal = reader.gets.chomp
81
- process_signal(signal)
81
+ # Wait for descriptors or a 1 second timeout
82
+ readable, = IO.select(@active_descriptors, [], [], 1)
83
+ # Only process one descriptor per iteration.
84
+ # We don't want to process a descriptor that has been deleted.
85
+ reader = readable&.first
86
+ if reader == signal_pipe_r
87
+ signal = reader.gets.chomp
88
+ process_signal(signal)
89
+ elsif reader == file_descriptor_server.socket_server
90
+ file_descriptor_server.serve_file_descriptor
91
+ @active_descriptors.delete(file_descriptor_server.socket_server)
92
+ graceful_shutdown
93
+ elsif reader == socket_server
94
+ accept_client_connection
95
+ end
96
+
97
+ if @shutdown
98
+ if active_connections.zero?
99
+ logger.debug "No more active connections. Exiting'"
100
+ Process.exit(0)
101
+ else
102
+ logger.debug "#{active_connections} connections still active"
82
103
  end
83
104
  end
105
+
84
106
  end
85
107
  end
86
108
 
@@ -119,22 +141,18 @@ module Uninterruptible
119
141
  # in the env, reconnect to that file descriptor.
120
142
  def establish_socket_server
121
143
  @socket_server = Uninterruptible::Binder.new(server_configuration.bind).bind_to_socket
122
- # If there's a file descriptor present, take over from a previous instance of this server and kill it off
123
- kill_parent if ENV[FILE_DESCRIPTOR_SERVER_VAR]
124
-
125
- @socket_server.autoclose = false
126
- @socket_server.close_on_exec = false
127
144
 
128
145
  if server_configuration.tls_enabled?
129
146
  @socket_server = Uninterruptible::TLSServerFactory.new(server_configuration).wrap_with_tls(@socket_server)
130
147
  end
148
+ @socket_server
131
149
  end
132
150
 
133
- # Send a TERM signal to the parent process. This will be called by a newly spawned server if it has been started
134
- # by another instance of this server.
135
- def kill_parent
136
- logger.debug "Killing parent process #{Process.ppid}"
137
- Process.kill('TERM', Process.ppid)
151
+ # Create the UNIX socket server that will pass the server file descriptor
152
+ # to the child process when a restart occurs.
153
+ def establish_file_descriptor_server
154
+ @file_descriptor_server = FileDescriptorServer.new(socket_server)
155
+ @file_descriptor_server.socket_server
138
156
  end
139
157
 
140
158
  # Write the current pid out to pidfile_path if configured
@@ -155,6 +173,7 @@ module Uninterruptible
155
173
  @signal_pipe_w.puts(signal_name)
156
174
  end
157
175
  end
176
+ @signal_pipe_r
158
177
  end
159
178
 
160
179
  # When a signal has been caught, it should be passed here for the appropriate action to be taken
@@ -164,12 +183,11 @@ module Uninterruptible
164
183
  # @param [String] signal_name Signal to process
165
184
  def process_signal(signal_name)
166
185
  if signal_name == 'TERM'
167
- if $shutdown
186
+ if @shutdown
168
187
  logger.info "TERM received again, exiting immediately"
169
- Process.exit(1) if $shutdown
188
+ Process.exit(1)
170
189
  else
171
190
  logger.info "TERM received, starting graceful shutdown"
172
- $shutdown = true
173
191
  graceful_shutdown
174
192
  end
175
193
  elsif signal_name == 'USR1'
@@ -181,22 +199,12 @@ module Uninterruptible
181
199
  # Stop listening on socket_server, wait until all active connections have finished processing and exit with 0.
182
200
  def graceful_shutdown
183
201
  socket_server.close unless socket_server.closed?
184
-
185
- until active_connections.zero?
186
- logger.debug "#{active_connections} connections still active"
187
- sleep 0.5
188
- end
189
-
190
- logger.debug "No more active connections. Exiting'"
191
-
192
- Process.exit(0)
202
+ @active_descriptors.delete(socket_server)
203
+ @shutdown = true
193
204
  end
194
205
 
195
206
  # Start a new copy of this server, maintaining all current file descriptors and env.
196
207
  def hot_restart
197
- # Start a FileDescriptorServer running on a unix socket
198
- file_descriptor_server = FileDescriptorServer.new(socket_server)
199
-
200
208
  fork do
201
209
  # Let the new server know where to find the file descriptor server
202
210
  ENV[FILE_DESCRIPTOR_SERVER_VAR] = file_descriptor_server.socket_path
@@ -206,10 +214,6 @@ module Uninterruptible
206
214
 
207
215
  exec("bundle exec #{server_configuration.start_command}")
208
216
  end
209
-
210
- # Provide the new server with the file descriptor for @socket_server
211
- file_descriptor_server.serve_file_descriptor
212
- file_descriptor_server.close
213
217
  end
214
218
 
215
219
  def network_restrictions
@@ -1,3 +1,3 @@
1
1
  module Uninterruptible
2
- VERSION = "2.4.1".freeze
2
+ VERSION = "2.5.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uninterruptible
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Wentworth
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-28 00:00:00.000000000 Z
11
+ date: 2019-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler