Change:
What happened: A race condition in the plugin watcher might happen when a is created, started and immediately stopped and cleaned up. If executed several times, the unit test will expose this issue. The PR addressed part of the problem by serializing the handling of CREATE and DELETE events. However, a race might still happen in the function. describes in detail the problem. How to reproduce it (as minimally and precisely as possible): /sig storage /sig node
/assign

Discussion:
embed = [":go_default_library"], deps = [ "//pkg/kubelet/apis/pluginregistration/v1:go_default_library", <ins> "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", </ins> "//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/k8s.io/klog:go_default_library", ],

} w.fsWatcher = fsWatcher <ins> // Traverse plugin dir and add filesystem watchers before starting the plugin processing goroutine. if err := w.traversePluginDir(w.path); err != nil { w.Stop() return fmt.Errorf("failed to traverse plugin socket path %q, err: %v", w.path, err) } // Traverse deprecated plugin dir, if specified. if len(w.deprecatedPath) != 0 { if err := w.traversePluginDir(w.deprecatedPath); err != nil { w.Stop() return fmt.Errorf("failed to traverse deprecated plugin socket path %q, err: %v", w.deprecatedPath, err) } } </ins> w.wg.Add(1) go func(fsWatcher *fsnotify.Watcher) { defer w.wg.Done() for { select { case event := <-fsWatcher.Events: //TODO: Handle errors by taking corrective measures <del> w.wg.Add(1) func() { defer w.wg.Done() if event.Op&fsnotify.Create == fsnotify.Create { err := w.handleCreateEvent(event) if err != nil { klog.Errorf("error %v when handling create event: %s", err, event) } } else if event.Op&fsnotify.Remove == fsnotify.Remove { err := w.handleDeleteEvent(event) if err != nil { klog.Errorf("error %v when handling delete event: %s", err, event) } </del> <ins> if event.Op&fsnotify.Create == fsnotify.Create { err := w.handleCreateEvent(event) if err != nil { klog.Errorf("error %v when handling create event: %s", err, event) </ins> } <del> return }() </del> <ins> } else if event.Op&fsnotify.Remove == fsnotify.Remove { err := w.handleDeleteEvent(event) if err != nil { klog.Errorf("error %v when handling delete event: %s", err, event) } } </ins> continue case err := <-fsWatcher.Errors: if err != nil {

} }(fsWatcher) <del> // Traverse plugin dir after starting the plugin processing goroutine if err := w.traversePluginDir(w.path); err != nil { w.Stop() return fmt.Errorf("failed to traverse plugin socket path %q, err: %v", w.path, err) } // Traverse deprecated plugin dir, if specified. if len(w.deprecatedPath) != 0 { if err := w.traversePluginDir(w.deprecatedPath); err != nil { w.Stop() return fmt.Errorf("failed to traverse deprecated plugin socket path %q, err: %v", w.deprecatedPath, err) } } </del> return nil }

return fmt.Errorf("failed to watch %s, err: %v", path, err) } case mode&os.ModeSocket != 0: <del> w.wg.Add(1) go func() { defer w.wg.Done() w.fsWatcher.Events <- fsnotify.Event{ Name: path, Op:   fsnotify.Create, } }() </del> <ins> event := fsnotify.Event{ Name: path, Op:   fsnotify.Create, } //TODO: Handle errors by taking corrective measures if err := w.handleCreateEvent(event); err != nil { klog.Errorf("error %v when handling create event: %s", err, event) } </ins> default: klog.V(5).Infof("Ignoring file %s with mode %v", path, mode) }

"github.com/stretchr/testify/require" <ins> "k8s.io/apimachinery/pkg/util/wait" </ins> "k8s.io/klog" registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1" )

plugins[i] = p } <del> w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */) defer func() { require.NoError(t, w.Stop()) }() </del> var wg sync.WaitGroup for i := 0; i < len(plugins); i++ { wg.Add(1)

}(plugins[i]) } <ins> w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */) defer func() { require.NoError(t, w.Stop()) }() </ins> c := make(chan struct{}) go func() { defer close(c)

select { case <-c: return <del> case <-time.After(2 * time.Second): </del> <ins> case <-time.After(wait.ForeverTestTimeout): </ins> t.Fatalf("Timeout while waiting for the plugin registration status") } }

hdlr := NewExampleHandler(supportedVersions, false /* permitDeprecatedDir */) hdlr.AddPluginName(pluginName) <ins> c := make(chan struct{}) go func() { defer close(c) require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName))) require.False(t, waitForPluginRegistrationStatus(t, p.registrationStatus)) }() </ins> w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */) defer func() { require.NoError(t, w.Stop()) }() <del> require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName))) require.False(t, waitForPluginRegistrationStatus(t, p.registrationStatus)) </del> <ins> select { case <-c: return case <-time.After(wait.ForeverTestTimeout): t.Fatalf("Timeout while waiting for the plugin registration status") } </ins> } func waitForPluginRegistrationStatus(t *testing.T, statusChan chan registerapi.RegistrationStatus) bool {

select { case event := <-eventChan: return event == expected <del> case <-time.After(2 * time.Second): </del> <ins> case <-time.After(wait.ForeverTestTimeout): </ins> t.Fatalf("Timed out while waiting for registration status %v", expected) }