on main help sign in
fix(server): spawn after stale shutdown + handle socket errors
d17d61cChristian Rocha
internal/cmd/root.go+25−11
@@ -382,13 +382,23 @@ func ensureServer(cmd *cobra.Command, hostURL *url.URL) error {
382 switch hostURL.Scheme {
382 switch hostURL.Scheme {
383 case "unix", "npipe":
383 case "unix", "npipe":
384 needsStart := false
384 needsStart := false
385+ _, statErr := os.Stat(hostURL.Host)
386+ switch {
387+ case statErr == nil:
388+ restarted, err := restartIfStale(cmd, hostURL)
389+ if err != nil {
390+ slog.Warn("Failed to check server version", "error", err)
391+ }
392+ needsStart = restarted || err != nil
385− if _, err := os.Stat(hostURL.Host); err != nil && errors.Is(err, fs.ErrNotExist) {
393+ case errors.Is(statErr, fs.ErrNotExist):
386 needsStart = true
394 needsStart = true
387− } else if err == nil {
395+ default:
396+ slog.Warn("Unexpected error stat'ing server socket, attempting cleanup",
397+ "path", hostURL.Host, "error", statErr)
388− if err := restartIfStale(cmd, hostURL); err != nil {
398+ if err := os.Remove(hostURL.Host); err != nil && !errors.Is(err, fs.ErrNotExist) {
389− slog.Warn("Failed to check server version, restarting", "error", err)
399+ return fmt.Errorf("failed to remove stale server socket %q: %v", hostURL.Host, err)
390− needsStart = true
391 }
400 }
401+ needsStart = true
392 }
402 }
393
403
394 if needsStart {
404 if needsStart {
@@ -517,17 +527,21 @@ func probeHealth(ctx context.Context, h *http.Client, reqURL string, hostURL *ur
517// restartIfStale checks whether the running server matches the current
527// restartIfStale checks whether the running server matches the current
518// client version. When they differ, it sends a shutdown command and
528// client version. When they differ, it sends a shutdown command and
519// removes the stale socket so the caller can start a fresh server.
529// removes the stale socket so the caller can start a fresh server.
530+//
531+// It returns restarted=true when it has shut down a stale server and the
532+// caller must spawn a new one. When the server matches the client version
533+// (or the check itself fails), restarted is false.
520−func restartIfStale(cmd *cobra.Command, hostURL *url.URL) error {
534+func restartIfStale(cmd *cobra.Command, hostURL *url.URL) (restarted bool, err error) {
521 c, err := client.NewClient("", hostURL.Scheme, hostURL.Host)
535 c, err := client.NewClient("", hostURL.Scheme, hostURL.Host)
522 if err != nil {
536 if err != nil {
523− return err
537+ return false, err
524 }
538 }
525 vi, err := c.VersionInfo(cmd.Context())
539 vi, err := c.VersionInfo(cmd.Context())
526 if err != nil {
540 if err != nil {
527− return err
541+ return false, err
528 }
542 }
529 if vi.Version == version.Version {
543 if vi.Version == version.Version {
530− return nil
544+ return false, nil
531 }
545 }
532 slog.Info("Server version mismatch, restarting",
546 slog.Info("Server version mismatch, restarting",
533 "server", vi.Version,
547 "server", vi.Version,
@@ -541,13 +555,13 @@ func restartIfStale(cmd *cobra.Command, hostURL *url.URL) error {
541 }
555 }
542 select {
556 select {
543 case <-cmd.Context().Done():
557 case <-cmd.Context().Done():
544− return cmd.Context().Err()
558+ return true, cmd.Context().Err()
545 case <-time.After(100 * time.Millisecond):
559 case <-time.After(100 * time.Millisecond):
546 }
560 }
547 }
561 }
548 // Force-remove if the socket is still lingering.
562 // Force-remove if the socket is still lingering.
549 _ = os.Remove(hostURL.Host)
563 _ = os.Remove(hostURL.Host)
550− return nil
564+ return true, nil
551}
565}
552
566
553var safeNameRegexp = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
567var safeNameRegexp = regexp.MustCompile(`[^a-zA-Z0-9._-]`)