github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfs/fs_notifications.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 libfs
     6  
     7  import (
     8  	"sync"
     9  
    10  	"github.com/eapache/channels"
    11  	"github.com/keybase/client/go/logger"
    12  
    13  	"golang.org/x/net/context"
    14  )
    15  
    16  // FSNotifications processes notifications (arbitrary functions,
    17  // usually triggered by libkbfs) and lets other objects block on them,
    18  // usually for testing.
    19  type FSNotifications struct {
    20  	log logger.Logger
    21  
    22  	// notifications is a channel for notification functions (which
    23  	// take no value and have no return value).
    24  	notifications channels.Channel
    25  
    26  	// notificationGroup can be used by tests to know when libfs is
    27  	// done processing asynchronous notifications.
    28  	notificationGroup sync.WaitGroup
    29  
    30  	// protects access to the notifications channel member (though not
    31  	// sending/receiving)
    32  	notificationMutex sync.RWMutex
    33  }
    34  
    35  // NewFSNotifications creates a new FSNotifications object.
    36  func NewFSNotifications(log logger.Logger) *FSNotifications {
    37  	return &FSNotifications{log: log}
    38  }
    39  
    40  func (f *FSNotifications) processNotifications(ctx context.Context) {
    41  	for {
    42  		select {
    43  		case <-ctx.Done():
    44  			f.notificationMutex.Lock()
    45  			c := f.notifications
    46  			f.notifications = nil
    47  			f.notificationMutex.Unlock()
    48  			c.Close()
    49  			for range c.Out() {
    50  				// Drain the output queue to allow the Channel close
    51  				// Out() and shutdown any goroutines.
    52  				f.log.CWarningf(ctx,
    53  					"Throwing away notification after shutdown")
    54  			}
    55  			return
    56  		case i := <-f.notifications.Out():
    57  			func() {
    58  				defer f.notificationGroup.Done()
    59  				notifyFn, ok := i.(func())
    60  				if !ok {
    61  					f.log.CWarningf(ctx, "Got a bad notification function: %v", i)
    62  					return
    63  				}
    64  				notifyFn()
    65  			}()
    66  		}
    67  	}
    68  }
    69  
    70  // QueueNotification queues a notification, which must be
    71  // goroutine-safe.
    72  func (f *FSNotifications) QueueNotification(fn func()) {
    73  	f.notificationMutex.RLock()
    74  	defer f.notificationMutex.RUnlock()
    75  	if f.notifications == nil {
    76  		f.log.Warning("Ignoring notification, no available channel")
    77  		return
    78  	}
    79  	f.notificationGroup.Add(1)
    80  	f.notifications.In() <- fn
    81  }
    82  
    83  // LaunchProcessor launches the notification processor.
    84  func (f *FSNotifications) LaunchProcessor(ctx context.Context) {
    85  	f.notificationMutex.Lock()
    86  	defer f.notificationMutex.Unlock()
    87  
    88  	f.log.CDebugf(ctx, "Launching notifications channel")
    89  	// The notifications channel needs to have "infinite" capacity,
    90  	// because otherwise we risk a deadlock between libkbfs and
    91  	// libfs.  The notification processor sends invalidates to the
    92  	// kernel.  In osxfuse 3.X, the kernel can call back into userland
    93  	// during an invalidate (a GetAttr()) call, which in turn takes
    94  	// locks within libkbfs.  So if libkbfs ever gets blocked while
    95  	// trying to enqueue a notification (while it is holding locks),
    96  	// we could have a deadlock.  Yes, if there are too many
    97  	// outstanding notifications we'll run out of memory and crash,
    98  	// but otherwise we risk deadlock.  Which is worse?
    99  	f.notifications = channels.NewInfiniteChannel()
   100  
   101  	// start the notification processor
   102  	go f.processNotifications(ctx)
   103  }
   104  
   105  // Wait waits until all current notifications are done.
   106  func (f *FSNotifications) Wait() {
   107  	f.notificationGroup.Wait()
   108  }