-
Notifications
You must be signed in to change notification settings - Fork 31
/
dll_history.clj
159 lines (141 loc) · 5.56 KB
/
dll_history.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
(ns knossos.wgl.dll-history
"A history traversal structure built around a mutable unsynchronized doubly
linked list which supports O(1) removal and insertion of nodes."
(:refer-clojure :exclude [next])
(:require [clojure.core :as c]
[knossos.history :as history]
[knossos.op :as op]
[clojure.tools.logging :refer [info debug warn]]
[potemkin :refer [definterface+ deftype+]]))
(definterface+ INode
(^knossos.wgl.dll_history.INode prev [n])
(^knossos.wgl.dll_history.INode next [n])
(op [n])
(^knossos.wgl.dll_history.INode match [n])
(^knossos.wgl.dll_history.INode setPrev [n prev])
(^knossos.wgl.dll_history.INode setNext [n next])
(^knossos.wgl.dll_history.INode setMatch [n match])
(node-seq [n]))
(deftype+ Node [^:unsynchronized-mutable prev
^:unsynchronized-mutable next
op
^:unsynchronized-mutable match]
knossos.wgl.dll_history.INode
(prev [_] prev)
(next [_] next)
(op [_] op)
(match [_] match)
(setPrev [this p] (set! prev p) this)
(setNext [this n] (set! next n) this)
(setMatch [this m] (set! match m) this)
(node-seq [this]
(cons this (when-let [n next]
(lazy-seq (node-seq n)))))
clojure.lang.Seqable
(seq [this]
(if (nil? prev)
; We're a head node, skip us
(seq next)
; OK regular node
(cons op (when-let [n next]
(lazy-seq (seq n))))))
clojure.lang.Reversible
(rseq [this]
(if (nil? prev)
; We're a head node, done.
nil
(cons op (when-let [p prev]
(lazy-seq (rseq p))))))
java.lang.Object
(toString [this]
(str "#Node" {:op op, :match match})))
(defn ^INode dll-history
"Constructs a double-linked-list history from any other type of history.
Returns a head node whose next entry is the first entry in the history."
[history]
(let [head (Node. nil nil nil nil)]
(loop [history (seq history)
calls (transient {})
infos (transient [])
^Node prev head]
(if history
(let [op (first history)]
(cond
; For invokes, we append a node to the list and record a call entry
(op/invoke? op)
(let [node (Node. prev nil op nil)]
(.setNext prev node)
(recur (c/next history)
(assoc! calls (:process op) node)
infos
node))
; On completion, append a node to the list and fill in our invoke
; node's match
(op/ok? op)
(let [node (Node. prev nil op nil)
invoke (get calls (:process op))]
(.setNext prev node)
(.setMatch ^Node invoke node)
(recur (c/next history)
(dissoc! calls (:process op))
infos
node))
; We don't do failure
(op/fail? op)
(throw (IllegalArgumentException.
"Can't compute dll-histories over histories with :fail ops"))
; Save infos for later
(op/info? op)
(recur (c/next history) calls (conj! infos op) prev)))
; OK we're out of history elements! Append info nodes
(loop [calls calls
infos (seq (persistent! infos))
^Node prev prev]
(if infos
; Append an info node
(let [op (first infos)]
(if-let [invoke (get calls (:process op))]
; We have an invocation
(let [node (Node. prev nil op nil)]
(.setNext prev node)
(.setMatch ^Node invoke node)
(recur (dissoc! calls (:process op))
(c/next infos)
node))
; No invocation, skip
(recur calls (c/next infos) prev)))
; OK we're done, but just to make sure
(let [calls (persistent! calls)]
(when-not (empty? calls)
(debug "Expected all invocations to have a matching :ok or :info, but invocations by processes "
(pr-str (keys calls))
" went unmatched. This might indicate a malformed history, but we're going to go ahead and check it anyway by inserting :info events for these uncompleted invocations.")
(recur (transient calls)
(map (fn [[process invoke-node]]
(let [op (.op ^Node invoke-node)]
(op/info process (:f op) (:value op))))
calls)
prev)))))))
head))
(defn lift!
"Excises a node from the history by stitching together its next and previous
nodes, and likewise for its match."
[^Node entry]
(let [prev ^INode (.prev entry)
next ^INode (.next entry)]
(.setNext prev next)
(.setPrev next prev)
(when-let [match ^INode (.match entry)]
(.setNext ^INode (.prev match) (.next match))
(when-let [n ^INode (.next match)]
(.setPrev n (.prev match))))))
(defn unlift!
"Adds a node back into the history by linking its next and previous back to
where they should be, and the same for its match."
[^Node entry]
(when-let [match ^INode (.match entry)]
(.setNext ^INode (.prev match) match)
(when-let [n ^INode (.next match)]
(.setPrev n match)))
(.setNext ^INode (.prev entry) entry)
(.setPrev ^INode (.next entry) entry))