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  */