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 }