<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"time"

	"github.com/go-zero-boilerplate/loggers"

	"github.com/golang-devops/exec-logger/exec_logger_constants"
)

func NewCommandExecer(logger loggers.LoggerStdIO, stdErrIsError bool, timeoutKillDuration time.Duration, runArgs []string) *commandExecer {
	statusHandler := &execStatusHandler{
		localContextFilePath: exec_logger_constants.LOCAL_CONTEXT_FILE_NAME,
		aliveFilePath:        exec_logger_constants.ALIVE_FILE_NAME,
		exitedFilePath:       exec_logger_constants.EXITED_FILE_NAME,
		mustAbortFilePath:    exec_logger_constants.MUST_ABORT_FILE_NAME,
	}

	return &commandExecer{
		logger:              logger,
		logFilePath:         exec_logger_constants.LOG_FILE_NAME,
		stdErrIsError:       stdErrIsError,
		timeoutKillDuration: timeoutKillDuration,
		runArgs:             runArgs,
		statusHandler:       statusHandler,
		stdioHandler:        nil, //Set inside `Run` method
	}
}

type commandExecer struct {
	logger              loggers.LoggerStdIO
	logFilePath         string
	stdErrIsError       bool
	timeoutKillDuration time.Duration
	runArgs             []string
	statusHandler       *execStatusHandler
	stdioHandler        *stdioHandler
}

func (c *commandExecer) abortProcess(cmd *exec.Cmd) {
	defer func() {
		if rec := recover(); rec != nil {
			c.stdioHandler.writeErrorLine(fmt.Sprintf("Kill process attempt recovered, recovery: %+v", rec))
		}
	}()

	pid := cmd.Process.Pid
	force := true
	if killErr := KillProcessTree(pid, force); killErr != nil { //if killErr := cmd.Process.Kill(); killErr != nil {
		c.stdioHandler.writeErrorLine(fmt.Sprintf("Cannot kill process with PID %d, error: %s", pid, killErr.Error()))
	}
	c.stdioHandler.writeFileLine(fmt.Sprintf("Successfully killed process with PID %d", pid))
}

func (c *commandExecer) cleanupBeforeStarting() error {
	if err := os.Remove(c.statusHandler.aliveFilePath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Cannot remove alive file '%s', error: %s", c.statusHandler.aliveFilePath, err.Error())
		}
	}
	if err := os.Remove(c.statusHandler.exitedFilePath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Cannot remove exited file '%s', error: %s", c.statusHandler.exitedFilePath, err.Error())
		}
	}
	if err := os.Remove(c.statusHandler.mustAbortFilePath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Cannot remove must-abort file '%s', error: %s", c.statusHandler.mustAbortFilePath, err.Error())
		}
	}
	return nil
}

func (c *commandExecer) runCommand() (exitCode int, returnErr error) {
	if err := c.cleanupBeforeStarting(); err != nil {
		return -1, err
	}

	cmd := exec.Command(c.runArgs[0], c.runArgs[1:]...)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return -1, err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return -1, err
	}

	err = cmd.Start()
	if err != nil {
		return -1, err
	}

	err = c.statusHandler.WriteLocalContextFile()
	if err != nil {
		c.stdioHandler.writeErrorLine(fmt.Sprintf("Cannot write local context, error: %s", err.Error()))
		//do not want to exit due to this error
	}

	go func(sh *execStatusHandler) {
		for {
			sh.WriteAlive()
			time.Sleep(2 * time.Second)
		}
	}(c.statusHandler)

	go func(sh *execStatusHandler) {
		for {
			if mustAbort, checkErr := sh.CheckMustAbort(); checkErr != nil {
				c.stdioHandler.writeFileLine(fmt.Sprintf("Unable to check for abort request, error: %s", checkErr.Error()))
			} else if mustAbort {
				c.stdioHandler.writeFileLine("Got ABORT message")
				c.abortProcess(cmd)
				break
			}
			time.Sleep(2 * time.Second)
		}
	}(c.statusHandler)

	c.stdioHandler.stdoutScanner = bufio.NewScanner(stdout)
	c.stdioHandler.stderrScanner = bufio.NewScanner(stderr)

	var wg sync.WaitGroup
	wg.Add(2)<commit_msg>Log the Pid (process ID) once it started<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"time"

	"github.com/go-zero-boilerplate/loggers"

	"github.com/golang-devops/exec-logger/exec_logger_constants"
)

func NewCommandExecer(logger loggers.LoggerStdIO, stdErrIsError bool, timeoutKillDuration time.Duration, runArgs []string) *commandExecer {
	statusHandler := &execStatusHandler{
		localContextFilePath: exec_logger_constants.LOCAL_CONTEXT_FILE_NAME,
		aliveFilePath:        exec_logger_constants.ALIVE_FILE_NAME,
		exitedFilePath:       exec_logger_constants.EXITED_FILE_NAME,
		mustAbortFilePath:    exec_logger_constants.MUST_ABORT_FILE_NAME,
	}

	return &commandExecer{
		logger:              logger,
		logFilePath:         exec_logger_constants.LOG_FILE_NAME,
		stdErrIsError:       stdErrIsError,
		timeoutKillDuration: timeoutKillDuration,
		runArgs:             runArgs,
		statusHandler:       statusHandler,
		stdioHandler:        nil, //Set inside `Run` method
	}
}

type commandExecer struct {
	logger              loggers.LoggerStdIO
	logFilePath         string
	stdErrIsError       bool
	timeoutKillDuration time.Duration
	runArgs             []string
	statusHandler       *execStatusHandler
	stdioHandler        *stdioHandler
}

func (c *commandExecer) abortProcess(cmd *exec.Cmd) {
	defer func() {
		if rec := recover(); rec != nil {
			c.stdioHandler.writeErrorLine(fmt.Sprintf("Kill process attempt recovered, recovery: %+v", rec))
		}
	}()

	pid := cmd.Process.Pid
	force := true
	if killErr := KillProcessTree(pid, force); killErr != nil { //if killErr := cmd.Process.Kill(); killErr != nil {
		c.stdioHandler.writeErrorLine(fmt.Sprintf("Cannot kill process with PID %d, error: %s", pid, killErr.Error()))
	}
	c.stdioHandler.writeFileLine(fmt.Sprintf("Successfully killed process with PID %d", pid))
}

func (c *commandExecer) cleanupBeforeStarting() error {
	if err := os.Remove(c.statusHandler.aliveFilePath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Cannot remove alive file '%s', error: %s", c.statusHandler.aliveFilePath, err.Error())
		}
	}
	if err := os.Remove(c.statusHandler.exitedFilePath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Cannot remove exited file '%s', error: %s", c.statusHandler.exitedFilePath, err.Error())
		}
	}
	if err := os.Remove(c.statusHandler.mustAbortFilePath); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("Cannot remove must-abort file '%s', error: %s", c.statusHandler.mustAbortFilePath, err.Error())
		}
	}
	return nil
}

func (c *commandExecer) runCommand() (exitCode int, returnErr error) {
	if err := c.cleanupBeforeStarting(); err != nil {
		return -1, err
	}

	cmd := exec.Command(c.runArgs[0], c.runArgs[1:]...)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return -1, err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return -1, err
	}

	err = cmd.Start()
	if err != nil {
		return -1, err
	}

	err = c.statusHandler.WriteLocalContextFile()
	if err != nil {
		c.stdioHandler.writeErrorLine(fmt.Sprintf("Cannot write local context, error: %s", err.Error()))
		//do not want to exit due to this error
	}

	c.stdioHandler.writeFileLine(fmt.Sprintf("Process started with PID %d", cmd.Process.Pid))

	go func(sh *execStatusHandler) {
		for {
			sh.WriteAlive()
			time.Sleep(2 * time.Second)
		}
	}(c.statusHandler)

	go func(sh *execStatusHandler) {
		for {
			if mustAbort, checkErr := sh.CheckMustAbort(); checkErr != nil {
				c.stdioHandler.writeFileLine(fmt.Sprintf("Unable to check for abort request, error: %s", checkErr.Error()))
			} else if mustAbort {
				c.stdioHandler.writeFileLine("Got ABORT message")
				c.abortProcess(cmd)
				break
			}
			time.Sleep(2 * time.Second)
		}
	}(c.statusHandler)

	c.stdioHandler.stdoutScanner = bufio.NewScanner(stdout)
	c.stdioHandler.stderrScanner = bufio.NewScanner(stderr)

	var wg sync.WaitGroup
	wg.Add(2)
{'fraction_non_alphanumeric': 0.12836358954551308, 'fraction_numerical': 0.002317497103128621, 'mean_word_length': 3.882463859208045, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3572933', 'n_tokens_mistral': 2825, 'n_tokens_neox': 2635, 'n_words': 592}