Rod Hynes 6 лет назад
Родитель
Сommit
b60b74ef04
2 измененных файлов с 51 добавлено и 38 удалено
  1. 48 35
      vendor/github.com/Psiphon-Labs/bolt/tx.go
  2. 3 3
      vendor/vendor.json

+ 48 - 35
vendor/github.com/Psiphon-Labs/bolt/tx.go

@@ -383,7 +383,16 @@ func (tx *Tx) CopyFile(path string, mode os.FileMode) error {
 // the same time.
 func (tx *Tx) Check() <-chan error {
 	ch := make(chan error)
-	go tx.check(ch)
+	// [Psiphon]
+	// This code is modified to use the single-error check function while
+	// preserving the existing bolt Check API.
+	go func() {
+		err := tx.check()
+		if err != nil {
+			ch <- err
+		}
+		close(ch)
+	}()
 	return ch
 }
 
@@ -391,36 +400,23 @@ func (tx *Tx) Check() <-chan error {
 // SynchronousCheck performs the Check function in the current goroutine,
 // allowing the caller to recover from any panics or faults.
 func (tx *Tx) SynchronousCheck() error {
-	checkErrChannel := make(chan error)
-
-	// tx.check may send multiple errors to the channel, and we must consume them
-	// all to ensure tx.check terminates. Only the first error is returned from
-	// SynchronousCheck.
-	firstErrChannel := make(chan error)
-	go func() {
-		var err error
-		for nextErr := range checkErrChannel {
-			if err != nil {
-				err = nextErr
-			}
-		}
-		firstErrChannel <- err
-	}()
-
-	// Invoke bolt code that may panic/segfault in the current goroutine.
-	tx.check(checkErrChannel)
-
-	return <-firstErrChannel
+	return tx.check()
 }
 
-func (tx *Tx) check(ch chan error) {
+// [Psiphon]
+// check is modified to stop and return on the first error. This prevents some
+// long running loops, perhaps due to looping based on corrupt data, that we
+// have observed when testing check against corrupted database files. Since
+// Psiphon will recover by resetting (deleting) the datastore on any error,
+// more than one error is not useful information in our case.
+func (tx *Tx) check() error {
 	// Check if any pages are double freed.
 	freed := make(map[pgid]bool)
 	all := make([]pgid, tx.db.freelist.count())
 	tx.db.freelist.copyall(all)
 	for _, id := range all {
 		if freed[id] {
-			ch <- fmt.Errorf("page %d: already freed", id)
+			return fmt.Errorf("page %d: already freed", id)
 		}
 		freed[id] = true
 	}
@@ -434,53 +430,70 @@ func (tx *Tx) check(ch chan error) {
 	}
 
 	// Recursively check buckets.
-	tx.checkBucket(&tx.root, reachable, freed, ch)
+	err := tx.checkBucket(&tx.root, reachable, freed)
+	if err != nil {
+		return err
+	}
 
 	// Ensure all pages below high water mark are either reachable or freed.
 	for i := pgid(0); i < tx.meta.pgid; i++ {
 		_, isReachable := reachable[i]
 		if !isReachable && !freed[i] {
-			ch <- fmt.Errorf("page %d: unreachable unfreed", int(i))
+			return fmt.Errorf("page %d: unreachable unfreed", int(i))
 		}
 	}
 
-	// Close the channel to signal completion.
-	close(ch)
+	return nil
 }
 
-func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool, ch chan error) {
+// [Psiphon]
+// checkBucket is modified to stop and return on the first error.
+func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool) error {
 	// Ignore inline buckets.
 	if b.root == 0 {
-		return
+		return nil
 	}
 
+	var err error
+
 	// Check every page used by this bucket.
 	b.tx.forEachPage(b.root, 0, func(p *page, _ int) {
 		if p.id > tx.meta.pgid {
-			ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid))
+			err = fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid))
+			return
 		}
 
 		// Ensure each page is only referenced once.
 		for i := pgid(0); i <= pgid(p.overflow); i++ {
 			var id = p.id + i
 			if _, ok := reachable[id]; ok {
-				ch <- fmt.Errorf("page %d: multiple references", int(id))
+				err = fmt.Errorf("page %d: multiple references", int(id))
+				return
 			}
 			reachable[id] = p
 		}
 
 		// We should only encounter un-freed leaf and branch pages.
 		if freed[p.id] {
-			ch <- fmt.Errorf("page %d: reachable freed", int(p.id))
+			err = fmt.Errorf("page %d: reachable freed", int(p.id))
+			return
 		} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 {
-			ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ())
+			err = fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ())
+			return
 		}
 	})
 
+	if err != nil {
+		return err
+	}
+
 	// Check each bucket within this bucket.
-	_ = b.ForEach(func(k, v []byte) error {
+	return b.ForEach(func(k, v []byte) error {
 		if child := b.Bucket(k); child != nil {
-			tx.checkBucket(child, reachable, freed, ch)
+			err := tx.checkBucket(child, reachable, freed)
+			if err != nil {
+				return err
+			}
 		}
 		return nil
 	})

+ 3 - 3
vendor/vendor.json

@@ -33,10 +33,10 @@
 			"revisionTime": "2017-02-28T16:03:01Z"
 		},
 		{
-			"checksumSHA1": "v2r1JWhVzxu8q099mzH+VNJ9GiI=",
+			"checksumSHA1": "I0Gc6+Xq9KQF6ifkVEet1leubD4=",
 			"path": "github.com/Psiphon-Labs/bolt",
-			"revision": "7494fc3896a47cb6732b87e0b18312cb05ca241a",
-			"revisionTime": "2019-07-30T19:27:05Z"
+			"revision": "94750aa2185e6ee4217105064949acace0156564",
+			"revisionTime": "2019-07-31T17:17:12Z"
 		},
 		{
 			"checksumSHA1": "d3DwsdacdFn1/KCG/2uPV1PwR3s=",