@@ -5,14 +5,16 @@ package part
55
66import (
77 "bytes"
8+ "fmt"
9+ "os"
810)
911
1012// Txn is a transaction against a tree. It allows doing efficient
1113// modifications to a tree by caching and reusing cloned nodes.
1214type Txn [T any ] struct {
13- root * header [T ]
14- opts options
15- size int // the number of objects in the tree
15+ oldRoot , root * header [T ]
16+ opts options
17+ size int // the number of objects in the tree
1618
1719 // mutated is the set of nodes mutated in this transaction
1820 // that we can keep mutating without cloning them again.
@@ -174,6 +176,10 @@ func (txn *Txn[T]) Notify() {
174176 close (ch )
175177 }
176178 clear (txn .watches )
179+
180+ if ! txn .opts .rootOnlyWatch () {
181+ validateRemovedWatches (txn .oldRoot , txn .root )
182+ }
177183}
178184
179185// PrintTree to the standard output. For debugging.
@@ -393,6 +399,7 @@ func (txn *Txn[T]) delete(root *header[T], key []byte) (oldValue T, hadOld bool,
393399 // Target is the root, clear it.
394400 if root .isLeaf () || newRoot .size () == 0 {
395401 // Replace leaf or empty root with a node4
402+ txn .watches [root .watch ] = struct {}{}
396403 newRoot = newNode4 [T ]()
397404 } else {
398405 newRoot = txn .cloneNode (root )
@@ -420,6 +427,9 @@ func (txn *Txn[T]) delete(root *header[T], key []byte) (oldValue T, hadOld bool,
420427 children [target .index ] = target .node
421428 } else if target .node .size () == 0 && (target .node == this || target .node .getLeaf () == nil ) {
422429 // The node is empty, remove it from the parent.
430+ if target .node .watch != nil {
431+ txn .watches [target .node .watch ] = struct {}{}
432+ }
423433 parent .node .remove (target .index )
424434 } else {
425435 // Update the target (as it may have been cloned)
@@ -473,3 +483,48 @@ func (txn *Txn[T]) delete(root *header[T], key []byte) (oldValue T, hadOld bool,
473483 newRoot = parents [0 ].node
474484 return
475485}
486+
487+ var runValidation = os .Getenv ("PART_VALIDATE" ) != ""
488+
489+ func validateRemovedWatches [T any ](oldRoot * header [T ], newRoot * header [T ]) {
490+ if ! runValidation {
491+ return
492+ }
493+ var collectWatches func (depth int , watches map [<- chan struct {}]int , node * header [T ])
494+ collectWatches = func (depth int , watches map [<- chan struct {}]int , node * header [T ]) {
495+ if node == nil {
496+ return
497+ }
498+ if node .watch == nil {
499+ panic ("nil watch channel" )
500+ }
501+ watches [node .watch ] = depth
502+ if leaf := node .getLeaf (); leaf != nil && ! node .isLeaf () {
503+ watches [leaf .watch ] = depth
504+ }
505+ for _ , child := range node .children () {
506+ if child != nil {
507+ collectWatches (depth + 1 , watches , child )
508+ }
509+ }
510+ }
511+ oldWatches := map [<- chan struct {}]int {}
512+ collectWatches (0 , oldWatches , oldRoot )
513+ newWatches := map [<- chan struct {}]int {}
514+ collectWatches (0 , newWatches , newRoot )
515+
516+ // Any nodes that are not part of the new tree must have their watch channels closed.
517+ for watch := range newWatches {
518+ delete (oldWatches , watch )
519+ }
520+ for watch , depth := range oldWatches {
521+ select {
522+ case <- watch :
523+ default :
524+ oldRoot .printTree (0 )
525+ fmt .Println ("---" )
526+ newRoot .printTree (0 )
527+ panic (fmt .Sprintf ("dropped watch channel %p at depth %d not closed" , watch , depth ))
528+ }
529+ }
530+ }
0 commit comments