2

I've installed samestep/boot-refresh 0.1.0. In the boot REPL, when I change a source file and type:

boot.user=> (boot (refresh))

I get:

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

What am I doing wrong?


Here's the full stack trace:

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]]}

My own code isn't mentioned in the stack trace. I have seen (boot (refresh)) work from the REPL before, but I haven't been able to find out what I'm doing differently to cause this error.


Update 1

After a long binary search with many print statements—since, as explained in @amalloy's answer, the stack trace for the real exception is inaccessible—I discovered this:

In a namespace called move-test, this statement:

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

was causing (boot (refresh)) to fail. When I replaced it with:

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

in the ns statement, (boot (refresh)) worked again. That fixes the current problem for now, but I'd still like to know what's going on.


Update 2

Continuing to try to get (boot (refresh)) to work, it's become clear that the problem is different every time. @amalloy's answer suggests that the real answer to this question would be some way to find the exception and stack trace that c.t.n.repl/print-and-return is failing to store in *e. I've tried a few ideas, but the relevant variables seem to be private and hard to dig out.

How can you find out the error that causes (boot (refresh)) to fail?

Ben Kovitz
  • 4,920
  • 1
  • 22
  • 50
  • The update is a separate question really, about how to require vars. But in brief: you must require vars from another namespace if you wish to use them. – amalloy Mar 29 '17 at 05:14
  • @amalloy After further exploration, it appears that the problem is different every time. Now posting a second update… – Ben Kovitz Apr 01 '17 at 13:02
  • 1
    I ran into the same error message and filed an issue about it with boot-refresh, though I doubt the problem is there. (My issue was that I refactored a function into another ns and failed to update the references). The trick with (bind) did not work for me. – James Aug 11 '17 at 03:43

2 Answers2

2

I don't know about boot or c.t.namespace, but that error trace looks like boot is running refresh from a new thread, and refresh is running into some error. It tries to propagate the error for you by setting *e, but it can't set *e because it's not on the repl thread. So instead of seeing the real error, you're getting this useless "error caused by failing to report an error".

The error seems to be detected here, and c.t.namespace tries to avoid setting *e when there's no repl running (by checking whether *e is bound), but it incorrectly assumes that if a repl is running then the repl thread must be the one it's being called from, whereas apparently boot calls it from a different thread. Have you tried just calling (refresh)? I don't know what the (boot ...) wrapper is supposed to do, but you might not need it, and it seems to be causing trouble. This also explains why you've seen (boot (refresh)) work: the (boot ...) wrapper (probably) doesn't break things itself, but instead just causes the error reporting to get worse when something else is broken.

Of course, once you fix that issue, the result will not be that your refresh works: you'll just be able to see the actual error, instead of this meta-error! Hopefully that will be enough to help you make progress.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • Thanks! This might be just the insight I need. `(refresh)` returns a boot "task" that does the refresh correctly in boot—or at least that's my current understanding. To run the task from the REPL, I _think_ you need to do `(boot (refresh))`, but the documentation is [very light](https://github.com/samestep/boot-refresh). Now continuing to poke around… – Ben Kovitz Mar 28 '17 at 19:00
0

The repl usually has *e bound in the environment of where you execute your code. If anything that needs *e is ran in another thread or outside of the repl it will fail to have an *e to bind to.

I have by-passed this issue by using with-binding to ensure that the refresh fn will always have *eto bind the error to.

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

Once refresh has *e to bind the error to, it should prn the error for you.

  • When I do `(with-bindings {#'*e nil} (boot (refresh)))`, I get the same error message. I figure the bug must be in `boot`. `(with-bindings {#'*e nil} (refresh))` merely returned the object returned by `(refresh)`. Are you running `boot`? – Ben Kovitz Sep 18 '17 at 10:52