dash 4.0.2 → 4.0.3
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 +4 -4
- data/lib/dash/cli/base.rb +27 -1
- data/lib/dash/cli/lock.rb +35 -5
- data/lib/dash/configuration/docs/sshkit.yml +4 -0
- data/lib/dash/sshkit_with_ext.rb +44 -0
- data/lib/dash/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 563633a7568cdc7c4567bbe4957af6e5212e37b3ae99ddda0e990552f949762f
|
|
4
|
+
data.tar.gz: dda127134f2d0ef7fc4e7636ce7132106842855f9084d082a5a3b65c41313b56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8ebab61a7f7f4df70ecd48e5e95415198a01990e6ae6ce93dca062775f2d11b91d2a971bcf9b0b13b4cd3aa6940700c45f8fc73bc1c50be64fe9478e2e1d7ca8
|
|
7
|
+
data.tar.gz: 5cda006f0c95a79eea3198c9caa089d5e3b3d064860b6d031efe12b65513b2d4219ce04b62b5a9c66532bbda38681ba0ec8dd6cd51143b270588a804be647488
|
data/lib/dash/cli/base.rb
CHANGED
|
@@ -217,7 +217,7 @@ module Dash::Cli
|
|
|
217
217
|
|
|
218
218
|
break
|
|
219
219
|
rescue LockHeldError
|
|
220
|
-
|
|
220
|
+
roll_back_server_lock(held)
|
|
221
221
|
|
|
222
222
|
unless details_shown
|
|
223
223
|
# The holder can release between our failed mkdir and this read.
|
|
@@ -248,12 +248,28 @@ module Dash::Cli
|
|
|
248
248
|
|
|
249
249
|
say "Waiting #{interval}s for the server lock (#{remaining}s remaining)...", :magenta
|
|
250
250
|
sleep [ interval, remaining ].min
|
|
251
|
+
rescue StandardError
|
|
252
|
+
# Anything else - a dropped SSH connection on a host that idled
|
|
253
|
+
# through the wait, a full disk - leaves the hosts we did take
|
|
254
|
+
# locked. holding_server_lock? is still false, so with_server_lock's
|
|
255
|
+
# ensure never runs and nothing else would ever release them.
|
|
256
|
+
roll_back_server_lock(held)
|
|
257
|
+
raise
|
|
251
258
|
end
|
|
252
259
|
end
|
|
253
260
|
|
|
254
261
|
DASH.holding_server_lock = true
|
|
255
262
|
end
|
|
256
263
|
|
|
264
|
+
# Never raises: on the contention path a raise here would abandon the
|
|
265
|
+
# locks it was rolling back, and on the failure path it would replace the
|
|
266
|
+
# error the operator needs to see.
|
|
267
|
+
def roll_back_server_lock(held)
|
|
268
|
+
release_server_lock_on(held)
|
|
269
|
+
rescue StandardError => e
|
|
270
|
+
say "Error releasing the server lock on #{Array(held).join(", ")}: #{e.message}", :red
|
|
271
|
+
end
|
|
272
|
+
|
|
257
273
|
def release_server_lock
|
|
258
274
|
say "Releasing the server lock...", :magenta
|
|
259
275
|
release_server_lock_on(server_lock_hosts)
|
|
@@ -264,12 +280,22 @@ module Dash::Cli
|
|
|
264
280
|
# Only ever called with hosts this process actually locked, so a missing
|
|
265
281
|
# directory means someone already cleaned up, not that we may delete
|
|
266
282
|
# another deploy's lock.
|
|
283
|
+
#
|
|
284
|
+
# Every host is attempted even after one fails - stopping at the first
|
|
285
|
+
# error would strand the remaining locks with no one left to release them
|
|
286
|
+
# - and the first error is re-raised once the sweep is done.
|
|
267
287
|
def release_server_lock_on(hosts)
|
|
288
|
+
error = nil
|
|
289
|
+
|
|
268
290
|
Array(hosts).each do |host|
|
|
269
291
|
execute_lock_release(lock: DASH.server_lock, hosts: host)
|
|
270
292
|
rescue LockMissingError
|
|
271
293
|
nil
|
|
294
|
+
rescue StandardError => e
|
|
295
|
+
error ||= e
|
|
272
296
|
end
|
|
297
|
+
|
|
298
|
+
raise error if error
|
|
273
299
|
end
|
|
274
300
|
|
|
275
301
|
def server_lock_hosts
|
data/lib/dash/cli/lock.rb
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
class Dash::Cli::Lock < Dash::Cli::Base
|
|
2
2
|
desc "status", "Report lock status"
|
|
3
|
+
option :server, type: :boolean, default: false, desc: "Report the shared server lock instead of the deploy lock"
|
|
3
4
|
def status
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
if options[:server]
|
|
6
|
+
report_server_lock_status
|
|
7
|
+
else
|
|
8
|
+
handle_missing_lock do
|
|
9
|
+
puts capture_lock_status
|
|
10
|
+
end
|
|
6
11
|
end
|
|
7
12
|
end
|
|
8
13
|
|
|
@@ -18,10 +23,16 @@ class Dash::Cli::Lock < Dash::Cli::Base
|
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
desc "release", "Release the deploy lock"
|
|
26
|
+
option :server, type: :boolean, default: false, desc: "Release the shared server lock instead of the deploy lock"
|
|
21
27
|
def release
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
say "Released the
|
|
28
|
+
if options[:server]
|
|
29
|
+
release_server_lock_on(server_lock_hosts)
|
|
30
|
+
say "Released the server lock"
|
|
31
|
+
else
|
|
32
|
+
handle_missing_lock do
|
|
33
|
+
execute_lock_release
|
|
34
|
+
say "Released the deploy lock"
|
|
35
|
+
end
|
|
25
36
|
end
|
|
26
37
|
end
|
|
27
38
|
|
|
@@ -31,4 +42,23 @@ class Dash::Cli::Lock < Dash::Cli::Base
|
|
|
31
42
|
rescue LockMissingError
|
|
32
43
|
say "There is no deploy lock"
|
|
33
44
|
end
|
|
45
|
+
|
|
46
|
+
# The server lock is taken per host rather than per destination, so report
|
|
47
|
+
# each one: an acquire that failed part-way leaves it held on a subset.
|
|
48
|
+
def report_server_lock_status
|
|
49
|
+
server_lock_hosts.each do |host|
|
|
50
|
+
if (status = server_lock_status_on(host))
|
|
51
|
+
say "Server lock on #{host}:"
|
|
52
|
+
puts status
|
|
53
|
+
else
|
|
54
|
+
say "There is no server lock on #{host}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def server_lock_status_on(host)
|
|
60
|
+
capture_lock_status(lock: DASH.server_lock, hosts: host)
|
|
61
|
+
rescue LockMissingError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
34
64
|
end
|
|
@@ -20,6 +20,10 @@ sshkit:
|
|
|
20
20
|
#
|
|
21
21
|
# Kamal sets a long idle timeout of 900 seconds on connections to try to avoid
|
|
22
22
|
# re-connection storms after an idle period, such as building an image or waiting for CI.
|
|
23
|
+
#
|
|
24
|
+
# A pooled connection that the network dropped while idle is evicted and the command
|
|
25
|
+
# retried once on a fresh connection, so a long wait elsewhere (a server-lock poll, a
|
|
26
|
+
# loadbalancer reboot) does not fail the next command on another host.
|
|
23
27
|
pool_idle_timeout: 300
|
|
24
28
|
|
|
25
29
|
# DNS retry settings
|
data/lib/dash/sshkit_with_ext.rb
CHANGED
|
@@ -170,6 +170,50 @@ class SSHKit::Backend::Netssh
|
|
|
170
170
|
end
|
|
171
171
|
end
|
|
172
172
|
prepend LimitConcurrentStartsInstance
|
|
173
|
+
|
|
174
|
+
# A pooled session that sat idle while dash was busy elsewhere (a server-lock
|
|
175
|
+
# wait, a loadbalancer reboot) gets dropped by NATs and cloud networks without
|
|
176
|
+
# either end noticing: net-ssh only sends keepalives from inside its event
|
|
177
|
+
# loop, and the pool's liveness probe is a non-blocking `process(0)`, so the
|
|
178
|
+
# session looks fine until the first command on it dies. Evict that session
|
|
179
|
+
# and rerun the block once on a fresh connection.
|
|
180
|
+
#
|
|
181
|
+
# The block is rerun, so a drop *mid-command* runs the command twice. That
|
|
182
|
+
# is accepted: the errors here are the idle-drop ones, where nothing reached
|
|
183
|
+
# the server, and the alternative is the deploy failing on the next host.
|
|
184
|
+
module ReconnectOnStaleConnection
|
|
185
|
+
STALE_CONNECTION_ERRORS = [ Errno::ECONNRESET, Errno::EPIPE, Net::SSH::Disconnect, Net::SSH::Timeout ].freeze
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
def with_ssh
|
|
189
|
+
reconnected = false
|
|
190
|
+
|
|
191
|
+
begin
|
|
192
|
+
super do |ssh|
|
|
193
|
+
yield ssh
|
|
194
|
+
rescue *STALE_CONNECTION_ERRORS
|
|
195
|
+
evict_stale_session(ssh)
|
|
196
|
+
raise
|
|
197
|
+
end
|
|
198
|
+
rescue *STALE_CONNECTION_ERRORS => e
|
|
199
|
+
raise if reconnected
|
|
200
|
+
|
|
201
|
+
reconnected = true
|
|
202
|
+
SSHKit.config.output.warn("Reconnecting to #{host}: #{e.message}")
|
|
203
|
+
retry
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# `close` waits for channel-close acknowledgements, which never come over
|
|
208
|
+
# a dead socket. `shutdown!` just closes the socket, and a closed session
|
|
209
|
+
# is what makes the pool drop it instead of caching it again.
|
|
210
|
+
def evict_stale_session(ssh)
|
|
211
|
+
ssh.shutdown!
|
|
212
|
+
rescue StandardError
|
|
213
|
+
nil
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
prepend ReconnectOnStaleConnection
|
|
173
217
|
end
|
|
174
218
|
|
|
175
219
|
class SSHKit::Runner::Parallel
|
data/lib/dash/version.rb
CHANGED