uninterruptible 2.4.1 → 2.5.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.
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