thheller / shadow-cljs

ClojureScript compilation made easy
https://github.com/thheller/shadow-cljs
Eclipse Public License 1.0
2.26k stars 178 forks source link

Previous runtime disappears when executing long functions in REPL #963

Closed shanrauf closed 3 months ago

shanrauf commented 2 years ago

When I run long functions in the REPL, after 20 seconds I see The previously used runtime disappeared. Will attempt to pick a new one when available but your state might be gone. Examples are like long benchmark/profiling functions, long Datascript queries, when I'm running (js-debugger), etc. I'll see the output of any function less than that execution time in the REPL. Interestingly, if I println within that 20sec frame, the timer "resets". Also, even if I get that message about the runtime, the desired output WILL show up in the JS console (just not the REPL) no matter what, so the code is still running, but the REPL is unaware of that?

It feels like something is polling to see if a function executed in the REPL outputs anything within 20 seconds and if not, it assumes that the runtime is dead, but that's clearly not the case because the desired output does show up in the JS console.

I've seen potentially relevant issues but none seem to clearly help me:

My shadow-cljs.edn:

{:source-paths
 ["src/main"]

 :dependencies
 [[org.clojure/clojure "1.10.3"]
  [org.clojure/clojurescript "1.10.866"]
  [reagent "0.10.0"]
  [datascript "1.2.8"]
  [com.taoensso/tufte "2.2.0"]
  [cljs-http "0.1.46"]
  [binaryage/devtools "1.0.4"]]
 :dev-http {8080 "public"}
 :builds
 {:frontend
  {:target :browser
   :modules {:main {:init-fn roamtable.core/init}}}
  :query-browser
  {:target :browser
   :preloads [binaryage/devtools]
   :devtools
   {:repl-timeout 1000000}
   :compiler-options {:source-map true
                      :source-map-include-sources-content true}
   :modules {:main {:init-fn query.core/init}}}
  :test
  {:target    :node-test
   :output-to "out/node-tests.js"
   :ns-regexp "-spec$"
   :autorun   true}}}
thheller commented 2 years ago

Currently there is a hardcoded timeout of about 15sec on the server side that kicks runtimes that don't respond to heartbeats in that time. Usually that is enough since something usually goes async and leaves a chance for the heartbeat response. If you however have an entirely blocking task that never yields (or pause in the debugger) that'll look like a dead connection on the server and get kicked.

That heartbeat timeout however basically only exists because of react-native Android which for some reason on app reload doesn't disconnect websockets. So I should probably add a config option for this so it either doens't kick at all or just waits much longer.

thheller commented 2 years ago

Actually I forgot that there are various other reasons the heartbeat does what it does. Mostly around proper handling of websocket reconnects after Sleep since each Browser/OS seems to handle that differently.