github.com/janelia-flyem/dvid@v1.0.0/datatype/labelmap/write.go (about)

     1  package labelmap
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"sync"
     7  
     8  	"github.com/janelia-flyem/dvid/datastore"
     9  	"github.com/janelia-flyem/dvid/datatype/common/downres"
    10  	"github.com/janelia-flyem/dvid/datatype/common/labels"
    11  	"github.com/janelia-flyem/dvid/datatype/imageblk"
    12  	"github.com/janelia-flyem/dvid/dvid"
    13  	"github.com/janelia-flyem/dvid/server"
    14  	"github.com/janelia-flyem/dvid/storage"
    15  )
    16  
    17  type putOperation struct {
    18  	data       []byte // the full label volume sent to PUT
    19  	scale      uint8
    20  	subvol     *dvid.Subvolume
    21  	indexZYX   dvid.IndexZYX
    22  	version    dvid.VersionID
    23  	mutate     bool   // if false, we just ingest without needing to GET previous value
    24  	mutID      uint64 // should be unique within a server's uptime.
    25  	downresMut *downres.Mutation
    26  	blockCh    chan blockChange
    27  }
    28  
    29  // PutLabels persists voxels from a subvolume into the storage engine.  This involves transforming
    30  // a supplied volume of uint64 with known geometry into many labels.Block that tiles the subvolume.
    31  // Messages are sent to subscribers for ingest events.
    32  func (d *Data) PutLabels(v dvid.VersionID, subvol *dvid.Subvolume, data []byte, roiname dvid.InstanceName, mutate bool) error {
    33  	if subvol.DataShape().ShapeDimensions() != 3 {
    34  		return fmt.Errorf("cannot store labels for data %q in non 3D format", d.DataName())
    35  	}
    36  
    37  	// Make sure data is block-aligned
    38  	if !dvid.BlockAligned(subvol, d.BlockSize()) {
    39  		return fmt.Errorf("cannot store labels for data %q in non-block aligned geometry %s -> %s", d.DataName(), subvol.StartPoint(), subvol.EndPoint())
    40  	}
    41  
    42  	// Make sure the received data buffer is of appropriate size.
    43  	labelBytes := subvol.Size().Prod() * 8
    44  	if labelBytes != int64(len(data)) {
    45  		return fmt.Errorf("expected %d bytes for data %q label PUT but only received %d bytes", labelBytes, d.DataName(), len(data))
    46  	}
    47  
    48  	r, err := imageblk.GetROI(v, roiname, subvol)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	// Only do voxel-based mutations one at a time.  This lets us remove handling for block-level concurrency.
    54  	d.voxelMu.Lock()
    55  	defer d.voxelMu.Unlock()
    56  
    57  	// Keep track of changing extents, labels and mark repo as dirty if changed.
    58  	var extentChanged bool
    59  	defer func() {
    60  		if extentChanged {
    61  			err := datastore.SaveDataByVersion(v, d)
    62  			if err != nil {
    63  				dvid.Infof("Error in trying to save repo on change: %v\n", err)
    64  			}
    65  		}
    66  	}()
    67  
    68  	// Track point extents
    69  	ctx := datastore.NewVersionedCtx(d, v)
    70  	extents := d.Extents()
    71  	if extents.AdjustPoints(subvol.StartPoint(), subvol.EndPoint()) {
    72  		extentChanged = true
    73  		if err := d.PostExtents(ctx, extents.MinPoint, extents.MaxPoint); err != nil {
    74  			return err
    75  		}
    76  	}
    77  
    78  	// extract buffer interface if it exists
    79  	var putbuffer storage.RequestBuffer
    80  	store, err := datastore.GetOrderedKeyValueDB(d)
    81  	if err != nil {
    82  		return fmt.Errorf("Data type imageblk had error initializing store: %v\n", err)
    83  	}
    84  	if req, ok := store.(storage.KeyValueRequester); ok {
    85  		putbuffer = req.NewBuffer(ctx)
    86  	}
    87  
    88  	// Iterate through index space for this data.
    89  	mutID := d.NewMutationID()
    90  	downresMut := downres.NewMutation(d, v, mutID)
    91  
    92  	wg := new(sync.WaitGroup)
    93  
    94  	blockCh := make(chan blockChange, 100)
    95  	svmap, err := getMapping(d, v)
    96  	if err != nil {
    97  		return fmt.Errorf("PutLabels couldn't get mapping for data %q, version %d: %v", d.DataName(), v, err)
    98  	}
    99  	go d.aggregateBlockChanges(v, svmap, blockCh)
   100  
   101  	blocks := 0
   102  	for it, err := subvol.NewIndexZYXIterator(d.BlockSize()); err == nil && it.Valid(); it.NextSpan() {
   103  		i0, i1, err := it.IndexSpan()
   104  		if err != nil {
   105  			close(blockCh)
   106  			return err
   107  		}
   108  		ptBeg := i0.Duplicate().(dvid.ChunkIndexer)
   109  		ptEnd := i1.Duplicate().(dvid.ChunkIndexer)
   110  
   111  		begX := ptBeg.Value(0)
   112  		endX := ptEnd.Value(0)
   113  
   114  		if extents.AdjustIndices(ptBeg, ptEnd) {
   115  			extentChanged = true
   116  		}
   117  
   118  		wg.Add(int(endX-begX) + 1)
   119  		c := dvid.ChunkPoint3d{begX, ptBeg.Value(1), ptBeg.Value(2)}
   120  		for x := begX; x <= endX; x++ {
   121  			c[0] = x
   122  			curIndex := dvid.IndexZYX(c)
   123  
   124  			// Don't PUT if this index is outside a specified ROI
   125  			if r != nil && r.Iter != nil && !r.Iter.InsideFast(curIndex) {
   126  				wg.Done()
   127  				continue
   128  			}
   129  
   130  			putOp := &putOperation{
   131  				data:       data,
   132  				subvol:     subvol,
   133  				indexZYX:   curIndex,
   134  				version:    v,
   135  				mutate:     mutate,
   136  				mutID:      mutID,
   137  				downresMut: downresMut,
   138  				blockCh:    blockCh,
   139  			}
   140  			server.CheckChunkThrottling()
   141  			go d.putChunk(putOp, wg, putbuffer)
   142  			blocks++
   143  		}
   144  	}
   145  	wg.Wait()
   146  	close(blockCh)
   147  
   148  	// if a bufferable op, flush
   149  	if putbuffer != nil {
   150  		putbuffer.Flush()
   151  	}
   152  
   153  	return downresMut.Execute()
   154  }
   155  
   156  // Puts a chunk of data as part of a mapped operation.
   157  // Only some multiple of the # of CPU cores can be used for chunk handling before
   158  // it waits for chunk processing to abate via the buffered server.HandlerToken channel.
   159  func (d *Data) putChunk(op *putOperation, wg *sync.WaitGroup, putbuffer storage.RequestBuffer) {
   160  	defer func() {
   161  		// After processing a chunk, return the token.
   162  		server.HandlerToken <- 1
   163  
   164  		// Notify the requestor that this chunk is done.
   165  		wg.Done()
   166  	}()
   167  
   168  	bcoord := op.indexZYX.ToIZYXString()
   169  	ctx := datastore.NewVersionedCtx(d, op.version)
   170  
   171  	// If we are mutating, get the previous label Block
   172  	var scale uint8
   173  	var oldBlock *labels.PositionedBlock
   174  	if op.mutate {
   175  		var err error
   176  		if oldBlock, err = d.getLabelPositionedBlock(ctx, scale, bcoord); err != nil {
   177  			dvid.Errorf("Unable to load previous block in %q, key %v: %v\n", d.DataName(), bcoord, err)
   178  			return
   179  		}
   180  	}
   181  
   182  	// Get the current label Block from the received label array
   183  	blockSize, ok := d.BlockSize().(dvid.Point3d)
   184  	if !ok {
   185  		dvid.Errorf("can't putChunk() on data %q with non-3d block size: %s", d.DataName(), d.BlockSize())
   186  		return
   187  	}
   188  	curBlock, err := labels.SubvolumeToBlock(op.subvol, op.data, op.indexZYX, blockSize)
   189  	if err != nil {
   190  		dvid.Errorf("error creating compressed block from label array at %s", op.subvol)
   191  		return
   192  	}
   193  	go d.updateBlockMaxLabel(op.version, curBlock)
   194  
   195  	blockData, _ := curBlock.MarshalBinary()
   196  	serialization, err := dvid.SerializeData(blockData, d.Compression(), d.Checksum())
   197  	if err != nil {
   198  		dvid.Errorf("Unable to serialize block in %q: %v\n", d.DataName(), err)
   199  		return
   200  	}
   201  
   202  	store, err := datastore.GetOrderedKeyValueDB(d)
   203  	if err != nil {
   204  		dvid.Errorf("Data type imageblk had error initializing store: %v\n", err)
   205  		return
   206  	}
   207  
   208  	callback := func(ready chan error) {
   209  		if ready != nil {
   210  			if resperr := <-ready; resperr != nil {
   211  				dvid.Errorf("Unable to PUT voxel data for block %v: %v\n", bcoord, resperr)
   212  				return
   213  			}
   214  		}
   215  		var event string
   216  		var delta interface{}
   217  		if oldBlock != nil && op.mutate {
   218  			event = labels.MutateBlockEvent
   219  			block := MutatedBlock{op.mutID, bcoord, &(oldBlock.Block), curBlock}
   220  			d.handleBlockMutate(op.version, op.blockCh, block)
   221  			delta = block
   222  		} else {
   223  			event = labels.IngestBlockEvent
   224  			block := IngestedBlock{op.mutID, bcoord, curBlock}
   225  			d.handleBlockIndexing(op.version, op.blockCh, block)
   226  			delta = block
   227  		}
   228  		if err := op.downresMut.BlockMutated(bcoord, curBlock); err != nil {
   229  			dvid.Errorf("data %q publishing downres: %v\n", d.DataName(), err)
   230  		}
   231  		evt := datastore.SyncEvent{d.DataUUID(), event}
   232  		msg := datastore.SyncMessage{event, op.version, delta}
   233  		if err := datastore.NotifySubscribers(evt, msg); err != nil {
   234  			dvid.Errorf("Unable to notify subscribers of event %s in %s\n", event, d.DataName())
   235  		}
   236  	}
   237  
   238  	// put data -- use buffer if available
   239  	tk := NewBlockTKeyByCoord(op.scale, bcoord)
   240  	if putbuffer != nil {
   241  		ready := make(chan error, 1)
   242  		go callback(ready)
   243  		putbuffer.PutCallback(ctx, tk, serialization, ready)
   244  	} else {
   245  		if err := store.Put(ctx, tk, serialization); err != nil {
   246  			dvid.Errorf("Unable to PUT voxel data for block %s: %v\n", bcoord, err)
   247  			return
   248  		}
   249  		callback(nil)
   250  	}
   251  }
   252  
   253  // Writes a XY image into the blocks that intersect it.  This function assumes the
   254  // blocks have been allocated and if necessary, filled with old data.
   255  func (d *Data) writeXYImage(v dvid.VersionID, vox *imageblk.Voxels, b storage.TKeyValues) (extentChanged bool, err error) {
   256  
   257  	// Setup concurrency in image -> block transfers.
   258  	var wg sync.WaitGroup
   259  	defer wg.Wait()
   260  
   261  	// Iterate through index space for this data using ZYX ordering.
   262  	blockSize := d.BlockSize()
   263  	var startingBlock int32
   264  
   265  	for it, err := vox.NewIndexIterator(blockSize); err == nil && it.Valid(); it.NextSpan() {
   266  		indexBeg, indexEnd, err := it.IndexSpan()
   267  		if err != nil {
   268  			return extentChanged, err
   269  		}
   270  
   271  		ptBeg := indexBeg.Duplicate().(dvid.ChunkIndexer)
   272  		ptEnd := indexEnd.Duplicate().(dvid.ChunkIndexer)
   273  
   274  		// Track point extents
   275  		if d.Extents().AdjustIndices(ptBeg, ptEnd) {
   276  			extentChanged = true
   277  		}
   278  
   279  		// Do image -> block transfers in concurrent goroutines.
   280  		begX := ptBeg.Value(0)
   281  		endX := ptEnd.Value(0)
   282  
   283  		server.CheckChunkThrottling()
   284  		wg.Add(1)
   285  		go func(blockNum int32) {
   286  			c := dvid.ChunkPoint3d{begX, ptBeg.Value(1), ptBeg.Value(2)}
   287  			for x := begX; x <= endX; x++ {
   288  				c[0] = x
   289  				curIndex := dvid.IndexZYX(c)
   290  				b[blockNum].K = NewBlockTKey(0, &curIndex)
   291  
   292  				// Write this slice data into the block.
   293  				vox.WriteBlock(&(b[blockNum]), blockSize)
   294  				blockNum++
   295  			}
   296  			server.HandlerToken <- 1
   297  			wg.Done()
   298  		}(startingBlock)
   299  
   300  		startingBlock += (endX - begX + 1)
   301  	}
   302  	return
   303  }
   304  
   305  // KVWriteSize is the # of key-value pairs we will write as one atomic batch write.
   306  const KVWriteSize = 500
   307  
   308  // TODO -- Clean up all the writing and simplify now that we have block-aligned writes.
   309  // writeBlocks ingests blocks of voxel data asynchronously using batch writes.
   310  func (d *Data) writeBlocks(v dvid.VersionID, b storage.TKeyValues, wg1, wg2 *sync.WaitGroup) error {
   311  	batcher, err := datastore.GetKeyValueBatcher(d)
   312  	if err != nil {
   313  		return err
   314  	}
   315  
   316  	preCompress, postCompress := 0, 0
   317  	blockSize := d.BlockSize().(dvid.Point3d)
   318  
   319  	ctx := datastore.NewVersionedCtx(d, v)
   320  	evt := datastore.SyncEvent{d.DataUUID(), labels.IngestBlockEvent}
   321  
   322  	server.CheckChunkThrottling()
   323  	blockCh := make(chan blockChange, 100)
   324  	svmap, err := getMapping(d, v)
   325  	if err != nil {
   326  		return fmt.Errorf("writeBlocks couldn't get mapping for data %q, version %d: %v", d.DataName(), v, err)
   327  	}
   328  	go d.aggregateBlockChanges(v, svmap, blockCh)
   329  	go func() {
   330  		defer func() {
   331  			wg1.Done()
   332  			wg2.Done()
   333  			dvid.Debugf("Wrote voxel blocks.  Before %s: %d bytes.  After: %d bytes\n", d.Compression(), preCompress, postCompress)
   334  			close(blockCh)
   335  			server.HandlerToken <- 1
   336  		}()
   337  
   338  		mutID := d.NewMutationID()
   339  		batch := batcher.NewBatch(ctx)
   340  		for i, block := range b {
   341  			preCompress += len(block.V)
   342  			lblBlock, err := labels.MakeBlock(block.V, blockSize)
   343  			if err != nil {
   344  				dvid.Errorf("unable to compute dvid block compression in %q: %v\n", d.DataName(), err)
   345  				return
   346  			}
   347  			go d.updateBlockMaxLabel(v, lblBlock)
   348  
   349  			compressed, _ := lblBlock.MarshalBinary()
   350  			serialization, err := dvid.SerializeData(compressed, d.Compression(), d.Checksum())
   351  			if err != nil {
   352  				dvid.Errorf("Unable to serialize block in %q: %v\n", d.DataName(), err)
   353  				return
   354  			}
   355  			postCompress += len(serialization)
   356  			batch.Put(block.K, serialization)
   357  
   358  			_, indexZYX, err := DecodeBlockTKey(block.K)
   359  			if err != nil {
   360  				dvid.Errorf("Unable to recover index from block key: %v\n", block.K)
   361  				return
   362  			}
   363  
   364  			block := IngestedBlock{mutID, indexZYX.ToIZYXString(), lblBlock}
   365  			d.handleBlockIndexing(v, blockCh, block)
   366  
   367  			msg := datastore.SyncMessage{labels.IngestBlockEvent, v, block}
   368  			if err := datastore.NotifySubscribers(evt, msg); err != nil {
   369  				dvid.Errorf("Unable to notify subscribers of ChangeBlockEvent in %s\n", d.DataName())
   370  				return
   371  			}
   372  
   373  			// Check if we should commit
   374  			if i%KVWriteSize == KVWriteSize-1 {
   375  				if err := batch.Commit(); err != nil {
   376  					dvid.Errorf("Error on trying to write batch: %v\n", err)
   377  					return
   378  				}
   379  				batch = batcher.NewBatch(ctx)
   380  			}
   381  		}
   382  		if err := batch.Commit(); err != nil {
   383  			dvid.Errorf("Error on trying to write batch: %v\n", err)
   384  			return
   385  		}
   386  	}()
   387  	return nil
   388  }
   389  
   390  func (d *Data) blockChangesExtents(extents *dvid.Extents, bx, by, bz int32) bool {
   391  	blockSize := d.BlockSize().(dvid.Point3d)
   392  	start := dvid.Point3d{bx * blockSize[0], by * blockSize[1], bz * blockSize[2]}
   393  	end := dvid.Point3d{start[0] + blockSize[0] - 1, start[1] + blockSize[1] - 1, start[2] + blockSize[2] - 1}
   394  	return extents.AdjustPoints(start, end)
   395  }
   396  
   397  // storeBlocks reads blocks from io.ReadCloser and puts them in store, handling metadata bookkeeping
   398  // unlike ingestBlocks function.
   399  func (d *Data) storeBlocks(ctx *datastore.VersionedCtx, r io.ReadCloser, scale uint8, downscale bool, compression string, indexing bool) error {
   400  	if r == nil {
   401  		return fmt.Errorf("no data blocks POSTed")
   402  	}
   403  
   404  	if downscale && scale != 0 {
   405  		return fmt.Errorf("cannot downscale blocks of scale > 0")
   406  	}
   407  
   408  	switch compression {
   409  	case "", "blocks":
   410  	default:
   411  		return fmt.Errorf(`compression must be "blocks" (default) at this time`)
   412  	}
   413  
   414  	timedLog := dvid.NewTimeLog()
   415  	store, err := datastore.GetOrderedKeyValueDB(d)
   416  	if err != nil {
   417  		return fmt.Errorf("Data type labelmap had error initializing store: %v", err)
   418  	}
   419  
   420  	// Only do voxel-based mutations one at a time.  This lets us remove handling for block-level concurrency.
   421  	d.voxelMu.Lock()
   422  	defer d.voxelMu.Unlock()
   423  
   424  	d.StartUpdate()
   425  	defer d.StopUpdate()
   426  
   427  	// extract buffer interface if it exists
   428  	var putbuffer storage.RequestBuffer
   429  	if req, ok := store.(storage.KeyValueRequester); ok {
   430  		putbuffer = req.NewBuffer(ctx)
   431  	}
   432  
   433  	mutID := d.NewMutationID()
   434  	var downresMut *downres.Mutation
   435  	if downscale {
   436  		downresMut = downres.NewMutation(d, ctx.VersionID(), mutID)
   437  	}
   438  
   439  	svmap, err := getMapping(d, ctx.VersionID())
   440  	if err != nil {
   441  		return fmt.Errorf("ReceiveBlocks couldn't get mapping for data %q, version %d: %v", d.DataName(), ctx.VersionID(), err)
   442  	}
   443  	var blockCh chan blockChange
   444  	var putWG, processWG sync.WaitGroup
   445  	if indexing {
   446  		blockCh = make(chan blockChange, 100)
   447  		processWG.Add(1)
   448  		go func() {
   449  			d.aggregateBlockChanges(ctx.VersionID(), svmap, blockCh)
   450  			processWG.Done()
   451  		}()
   452  	}
   453  
   454  	callback := func(bcoord dvid.IZYXString, block *labels.Block, ready chan error) {
   455  		if ready != nil {
   456  			if resperr := <-ready; resperr != nil {
   457  				dvid.Errorf("Unable to PUT voxel data for block %v: %v\n", bcoord, resperr)
   458  				return
   459  			}
   460  		}
   461  		event := labels.IngestBlockEvent
   462  		ingestBlock := IngestedBlock{mutID, bcoord, block}
   463  		if scale == 0 {
   464  			if indexing {
   465  				d.handleBlockIndexing(ctx.VersionID(), blockCh, ingestBlock)
   466  			}
   467  			go d.updateBlockMaxLabel(ctx.VersionID(), ingestBlock.Data)
   468  			evt := datastore.SyncEvent{d.DataUUID(), event}
   469  			msg := datastore.SyncMessage{event, ctx.VersionID(), ingestBlock}
   470  			if err := datastore.NotifySubscribers(evt, msg); err != nil {
   471  				dvid.Errorf("Unable to notify subscribers of event %s in %s\n", event, d.DataName())
   472  			}
   473  			if downscale {
   474  				if err := downresMut.BlockMutated(bcoord, block); err != nil {
   475  					dvid.Errorf("data %q publishing downres: %v\n", d.DataName(), err)
   476  				}
   477  			}
   478  		}
   479  
   480  		putWG.Done()
   481  	}
   482  
   483  	if d.Compression().Format() != dvid.Gzip {
   484  		return fmt.Errorf("labelmap %q cannot accept GZIP /blocks POST since it internally uses %s", d.DataName(), d.Compression().Format())
   485  	}
   486  	var extentsChanged bool
   487  	extents, err := d.GetExtents(ctx)
   488  	if err != nil {
   489  		return err
   490  	}
   491  	var numBlocks int
   492  	for {
   493  		block, compressed, bx, by, bz, err := readStreamedBlock(r, scale)
   494  		if err == io.EOF {
   495  			break
   496  		}
   497  		if err != nil {
   498  			return err
   499  		}
   500  		bcoord := dvid.ChunkPoint3d{bx, by, bz}.ToIZYXString()
   501  		tk := NewBlockTKeyByCoord(scale, bcoord)
   502  		if scale == 0 {
   503  			if mod := d.blockChangesExtents(&extents, bx, by, bz); mod {
   504  				extentsChanged = true
   505  			}
   506  			go d.updateBlockMaxLabel(ctx.VersionID(), block)
   507  		}
   508  		serialization, err := dvid.SerializePrecompressedData(compressed, d.Compression(), d.Checksum())
   509  		if err != nil {
   510  			return fmt.Errorf("can't serialize received block %s data: %v", bcoord, err)
   511  		}
   512  		putWG.Add(1)
   513  		if putbuffer != nil {
   514  			ready := make(chan error, 1)
   515  			go callback(bcoord, block, ready)
   516  			putbuffer.PutCallback(ctx, tk, serialization, ready)
   517  		} else {
   518  			if err := store.Put(ctx, tk, serialization); err != nil {
   519  				return fmt.Errorf("Unable to PUT voxel data for block %s: %v", bcoord, err)
   520  			}
   521  			go callback(bcoord, block, nil)
   522  		}
   523  		numBlocks++
   524  	}
   525  
   526  	putWG.Wait()
   527  	if blockCh != nil {
   528  		close(blockCh)
   529  	}
   530  	processWG.Wait()
   531  
   532  	if extentsChanged {
   533  		if err := d.PostExtents(ctx, extents.StartPoint(), extents.EndPoint()); err != nil {
   534  			dvid.Criticalf("could not modify extents for labelmap %q: %v\n", d.DataName(), err)
   535  		}
   536  	}
   537  
   538  	// if a bufferable op, flush
   539  	if putbuffer != nil {
   540  		putbuffer.Flush()
   541  	}
   542  	if downscale {
   543  		if err := downresMut.Execute(); err != nil {
   544  			return err
   545  		}
   546  	}
   547  	timedLog.Infof("Received and stored %d blocks for labelmap %q", numBlocks, d.DataName())
   548  	return nil
   549  }
   550  
   551  // Writes supervoxel blocks without worrying about overlap or computation of indices, syncs, maxlabel, or extents.
   552  // Additional goroutines are not spawned so caller can set concurrency through parallel evocations.
   553  func (d *Data) ingestBlocks(ctx *datastore.VersionedCtx, r io.ReadCloser, scale uint8) error {
   554  	if r == nil {
   555  		return fmt.Errorf("no data blocks POSTed")
   556  	}
   557  
   558  	timedLog := dvid.NewTimeLog()
   559  	store, err := datastore.GetKeyValueDB(d)
   560  	if err != nil {
   561  		return fmt.Errorf("Data type labelmap had error initializing store: %v", err)
   562  	}
   563  
   564  	if d.Compression().Format() != dvid.Gzip {
   565  		return fmt.Errorf("labelmap %q cannot accept GZIP /blocks POST since it internally uses %s", d.DataName(), d.Compression().Format())
   566  	}
   567  
   568  	d.StartUpdate()
   569  	defer d.StopUpdate()
   570  
   571  	var numBlocks int
   572  	for {
   573  		_, compressed, bx, by, bz, err := readStreamedBlock(r, scale)
   574  		if err == io.EOF {
   575  			break
   576  		}
   577  		if err != nil {
   578  			return err
   579  		}
   580  		bcoord := dvid.ChunkPoint3d{bx, by, bz}.ToIZYXString()
   581  		tk := NewBlockTKeyByCoord(scale, bcoord)
   582  		serialization, err := dvid.SerializePrecompressedData(compressed, d.Compression(), d.Checksum())
   583  		if err != nil {
   584  			return fmt.Errorf("can't serialize received block %s data: %v", bcoord, err)
   585  		}
   586  		if err := store.Put(ctx, tk, serialization); err != nil {
   587  			return fmt.Errorf("unable to PUT voxel data for block %s: %v", bcoord, err)
   588  		}
   589  		numBlocks++
   590  	}
   591  
   592  	timedLog.Infof("Received and stored %d blocks for labelmap %q", numBlocks, d.DataName())
   593  	return nil
   594  }