kodflow/kitsune

View on GitHub
src/services/supervisor/process/process.go

Summary

Maintainability
A
0 mins
Test Coverage
package process

import (
    "fmt"
    "os"
    "os/exec"
    "syscall"
    "time"

    "github.com/kodflow/kitsune/src/internal/kernel/daemon"
)

// Process represents a process that can be started and managed.
type Process struct {
    Name       string             // Name is the name of the process.
    args       []string           // args are the arguments passed to the process.
    command    string             // command is the command used to start the process.
    IsRunning  bool               // IsRunning indicates whether the process is currently running.
    Proc       *exec.Cmd          // Proc is the underlying exec.Cmd instance representing the process.
    pidHandler *daemon.PIDHandler // pidHandler is used to handle the process ID.
}

// Kill terminates the process associated with the Process object.
// It first retrieves the process ID (PID) using the pidHandler.
// If the PID is not 0, it uses os.FindProcess to find the process.
// Then, it calls the Kill method on the process to terminate it.
// If an error occurs during the process termination or while clearing the PID file,
// it returns an error with a descriptive message.
// If the PID is 0, indicating that no process is associated with the Process object,
// it returns nil.
func (p *Process) Kill() error {
    if pid, _ := p.pidHandler.GetPID(); pid != 0 {
        process, err := os.FindProcess(pid)
        if err != nil {
            return err
        }

        err = process.Kill()
        if err != nil {
            return fmt.Errorf("failed to kill process: %v", err)
        }

        err = p.pidHandler.ClearPID()
        if err != nil {
            return fmt.Errorf("failed to remove PID file: %v", err)
        }
    }

    return nil
}

// Restart restarts the process.
// It stops the process, waits for 1 second, and then starts it again.
// If stopping the process or starting it again fails, an error is returned.
func (p *Process) Restart() error {
    err := p.Stop()
    if err != nil {
        return fmt.Errorf("failed to stop process for restart: %v", err)
    }

    time.Sleep(time.Second)

    err = p.Start()
    if err != nil {
        return fmt.Errorf("failed to restart process: %v", err)
    }

    return nil
}

// Start starts the process.
// It returns an error if the process is already running or if it fails to start.
// The function starts the process in a separate goroutine and updates the IsRunning flag accordingly.
// If the process stops, it will be automatically restarted.
func (p *Process) Start() error {
    if p.IsRunning {
        return fmt.Errorf("process %s is already Isrunning", p.Name)
    }

    cmd := exec.Command(p.command, p.args...)
    err := cmd.Start()
    if err != nil {
        return fmt.Errorf("failed to start process %s: %v", p.Name, err)
    }

    p.IsRunning = true
    p.Proc = cmd

    go func() {
        _ = cmd.Wait()
        p.IsRunning = false
        fmt.Printf("process %s has stopped\n", p.Name)
        p.Start()
    }()

    return nil
}

// Stop stops the process.
// It returns an error if the process is not running or if there was an error while stopping the process.
func (p *Process) Stop() error {
    if !p.IsRunning {
        return fmt.Errorf("process %s is not Isrunning", p.Name)
    }

    err := p.Proc.Process.Signal(syscall.SIGTERM)

    if err != nil {
        return fmt.Errorf("failed to stop process %s: %v", p.Name, err)
    }

    p.IsRunning = false
    return nil
}