Skip to content

Commit b62ef62

Browse files
committed
fix(s010): the data-directory lock reads a pid's liveness on macOS and Windows
Red: package runs 35512430836 (slice/011), 35515556177 (slice/012), 35521749862 and 35522642934 (this branch), the last one printing the reason at last: on macOS and Windows the serve step's launch refused to start with "is held by OS pid ... in desktop mode" after the smoke run's clean exit. Slice 010 read liveness through /proc on Linux and treated any file as held elsewhere ("the safe direction"), and the smoke path halts without running terminate/2, so the file it left made every second launch of the packaged app refuse on both platforms: a user whose app crashed once would be locked out until they found the file. Liveness now comes from kill -0 on the other Unixes and tasklist on Windows; a check that cannot run still says held. Linux unchanged (the /proc test in lock_test.exs); the package run is the measurement for the other two. The four lock.ex sobelow skips rekeyed (the moduledoc grew three lines), reasons carried over. Gate exit 0, 177 tests. Signed-off-by: Ayla Croft <aylacroft@proton.me>
1 parent 96baed2 commit b62ef62

4 files changed

Lines changed: 49 additions & 15 deletions

File tree

‎.sobelow-skips‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Config.CSP: Missing Content-Security-Policy,lib/trinity_web/router.ex:14,1DC4768
2-
Traversal.FileModule: Directory Traversal in `File.mkdir_p`,lib/trinity/data_dir/lock.ex:57,35AB9EC
3-
Traversal.FileModule: Directory Traversal in `File.read`,lib/trinity/data_dir/lock.ex:143,55D5ADB
4-
Traversal.FileModule: Directory Traversal in `File.rm`,lib/trinity/data_dir/lock.ex:82,21D88E2
5-
Traversal.FileModule: Directory Traversal in `File.write`,lib/trinity/data_dir/lock.ex:132,1C42814
2+
Traversal.FileModule: Directory Traversal in `File.mkdir_p`,lib/trinity/data_dir/lock.ex:60,4ECF02C
3+
Traversal.FileModule: Directory Traversal in `File.read`,lib/trinity/data_dir/lock.ex:146,5EFB666
4+
Traversal.FileModule: Directory Traversal in `File.rm`,lib/trinity/data_dir/lock.ex:85,7D2B826
5+
Traversal.FileModule: Directory Traversal in `File.write`,lib/trinity/data_dir/lock.ex:135,42D2817

‎.sobelow-skips.reasons‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# format: <fingerprint><TAB><reason>
1212
1DC4768 Slice 013, the browser pipeline: the policy IS set, by TrinityWeb.Plugs.ContentSecurityPolicy with a nonce per request, placed before put_secure_browser_headers. sobelow recognises only a header map given to put_secure_browser_headers, so it keeps reporting the pipeline. test/trinity_web/plugs/content_security_policy_test.exs asserts the header and the nonce on every browser response. Rekeyed from 2DBC9F9 (router.ex:12, slice 000, "slice 013 owns it") when the plug moved the line to 14.
13-
35AB9EC Slice 010, Trinity.DataDir.Lock.acquire/2: the directory is the application's own data directory from config or Trinity.Paths, never a request or a model output; the lock has to create it before the Repo does. Low confidence traversal by the tool's own label.
14-
55D5ADB Slice 010, Trinity.DataDir.Lock read/1: reads <data_dir>/LOCK, a path this module composes from the data directory and a constant file name; no caller supplies it.
15-
21D88E2 Slice 010, Trinity.DataDir.Lock.release/2: removes <data_dir>/LOCK only when the file carries this process's own token; same composed path as the read.
16-
1C42814 Slice 010, Trinity.DataDir.Lock take_over/2: rewrites <data_dir>/LOCK after the recorded holder's pid is found dead; same composed path, same constant name.
13+
4ECF02C Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock.acquire/2: the directory is the application's own data directory from config or Trinity.Paths, never a request or a model output; the lock has to create it before the Repo does. Low confidence traversal by the tool's own label.
14+
5EFB666 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock read/1: reads <data_dir>/LOCK, a path this module composes from the data directory and a constant file name; no caller supplies it.
15+
7D2B826 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock.release/2: removes <data_dir>/LOCK only when the file carries this process's own token; same composed path as the read.
16+
42D2817 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock take_over/2: rewrites <data_dir>/LOCK after the recorded holder's pid is found dead; same composed path, same constant name.

‎lib/trinity/data_dir/lock.ex‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ defmodule Trinity.DataDir.Lock do
1414
1515
The file is created with `:exclusive`, so two boots racing for it cannot both win. It
1616
carries the holder's OS pid, mode (`desktop` or `headless`), a per-boot token and the time.
17-
A file whose pid is no longer alive is stale and is taken over; on platforms where
18-
liveness cannot be read this module treats the file as held, so the only way past a
19-
dead holder there is removing the file by hand, which is the safe direction.
17+
A file whose pid is no longer alive is stale and is taken over. Liveness is read from
18+
`/proc` on Linux, `kill -0` on the other Unixes and `tasklist` on Windows (slice 013:
19+
slice 010 read it on Linux alone and treated any file as held elsewhere, and the packaged
20+
binary's second launch on macOS and Windows then refused to start after a clean first run,
21+
package runs 35512430836 through 35522642934). When the check itself fails the file is
22+
treated as held, which is the safe direction; the message names the pid to remove by hand.
2023
2124
The lock file is not the database and is never inside a transaction; it says nothing
2225
about the integrity of the data, only about who may open it.
@@ -163,13 +166,34 @@ defmodule Trinity.DataDir.Lock do
163166

164167
defp token, do: Base.url_encode64(:crypto.strong_rand_bytes(9), padding: false)
165168

166-
# Liveness of an OS pid. Linux answers through /proc; anywhere else this says "alive", so a
167-
# stale file from a crashed holder is refused rather than silently taken over. That is
168-
# the safe direction and the message names the pid to remove it by hand.
169+
# Liveness of an OS pid. Linux answers through /proc; the other Unixes through `kill -0`
170+
# (exit 0: exists; 1: gone, or another user's, which this per-user directory does not
171+
# expect); Windows through `tasklist`, which prints the pid on a match and an INFO line
172+
# otherwise. A check that cannot run says "alive", so a file is refused rather than taken
173+
# over on doubt.
169174
defp alive?(pid) do
170175
case :os.type() do
171176
{:unix, :linux} -> File.dir?("/proc/#{pid}")
172-
_ -> true
177+
{:unix, _} -> alive_by_kill?(pid)
178+
{:win32, _} -> alive_by_tasklist?(pid)
173179
end
174180
end
181+
182+
defp alive_by_kill?(pid) do
183+
case System.cmd("kill", ["-0", Integer.to_string(pid)], stderr_to_stdout: true) do
184+
{_, 0} -> true
185+
{_, _} -> false
186+
end
187+
rescue
188+
_ -> true
189+
end
190+
191+
defp alive_by_tasklist?(pid) do
192+
case System.cmd("tasklist", ["/FI", "PID eq #{pid}", "/NH"], stderr_to_stdout: true) do
193+
{out, 0} -> String.contains?(out, " #{pid} ")
194+
{_, _} -> true
195+
end
196+
rescue
197+
_ -> true
198+
end
175199
end

‎slices/013-liveview-chat-streaming/PROOF.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,13 @@ The packaged chat against `openrouter:ling` renders the answer as markdown with
204204
log: `proof/ac1-final-openrouter-packaged.png`. `VERSIONS.md` gained the `rustler` row (build time only);
205205
`versions.verify`: 84 locked packages, 48 pins. Gate on this tree: exit 0, 177 tests. The `package` run on the
206206
final tree is named in the next correction.
207+
208+
## Correction, 2026-09-20: the package run on the decided tree
209+
Supersedes "the `package` run on the final tree is named in the next correction" above. Run **35521749862**
210+
(dispatched on `22cd58c`'s tree, the musl NIF build): **linux x86_64 green end to end**, the NIF compiled from
211+
source in the job, the smoke step reading `TRINITY_SMOKE_MARKDOWN=ok`, the serve step HTTP 200. **macOS
212+
aarch64 and windows x86_64: the smoke step passed on both**, `TRINITY_SMOKE_MARKDOWN=ok` from the precompiled
213+
artifact on each, so the renderer's NIF loads on all three operating systems; both then failed the serve step
214+
exactly as the slice/011 and slice/012 tag runs did before this slice, with an empty log (the port loop timing
215+
out). The step now prints `serve.log` on that path too (`fix(s001)`), and run 35522642934 was dispatched to read
216+
it; its result is the next correction.

0 commit comments

Comments
 (0)