You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

shadow-cljs compile命令提示构建完成但进程不退出的问题

Shadow-cljs compile/release 进程不退出问题排查与解决

问题背景

使用Clojure/ClojureScript、shadow-cljs、re-frame等技术栈开发动态Web应用,本地shadow-cljs watch app运行正常,但执行shadow-cljs compile app或release app时,命令输出Build completed后进程始终不退出,表现与watch命令一致。该问题在本地和GitHub Actions环境均复现,导致CI环境持续计费。已排除user.clj、默认Build hooks、预启动shadow-cljs start进程等影响,怀疑与项目中的自定义宏有关。

原因分析

从提供的宏代码来看,问题大概率出在编译期宏执行引发的资源残留或非守护线程未终止:

  • 自定义slurp宏在编译期执行文件存在性检查与读取操作,虽然clojure.core/slurp会自动关闭流,但(.exists (io/file file))的文件系统操作可能触发某些类加载,意外启动非守护线程;
  • read-open-config宏中调用System/getenv获取环境变量,编译期执行系统环境调用可能导致JVM加载额外的系统级资源,这些资源未在编译完成后被正确清理;
  • 宏的编译期执行逻辑存在潜在竞态(比如文件检查后读取前文件被删除),可能引发未捕获的资源泄漏,导致JVM无法正常退出。

解决方案

方案1:优化宏的实现逻辑

修改自定义宏,避免不必要的文件检查,改用异常捕获处理文件不存在的情况,同时确保编译期操作的简洁性:

(defmacro slurp [file & [default]]
  (try
    (clojure.core/slurp file)
    (catch java.io.FileNotFoundException _
      default)))

(defmacro read-open-config [env-var]
  (let [config-path (str "config/" (System/getenv env-var) "-open.edn")]
    (try
      (clojure.edn/read-string (slurp config-path))
      (catch java.io.FileNotFoundException _
        {}))))

方案2:替换宏为编译前Hook注入(推荐)

避免在宏中执行编译期IO操作,改用shadow-cljs的Build Hooks在编译前读取配置,通过closure-defines注入到ClojureScript代码中,彻底消除宏带来的潜在问题:

  1. 在shadow-cljs.edn中添加Build Hook配置:
:build-hooks [my.build/config-hook]
  1. 创建src/my/build.clj实现Hook逻辑:
(ns my.build
  (:require [clojure.java.io :as io]
            [clojure.edn :as edn]
            [shadow.cljs.build :as build]))

(defn config-hook [build-state]
  (let [env-name (or (System/getenv "ENV_VAR") "dev")
        config-file (io/file "config" (str env-name "-open.edn"))
        config (if (.exists config-file)
                 (edn/read-string (slurp config-file))
                 {})]
    ;; 将配置注入到ClojureScript的全局常量中
    (assoc-in build-state
              [:compiler-options :closure-defines 'my.config/open-config]
              config)))
  1. 在ClojureScript代码中引用配置:
(ns my.config)
(def ^:const open-config js/my.config.open-config)

方案3:排查残留线程(用于定位根因)

本地执行shadow-cljs compile app后,用以下命令排查残留的非守护线程:

  1. 用jps找到shadow-cljs进程ID;
  2. 执行jstack <进程ID>查看线程栈,找到未终止的非守护线程,定位其来源(比如第三方依赖或内部代码启动的线程),针对性修复。

内容的提问来源于stack exchange,提问作者Pedro Delfino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 18:50:26