<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"
)

type supervisor struct {
	prog string
	argv []string

	cmd     *exec.Cmd
	startAt time.Time
	mu      sync.RWMutex

	signaled   bool
	signaledMu sync.RWMutex

	hupped   bool
	huppedMu sync.RWMutex
}

func (sv *supervisor) setSignaled(signaled bool) {
	sv.signaledMu.Lock()
	defer sv.signaledMu.Unlock()
	sv.signaled = signaled
}

func (sv *supervisor) getSignaled() bool {
	sv.signaledMu.RLock()
	defer sv.signaledMu.RUnlock()
	return sv.signaled
}

func (sv *supervisor) setHupped(hupped bool) {
	sv.huppedMu.Lock()
	defer sv.huppedMu.Unlock()
	sv.hupped = hupped
}

func (sv *supervisor) getHupped() bool {
	sv.huppedMu.RLock()
	defer sv.huppedMu.RUnlock()
	return sv.hupped
}

// If the child process dies within 30 seconds, it is regarded as launching failure
// and terminate the process without crash recovery
var spawnInterval = 30 * time.Second

func (sv *supervisor) launched() bool {
	return sv.getCmd().Process != nil && time.Now().After(sv.startAt.Add(spawnInterval))
}

func (sv *supervisor) buildCmd() *exec.Cmd {
	argv := append(sv.argv, "-child")
	cmd := exec.Command(sv.prog, argv...)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd
}

func (sv *supervisor) getCmd() *exec.Cmd {
	sv.mu.RLock()
	defer sv.mu.RUnlock()
	return sv.cmd
}

func (sv *supervisor) start() error {
	sv.setHupped(false)
	sv.mu.Lock()
	defer sv.mu.Unlock()
	sv.cmd = sv.buildCmd()
	sv.startAt = time.Now()
	return sv.cmd.Start()
}

func (sv *supervisor) stop(sig os.Signal) error {
	sv.setSignaled(true)
	return sv.getCmd().Process.Signal(sig)
}

func (sv *supervisor) configtest() error {
	argv := append([]string{"configtest"}, sv.argv...)
	cmd := exec.Command(sv.prog, argv...)
	buf := &bytes.Buffer{}
	cmd.Stderr = buf
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("configtest failed: %s", buf.String())
	}
	return nil
}

func (sv *supervisor) reload() error {
	err := sv.configtest()
	if err != nil {
		return err
	}
	sv.setHupped(true)
	return sv.getCmd().Process.Signal(syscall.SIGTERM)
}

func (sv *supervisor) wait() (err error) {
	for {
		err = sv.cmd.Wait()
		if sv.getSignaled() || (!sv.getHupped() && !sv.launched()) {
			break
		}
		if err != nil {
			logger.Warningf("mackerel-agent abnormally finished with following error and try to restart it: %s", err.Error())
		}
		sv.start()
	}
	return
}

func (sv *supervisor) handleSignal(ch <-chan os.Signal) {
	for sig := range ch {
		if sig == syscall.SIGHUP {
			logger.Infof("receiving HUP, spawning a new mackerel-agent")
			err := sv.reload()
			if err != nil {
				logger.Warningf("failed to reload: %s", err.Error())
			}
		} else {
			sv.stop(sig)
		}
	}
}

func (sv *supervisor) supervise(c chan os.Signal) error {
	sv.start()
	if c == nil {
		c := make(chan os.Signal, 1)
	}
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
	go sv.handleSignal(c)<commit_msg>add error handling of sv.start()<commit_after>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"
)

type supervisor struct {
	prog string
	argv []string

	cmd     *exec.Cmd
	startAt time.Time
	mu      sync.RWMutex

	signaled   bool
	signaledMu sync.RWMutex

	hupped   bool
	huppedMu sync.RWMutex
}

func (sv *supervisor) setSignaled(signaled bool) {
	sv.signaledMu.Lock()
	defer sv.signaledMu.Unlock()
	sv.signaled = signaled
}

func (sv *supervisor) getSignaled() bool {
	sv.signaledMu.RLock()
	defer sv.signaledMu.RUnlock()
	return sv.signaled
}

func (sv *supervisor) setHupped(hupped bool) {
	sv.huppedMu.Lock()
	defer sv.huppedMu.Unlock()
	sv.hupped = hupped
}

func (sv *supervisor) getHupped() bool {
	sv.huppedMu.RLock()
	defer sv.huppedMu.RUnlock()
	return sv.hupped
}

// If the child process dies within 30 seconds, it is regarded as launching failure
// and terminate the process without crash recovery
var spawnInterval = 30 * time.Second

func (sv *supervisor) launched() bool {
	return sv.getCmd().Process != nil && time.Now().After(sv.startAt.Add(spawnInterval))
}

func (sv *supervisor) buildCmd() *exec.Cmd {
	argv := append(sv.argv, "-child")
	cmd := exec.Command(sv.prog, argv...)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd
}

func (sv *supervisor) getCmd() *exec.Cmd {
	sv.mu.RLock()
	defer sv.mu.RUnlock()
	return sv.cmd
}

func (sv *supervisor) start() error {
	sv.setHupped(false)
	sv.mu.Lock()
	defer sv.mu.Unlock()
	sv.cmd = sv.buildCmd()
	sv.startAt = time.Now()
	return sv.cmd.Start()
}

func (sv *supervisor) stop(sig os.Signal) error {
	sv.setSignaled(true)
	return sv.getCmd().Process.Signal(sig)
}

func (sv *supervisor) configtest() error {
	argv := append([]string{"configtest"}, sv.argv...)
	cmd := exec.Command(sv.prog, argv...)
	buf := &bytes.Buffer{}
	cmd.Stderr = buf
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("configtest failed: %s", buf.String())
	}
	return nil
}

func (sv *supervisor) reload() error {
	err := sv.configtest()
	if err != nil {
		return err
	}
	sv.setHupped(true)
	return sv.getCmd().Process.Signal(syscall.SIGTERM)
}

func (sv *supervisor) wait() (err error) {
	for {
		err = sv.cmd.Wait()
		if sv.getSignaled() || (!sv.getHupped() && !sv.launched()) {
			break
		}
		if err != nil {
			logger.Warningf("mackerel-agent abnormally finished with following error and try to restart it: %s", err.Error())
		}
		err = sv.start()
		if err != nil {
			break
		}
	}
	return
}

func (sv *supervisor) handleSignal(ch <-chan os.Signal) {
	for sig := range ch {
		if sig == syscall.SIGHUP {
			logger.Infof("receiving HUP, spawning a new mackerel-agent")
			err := sv.reload()
			if err != nil {
				logger.Warningf("failed to reload: %s", err.Error())
			}
		} else {
			sv.stop(sig)
		}
	}
}

func (sv *supervisor) supervise(c chan os.Signal) error {
	err := sv.start()
	if err != nil {
		return err
	}
	if c == nil {
		c = make(chan os.Signal, 1)
	}
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
	go sv.handleSignal(c)
{'fraction_non_alphanumeric': 0.15388421573478475, 'fraction_numerical': 0.0016493485073396008, 'mean_word_length': 3.636085626911315, 'pattern_counts': {'":': 0, '<': 5, '<?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': '5780440', 'n_tokens_mistral': 2440, 'n_tokens_neox': 2337, 'n_words': 582}