github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/journal_manager_util.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"errors"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbfs/data"
    12  	"github.com/keybase/client/go/kbfs/tlf"
    13  	"github.com/keybase/client/go/logger"
    14  	"golang.org/x/net/context"
    15  	"golang.org/x/sync/errgroup"
    16  )
    17  
    18  // GetJournalManager returns the JournalManager tied to a particular
    19  // config.
    20  func GetJournalManager(getter blockServerGetter) (*JournalManager, error) {
    21  	bserver := getter.BlockServer()
    22  	jbserver, ok := bserver.(journalBlockServer)
    23  	if !ok {
    24  		return nil, errors.New("Write journal not enabled")
    25  	}
    26  	return jbserver.jManager, nil
    27  }
    28  
    29  // TLFJournalEnabled returns true if journaling is enabled for the
    30  // given TLF.
    31  func TLFJournalEnabled(getter blockServerGetter, tlfID tlf.ID) bool {
    32  	if jManager, err := GetJournalManager(getter); err == nil {
    33  		return jManager.JournalEnabled(tlfID)
    34  	}
    35  	return false
    36  }
    37  
    38  // WaitForTLFJournal waits for the corresponding journal to flush, if
    39  // one exists.
    40  func WaitForTLFJournal(ctx context.Context, config Config, tlfID tlf.ID,
    41  	log logger.Logger) error {
    42  	if jManager, err := GetJournalManager(config); err == nil {
    43  		log.CDebugf(ctx, "Waiting for journal to flush")
    44  		if err := jManager.Wait(ctx, tlfID); err != nil {
    45  			return err
    46  		}
    47  	}
    48  	return nil
    49  }
    50  
    51  // FillInJournalStatusUnflushedPaths adds the unflushed paths to the
    52  // given journal status.
    53  func FillInJournalStatusUnflushedPaths(ctx context.Context, config Config,
    54  	jStatus *JournalManagerStatus, tlfIDs []tlf.ID) error {
    55  	if len(tlfIDs) == 0 {
    56  		// Nothing to do.
    57  		return nil
    58  	}
    59  
    60  	// Get the folder statuses in parallel.
    61  	eg, groupCtx := errgroup.WithContext(ctx)
    62  	statusesToFetch := make(chan tlf.ID, len(tlfIDs))
    63  	unflushedPaths := make(chan []string, len(tlfIDs))
    64  	storedBytes := make(chan int64, len(tlfIDs))
    65  	unflushedBytes := make(chan int64, len(tlfIDs))
    66  	endEstimates := make(chan *time.Time, len(tlfIDs))
    67  	errIncomplete := errors.New("Incomplete status")
    68  	statusFn := func() error {
    69  		for tlfID := range statusesToFetch {
    70  			select {
    71  			case <-groupCtx.Done():
    72  				return groupCtx.Err()
    73  			default:
    74  			}
    75  
    76  			status, _, err := config.KBFSOps().FolderStatus(
    77  				groupCtx, data.FolderBranch{Tlf: tlfID, Branch: data.MasterBranch})
    78  			if err != nil {
    79  				return err
    80  			}
    81  			if status.Journal == nil {
    82  				continue
    83  			}
    84  			up := status.Journal.UnflushedPaths
    85  			unflushedPaths <- up
    86  			if len(up) > 0 && up[len(up)-1] == incompleteUnflushedPathsMarker {
    87  				// There were too many paths to process.  Return an
    88  				// error to stop the other statuses since we have
    89  				// enough to return now.
    90  				return errIncomplete
    91  			}
    92  			storedBytes <- status.Journal.StoredBytes
    93  			unflushedBytes <- status.Journal.UnflushedBytes
    94  			endEstimates <- status.Journal.EndEstimate
    95  		}
    96  		return nil
    97  	}
    98  
    99  	// Do up to 10 statuses at a time.
   100  	numWorkers := len(tlfIDs)
   101  	if numWorkers > 10 {
   102  		numWorkers = 10
   103  	}
   104  	for i := 0; i < numWorkers; i++ {
   105  		eg.Go(statusFn)
   106  	}
   107  	for _, tlfID := range tlfIDs {
   108  		statusesToFetch <- tlfID
   109  	}
   110  	close(statusesToFetch)
   111  	if err := eg.Wait(); err != nil && err != errIncomplete {
   112  		return err
   113  	}
   114  	close(unflushedPaths)
   115  	close(storedBytes)
   116  	close(unflushedBytes)
   117  	close(endEstimates)
   118  
   119  	// Aggregate all the paths together, but only allow one incomplete
   120  	// marker, at the very end.
   121  	incomplete := false
   122  	for up := range unflushedPaths {
   123  		for _, p := range up {
   124  			if p == incompleteUnflushedPathsMarker {
   125  				incomplete = true
   126  				continue
   127  			}
   128  			jStatus.UnflushedPaths = append(jStatus.UnflushedPaths, p)
   129  		}
   130  	}
   131  	if incomplete {
   132  		jStatus.UnflushedPaths = append(jStatus.UnflushedPaths,
   133  			incompleteUnflushedPathsMarker)
   134  	} else {
   135  		// Replace the existing unflushed byte count with one
   136  		// that's guaranteed consistent with the unflushed
   137  		// paths, and also replace the existing stored byte
   138  		// count with one that's guaranteed consistent with
   139  		// the new unflushed byte count.
   140  		jStatus.StoredBytes = 0
   141  		for sb := range storedBytes {
   142  			jStatus.StoredBytes += sb
   143  		}
   144  		jStatus.UnflushedBytes = 0
   145  		for ub := range unflushedBytes {
   146  			jStatus.UnflushedBytes += ub
   147  		}
   148  		// Pick the latest end estimate.
   149  		for e := range endEstimates {
   150  			if e != nil &&
   151  				(jStatus.EndEstimate == nil || jStatus.EndEstimate.Before(*e)) {
   152  				jStatus.EndEstimate = e
   153  			}
   154  		}
   155  	}
   156  	return nil
   157  }