github.com/janelia-flyem/dvid@v1.0.0/datatype/labelsz/sync.go (about) 1 package labelsz 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/janelia-flyem/dvid/datastore" 10 "github.com/janelia-flyem/dvid/datatype/annotation" 11 "github.com/janelia-flyem/dvid/dvid" 12 "github.com/janelia-flyem/dvid/server" 13 "github.com/janelia-flyem/dvid/storage" 14 ) 15 16 // Number of change messages we can buffer before blocking on sync channel. 17 const syncBufferSize = 1000 18 19 // InitDataHandlers launches goroutines to handle each labelblk instance's syncs. 20 func (d *Data) InitDataHandlers() error { 21 if d.syncCh != nil || d.syncDone != nil { 22 return nil 23 } 24 d.syncCh = make(chan datastore.SyncMessage, syncBufferSize) 25 d.syncDone = make(chan *sync.WaitGroup) 26 27 // Launch handlers of sync events. 28 fmt.Printf("Launching sync event handler for data %q...\n", d.DataName()) 29 go d.processEvents() 30 return nil 31 } 32 33 // Shutdown terminates blocks until syncs are done then terminates background goroutines processing data. 34 func (d *Data) Shutdown(wg *sync.WaitGroup) { 35 if d.syncDone != nil { 36 dwg := new(sync.WaitGroup) 37 dwg.Add(1) 38 d.syncDone <- dwg 39 dwg.Wait() // Block until we are done. 40 } 41 wg.Done() 42 } 43 44 // GetSyncSubs implements the datastore.Syncer interface. Returns a list of subscriptions 45 // to the sync data instance that will notify the receiver. 46 func (d *Data) GetSyncSubs(synced dvid.Data) (datastore.SyncSubs, error) { 47 if d.syncCh == nil { 48 if err := d.InitDataHandlers(); err != nil { 49 return nil, fmt.Errorf("unable to initialize handlers for data %q: %v\n", d.DataName(), err) 50 } 51 } 52 53 subs := datastore.SyncSubs{ 54 datastore.SyncSub{ 55 Event: datastore.SyncEvent{synced.DataUUID(), annotation.ModifyElementsEvent}, 56 Notify: d.DataUUID(), 57 Ch: d.syncCh, 58 }, 59 // datastore.SyncSub{ 60 // Event: datastore.SyncEvent{synced.DataUUID(), annotation.SetElementsEvent}, 61 // Notify: d.DataUUID(), 62 // Ch: d.SyncCh, 63 // }, 64 } 65 return subs, nil 66 } 67 68 // SyncPending returns true if any sync messages are in queue 69 func (d *Data) SyncPending() bool { 70 return len(d.syncCh) > 0 71 } 72 73 // If annotation elements are added or deleted, adjust the label counts. 74 func (d *Data) processEvents() { 75 defer func() { 76 if e := recover(); e != nil { 77 msg := fmt.Sprintf("Panic detected on labelsz sync thread: %+v\n", e) 78 dvid.ReportPanic(msg, server.WebServer()) 79 } 80 }() 81 batcher, err := datastore.GetKeyValueBatcher(d) 82 if err != nil { 83 dvid.Errorf("Exiting sync goroutine for labelsz %q after annotation modifications: %v\n", d.DataName(), err) 84 return 85 } 86 var stop bool 87 var wg *sync.WaitGroup 88 var droppedDeltas uint64 89 for { 90 select { 91 case wg = <-d.syncDone: 92 queued := len(d.syncCh) 93 if queued > 0 { 94 dvid.Infof("Received shutdown signal for %q sync events (%d in queue)\n", d.DataName(), queued) 95 stop = true 96 } else { 97 dvid.Infof("Shutting down sync event handler for instance %q...\n", d.DataName()) 98 wg.Done() 99 return 100 } 101 case msg := <-d.syncCh: 102 if d.uninitialized { 103 droppedDeltas++ 104 if droppedDeltas%100 == 0 { 105 dvid.Criticalf("Dropped %d deltas in past because stats not available.\n", droppedDeltas) 106 } 107 continue 108 } 109 d.StartUpdate() 110 ctx := datastore.NewVersionedCtx(d, msg.Version) 111 switch delta := msg.Delta.(type) { 112 case annotation.DeltaModifyElements: 113 d.modifyElements(ctx, delta, batcher) 114 default: 115 dvid.Criticalf("Cannot sync annotations from modify element. Got unexpected delta: %v\n", msg) 116 } 117 d.StopUpdate() 118 119 if stop && len(d.syncCh) == 0 { 120 dvid.Criticalf("Dropped %d deltas because stats weren't available.\n", droppedDeltas) 121 dvid.Infof("Shutting down sync even handler for instance %q after draining sync events.\n", d.DataName()) 122 wg.Done() 123 return 124 } 125 } 126 } 127 } 128 129 // returned map will only include labels that had previously been seen (has key) 130 func (d *Data) getCounts(ctx *datastore.VersionedCtx, labels map[indexedLabel]int32) (counts map[indexedLabel]uint32, err error) { 131 var store storage.OrderedKeyValueDB 132 store, err = datastore.GetOrderedKeyValueDB(d) 133 if err != nil { 134 return 135 } 136 137 counts = make(map[indexedLabel]uint32, len(labels)) 138 var i IndexType 139 var label uint64 140 var val []byte 141 for il := range labels { 142 i, label, err = decodeIndexedLabel(il) 143 if err != nil { 144 return 145 } 146 147 val, err = store.Get(ctx, NewTypeLabelTKey(i, label)) 148 if err != nil { 149 return 150 } 151 if val == nil { 152 continue 153 } 154 if len(val) != 4 { 155 err = fmt.Errorf("bad size in value for index type %s, label %d: value has length %d", i, label, len(val)) 156 return 157 } 158 counts[il] = binary.LittleEndian.Uint32(val) 159 } 160 return 161 } 162 163 func (d *Data) modifyElements(ctx *datastore.VersionedCtx, delta annotation.DeltaModifyElements, batcher storage.KeyValueBatcher) { 164 t0 := time.Now() 165 mutation := fmt.Sprintf("sync of labelsz %s", d.DataName()) 166 var diagnostic string 167 successful := true 168 169 mods := make(map[indexedLabel]int32) 170 for _, elemPos := range delta.Add { 171 if d.inROI(elemPos.Pos) { 172 i := toIndexedLabel(elemPos) 173 mods[i]++ 174 if elemPos.Kind.IsSynaptic() { 175 i = newIndexedLabel(AllSyn, elemPos.Label) 176 mods[i]++ 177 } 178 } 179 } 180 for _, elemPos := range delta.Del { 181 if d.inROI(elemPos.Pos) { 182 i := toIndexedLabel(elemPos) 183 mods[i]-- 184 if elemPos.Kind.IsSynaptic() { 185 i = newIndexedLabel(AllSyn, elemPos.Label) 186 mods[i]-- 187 } 188 } 189 } 190 191 // d.Lock() 192 // defer d.Unlock() 193 194 // Get old counts for the modified labels. 195 counts, err := d.getCounts(ctx, mods) 196 if err != nil { 197 diagnostic = fmt.Sprintf("labelsz %s couldn't get counts for modified labels: %v\n", d.DataName(), err) 198 dvid.Errorf("labelsz %q couldn't get counts for modified labels: %v\n", d.DataName(), err) 199 successful = false 200 } else { 201 // Modify the keys based on the change in counts, then delete or store. 202 batch := batcher.NewBatch(ctx) 203 for il, change := range mods { 204 if change == 0 { 205 continue 206 } 207 i, label, err := decodeIndexedLabel(il) 208 if err != nil { 209 dvid.Criticalf("couldn't decode indexedLabel %s for modify elements sync of %s: %v\n", il, d.DataName(), err) 210 continue 211 } 212 213 // check if we had prior key that needs to be deleted. 214 count, found := counts[il] 215 if found { 216 batch.Delete(NewTypeSizeLabelTKey(i, count, label)) 217 } 218 219 // add new count 220 if change < 0 && -change > int32(count) { 221 dvid.Criticalf("labelsz %q received element mod that would subtract %d with only count %d! Setting floor at 0.\n", d.DataName(), -change, count) 222 change = int32(-count) 223 } 224 newcount := uint32(int32(count) + change) 225 226 // If it's at zero, we've merged or removed it so delete the count. 227 if newcount == 0 { 228 batch.Delete(NewTypeLabelTKey(i, label)) 229 batch.Delete(NewTypeSizeLabelTKey(i, newcount, label)) 230 continue 231 } 232 233 // store the data. 234 buf := make([]byte, 4) 235 binary.LittleEndian.PutUint32(buf, newcount) 236 batch.Put(NewTypeLabelTKey(i, label), buf) 237 batch.Put(NewTypeSizeLabelTKey(i, newcount, label), nil) 238 } 239 240 if err := batch.Commit(); err != nil { 241 diagnostic = fmt.Sprintf("bad commit in labelsz %s during sync of modify elements: %v\n", d.DataName(), err) 242 dvid.Criticalf("bad commit in labelsz %q during sync of modify elements: %v\n", d.DataName(), err) 243 successful = false 244 } 245 } 246 if server.KafkaAvailable() { 247 t := time.Since(t0) 248 activity := map[string]interface{}{ 249 "time": t0.Unix(), 250 "duration": t.Seconds() * 1000.0, 251 "mutation": mutation, 252 "successful": successful, 253 } 254 if diagnostic != "" { 255 activity["diagnostic"] = diagnostic 256 } 257 storage.LogActivityToKafka(activity) 258 } 259 } 260 261 /* 262 func (d *Data) syncSet(in <-chan datastore.SyncMessage, done <-chan struct{}) { 263 batcher, err := datastore.GetKeyValueBatcher(d) 264 if err != nil { 265 dvid.Errorf("Exiting sync goroutine for labelsz %q after annotation sets: %v\n", d.DataName(), err) 266 return 267 } 268 for msg := range in { 269 select { 270 case <-done: 271 return 272 default: 273 } 274 } 275 } 276 */