ask-app-server 0.4.12 → 0.4.13
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/CHANGELOG.md +16 -0
- data/lib/ask/app_server/socket_server.rb +34 -2
- data/lib/ask/app_server/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: 7108a931b0787f70885182225d0a1623f238d503b9c943ebaf0578bf4a7fb35f
|
|
4
|
+
data.tar.gz: a1cb98beb16710a0133d42eb31d4481215451978dd4a1a65e487da74e080e9eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 020b6bc818e607773e47a0b53e63b7b57a8d100cc14310ffba6218df5637984b13a7b41f79794d1b2ed5e763cea2ac9fbcf4afc902d841ef82dcec716bbd86f4
|
|
7
|
+
data.tar.gz: 457b10ce1090f738709d0f328400035b41fabfda09bfc3ddb38ef7ae770bc1e954ddb07007f6ded2c5d22ad6b2e657e2a54b710e11d40eb5af74d931c3c9c335
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.13] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A stopped host no longer deletes its successor's socket.** The
|
|
8
|
+
socket path is a fixed, shared name and hosts overlap: `#start`
|
|
9
|
+
unlinks a stale path, so a successor launched over a live host takes
|
|
10
|
+
the name, and the predecessor's later exit (idle timeout, crash,
|
|
11
|
+
manual kill) ran an unconditional `rm_f` that deleted the path the
|
|
12
|
+
successor was listening on. Every client after that got ENOENT while
|
|
13
|
+
the successor stayed alive and looked healthy. The server now records
|
|
14
|
+
the (device, inode) it bound and removes the file only if it is still
|
|
15
|
+
that one. Seen in the wild: a host orphaned two weeks earlier was
|
|
16
|
+
killed during a routine cleanup and silently broke the host that had
|
|
17
|
+
replaced it.
|
|
18
|
+
|
|
3
19
|
## [0.4.0] - 2026-08-11
|
|
4
20
|
|
|
5
21
|
### Added
|
|
@@ -47,20 +47,37 @@ module Ask
|
|
|
47
47
|
FileUtils.mkdir_p(File.dirname(@socket_path))
|
|
48
48
|
FileUtils.rm_f(@socket_path) # stale socket from a previous run
|
|
49
49
|
@listener = UNIXServer.new(@socket_path)
|
|
50
|
+
# Remember which inode we bound. A later host on the same path
|
|
51
|
+
# unlinks ours and binds its own; without this, our #stop would
|
|
52
|
+
# delete the successor's socket (see #stop).
|
|
53
|
+
@bound_identity = identity_of(@socket_path)
|
|
50
54
|
@acceptor = Thread.new { accept_loop }
|
|
51
55
|
@pusher = Thread.new { pusher_loop }
|
|
52
56
|
@logger.debug("Socket server listening on #{@socket_path}")
|
|
53
57
|
self
|
|
54
58
|
end
|
|
55
59
|
|
|
56
|
-
# Stop accepting, close all connections, and remove the socket file
|
|
60
|
+
# Stop accepting, close all connections, and remove the socket file
|
|
61
|
+
# — but only if it is still OURS.
|
|
62
|
+
#
|
|
63
|
+
# The socket path is a fixed, shared name, and hosts overlap: a
|
|
64
|
+
# predecessor starting up unlinks a stale path, and a successor
|
|
65
|
+
# started over a live one unlinks its predecessor's. The
|
|
66
|
+
# predecessor then exits (idle timeout, crash, manual kill) and an
|
|
67
|
+
# unconditional rm_f would delete the path the SUCCESSOR is
|
|
68
|
+
# listening on — every client after that gets ENOENT while the
|
|
69
|
+
# successor stays alive and looks healthy. Comparing the inode we
|
|
70
|
+
# bound against what is on disk now keeps a dying process from
|
|
71
|
+
# taking the live one down with it.
|
|
57
72
|
def stop
|
|
58
73
|
@running = false
|
|
59
74
|
@acceptor&.kill rescue nil
|
|
60
75
|
@pusher&.kill rescue nil
|
|
61
76
|
@protocol.connections.each { |conn| @protocol.remove_connection(conn) }
|
|
62
77
|
@listener&.close rescue nil
|
|
63
|
-
FileUtils.rm_f(@socket_path)
|
|
78
|
+
FileUtils.rm_f(@socket_path) if ours_on_disk?
|
|
79
|
+
rescue SystemCallError
|
|
80
|
+
nil
|
|
64
81
|
end
|
|
65
82
|
|
|
66
83
|
# Block until the acceptor stops (the process's main loop in
|
|
@@ -76,6 +93,21 @@ module Ask
|
|
|
76
93
|
|
|
77
94
|
private
|
|
78
95
|
|
|
96
|
+
# (device, inode) of the socket at path, or nil when it is gone.
|
|
97
|
+
def identity_of(path)
|
|
98
|
+
stat = File.stat(path)
|
|
99
|
+
[stat.dev, stat.ino]
|
|
100
|
+
rescue SystemCallError
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# True when the path still points at the socket WE bound. False once
|
|
105
|
+
# a successor has replaced it (or it was removed altogether), which
|
|
106
|
+
# means the file is not ours to delete.
|
|
107
|
+
def ours_on_disk?
|
|
108
|
+
@bound_identity && identity_of(@socket_path) == @bound_identity
|
|
109
|
+
end
|
|
110
|
+
|
|
79
111
|
def accept_loop
|
|
80
112
|
while @running
|
|
81
113
|
socket = @listener.accept
|