Skip to content

Commit

Permalink
Add up-to extension
Browse files Browse the repository at this point in the history
  • Loading branch information
aroemers committed Oct 20, 2024
1 parent adfeae1 commit c9815c4
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 7 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## Unreleased

### Added

- The `watchpoint` now receives notifications when a state is referenced (i.e. `deref` or `force`) as well, with the third argument set to `:referred`.
- A small `up-to` extension has been added, allowing one to stop a state and its dependencies, in reverse order.

## 2.0.0

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ Those two functions are actually implemented using the library's extension point

The library has a public `watchpoint` var.
You can watch this var by using Clojure's `add-watch`.
The registered watch functions receive `:starting`, `:started`, `:stopping` or `:stopped` and the State object.
The registered watch functions receive `:referred`, `:starting`, `:started`, `:stopping` or `:stopped` and the State object.

Try the following example:

Expand Down
11 changes: 6 additions & 5 deletions src/redelay/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@

;;; Watchpoint.

(defonce ^{:doc "Add watches to this var to be notified of state changes, using
`add-watch`. The third argument to the watch fn will be one of
`:starting`, `:started`, `:stopping` or `:stopped`. The fourth
argument is the State object."}
watchpoint
(defonce ^{:doc "Add watches to this var to be notified of state
changes, using `add-watch`. The third argument to the watch fn will
be one of `:referring`, `:starting`, `:started`, `:stopping` or
`:stopped`. The fourth argument is the State object."} watchpoint
(var watchpoint))


Expand All @@ -24,6 +23,7 @@
(deftype State [name start-fn stop-fn value meta]
clojure.lang.IDeref
(deref [this]
(.notifyWatches watchpoint :referring this)
(when-not (realized? this)
(locking this
(when-not (realized? this)
Expand All @@ -33,6 +33,7 @@
(reset! value result)
(.notifyWatches watchpoint :started this))
(catch Exception e
(.notifyWatches watchpoint :aborted this)
(throw (ex-info "Exception thrown when starting state" {:state this} e)))))))
@value)

Expand Down
46 changes: 46 additions & 0 deletions src/redelay/extensions/up_to.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
(ns redelay.extensions.up-to
"A small extension that allows stopping a state and its dependents (in
reverse order).
Make sure this namespace is loaded before realizing states, in order
to hook into the core's extension point (`watchpoint`). The internal
dependency graph is determined by watching which states are referred
to while starting a state."
(:require [redelay.core :as core]))

;;; Internals

(defonce ^:private closeables (atom {}))
(defonce ^:private starting (atom ()))

(defn- watch [_ _ change state]
(case change
:referring (when-let [start (peek @starting)]
(swap! closeables update start (fnil conj (hash-set)) state))
:starting (swap! starting conj state)
:aborted (reset! starting ())
:started (swap! starting pop)
:stopped (swap! closeables dissoc state)
nil))

(add-watch core/watchpoint ::up-to watch)

(defn- transitive [state]
(let [dependencies @closeables]
(loop [todo (list state) result (list)]
(if-let [head (first todo)]
(let [deps (keep (fn [[state deps]] (when (contains? deps head) state)) dependencies)]
(recur (reduce conj (pop todo) deps)
(cons head result)))
(distinct result)))))

;;; Extension API

(defn stop
"Stop the state and its (realized) dependents, in reverse order."
[state]
(when (realized? state)
(let [ordered (transitive state)]
(doseq [closeable ordered]
(.close closeable))
ordered)))
4 changes: 3 additions & 1 deletion test/redelay/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@
(try
(is (= 42 @forty-two))
(stop)
(is (= [[::test watchpoint :starting forty-two]
(is (= [[::test watchpoint :referring forty-two]
[::test watchpoint :starting forty-two]
[::test watchpoint :referring two]
[::test watchpoint :starting two]
[::test watchpoint :started two]
[::test watchpoint :started forty-two]
Expand Down

0 comments on commit c9815c4

Please sign in to comment.