From 95ead8ffba312524c233063f89635953c44f2a73 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 29 Apr 2024 10:07:52 -0400 Subject: [PATCH] Restart server on failure when running Windows app (#3985) * app: restart server on failure * fix linter * address comments * refactor log directory creation to be where logs are written * check all log dir creation errors --- app/lifecycle/server.go | 64 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/app/lifecycle/server.go b/app/lifecycle/server.go index 8680e7bc..3c11edb8 100644 --- a/app/lifecycle/server.go +++ b/app/lifecycle/server.go @@ -43,37 +43,36 @@ func getCLIFullPath(command string) string { return command } -func SpawnServer(ctx context.Context, command string) (chan int, error) { - done := make(chan int) - - logDir := filepath.Dir(ServerLogFile) - _, err := os.Stat(logDir) - if errors.Is(err, os.ErrNotExist) { - if err := os.MkdirAll(logDir, 0o755); err != nil { - return done, fmt.Errorf("create ollama server log dir %s: %v", logDir, err) - } - } - +func start(ctx context.Context, command string) (*exec.Cmd, error) { cmd := getCmd(ctx, getCLIFullPath(command)) - // send stdout and stderr to a file stdout, err := cmd.StdoutPipe() if err != nil { - return done, fmt.Errorf("failed to spawn server stdout pipe %s", err) + return nil, fmt.Errorf("failed to spawn server stdout pipe: %w", err) } stderr, err := cmd.StderrPipe() if err != nil { - return done, fmt.Errorf("failed to spawn server stderr pipe %s", err) - } - stdin, err := cmd.StdinPipe() - if err != nil { - return done, fmt.Errorf("failed to spawn server stdin pipe %s", err) + return nil, fmt.Errorf("failed to spawn server stderr pipe: %w", err) } // TODO - rotation logFile, err := os.OpenFile(ServerLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755) if err != nil { - return done, fmt.Errorf("failed to create server log %w", err) + return nil, fmt.Errorf("failed to create server log: %w", err) } + + logDir := filepath.Dir(ServerLogFile) + _, err = os.Stat(logDir) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("stat ollama server log dir %s: %v", logDir, err) + + } + + if err := os.MkdirAll(logDir, 0o755); err != nil { + return nil, fmt.Errorf("create ollama server log dir %s: %v", logDir, err) + } + } + go func() { defer logFile.Close() io.Copy(logFile, stdout) //nolint:errcheck @@ -117,19 +116,33 @@ func SpawnServer(ctx context.Context, command string) (chan int, error) { // run the command and wait for it to finish if err := cmd.Start(); err != nil { - return done, fmt.Errorf("failed to start server %w", err) + return nil, fmt.Errorf("failed to start server %w", err) } if cmd.Process != nil { slog.Info(fmt.Sprintf("started ollama server with pid %d", cmd.Process.Pid)) } slog.Info(fmt.Sprintf("ollama server logs %s", ServerLogFile)) + return cmd, nil +} + +func SpawnServer(ctx context.Context, command string) (chan int, error) { + done := make(chan int) + go func() { // Keep the server running unless we're shuttind down the app crashCount := 0 for { + slog.Info("starting server...") + cmd, err := start(ctx, command) + if err != nil { + crashCount++ + slog.Error(fmt.Sprintf("failed to start server %s", err)) + time.Sleep(500 * time.Millisecond * time.Duration(crashCount)) + continue + } + cmd.Wait() //nolint:errcheck - stdin.Close() var code int if cmd.ProcessState != nil { code = cmd.ProcessState.ExitCode() @@ -143,15 +156,12 @@ func SpawnServer(ctx context.Context, command string) (chan int, error) { default: crashCount++ slog.Warn(fmt.Sprintf("server crash %d - exit code %d - respawning", crashCount, code)) - time.Sleep(500 * time.Millisecond) - if err := cmd.Start(); err != nil { - slog.Error(fmt.Sprintf("failed to restart server %s", err)) - // Keep trying, but back off if we keep failing - time.Sleep(time.Duration(crashCount) * time.Second) - } + time.Sleep(500 * time.Millisecond * time.Duration(crashCount)) + break } } }() + return done, nil }