github.com/balzaczyy/golucene@v0.0.0-20151210033525-d0be9ee89713/core/index/deleteQueue.go (about) 1 package index 2 3 import ( 4 "fmt" 5 "sync" 6 "sync/atomic" 7 ) 8 9 // index/DocumentsWriterDeleteQueue.java 10 11 /* 12 DocumentsWriterDeleteQueue is a non-blocking linked pending deletes 13 queue. In contrast to other queue implementation we only maintain the 14 tail of the queue. A delete queue is always used in a context of a 15 set of DWPTs and a global delete pool. Each of the DWPT and the 16 global pool need to maintain their 'own' head of the queue (as a 17 DeleteSlice instance per DWPT). The difference between the DWPT and 18 the global pool is that the DWPT starts maintaining a head once it 19 has added its first document since for its segments private deletes 20 only the deletes after that document are relevant. The global pool 21 instead starts maintaining the head once this instance is created by 22 taking the sentinel instance as its initial head. 23 24 Since each DeleteSlice maintains its own head and list is only single 25 linked, the garbage collector takes care of pruning the list for us. 26 All nodes in the list that are still relevant should be either 27 directly or indirectly referenced by one of the DWPT's private 28 DeleteSlice or by the global BufferedUpdates slice. 29 30 Each DWPT as well as the global delete pool maintain their private 31 DeleteSlice instance. In the DWPT case, updating a slice is equivalent 32 to atomically finishing the document. The slice update guarantees a 33 "happens before" relationship to all other updates in the same 34 indexing session. When a DWPT updates a document it: 35 36 1. consumes a document and finishes its processing 37 2. updates its private DeleteSlice either by calling updateSlice() or 38 addTermToDeleteSlice() (if the document has a delTerm) 39 3. applies all deletes in the slice to its private BufferedUpdates 40 and resets it 41 4. increments its internal document id 42 43 The DWPT also doesn't apply its current docments delete term until it 44 has updated its delete slice which ensures the consistency of the 45 update. If the update fails before the DeleteSlice could have been 46 updated the deleteTerm will also not be added to its private deletes 47 neither to the global deletes. 48 */ 49 type DocumentsWriterDeleteQueue struct { 50 tail *Node // volatile 51 globalSlice *DeleteSlice 52 globalBufferedUpdates *BufferedUpdates 53 globalBufferLock sync.Locker 54 55 generation int64 56 } 57 58 func newDocumentsWriterDeleteQueue() *DocumentsWriterDeleteQueue { 59 return newDocumentsWriterDeleteQueueWithGeneration(0) 60 } 61 62 func newDocumentsWriterDeleteQueueWithGeneration(generation int64) *DocumentsWriterDeleteQueue { 63 return newDocumentsWriterDeleteQueueWith(newBufferedUpdates(), generation) 64 } 65 66 func newDocumentsWriterDeleteQueueWith(globalBufferedUpdates *BufferedUpdates, generation int64) *DocumentsWriterDeleteQueue { 67 tail := newNode(nil) 68 return &DocumentsWriterDeleteQueue{ 69 globalBufferedUpdates: globalBufferedUpdates, 70 globalBufferLock: &sync.Mutex{}, 71 generation: generation, 72 // we use a sentinel instance as our initial tail. No slice will 73 // ever try to apply this tail since the head is always omitted. 74 tail: tail, // sentinel 75 globalSlice: newDeleteSlice(tail), 76 } 77 } 78 79 /* Invariant for document update */ 80 func (q *DocumentsWriterDeleteQueue) add(term *Term, slice *DeleteSlice) { 81 panic("not implemented yet") 82 } 83 84 func (dq *DocumentsWriterDeleteQueue) freezeGlobalBuffer(callerSlice *DeleteSlice) *FrozenBufferedUpdates { 85 dq.globalBufferLock.Lock() 86 defer dq.globalBufferLock.Unlock() 87 88 // Here we freeze the global buffer so we need to lock it, apply 89 // all deletes in the queue and reset the global slice to let the 90 // GC prune the queue. 91 currentTail := dq.tail 92 // take the current tail and make this local. Any changes after 93 // this call are applied later and not relevant here 94 if callerSlice != nil { 95 // update the callers slices so we are on the same page 96 callerSlice.tail = currentTail 97 } 98 if dq.globalSlice.tail != currentTail { 99 dq.globalSlice.tail = currentTail 100 dq.globalSlice.apply(dq.globalBufferedUpdates, MAX_INT) 101 } 102 103 packet := freezeBufferedUpdates(dq.globalBufferedUpdates, false) 104 dq.globalBufferedUpdates.clear() 105 return packet 106 } 107 108 func (dq *DocumentsWriterDeleteQueue) anyChanges() bool { 109 dq.globalBufferLock.Lock() 110 defer dq.globalBufferLock.Unlock() 111 // check if all items in the global slice were applied 112 // and if the global slice is up-to-date 113 // and if globalBufferedUpdates has changes 114 return dq.globalBufferedUpdates.any() || 115 !dq.globalSlice.isEmpty() || 116 dq.globalSlice.tail != dq.tail || 117 dq.tail.next != nil 118 } 119 120 func (dq *DocumentsWriterDeleteQueue) newSlice() *DeleteSlice { 121 return newDeleteSlice(dq.tail) 122 } 123 124 func (q *DocumentsWriterDeleteQueue) updateSlice(slice *DeleteSlice) bool { 125 if slice.tail != q.tail { // if we are the same just 126 slice.tail = q.tail 127 return true 128 } 129 return false 130 } 131 132 func (dq *DocumentsWriterDeleteQueue) clear() { 133 dq.globalBufferLock.Lock() 134 defer dq.globalBufferLock.Unlock() 135 136 currentTail := dq.tail 137 dq.globalSlice.head, dq.globalSlice.tail = currentTail, currentTail 138 dq.globalBufferedUpdates.clear() 139 } 140 141 func (q *DocumentsWriterDeleteQueue) RamBytesUsed() int64 { 142 return atomic.LoadInt64(&q.globalBufferedUpdates.bytesUsed) 143 } 144 145 func (dq *DocumentsWriterDeleteQueue) String() string { 146 return fmt.Sprintf("DWDQ: [ generation: %v ]", dq.generation) 147 } 148 149 type DeleteSlice struct { 150 head *Node // we don't apply this one 151 tail *Node 152 } 153 154 func newDeleteSlice(currentTail *Node) *DeleteSlice { 155 assert(currentTail != nil) 156 // Initially this is a 0 length slice pointing to the 'current' 157 // tail of the queue. Once we update the slice we only need to 158 // assig the tail and have a new slice 159 return &DeleteSlice{head: currentTail, tail: currentTail} 160 } 161 162 func (ds *DeleteSlice) apply(del *BufferedUpdates, docIDUpto int) { 163 if ds.head == ds.tail { 164 // 0 length slice 165 return 166 } 167 // When we apply a slice we take the head and get its next as our 168 // first item to apply and continue until we applied the tail. If 169 // the head and tail in this slice are not equal then there will be 170 // at least one more non-nil node in the slice! 171 for current := ds.head; current != ds.tail; { 172 current = current.next 173 assert2(current != nil, 174 "slice property violated between the head on the tail must not be a null node") 175 current.apply(del, docIDUpto) 176 } 177 ds.reset() 178 } 179 180 func (ds *DeleteSlice) reset() { 181 // reset to 0 length slice 182 ds.head = ds.tail 183 } 184 185 /* 186 Returns true iff the given item is identical to the item held by the 187 slice's tail, otherwise false. 188 */ 189 func (ds *DeleteSlice) isTailItem(item interface{}) bool { 190 return ds.tail.item == item 191 } 192 193 func (ds *DeleteSlice) isEmpty() bool { 194 return ds.head == ds.tail 195 } 196 197 type Node struct { 198 next *Node // volatile 199 item interface{} 200 } 201 202 func newNode(item interface{}) *Node { 203 return &Node{item: item} 204 } 205 206 func (node *Node) apply(BufferedUpdates *BufferedUpdates, docIDUpto int) { 207 panic("sentinel item must never be applied") 208 }