<commit_before>// Copyright 2013 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gosh

import (
	"os/exec"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	/**
	 * 'Unstarted' is the state of a command that has been constructed, but execution has not yet begun.
	 */
	UNSTARTED int32 = iota

	/**
	 * 'Running' is the state of a command that has begun execution, but not yet finished.
	 */
	RUNNING

	/**
	 * 'Finished' is the state of a command that has finished normally.
	 *
	 * The exit code may or may not have been success, but at the very least we
	 * successfully observed that exit code.
	 */
	FINISHED

	/**
	 * 'Panicked' is the state of a command that at some point began execution, but has encountered
	 * serious problems.
	 *
	 * It may not be clear whether or not the command is still running, since a panic implies we no
	 * longer have completely clear visibility to the command on the underlying system.  The exit
	 * code may not be reliably known.
	 */
	PANICKED
)

func NewRunningCommand(cmd *exec.Cmd) *RunningCommand {
	return &RunningCommand{
		cmd:      cmd,
		state:    UNSTARTED,
		exitCh:   make(chan bool),
		exitCode: -1,
	}
}

type RunningCommand struct {
	mutex sync.Mutex

	/** Always access this with functions from the atomic package, and when
	* transitioning states set the status after all other fields are mutated,
	* so that checks of State() serve as a memory barrier for all. */
	state int32

	cmd *exec.Cmd
	//TODO: or: callback func() int

	/** If this is set, game over. */
	err error

	/** Wait for this to close in order to wait for the process to return. */
	exitCh chan bool

	/** Exit code if we're state==FINISHED and exit codes are possible on this platform, or
	 * -1 if we're not there yet.  Will not change after exitCh has closed. */
	exitCode int

	/** Functions to call back when the command has exited. */
	exitListeners []func(*RunningCommand)
}

func (cmd *RunningCommand) State() int32 {
	return atomic.LoadInt32(&cmd.state)
}

/** Returns true if the command is current running. */
func (cmd *RunningCommand) IsRunning() bool {
	state := cmd.State()
	return state == RUNNING
}

/** Returns true if the command has ever been started (including if the command is already finished). */
func (cmd *RunningCommand) IsStarted() bool {
	state := cmd.State()
	return state == RUNNING || state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished (either gracefully, or with internal errors). */
func (cmd *RunningCommand) IsDone() bool {
	state := cmd.State()
	return state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished gracefully.  (A nonzero exit code may still be set.) */
func (cmd *RunningCommand) IsFinishedGracefully() bool {
	state := cmd.State()
	return state == FINISHED
}

func (cmd *RunningCommand) Start() {
	if err := cmd.startCalmly(); err != nil {
		panic(err)
	}
}

func (cmd *RunningCommand) startCalmly() error {
	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if cmd.IsStarted() {
		return nil
	}

	atomic.StoreInt32(&cmd.state, RUNNING)
	if err := cmd.cmd.Start(); err != nil {
		cmd.finalState(CommandStartError{cause: err})
		return cmd.err
	}

	go cmd.waitAndHandleExit()
	return nil
}

func (cmd *RunningCommand) waitAndHandleExit() {
	err := cmd.cmd.Wait()

	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()<commit_msg>RunningCommand.Start() now returns the same object for chaining convenience.<commit_after>// Copyright 2013 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gosh

import (
	"os/exec"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	/**
	 * 'Unstarted' is the state of a command that has been constructed, but execution has not yet begun.
	 */
	UNSTARTED int32 = iota

	/**
	 * 'Running' is the state of a command that has begun execution, but not yet finished.
	 */
	RUNNING

	/**
	 * 'Finished' is the state of a command that has finished normally.
	 *
	 * The exit code may or may not have been success, but at the very least we
	 * successfully observed that exit code.
	 */
	FINISHED

	/**
	 * 'Panicked' is the state of a command that at some point began execution, but has encountered
	 * serious problems.
	 *
	 * It may not be clear whether or not the command is still running, since a panic implies we no
	 * longer have completely clear visibility to the command on the underlying system.  The exit
	 * code may not be reliably known.
	 */
	PANICKED
)

func NewRunningCommand(cmd *exec.Cmd) *RunningCommand {
	return &RunningCommand{
		cmd:      cmd,
		state:    UNSTARTED,
		exitCh:   make(chan bool),
		exitCode: -1,
	}
}

type RunningCommand struct {
	mutex sync.Mutex

	/** Always access this with functions from the atomic package, and when
	* transitioning states set the status after all other fields are mutated,
	* so that checks of State() serve as a memory barrier for all. */
	state int32

	cmd *exec.Cmd
	//TODO: or: callback func() int

	/** If this is set, game over. */
	err error

	/** Wait for this to close in order to wait for the process to return. */
	exitCh chan bool

	/** Exit code if we're state==FINISHED and exit codes are possible on this platform, or
	 * -1 if we're not there yet.  Will not change after exitCh has closed. */
	exitCode int

	/** Functions to call back when the command has exited. */
	exitListeners []func(*RunningCommand)
}

func (cmd *RunningCommand) State() int32 {
	return atomic.LoadInt32(&cmd.state)
}

/** Returns true if the command is current running. */
func (cmd *RunningCommand) IsRunning() bool {
	state := cmd.State()
	return state == RUNNING
}

/** Returns true if the command has ever been started (including if the command is already finished). */
func (cmd *RunningCommand) IsStarted() bool {
	state := cmd.State()
	return state == RUNNING || state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished (either gracefully, or with internal errors). */
func (cmd *RunningCommand) IsDone() bool {
	state := cmd.State()
	return state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished gracefully.  (A nonzero exit code may still be set.) */
func (cmd *RunningCommand) IsFinishedGracefully() bool {
	state := cmd.State()
	return state == FINISHED
}

func (cmd *RunningCommand) Start() *RunningCommand {
	if err := cmd.startCalmly(); err != nil {
		panic(err)
	}
	return cmd
}

func (cmd *RunningCommand) startCalmly() error {
	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if cmd.IsStarted() {
		return nil
	}

	atomic.StoreInt32(&cmd.state, RUNNING)
	if err := cmd.cmd.Start(); err != nil {
		cmd.finalState(CommandStartError{cause: err})
		return cmd.err
	}

	go cmd.waitAndHandleExit()
	return nil
}

func (cmd *RunningCommand) waitAndHandleExit() {
	err := cmd.cmd.Wait()

	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()
{'fraction_non_alphanumeric': 0.10342195649408473, 'fraction_numerical': 0.005088411143620405, 'mean_word_length': 3.5209890741805636, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '2433133', 'n_tokens_mistral': 2417, 'n_tokens_neox': 2278, 'n_words': 1006}