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