Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 7868360

Browse files
RemcodMstephpalis
authored andcommitted
Ensure tombstones created before kubexit started are read
This commit changes the `Watch` function inside the `tombstone` package to also emit an initial event besides the `fsnotify` events. This initial event is called immediatly when `Watch` is called and the watcher has been setup. This change allows kubexit to detect tombstones written before kubexit was started. This prevents possible race conditions as described by #8. In order for this change to work, the `tombstone.EventHandler` type was changed. It now requires a function with 3 arguments: The graveyard, the tombstone and the operation instead of an `fsnotify.Event`. Reason being that the initial event is not an `fsnotify.Event`. The functions implementing an `tombstone.EventHandler` are changed accordingly. This change on its own introduces a new bug where the tombstone is written as part of an initial event, but the child process will still start because `child.Start()` is being called after the watcher has been setup. To overcome this issue, the shutdown state of the child is tracked in a new flag, which is set if `ShutdownNow()` or `ShutdownWithTimeout()` is executed.
1 parent 179bf8b commit 7868360

File tree

3 files changed

+52
-24
lines changed

3 files changed

+52
-24
lines changed

cmd/kubexit/main.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,11 @@ func onDeathOfAny(deathDeps []string, callback func()) tombstone.EventHandler {
318318
deathDepSet[depName] = struct{}{}
319319
}
320320

321-
return func(event fsnotify.Event) {
322-
if event.Op&fsnotify.Create != fsnotify.Create && event.Op&fsnotify.Write != fsnotify.Write {
323-
// ignore other events
321+
return func(graveyard string, name string, op fsnotify.Op) {
322+
if op != 0 && op&fsnotify.Create != fsnotify.Create && op&fsnotify.Write != fsnotify.Write {
323+
// ignore events other than initial, create and write
324324
return
325325
}
326-
graveyard := filepath.Dir(event.Name)
327-
name := filepath.Base(event.Name)
328326

329327
log.Info("Tombstone modified:", "name", name)
330328
if _, ok := deathDepSet[name]; !ok {

pkg/supervisor/supervisor.go

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Supervisor struct {
1818
cmd *exec.Cmd
1919
sigCh chan os.Signal
2020
startStopLock sync.Mutex
21+
shutdown bool
2122
shutdownTimer *time.Timer
2223
}
2324

@@ -32,13 +33,18 @@ func New(name string, args ...string) *Supervisor {
3233
cmd.Env = os.Environ()
3334
return &Supervisor{
3435
cmd: cmd,
36+
shutdown: false,
3537
}
3638
}
3739

3840
func (s *Supervisor) Start() error {
3941
s.startStopLock.Lock()
4042
defer s.startStopLock.Unlock()
4143

44+
if s.shutdown {
45+
return errors.New("not starting child process: shutdown already started")
46+
}
47+
4248
log.Printf("Starting: %s\n", s)
4349
if err := s.cmd.Start(); err != nil {
4450
return fmt.Errorf("failed to start child process: %v", err)
@@ -90,6 +96,8 @@ func (s *Supervisor) ShutdownNow() error {
9096
s.startStopLock.Lock()
9197
defer s.startStopLock.Unlock()
9298

99+
s.shutdown = true
100+
93101
if !s.isRunning() {
94102
log.Println("Skipping ShutdownNow: child process not running")
95103
return nil
@@ -109,6 +117,8 @@ func (s *Supervisor) ShutdownWithTimeout(timeout time.Duration) error {
109117
s.startStopLock.Lock()
110118
defer s.startStopLock.Unlock()
111119

120+
s.shutdown = true
121+
112122
if !s.isRunning() {
113123
log.Println("Skipping ShutdownWithTimeout: child process not running")
114124
return nil

pkg/tombstone/tombstone.go

+39-19
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import (
55
"encoding/json"
66
"fmt"
77
"io/ioutil"
8+
"log"
89
"os"
910
"path/filepath"
1011
"sync"
1112
"time"
1213

13-
"github.com/karlkfi/kubexit/pkg/log"
14+
kubelog "github.com/karlkfi/kubexit/pkg/log"
1415

1516
"github.com/fsnotify/fsnotify"
1617
"sigs.k8s.io/yaml"
@@ -62,7 +63,7 @@ func (t *Tombstone) RecordBirth() error {
6263
born := time.Now()
6364
t.Born = &born
6465

65-
log.Info("Creating tombstone:", "path", t.Path())
66+
kubelog.Info("Creating tombstone:", "path", t.Path())
6667
err := t.Write()
6768
if err != nil {
6869
return fmt.Errorf("failed to create tombstone: %v", err)
@@ -76,7 +77,7 @@ func (t *Tombstone) RecordDeath(exitCode int) error {
7677
t.Died = &died
7778
t.ExitCode = &code
7879

79-
log.Info("Updating tombstone:", "path", t.Path())
80+
kubelog.Info("Updating tombstone:", "path", t.Path())
8081
err := t.Write()
8182
if err != nil {
8283
return fmt.Errorf("failed to update tombstone: %v", err)
@@ -87,7 +88,7 @@ func (t *Tombstone) RecordDeath(exitCode int) error {
8788
func (t *Tombstone) String() string {
8889
inline, err := json.Marshal(t)
8990
if err != nil {
90-
log.Error(err, "Error: failed to marshal tombstone as json")
91+
kubelog.Error(err, "Error: failed to marshal tombstone as json")
9192
return "{}"
9293
}
9394
return string(inline)
@@ -113,24 +114,24 @@ func Read(graveyard, name string) (*Tombstone, error) {
113114
return &t, nil
114115
}
115116

116-
type EventHandler func(fsnotify.Event)
117+
type EventHandler func(string, string, fsnotify.Op)
117118

118119
// LoggingEventHandler is an example EventHandler that logs fsnotify events
119-
func LoggingEventHandler(event fsnotify.Event) {
120-
if event.Op&fsnotify.Create == fsnotify.Create {
121-
log.Info("Tombstone Watch: file created:", "name", event.Name)
120+
func LoggingEventHandler(graveyard string, tombstone string, op fsnotify.Op) {
121+
if op&fsnotify.Create == fsnotify.Create {
122+
log.Printf("Tombstone Watch: file created: %s/%s\n", graveyard, tombstone)
122123
}
123-
if event.Op&fsnotify.Remove == fsnotify.Remove {
124-
log.Info("Tombstone Watch: file removed:", "name", event.Name)
124+
if op&fsnotify.Remove == fsnotify.Remove {
125+
log.Printf("Tombstone Watch: file removed: %s/%s\n", graveyard, tombstone)
125126
}
126-
if event.Op&fsnotify.Write == fsnotify.Write {
127-
log.Info("Tombstone Watch: file modified:", "name", event.Name)
127+
if op&fsnotify.Write == fsnotify.Write {
128+
log.Printf("Tombstone Watch: file modified: %s/%s\n", graveyard, tombstone)
128129
}
129-
if event.Op&fsnotify.Rename == fsnotify.Rename {
130-
log.Info("Tombstone Watch: file renamed:", "name", event.Name)
130+
if op&fsnotify.Rename == fsnotify.Rename {
131+
log.Printf("Tombstone Watch: file renamed: %s/%s\n", graveyard, tombstone)
131132
}
132-
if event.Op&fsnotify.Chmod == fsnotify.Chmod {
133-
log.Info("Tombstone Watch: file chmoded:", "name", event.Name)
133+
if op&fsnotify.Chmod == fsnotify.Chmod {
134+
log.Printf("Tombstone Watch: file chmoded: %s/%s\n", graveyard, tombstone)
134135
}
135136
}
136137

@@ -147,18 +148,20 @@ func Watch(ctx context.Context, graveyard string, eventHandler EventHandler) err
147148
for {
148149
select {
149150
case <-ctx.Done():
150-
log.Info("Tombstone Watch: done", "graveyard", graveyard)
151+
kubelog.Info("Tombstone Watch: done", "graveyard", graveyard)
151152
return
152153
case event, ok := <-watcher.Events:
153154
if !ok {
154155
return
155156
}
156-
eventHandler(event)
157+
graveyard := filepath.Dir(event.Name)
158+
tombstone := filepath.Base(event.Name)
159+
eventHandler(graveyard, tombstone, event.Op)
157160
case err, ok := <-watcher.Errors:
158161
if !ok {
159162
return
160163
}
161-
log.Error(err, "Tombstone Watch: error", "graveyard", graveyard)
164+
kubelog.Error(err, "Tombstone Watch: error", "graveyard", graveyard)
162165
// TODO: wrap ctx with WithCancel and cancel on terminal errors, if any
163166
}
164167
}
@@ -168,5 +171,22 @@ func Watch(ctx context.Context, graveyard string, eventHandler EventHandler) err
168171
if err != nil {
169172
return fmt.Errorf("failed to add watcher: %v", err)
170173
}
174+
175+
// fire initial events after we started watching, this way no events are ever missed
176+
f, err := os.Open(graveyard)
177+
if err != nil {
178+
return fmt.Errorf("failed to watch graveyard: %v", err)
179+
}
180+
181+
files, err := f.Readdir(-1)
182+
f.Close()
183+
if err != nil {
184+
return fmt.Errorf("failed to watch for initial tombstones: %v", err)
185+
}
186+
187+
for _, file := range files {
188+
eventHandler(graveyard, file.Name(), 0)
189+
}
190+
171191
return nil
172192
}

0 commit comments

Comments
 (0)