diff --git a/deps.edn b/deps.edn index 005e75d5..d86ff0b5 100644 --- a/deps.edn +++ b/deps.edn @@ -49,7 +49,8 @@ :dev {:extra-paths ["src-dev" "classes"] :extra-deps { - org.openjfx/javafx-swing {:mvn/version "21.0.4-ea+1"} ;; for scenic view to run + org.openjfx/javafx-swing {:mvn/version "21.0.4-ea+1"} ;; for scenic view to run + io.github.tonsky/clj-reload {:mvn/version "0.7.1"} } :jvm-opts ["-Dvisualvm.display.name=FlowStorm" ;; for the profilers diff --git a/src-dbg/flow_storm/debugger/main.clj b/src-dbg/flow_storm/debugger/main.clj index 410e3467..7425cfed 100644 --- a/src-dbg/flow_storm/debugger/main.clj +++ b/src-dbg/flow_storm/debugger/main.clj @@ -58,6 +58,15 @@ [] (state-management/stop {})) +(defn debugger-config [] + (let [theme-prop (System/getProperty "flowstorm.theme") + title-prop (System/getProperty "flowstorm.title") + styles-prop (System/getProperty "flowstorm.styles")] + (cond-> {} + theme-prop (assoc :theme (keyword theme-prop)) + styles-prop (assoc :styles styles-prop) + title-prop (assoc :title title-prop)))) + (defn start-debugger "Run the debugger. @@ -80,64 +89,65 @@ ;; Initialize the JavaFX toolkit (ui-utils/init-toolkit) - (if local? - - ;; start components for local debugging - (do - (state-management/start {:only local-debugger-state-vars - :config config}) - (ui-main/setup-ui-from-runtime-config)) - - ;; else, start components for remote debugging - (let [ws-connected? (promise) - repl-connected? (promise) - fully-started (atom false) - signal-ws-connected (fn [conn?] - (ui-main/set-conn-status-lbl :ws conn?) - (dbg-state/set-connection-status :ws conn?)) - signal-repl-connected (fn [conn?] - (dbg-state/set-connection-status :repl conn?) - (ui-main/set-conn-status-lbl :repl conn?))] - (state-management/start {:only remote-debugger-state-vars - :config (assoc config - :on-ws-event events-queue/enqueue-event! - :on-ws-down (fn [] - (utils/log "WebSocket connection went away") - (signal-ws-connected false) - (ui-main/clear-ui)) - :on-ws-up (fn [_] - (signal-ws-connected true) - (deliver ws-connected? true) - - ;; This is kind of hacky but will handle the ClojureScript page reload situation. - ;; After a page reload all the runtime part has been restarted, so - ;; we can re-init it through the repl and also re-setup the ui with whatever the - ;; runtime contains in terms of settings. - ;; But we need to skip this the first time the ws connection comes up - ;; since maybe the system ins't fully started yet, we maybe don't even have a UI - (when @fully-started - (when (dbg-state/repl-config) - (repl-core/init-repl (dbg-state/env-kind))) - (ui-main/setup-ui-from-runtime-config))) - - :on-repl-down (fn [] - (signal-repl-connected false)) - :on-repl-up (fn [] - (deliver repl-connected? true) - (signal-repl-connected true)))}) - - (reset! fully-started true) - - ;; if there is a repl config wait for the connection before moving on - (when (and (dbg-state/repl-config) - @repl-connected?) - (signal-repl-connected true)) - - ;; once we have both the UI started and the runtime-connected - ;; initialize the UI with the info retrieved from the runtime - (when @ws-connected? - (signal-ws-connected true) - (ui-main/setup-ui-from-runtime-config)))) + (let [config (merge config (debugger-config))] + (if local? + + ;; start components for local debugging + (do + (state-management/start {:only local-debugger-state-vars + :config config}) + (ui-main/setup-ui-from-runtime-config)) + + ;; else, start components for remote debugging + (let [ws-connected? (promise) + repl-connected? (promise) + fully-started (atom false) + signal-ws-connected (fn [conn?] + (ui-main/set-conn-status-lbl :ws conn?) + (dbg-state/set-connection-status :ws conn?)) + signal-repl-connected (fn [conn?] + (dbg-state/set-connection-status :repl conn?) + (ui-main/set-conn-status-lbl :repl conn?))] + (state-management/start {:only remote-debugger-state-vars + :config (assoc config + :on-ws-event events-queue/enqueue-event! + :on-ws-down (fn [] + (utils/log "WebSocket connection went away") + (signal-ws-connected false) + (ui-main/clear-ui)) + :on-ws-up (fn [_] + (signal-ws-connected true) + (deliver ws-connected? true) + + ;; This is kind of hacky but will handle the ClojureScript page reload situation. + ;; After a page reload all the runtime part has been restarted, so + ;; we can re-init it through the repl and also re-setup the ui with whatever the + ;; runtime contains in terms of settings. + ;; But we need to skip this the first time the ws connection comes up + ;; since maybe the system ins't fully started yet, we maybe don't even have a UI + (when @fully-started + (when (dbg-state/repl-config) + (repl-core/init-repl (dbg-state/env-kind))) + (ui-main/setup-ui-from-runtime-config))) + + :on-repl-down (fn [] + (signal-repl-connected false)) + :on-repl-up (fn [] + (deliver repl-connected? true) + (signal-repl-connected true)))}) + + (reset! fully-started true) + + ;; if there is a repl config wait for the connection before moving on + (when (and (dbg-state/repl-config) + @repl-connected?) + (signal-repl-connected true)) + + ;; once we have both the UI started and the runtime-connected + ;; initialize the UI with the info retrieved from the runtime + (when @ws-connected? + (signal-ws-connected true) + (ui-main/setup-ui-from-runtime-config))))) ;; for both, local and remote diff --git a/src-dbg/flow_storm/debugger/ui/main.clj b/src-dbg/flow_storm/debugger/ui/main.clj index 7a1489de..eac76958 100644 --- a/src-dbg/flow_storm/debugger/ui/main.clj +++ b/src-dbg/flow_storm/debugger/ui/main.clj @@ -60,10 +60,11 @@ :stop (fn [] (stop-ui))) (defn clear-ui [] - (outputs-screen/clear-outputs-ui) + (ui-utils/run-later + (outputs-screen/clear-outputs-ui) - (doseq [fid (dbg-state/all-flows-ids)] - (flows-screen/clear-debugger-flow fid))) + (doseq [fid (dbg-state/all-flows-ids)] + (flows-screen/clear-debugger-flow fid)))) (defn bottom-box [] (let [progress-box (ui/h-box :childs []) @@ -374,18 +375,8 @@ (.setOnCloseRequest (event-handler [_] - ;; call with skip-ui-stop? true since if we are here - ;; we are already stopping the ui from closing the window (binding [*killing-ui-from-window-close?* true] - (let [stop-config (when (utils/storm-env?) - {:skip-index-stop? true})] - (if-let [stop-all (resolve 'flow-storm.api/stop)] - ;; if ui and runtime is running under the same jvm - ;; we can stop all - (stop-all stop-config) - - ;; else stop just the debugger - ((resolve 'flow-storm.debugger.main/stop-debugger)))))))) + ((resolve 'flow-storm.debugger.main/stop-debugger)))))) theme-listener (when (= :auto (:theme config)) (start-theme-listener diff --git a/src-dev/dev.clj b/src-dev/dev.clj index f5557b4e..44ffb5e0 100644 --- a/src-dev/dev.clj +++ b/src-dev/dev.clj @@ -46,7 +46,7 @@ (remove-watch dbg-state/state :spec-validator)) (defn start-local [] - (fs-api/local-connect {:skip-index-start? (not (nil? index-api/flow-thread-registry))}) + (fs-api/local-connect {}) (spec-instrument-state)) (defn start-shadow-remote [port build-id] @@ -56,10 +56,10 @@ (spec-instrument-state)) (defn stop [] - (fs-api/stop {:skip-index-stop? (utils/storm-env?)})) + (fs-api/stop)) (defn refresh [] - (let [running? dbg-state/state] + (let [running? (boolean dbg-state/state)] (log "Reloading system ...") (when running? (log "System is running, stopping it first ...") diff --git a/src-inst/flow_storm/api.clj b/src-inst/flow_storm/api.clj index 63a19722..2ead5000 100644 --- a/src-inst/flow_storm/api.clj +++ b/src-inst/flow_storm/api.clj @@ -4,7 +4,6 @@ Provides functionality to start the debugger and instrument forms." (:require [flow-storm.tracer :as tracer] - [flow-storm.runtime.outputs :as rt-outputs] [flow-storm.utils :refer [log] :as utils] [flow-storm.ns-reload-utils :as reload-utils] [hansel.api :as hansel] @@ -12,51 +11,56 @@ [flow-storm.runtime.debuggers-api :as dbg-api] [flow-storm.runtime.events :as rt-events] [flow-storm.runtime.values :as rt-values] - [flow-storm.jobs :as jobs] - [flow-storm.remote-websocket-client :as remote-websocket-client] - [flow-storm.runtime.indexes.api :as index-api] [clojure.string :as str] [clojure.stacktrace :as stacktrace])) -(defn stop +;; TODO: build script +;; Maybe we can figure out this ns names by scanning (all-ns) so +;; we don't need to list them here +;; Also maybe just finding main is enough, we can add to it a fn +;; that returns the rest of the functions we need +(def debugger-main-ns 'flow-storm.debugger.main) - "Stop the flow-storm runtime part gracefully. - If working in local mode will also stop the UI." +(defn start-debugger-ui + + "Start the debugger UI when available on the classpath. + Returns true when available, false otherwise." - ([] (stop {})) - ([{:keys [skip-index-stop?]}] - (let [stop-debugger (try - (resolve (symbol (name dbg-api/debugger-main-ns) "stop-debugger")) - (catch Exception _ nil))] + [config] - ;; NOTE: The order here is important until we replace this code with - ;; better component state management + (if-let [start-debugger (requiring-resolve (symbol (name debugger-main-ns) "start-debugger"))] + (do + (start-debugger config) + true) + (do + (log "It looks like the debugger UI isn't present on the classpath.") + false))) - (when-not skip-index-stop? - (index-api/stop)) +(defn stop-debugger-ui - ;; if we are running in local mode and running a debugger stop it - (when stop-debugger - (stop-debugger)) + "Stop the debugger UI if it has been started." + [] + (if-let [stop-debugger (requiring-resolve (symbol (name debugger-main-ns) "stop-debugger"))] + (stop-debugger) + (log "It looks like the debugger UI isn't present on the classpath."))) - (rt-events/clear-dispatch-fn!) - (rt-events/clear-pending-events!) - (rt-outputs/remove-tap!) +(defn stop - (dbg-api/interrupt-all-tasks) + "Stop the flow-storm runtime part gracefully. + If working in local mode will also stop the UI." - (rt-values/clear-vals-ref-registry) + [] - (jobs/stop-jobs) + (rt-events/clear-dispatch-fn!) - ;; stop remote websocket client if needed - (remote-websocket-client/stop-remote-websocket-client) + ;; if we are running in local mode and running a debugger stop it + (stop-debugger-ui) - (tracer/clear-breakpoints!) + (dbg-api/stop-runtime) - (log "System stopped")))) + (log "System fully stopped")) (defn local-connect @@ -79,11 +83,13 @@ ([config] (let [enqueue-event! (requiring-resolve 'flow-storm.debugger.events-queue/enqueue-event!) - config (assoc config - :local? true)] + config (assoc config :local? true)] + + (dbg-api/start-runtime) - (dbg-api/start-runtime enqueue-event! false config)))) + (start-debugger-ui config) + (rt-events/set-dispatch-fn enqueue-event!)))) (def jump-to-last-expression dbg-api/jump-to-last-expression-in-this-thread) diff --git a/src-inst/flow_storm/preload.cljs b/src-inst/flow_storm/preload.cljs index 9b5a0ffc..cc0ed7dc 100644 --- a/src-inst/flow_storm/preload.cljs +++ b/src-inst/flow_storm/preload.cljs @@ -1,5 +1,5 @@ (ns flow-storm.preload (:require [flow-storm.runtime.debuggers-api :as dbg-api])) -(dbg-api/setup-runtime) +(dbg-api/start-runtime) (dbg-api/remote-connect {}) diff --git a/src-inst/flow_storm/remote_websocket_client.clj b/src-inst/flow_storm/remote_websocket_client.clj index d8a49f12..fea1b1cb 100644 --- a/src-inst/flow_storm/remote_websocket_client.clj +++ b/src-inst/flow_storm/remote_websocket_client.clj @@ -57,7 +57,7 @@ (onClose [code reason remote?] (log (format "Connection with %s closed. code=%s reson=%s remote?=%s" uri-str code reason remote?)) - ((requiring-resolve 'flow-storm.api/stop))) + ((requiring-resolve 'flow-storm.runtime.debuggers-api/stop-runtime))) (onError [^Exception e] (log-error (format "WebSocket error connection %s" uri-str) e)))] diff --git a/src-inst/flow_storm/runtime/debuggers_api.cljc b/src-inst/flow_storm/runtime/debuggers_api.cljc index 5b80e7b3..87beb3a8 100644 --- a/src-inst/flow_storm/runtime/debuggers_api.cljc +++ b/src-inst/flow_storm/runtime/debuggers_api.cljc @@ -18,13 +18,6 @@ [flow-storm.runtime.types.fn-call-trace :as fn-call-trace] [flow-storm.runtime.types.expr-trace :as expr-trace])) -;; TODO: build script -;; Maybe we can figure out this ns names by scanning (all-ns) so -;; we don't need to list them here -;; Also maybe just finding main is enough, we can add to it a fn -;; that returns the rest of the functions we need -(def debugger-main-ns 'flow-storm.debugger.main) - ;; Utilities for long interruptible tasks (def interruptible-tasks @@ -618,82 +611,98 @@ (let [f (get api-fn fun-key)] (apply f args))) -#?(:clj - (defn setup-runtime +(defn runtime-started? [] + (index-api/indexes-started?)) - "Setup runtime based on jvm properties. Returns a config map." +#?(:clj + (defn start-runtime + "Start the runtime. Will not do anything if the runtime has been already started." [] - (let [theme-prop (System/getProperty "flowstorm.theme") - title-prop (System/getProperty "flowstorm.title") - styles-prop (System/getProperty "flowstorm.styles") - fn-call-limits (utils/parse-thread-fn-call-limits (System/getProperty "flowstorm.threadFnCallLimits")) - config (cond-> {} - theme-prop (assoc :theme (keyword theme-prop)) - styles-prop (assoc :styles styles-prop) - title-prop (assoc :title title-prop))] + + ;; NOTE: The order here is important until we replace this code with + ;; better component state management - (tracer/set-recording (if (= (System/getProperty "flowstorm.startRecording") "true") true false)) + (if (runtime-started?) + + (log "Runtime already started, skipping ...") + + (let [_ (log "Starting up runtime") + fn-call-limits (utils/parse-thread-fn-call-limits (System/getProperty "flowstorm.threadFnCallLimits"))] - (doseq [[fn-ns fn-name l] fn-call-limits] - (index-api/add-fn-call-limit fn-ns fn-name l)) + (tracer/set-recording (if (= (System/getProperty "flowstorm.startRecording") "true") true false)) - config)) + (doseq [[fn-ns fn-name l] fn-call-limits] + (index-api/add-fn-call-limit fn-ns fn-name l)) + + (index-api/start) - :cljs ;; ------------------------------------------------------------------------------------------------------------ - (defn setup-runtime + (rt-values/clear-vals-ref-registry) - "This is meant to be called by preloads to initialize the runtime side of things" + (rt-outputs/setup-tap!) - [] - (println "Setting up runtime") + (jobs/run-jobs) + + (log "Runtime started")))) - (index-api/start) + :cljs + (defn start-runtime - (println "Index started") + "This is meant to be called by preloads to initialize the runtime side of things. + Will not do anything if the runtime has been already started." - (let [recording? (if (= (env-prop "flowstorm.startRecording") "true") true false)] - (tracer/set-recording recording?) - (println "Recording set to " recording?)) + [] + (if (runtime-started?) + + (log "Runtime already started, skipping ...") - (let [fn-call-limits (utils/parse-thread-fn-call-limits (env-prop "flowstorm.threadFnCallLimits"))] - (doseq [[fn-ns fn-name l] fn-call-limits] - (index-api/add-fn-call-limit fn-ns fn-name l) - (println "Added function limit " fn-ns fn-name l))) + (do + (println "Starting up runtime") - (rt-values/clear-vals-ref-registry) - (println "Value references cleared") + (index-api/start) - (rt-outputs/setup-tap!) - (jobs/run-jobs) - (println "Runtime setup ready"))) + (let [recording? (if (= (env-prop "flowstorm.startRecording") "true") true false)] + (tracer/set-recording recording?) + (println "Recording set to " recording?)) -#?(:clj - (defn start-runtime [events-dispatch-fn skip-debugger-start? {:keys [skip-index-start?] :as config}] - (let [config (merge config (setup-runtime))] + (let [fn-call-limits (utils/parse-thread-fn-call-limits (env-prop "flowstorm.threadFnCallLimits"))] + (doseq [[fn-ns fn-name l] fn-call-limits] + (index-api/add-fn-call-limit fn-ns fn-name l))) + + (rt-values/clear-vals-ref-registry) + + (rt-outputs/setup-tap!) + (jobs/run-jobs) + + (println "Runtime started"))))) - ;; NOTE: The order here is important until we replace this code with - ;; better component state management +(defn stop-runtime [] + + (interrupt-all-tasks) + + (jobs/stop-jobs) - (when-not skip-index-start? - (index-api/start)) + (rt-outputs/remove-tap!) + + (rt-outputs/clear-outputs) - ;; start the debugger UI - (when-not skip-debugger-start? - (let [start-debugger (requiring-resolve (symbol (name debugger-main-ns) "start-debugger"))] - (start-debugger config))) + (rt-values/clear-vals-ref-registry) - (rt-events/set-dispatch-fn events-dispatch-fn) + (rt-events/clear-pending-events!) + + (index-api/clear-fn-call-limits) - (rt-values/clear-vals-ref-registry) + (remote-websocket-client/stop-remote-websocket-client) - (rt-outputs/setup-tap!) + (tracer/clear-breakpoints!) - (jobs/run-jobs)))) + (index-api/stop)) #?(:clj (defn remote-connect [config] + (start-runtime) + ;; connect to the remote websocket (remote-websocket-client/start-remote-websocket-client (assoc config @@ -705,7 +714,7 @@ remote-websocket-client/send))] (log "Connected to remote websocket") - (start-runtime enqueue-event! true config) + (rt-events/set-dispatch-fn enqueue-event!) (log "Remote Clojure runtime initialized")))))) diff --git a/src-inst/flow_storm/runtime/indexes/api.cljc b/src-inst/flow_storm/runtime/indexes/api.cljc index d5f8c0e6..f269b23e 100644 --- a/src-inst/flow_storm/runtime/indexes/api.cljc +++ b/src-inst/flow_storm/runtime/indexes/api.cljc @@ -128,9 +128,15 @@ (defn rm-fn-call-limit [fn-ns fn-name] (swap! fn-call-limits update fn-ns dissoc fn-name)) +(defn clear-fn-call-limits [] + (reset! fn-call-limits nil)) + (defn get-fn-call-limits [] @fn-call-limits) +(defn indexes-started? [] + (not (nil? flow-thread-registry))) + (defn check-fn-limit! "Automatically decrease the limit for the function if it exists. diff --git a/src-inst/flow_storm/runtime/indexes/thread_registry.cljc b/src-inst/flow_storm/runtime/indexes/thread_registry.cljc index 2e16effd..d3c3fa85 100644 --- a/src-inst/flow_storm/runtime/indexes/thread_registry.cljc +++ b/src-inst/flow_storm/runtime/indexes/thread_registry.cljc @@ -93,7 +93,7 @@ (reset! callbacks cbs) thread-reg) - (stop-thread-registry [_]) + (stop-thread-registry [_] nil) (record-total-order-entry [_ flow-id th-timeline entry] (-> (get @total-order-timelines flow-id) diff --git a/src-inst/flow_storm/storm_api.clj b/src-inst/flow_storm/storm_api.clj index 3d8498b0..d8c0e775 100644 --- a/src-inst/flow_storm/storm_api.clj +++ b/src-inst/flow_storm/storm_api.clj @@ -1,16 +1,22 @@ (ns flow-storm.storm-api (:require [flow-storm.api :as fs-api] [flow-storm.tracer :as tracer] - [flow-storm.runtime.debuggers-api :as dbg-api] - [flow-storm.runtime.indexes.api :as indexes-api])) + [flow-storm.runtime.debuggers-api :as dbg-api])) -(defn start-recorder [] - (dbg-api/setup-runtime) - (indexes-api/start)) +(defn start-recorder -(defn start-debugger [] - (let [config {:skip-index-start? true}] - (fs-api/local-connect config))) + "Called by ClojureStorm for initializing the runtime" + + [] + (dbg-api/start-runtime)) + +(defn start-debugger + + "Called by :dbg keyword evaluation to start the UI" + + [] + + (fs-api/local-connect {})) (def jump-to-last-expression dbg-api/jump-to-last-expression-in-this-thread) diff --git a/src-inst/flow_storm/storm_preload.cljs b/src-inst/flow_storm/storm_preload.cljs index e6754481..9d2320bd 100644 --- a/src-inst/flow_storm/storm_preload.cljs +++ b/src-inst/flow_storm/storm_preload.cljs @@ -6,5 +6,5 @@ ;; setup storm callback functions (tracer/hook-clojurescript-storm) -(dbg-api/setup-runtime) +(dbg-api/start-runtime) (dbg-api/remote-connect {})