2

我已经安装了samestep/boot-refresh 0.1.0。在启动 REPL中,当我更改源文件并键入:

boot.user=> (boot (refresh))

我得到:

java.lang.IllegalStateException: Can't set!: *e from non-binding thread

我究竟做错了什么?


这是完整的堆栈跟踪:

boot.user=> *e
#error {
 :cause "Can't set!: *e from non-binding thread"
 :via
 [{:type java.lang.IllegalStateException
   :message "Can't set!: *e from non-binding thread"
   :at [clojure.lang.Var set "Var.java" 218]}]
 :trace
 [[clojure.lang.Var set "Var.java" 218]
  [clojure.tools.namespace.repl$print_and_return invokeStatic "repl.clj" 22]
  [clojure.tools.namespace.repl$print_and_return invoke "repl.clj" 20]
  [clojure.tools.namespace.repl$do_refresh invokeStatic "repl.clj" 96]
  [clojure.tools.namespace.repl$do_refresh invoke "repl.clj" 82]
  [clojure.tools.namespace.repl$refresh invokeStatic "repl.clj" 145]
  [clojure.tools.namespace.repl$refresh doInvoke "repl.clj" 128]
  [clojure.lang.RestFn invoke "RestFn.java" 397]
  [samestep.boot_refresh$eval541$fn__542$fn__547$fn__548$fn__549 invoke "boot_refresh.clj" 14]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 646]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1881]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1881]
  [clojure.lang.RestFn invoke "RestFn.java" 425]
  [samestep.boot_refresh$eval541$fn__542$fn__547$fn__548 invoke "boot_refresh.clj" 13]
  [boot.core$run_tasks invoke "core.clj" 1019]
  [boot.core$boot$fn__918 invoke "core.clj" 1029]
  [clojure.core$binding_conveyor_fn$fn__4676 invoke "core.clj" 1938]
  [clojure.lang.AFn call "AFn.java" 18]
  [java.util.concurrent.FutureTask run "FutureTask.java" 266]
  [java.util.concurrent.ThreadPoolExecutor runWorker "ThreadPoolExecutor.java" 1142]
  [java.util.concurrent.ThreadPoolExecutor$Worker run "ThreadPoolExecutor.java" 617]
  [java.lang.Thread run "Thread.java" 745]]}

堆栈跟踪中没有提到我自己的代码。我以前看过(boot (refresh))REPL 的工作,但我无法找出我正在做的不同导致此错误的原因。


更新 1

在使用许多打印语句进行长时间的二进制搜索之后——正如@amalloy 的回答中所解释的那样,真正异常的堆栈跟踪是不可访问的——我发现了这一点:

在名为 的命名空间move-test中,此语句:

(def subset-sum-spec fargish.workspace-test/subset-sum-spec)

导致(boot (refresh))失败。当我将其替换为:

(:require … [fargish.workspace-test :refer [subset-sum-spec]] …)

ns声明中,(boot (refresh))再次工作。这暂时解决了当前的问题,但我仍然想知道发生了什么。


更新 2

继续尝试开始(boot (refresh))工作,很明显问题每次都不一样。@amalloy 的回答表明,这个问题的真正答案是找到ctnrepl/print-and-return无法存储的异常和堆栈跟踪的某种方式*e。我尝试了一些想法,但相关变量似乎是私有的,很难挖掘出来。

如何找出导致(boot (refresh))失败的错误?

4

2 回答 2

2

我不知道bootor c.t.namespace,但是那个错误跟踪看起来像 boot 是refresh从一个新线程运行的,并且遇到refresh了一些错误。它尝试通过设置为您传播错误*e,但它无法设置,*e因为它不在 repl 线程上。因此,您没有看到真正的错误,而是得到了这个无用的“由于未能报告错误而导致的错误”。

错误似乎在这里被检测到,并c.t.namespace试图避免*e在没有运行 repl 时设置(通过检查是否*e绑定),但它错误地假设如果 repl 正在运行,那么 repl 线程必须是它被调用的线程,而显然 boot 从不同的线程调用它。你试过打电话(refresh)吗?我不知道(boot ...)包装器应该做什么,但你可能不需要它,它似乎会造成麻烦。这也解释了为什么你已经看到(boot (refresh))了工作:(boot ...)包装器(可能)不会破坏东西本身,而是只会在其他东西被破坏时导致错误报告变得更糟。

当然,一旦你解决了这个问题,结果将不会是你的刷新工作:你只能看到实际的错误,而不是这个元错误!希望这足以帮助您取得进步。

于 2017-03-28T18:48:00.323 回答
0

repl 通常*e绑定在您执行代码的环境中。如果任何需要*e的东西在另一个线程或 repl 之外运行,它将无法*e绑定到。

我通过使用with-binding来绕过这个问题,以确保刷新 fn 始终必须*e将错误绑定到。

    (with-bindings {#'*e nil} (refresh))

一旦刷新必须*e将错误绑定到,它应该为您 prn 错误。

于 2017-04-06T01:13:37.010 回答