From a0feca651010bb6a62378ec7dba3c2a718edaebb Mon Sep 17 00:00:00 2001 From: Mega Yu Date: Thu, 23 Apr 2026 11:38:17 +0800 Subject: [PATCH] fix(cli): wait for db-worker exit on stop --- src/main/logseq/cli/server.cljs | 25 ++++++---- src/test/logseq/cli/server_test.cljs | 69 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/main/logseq/cli/server.cljs b/src/main/logseq/cli/server.cljs index ddf8f2db0a..48da2ac5e3 100644 --- a/src/main/logseq/cli/server.cljs +++ b/src/main/logseq/cli/server.cljs @@ -95,6 +95,10 @@ [pid] (daemon/pid-status pid)) +(defn- process-stopped? + [pid] + (not (contains? #{:alive :no-permission} (pid-status pid)))) + (defn- read-lock [path] (daemon/read-lock path)) @@ -220,7 +224,8 @@ (p/resolved (owner-mismatch-error repo requester-owner lock-owner)) (-> (p/let [_ (shutdown! lock)] (wait-for (fn [] - (p/resolved (not (fs/existsSync path)))) + (p/resolved (and (not (fs/existsSync path)) + (process-stopped? (:pid lock))))) {:timeout-ms 5000 :interval-ms 200}) {:ok? true @@ -232,14 +237,16 @@ (.kill js/process (:pid lock) "SIGTERM") (catch :default e (log/warn :cli-server-stop-sigterm-failed e)))) - (when (= :not-found (pid-status (:pid lock))) - (remove-lock! path)) - (if (fs/existsSync path) - {:ok? false - :error {:code :server-stop-timeout - :message "timed out stopping server"}} - {:ok? true - :data {:repo repo}}))))))))) + (let [pid-state (pid-status (:pid lock))] + (when (= :not-found pid-state) + (remove-lock! path)) + (if (or (fs/existsSync path) + (= :alive pid-state)) + {:ok? false + :error {:code :server-stop-timeout + :message "timed out stopping server"}} + {:ok? true + :data {:repo repo}})))))))))) (defn start-server! [config repo] diff --git a/src/test/logseq/cli/server_test.cljs b/src/test/logseq/cli/server_test.cljs index 03c31fb101..26c1009d14 100644 --- a/src/test/logseq/cli/server_test.cljs +++ b/src/test/logseq/cli/server_test.cljs @@ -166,6 +166,75 @@ (is false (str "unexpected error: " e)))) (p/finally done))))) +(deftest stop-server-waits-for-process-exit-after-lock-removal + (async done + (let [data-dir (node-helper/create-tmp-dir "cli-server-stop-waits-pid") + repo (str "logseq_db_stop_waits_" (subs (str (random-uuid)) 0 8)) + lock-file (cli-server/lock-path data-dir repo) + lock {:repo repo + :pid 424242 + :host "127.0.0.1" + :port 9101 + :owner-source :cli} + pid-state (atom :alive)] + (fs/mkdirSync (node-path/dirname lock-file) #js {:recursive true}) + (fs/writeFileSync lock-file (js/JSON.stringify (clj->js lock))) + (-> (p/with-redefs [daemon/http-request (fn [_] (p/resolved {:status 200 :body ""})) + daemon/pid-status (fn [_] @pid-state) + daemon/wait-for (fn [pred _opts] + (fs/unlinkSync lock-file) + (p/let [first-result (pred) + _ (is (= false first-result)) + _ (reset! pid-state :not-found) + second-result (pred)] + (is (= true second-result)) + true))] + (cli-server/stop-server! {:data-dir data-dir + :owner-source :cli} + repo)) + (p/then (fn [result] + (is (= true (:ok? result))) + (is (= repo (get-in result [:data :repo]))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally done))))) + +(deftest stop-server-times-out-when-lock-is-gone-but-process-is-still-alive + (async done + (let [data-dir (node-helper/create-tmp-dir "cli-server-stop-timeout-pid") + repo (str "logseq_db_stop_timeout_" (subs (str (random-uuid)) 0 8)) + lock-file (cli-server/lock-path data-dir repo) + lock {:repo repo + :pid 424242 + :host "127.0.0.1" + :port 9101 + :owner-source :cli} + kill-calls (atom [])] + (fs/mkdirSync (node-path/dirname lock-file) #js {:recursive true}) + (fs/writeFileSync lock-file (js/JSON.stringify (clj->js lock))) + (-> (test-helper/with-js-property-override + js/process + "kill" + (fn [pid signal] + (swap! kill-calls conj [pid signal]) + true) + (fn [] + (p/with-redefs [daemon/http-request (fn [_] (p/resolved {:status 200 :body ""})) + daemon/pid-status (fn [_] :alive) + daemon/wait-for (fn [_ _] + (fs/unlinkSync lock-file) + (p/rejected (ex-info "timeout" {:code :timeout})))] + (cli-server/stop-server! {:data-dir data-dir + :owner-source :cli} + repo)))) + (p/then (fn [result] + (is (= false (:ok? result))) + (is (= :server-stop-timeout (get-in result [:error :code]))) + (is (= [[424242 "SIGTERM"]] @kill-calls)))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally done))))) + (deftest restart-server-does-not-sigterm-external-owner-daemon (async done (let [data-dir (node-helper/create-tmp-dir "cli-server-owner-restart")