inversion_tree.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /**
  2. * A thread-safe tree which caches inverted matrices.
  3. *
  4. * Copyright 2016, Peter Collins
  5. */
  6. package reedsolomon
  7. import (
  8. "errors"
  9. "sync"
  10. )
  11. // The tree uses a Reader-Writer mutex to make it thread-safe
  12. // when accessing cached matrices and inserting new ones.
  13. type inversionTree struct {
  14. mutex *sync.RWMutex
  15. root inversionNode
  16. }
  17. type inversionNode struct {
  18. matrix matrix
  19. children []*inversionNode
  20. }
  21. // newInversionTree initializes a tree for storing inverted matrices.
  22. // Note that the root node is the identity matrix as it implies
  23. // there were no errors with the original data.
  24. func newInversionTree(dataShards, parityShards int) inversionTree {
  25. identity, _ := identityMatrix(dataShards)
  26. root := inversionNode{
  27. matrix: identity,
  28. children: make([]*inversionNode, dataShards+parityShards),
  29. }
  30. return inversionTree{
  31. mutex: &sync.RWMutex{},
  32. root: root,
  33. }
  34. }
  35. // GetInvertedMatrix returns the cached inverted matrix or nil if it
  36. // is not found in the tree keyed on the indices of invalid rows.
  37. func (t inversionTree) GetInvertedMatrix(invalidIndices []int) matrix {
  38. // Lock the tree for reading before accessing the tree.
  39. t.mutex.RLock()
  40. defer t.mutex.RUnlock()
  41. // If no invalid indices were give we should return the root
  42. // identity matrix.
  43. if len(invalidIndices) == 0 {
  44. return t.root.matrix
  45. }
  46. // Recursively search for the inverted matrix in the tree, passing in
  47. // 0 as the parent index as we start at the root of the tree.
  48. return t.root.getInvertedMatrix(invalidIndices, 0)
  49. }
  50. // errAlreadySet is returned if the root node matrix is overwritten
  51. var errAlreadySet = errors.New("the root node identity matrix is already set")
  52. // InsertInvertedMatrix inserts a new inverted matrix into the tree
  53. // keyed by the indices of invalid rows. The total number of shards
  54. // is required for creating the proper length lists of child nodes for
  55. // each node.
  56. func (t inversionTree) InsertInvertedMatrix(invalidIndices []int, matrix matrix, shards int) error {
  57. // If no invalid indices were given then we are done because the
  58. // root node is already set with the identity matrix.
  59. if len(invalidIndices) == 0 {
  60. return errAlreadySet
  61. }
  62. if !matrix.IsSquare() {
  63. return errNotSquare
  64. }
  65. // Lock the tree for writing and reading before accessing the tree.
  66. t.mutex.Lock()
  67. defer t.mutex.Unlock()
  68. // Recursively create nodes for the inverted matrix in the tree until
  69. // we reach the node to insert the matrix to. We start by passing in
  70. // 0 as the parent index as we start at the root of the tree.
  71. t.root.insertInvertedMatrix(invalidIndices, matrix, shards, 0)
  72. return nil
  73. }
  74. func (n inversionNode) getInvertedMatrix(invalidIndices []int, parent int) matrix {
  75. // Get the child node to search next from the list of children. The
  76. // list of children starts relative to the parent index passed in
  77. // because the indices of invalid rows is sorted (by default). As we
  78. // search recursively, the first invalid index gets popped off the list,
  79. // so when searching through the list of children, use that first invalid
  80. // index to find the child node.
  81. firstIndex := invalidIndices[0]
  82. node := n.children[firstIndex-parent]
  83. // If the child node doesn't exist in the list yet, fail fast by
  84. // returning, so we can construct and insert the proper inverted matrix.
  85. if node == nil {
  86. return nil
  87. }
  88. // If there's more than one invalid index left in the list we should
  89. // keep searching recursively.
  90. if len(invalidIndices) > 1 {
  91. // Search recursively on the child node by passing in the invalid indices
  92. // with the first index popped off the front. Also the parent index to
  93. // pass down is the first index plus one.
  94. return node.getInvertedMatrix(invalidIndices[1:], firstIndex+1)
  95. }
  96. // If there aren't any more invalid indices to search, we've found our
  97. // node. Return it, however keep in mind that the matrix could still be
  98. // nil because intermediary nodes in the tree are created sometimes with
  99. // their inversion matrices uninitialized.
  100. return node.matrix
  101. }
  102. func (n inversionNode) insertInvertedMatrix(invalidIndices []int, matrix matrix, shards, parent int) {
  103. // As above, get the child node to search next from the list of children.
  104. // The list of children starts relative to the parent index passed in
  105. // because the indices of invalid rows is sorted (by default). As we
  106. // search recursively, the first invalid index gets popped off the list,
  107. // so when searching through the list of children, use that first invalid
  108. // index to find the child node.
  109. firstIndex := invalidIndices[0]
  110. node := n.children[firstIndex-parent]
  111. // If the child node doesn't exist in the list yet, create a new
  112. // node because we have the writer lock and add it to the list
  113. // of children.
  114. if node == nil {
  115. // Make the length of the list of children equal to the number
  116. // of shards minus the first invalid index because the list of
  117. // invalid indices is sorted, so only this length of errors
  118. // are possible in the tree.
  119. node = &inversionNode{
  120. children: make([]*inversionNode, shards-firstIndex),
  121. }
  122. // Insert the new node into the tree at the first index relative
  123. // to the parent index that was given in this recursive call.
  124. n.children[firstIndex-parent] = node
  125. }
  126. // If there's more than one invalid index left in the list we should
  127. // keep searching recursively in order to find the node to add our
  128. // matrix.
  129. if len(invalidIndices) > 1 {
  130. // As above, search recursively on the child node by passing in
  131. // the invalid indices with the first index popped off the front.
  132. // Also the total number of shards and parent index are passed down
  133. // which is equal to the first index plus one.
  134. node.insertInvertedMatrix(invalidIndices[1:], matrix, shards, firstIndex+1)
  135. } else {
  136. // If there aren't any more invalid indices to search, we've found our
  137. // node. Cache the inverted matrix in this node.
  138. node.matrix = matrix
  139. }
  140. }