fix(serve): reset run dir after taking the kv lock and restore signals on shutdown
- commit
- b9b6781bd6acef2d23788059a801d2f0fe7179f0
- author
- brice · · 2026-10-10 06:41 UTC
- parent
- 620ae64
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +18 -2
M cmd/ermite/datadir.go +8 -2
| @@ -8,14 +8,20 @@ import ( | |||
| 8 | 8 | "path/filepath" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | // prepareDataDir creates the directories of the data root (0700) and | |
| 12 | - | // recreates run/ and run/git-home/, which hold nothing that survives a start. | |
| 11 | + | // prepareDataDir creates the persistent directories of the data root (0700). | |
| 13 | 12 | func prepareDataDir(root string) error { | |
| 14 | 13 | for _, d := range []string{"", "repos", filepath.Join("repos", ".staging"), "trash", "kv"} { | |
| 15 | 14 | if err := os.MkdirAll(filepath.Join(root, d), 0o700); err != nil { | |
| 16 | 15 | return fmt.Errorf("prepare data directory: %w", err) | |
| 17 | 16 | } | |
| 18 | 17 | } | |
| 18 | + | return nil | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // resetRunDir recreates run/ and run/git-home/, which hold nothing that | |
| 22 | + | // survives a start. Call it only once the kv directory lock is held: a second | |
| 23 | + | // instance on the same data root must fail before touching run/. | |
| 24 | + | func resetRunDir(root string) error { | |
| 19 | 25 | run := filepath.Join(root, "run") | |
| 20 | 26 | if err := os.RemoveAll(run); err != nil { | |
| 21 | 27 | return fmt.Errorf("reset %s: %w", run, err) | |
| @@ -8,14 +8,20 @@ import ( | |||||
| 8 | "path/filepath" | 8 | "path/filepath" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | // prepareDataDir creates the directories of the data root (0700) and | 11 | + | // prepareDataDir creates the persistent directories of the data root (0700). |
| 12 | - | // recreates run/ and run/git-home/, which hold nothing that survives a start. | |||
| 13 | func prepareDataDir(root string) error { | 12 | func prepareDataDir(root string) error { | ||
| 14 | for _, d := range []string{"", "repos", filepath.Join("repos", ".staging"), "trash", "kv"} { | 13 | for _, d := range []string{"", "repos", filepath.Join("repos", ".staging"), "trash", "kv"} { | ||
| 15 | if err := os.MkdirAll(filepath.Join(root, d), 0o700); err != nil { | 14 | if err := os.MkdirAll(filepath.Join(root, d), 0o700); err != nil { | ||
| 16 | return fmt.Errorf("prepare data directory: %w", err) | 15 | return fmt.Errorf("prepare data directory: %w", err) | ||
| 17 | } | 16 | } | ||
| 18 | } | 17 | } | ||
| 18 | + | return nil | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | // resetRunDir recreates run/ and run/git-home/, which hold nothing that | |||
| 22 | + | // survives a start. Call it only once the kv directory lock is held: a second | |||
| 23 | + | // instance on the same data root must fail before touching run/. | |||
| 24 | + | func resetRunDir(root string) error { | |||
| 19 | run := filepath.Join(root, "run") | 25 | run := filepath.Join(root, "run") | ||
| 20 | if err := os.RemoveAll(run); err != nil { | 26 | if err := os.RemoveAll(run); err != nil { | ||
| 21 | return fmt.Errorf("reset %s: %w", run, err) | 27 | return fmt.Errorf("reset %s: %w", run, err) | ||
M cmd/ermite/serve.go +10 -0
| @@ -47,6 +47,12 @@ func serve(ctx context.Context) error { | |||
| 47 | 47 | ctx = zerowrap.WithCtx(ctx, log) | |
| 48 | 48 | ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) | |
| 49 | 49 | defer stop() | |
| 50 | + | // Restore the default signal handling as soon as the first signal | |
| 51 | + | // arrives, so a second one kills the process during the shutdown. | |
| 52 | + | go func() { | |
| 53 | + | <-ctx.Done() | |
| 54 | + | stop() | |
| 55 | + | }() | |
| 50 | 56 | ||
| 51 | 57 | log.Info().Str("version", version()).Str("addr", cfg.Addr).Str("data_dir", cfg.DataDir).Msg("starting") | |
| 52 | 58 | ||
| @@ -63,6 +69,10 @@ func serve(ctx context.Context) error { | |||
| 63 | 69 | log.Error().Err(cerr).Msg("close metadata store") | |
| 64 | 70 | } | |
| 65 | 71 | }() | |
| 72 | + | // The kv store holds the directory lock: only now is run/ ours to reset. | |
| 73 | + | if err := resetRunDir(cfg.DataDir); err != nil { | |
| 74 | + | return err | |
| 75 | + | } | |
| 66 | 76 | ||
| 67 | 77 | path := os.Getenv("PATH") | |
| 68 | 78 | if path == "" { | |
| @@ -47,6 +47,12 @@ func serve(ctx context.Context) error { | |||||
| 47 | ctx = zerowrap.WithCtx(ctx, log) | 47 | ctx = zerowrap.WithCtx(ctx, log) | ||
| 48 | ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) | 48 | ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) | ||
| 49 | defer stop() | 49 | defer stop() | ||
| 50 | + | // Restore the default signal handling as soon as the first signal | |||
| 51 | + | // arrives, so a second one kills the process during the shutdown. | |||
| 52 | + | go func() { | |||
| 53 | + | <-ctx.Done() | |||
| 54 | + | stop() | |||
| 55 | + | }() | |||
| 50 | 56 | ||||
| 51 | log.Info().Str("version", version()).Str("addr", cfg.Addr).Str("data_dir", cfg.DataDir).Msg("starting") | 57 | log.Info().Str("version", version()).Str("addr", cfg.Addr).Str("data_dir", cfg.DataDir).Msg("starting") | ||
| 52 | 58 | ||||
| @@ -63,6 +69,10 @@ func serve(ctx context.Context) error { | |||||
| 63 | log.Error().Err(cerr).Msg("close metadata store") | 69 | log.Error().Err(cerr).Msg("close metadata store") | ||
| 64 | } | 70 | } | ||
| 65 | }() | 71 | }() | ||
| 72 | + | // The kv store holds the directory lock: only now is run/ ours to reset. | |||
| 73 | + | if err := resetRunDir(cfg.DataDir); err != nil { | |||
| 74 | + | return err | |||
| 75 | + | } | |||
| 66 | 76 | ||||
| 67 | path := os.Getenv("PATH") | 77 | path := os.Getenv("PATH") | ||
| 68 | if path == "" { | 78 | if path == "" { | ||