| ... | @@ -69,6 +69,28 @@ const runShell = (script: string) => { | ... | @@ -69,6 +69,28 @@ const runShell = (script: string) => { |
| 69 | | 69 | |
| 70 | const sh = (value: string) => `'${value.replaceAll("'", "'\\''")}'`; | 70 | const sh = (value: string) => `'${value.replaceAll("'", "'\\''")}'`; |
| 71 | | 71 | |
| | 72 | // Polls for up to five seconds until a background holder has taken effect. |
| | 73 | const awaitHolder = (condition: string) => |
| | 74 | `i=0; until ${condition}; do i=$((i + 1)); [ "$i" -lt 500 ] || exit 1; sleep 0.01; done`; |
| | 75 | |
| | 76 | // Holds a cache busy the way a running server does: the holder's argv[0] is the |
| | 77 | // runtime entry, which the prune and install scripts look for in /proc cmdline. |
| | 78 | // It reads a pipe the script keeps open on fd 7, so it exits with the script |
| | 79 | // instead of outliving it on a timer. |
| | 80 | const holdRuntimeBusy = (entry: string) => |
| | 81 | [ |
| | 82 | `exec 7> >(exec -a ${entry} cat >/dev/null 2>&1)`, |
| | 83 | awaitHolder(`grep -qF -- ${entry} /proc/[0-9]*/cmdline 2>/dev/null`), |
| | 84 | ].join("\n"); |
| | 85 | |
| | 86 | // Holds a cache's install lock the way a concurrent install does, and likewise |
| | 87 | // releases it when the script exits. |
| | 88 | const holdInstallLock = (lock: string) => |
| | 89 | [ |
| | 90 | `exec 6> >(exec 9> ${lock}; flock -x 9; cat >/dev/null 2>&1)`, |
| | 91 | awaitHolder(`! flock -n ${lock} true`), |
| | 92 | ].join("\n"); |
| | 93 | |
| 72 | const readField = (stdout: string, field: string) => { | 94 | const readField = (stdout: string, field: string) => { |
| 73 | const line = stdout.split("\n").find((candidate) => candidate.startsWith(`${field}:`)); | 95 | const line = stdout.split("\n").find((candidate) => candidate.startsWith(`${field}:`)); |
| 74 | if (line === undefined) throw new Error(`missing ${field} in fixture output: ${stdout}`); | 96 | if (line === undefined) throw new Error(`missing ${field} in fixture output: ${stdout}`); |
| ... | @@ -712,9 +734,7 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed | ... | @@ -712,9 +734,7 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed |
| 712 | `runtime_root=${sh(fixture.runtimeRoot)}`, | 734 | `runtime_root=${sh(fixture.runtimeRoot)}`, |
| 713 | `runtime_parent=${sh(fixture.runtimeParent)}`, | 735 | `runtime_parent=${sh(fixture.runtimeParent)}`, |
| 714 | 'rm "$runtime_root/.t3code-wsl-runtime-ready"', | 736 | 'rm "$runtime_root/.t3code-wsl-runtime-ready"', |
| 715 | 'sh -c "sleep 30" "$runtime_root/t3" >/dev/null 2>&1 &', | 737 | holdRuntimeBusy('"$runtime_root/t3"'), |
| 716 | "active_pid=$!", | | |
| 717 | "sleep 0.1", | | |
| 718 | fixture.installScript(), | 738 | fixture.installScript(), |
| 719 | 'stale=$(find "$runtime_parent" -maxdepth 1 -type d -name ".sha256-*.stale.*" -print -quit)', | 739 | 'stale=$(find "$runtime_parent" -maxdepth 1 -type d -name ".sha256-*.stale.*" -print -quit)', |
| 720 | 'test -n "$stale"', | 740 | 'test -n "$stale"', |
| ... | @@ -723,8 +743,6 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed | ... | @@ -723,8 +743,6 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed |
| 723 | "export HOME", | 743 | "export HOME", |
| 724 | buildWslRuntimePruneScript(fixture.runtimeId), | 744 | buildWslRuntimePruneScript(fixture.runtimeId), |
| 725 | 'test ! -e "$stale"', | 745 | 'test ! -e "$stale"', |
| 726 | "kill $active_pid", | | |
| 727 | "wait $active_pid 2>/dev/null || true", | | |
| 728 | ].join("\n"), | 746 | ].join("\n"), |
| 729 | ); | 747 | ); |
| 730 | | 748 | |
| ... | @@ -750,15 +768,8 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed | ... | @@ -750,15 +768,8 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed |
| 750 | 'touch -d "4 minutes ago" "$runtime_parent/sha256-active"', | 768 | 'touch -d "4 minutes ago" "$runtime_parent/sha256-active"', |
| 751 | 'touch -d "3 minutes ago" "$runtime_parent/sha256-old"', | 769 | 'touch -d "3 minutes ago" "$runtime_parent/sha256-old"', |
| 752 | 'touch -d "2 minutes ago" "$runtime_parent/sha256-locked"', | 770 | 'touch -d "2 minutes ago" "$runtime_parent/sha256-locked"', |
| 753 | 'sh -c "sleep 30" "$runtime_parent/sha256-active/t3" >/dev/null 2>&1 &', | 771 | holdRuntimeBusy('"$runtime_parent/sha256-active/t3"'), |
| 754 | "active_pid=$!", | 772 | holdInstallLock('"$runtime_parent/.sha256-locked.install.lock"'), |
| 755 | "(", | | |
| 756 | ' exec 9> "$runtime_parent/.sha256-locked.install.lock"', | | |
| 757 | " flock -x 9", | | |
| 758 | " sleep 30", | | |
| 759 | ") >/dev/null 2>&1 &", | | |
| 760 | "lock_pid=$!", | | |
| 761 | "sleep 0.1", | | |
| 762 | `HOME="$home"`, | 773 | `HOME="$home"`, |
| 763 | "export HOME", | 774 | "export HOME", |
| 764 | buildWslRuntimePruneScript("sha256-current"), | 775 | buildWslRuntimePruneScript("sha256-current"), |
| ... | @@ -769,9 +780,6 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed | ... | @@ -769,9 +780,6 @@ describe.skipIf(posixShellRunner === null)("WSL runtime install script (executed |
| 769 | 'test -d "$runtime_parent/versions"', | 780 | 'test -d "$runtime_parent/versions"', |
| 770 | 'test ! -e "$runtime_parent/sha256-old"', | 781 | 'test ! -e "$runtime_parent/sha256-old"', |
| 771 | 'test ! -e "$runtime_parent/sha256-markerless"', | 782 | 'test ! -e "$runtime_parent/sha256-markerless"', |
| 772 | "kill $active_pid $lock_pid", | | |
| 773 | "wait $active_pid 2>/dev/null || true", | | |
| 774 | "wait $lock_pid 2>/dev/null || true", | | |
| 775 | 'rm -rf "$work"', | 783 | 'rm -rf "$work"', |
| 776 | ].join("\n"), | 784 | ].join("\n"), |
| 777 | ); | 785 | ); |