github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/process_structs.go (about)

     1  package lang
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/lmorg/murex/builtins/pipes/streams"
    10  	"github.com/lmorg/murex/config"
    11  	"github.com/lmorg/murex/lang/parameters"
    12  	"github.com/lmorg/murex/lang/process"
    13  	"github.com/lmorg/murex/lang/ref"
    14  	"github.com/lmorg/murex/lang/runmode"
    15  	"github.com/lmorg/murex/lang/state"
    16  	"github.com/lmorg/murex/lang/stdio"
    17  )
    18  
    19  // Process - Each process running inside the murex shell will be one of these objects.
    20  // It is equivalent to the /proc directory on Linux, albeit queried through murex as JSON.
    21  // External processes will also appear in the host OS's process list.
    22  type Process struct {
    23  	Id                 uint32
    24  	cache              *cacheT
    25  	raw                []rune
    26  	Name               process.Name
    27  	Parameters         parameters.Parameters
    28  	namedPipes         []string
    29  	Context            context.Context
    30  	Stdin              stdio.Io
    31  	Stdout             stdio.Io
    32  	stdoutOldPtr       stdio.Io // only used when stdout is a tmp named pipe
    33  	Stderr             stdio.Io
    34  	ExitNum            int
    35  	Forks              *ForkManagement
    36  	WaitForTermination chan bool `json:"-"`
    37  	WaitForStopped     chan bool `json:"-"`
    38  	HasStopped         chan bool `json:"-"`
    39  	Done               func()    `json:"-"`
    40  	Kill               func()    `json:"-"`
    41  	Exec               process.Exec
    42  	Scope              *Process `json:"-"`
    43  	Parent             *Process `json:"-"`
    44  	Previous           *Process `json:"-"`
    45  	Next               *Process `json:"-"`
    46  	IsNot              bool
    47  	IsMethod           bool
    48  	OperatorLogicAnd   bool
    49  	OperatorLogicOr    bool
    50  	NamedPipeOut       string
    51  	NamedPipeErr       string
    52  	NamedPipeTest      string
    53  	hasTerminatedM     sync.Mutex `json:"-"`
    54  	hasTerminatedV     bool
    55  	State              state.State
    56  	Background         process.Background
    57  	RunMode            runmode.RunMode
    58  	Config             *config.Config
    59  	Tests              *Tests
    60  	testState          []string
    61  	Variables          *Variables
    62  	CreationTime       time.Time
    63  	StartTime          time.Time
    64  	FileRef            *ref.File
    65  	CCEvent            func(string, *Process) `json:"-"`
    66  	CCExists           func(string) bool      `json:"-"`
    67  	CCOut              *streams.Stdin         `json:"-"`
    68  	CCErr              *streams.Stdin         `json:"-"`
    69  }
    70  
    71  func (p *Process) Dump() interface{} {
    72  	dump := make(map[string]interface{})
    73  
    74  	dump["Id"] = p.Id
    75  	dump["Name"] = p.Name.String()
    76  	dump["Parameters"] = p.Parameters.Dump()
    77  	dump["Context_Set"] = p.Context != nil
    78  	dump["Stdin_Set"] = p.Stdin != nil
    79  	dump["Stdout_Set"] = p.Stdout != nil
    80  	dump["StdoutOldPtr_Set"] = p.stdoutOldPtr != nil
    81  	dump["Stderr_Set"] = p.Stderr != nil
    82  	dump["ExitNum"] = p.ExitNum
    83  	dump["Done_Set"] = p.Done != nil
    84  	dump["Kill_Set"] = p.Kill != nil
    85  	dump["Exec"] = &p.Exec
    86  	dump["Scope.Id"] = p.Scope.Id
    87  	dump["Parent.Id"] = p.Parent.Id
    88  	dump["Previous.Id"] = p.Previous.Id
    89  	dump["IsNot"] = p.IsNot
    90  	dump["IsMethod"] = p.IsMethod
    91  	dump["OperatorLogicAnd"] = p.OperatorLogicAnd
    92  	dump["OperatorLogicOr"] = p.OperatorLogicOr
    93  	dump["NamedPipeOut"] = p.NamedPipeOut
    94  	dump["NamedPipeErr"] = p.NamedPipeErr
    95  	dump["NamedPipeTest"] = p.NamedPipeTest
    96  	dump["HasTerminated"] = p.HasTerminated()
    97  	dump["HasCancelled"] = p.HasCancelled()
    98  	dump["State"] = p.State.String()
    99  	dump["Background"] = p.Background.String()
   100  	dump["RunMode"] = p.RunMode.String()
   101  	dump["RunMode.IsStrict"] = p.RunMode.IsStrict()
   102  	dump["Config_Set"] = p.Config != nil
   103  	dump["Tests_Set"] = p.Tests != nil
   104  	dump["testState"] = p.testState
   105  	dump["Variables_Set"] = p.Variables != nil
   106  	dump["CreationTime"] = p.CreationTime
   107  	dump["StartTime"] = p.StartTime
   108  	dump["FileRef"] = p.FileRef
   109  	dump["CCEvent_Set"] = p.CCEvent != nil
   110  	dump["CCExists_Set"] = p.CCExists != nil
   111  	dump["CCOut_Set"] = p.CCOut != nil
   112  	dump["CCErr_Set"] = p.CCErr != nil
   113  
   114  	return dump
   115  }
   116  
   117  // HasTerminated checks if process has terminated.
   118  // This is a function because terminated state can be subject to race conditions
   119  // so we need a mutex to make the state thread safe.
   120  func (p *Process) HasTerminated() (state bool) {
   121  	p.hasTerminatedM.Lock()
   122  	state = p.hasTerminatedV
   123  	p.hasTerminatedM.Unlock()
   124  	return
   125  }
   126  
   127  // HasCancelled is a wrapper function around context because it's a pretty ugly API
   128  func (p *Process) HasCancelled() bool {
   129  	select {
   130  	case <-p.Context.Done():
   131  		return true
   132  
   133  	default:
   134  		if p.State.Get() == state.Stopped {
   135  			return p.hasCancelledStopped()
   136  		}
   137  		return false
   138  	}
   139  }
   140  
   141  func (p *Process) hasCancelledStopped() bool {
   142  	select {
   143  	case <-p.Context.Done():
   144  		return true
   145  	case <-p.WaitForStopped:
   146  		return false
   147  	}
   148  }
   149  
   150  // SetTerminatedState sets the process terminated state.
   151  // This is a function because terminated state can be subject to race conditions
   152  // so we need a mutex to make the state thread safe.
   153  func (p *Process) SetTerminatedState(state bool) {
   154  	p.hasTerminatedM.Lock()
   155  	p.hasTerminatedV = state
   156  	p.hasTerminatedM.Unlock()
   157  }
   158  
   159  // ErrIfNotAMethod returns a standard error message for builtins not run as methods
   160  func (p *Process) ErrIfNotAMethod() error {
   161  	if !p.IsMethod {
   162  		return fmt.Errorf("`%s` expects to be pipelined", p.Name.String())
   163  	}
   164  	return nil
   165  }
   166  
   167  func (p *Process) KillForks(exitNum int) {
   168  	forks := p.Forks.GetForks()
   169  	for _, procs := range forks {
   170  		for i := range *procs {
   171  			(*procs)[i].ExitNum = exitNum
   172  			(*procs)[i].Done()
   173  		}
   174  	}
   175  }
   176  
   177  // Args returns a normalised function name and parameters
   178  func (p *Process) Args() (string, []string) {
   179  	return args(p.Name.String(), p.Parameters.StringArray())
   180  }
   181  
   182  func args(name string, params []string) (string, []string) {
   183  	if len(params) == 0 {
   184  		return name, []string{}
   185  	}
   186  
   187  	switch name {
   188  	case "exec":
   189  		return params[0], params[1:]
   190  
   191  	default:
   192  		return name, params
   193  	}
   194  }
   195  
   196  type foregroundProc struct {
   197  	mutex sync.Mutex
   198  	p     *Process
   199  }
   200  
   201  func newForegroundProc() *foregroundProc {
   202  	return &foregroundProc{p: ShellProcess}
   203  }
   204  
   205  func (fp *foregroundProc) Get() *Process {
   206  	fp.mutex.Lock()
   207  	p := fp.p
   208  	//if p == nil {
   209  	//	panic("Get() retrieved p")
   210  	//}
   211  	fp.mutex.Unlock()
   212  
   213  	return p
   214  }
   215  
   216  func (fp *foregroundProc) Set(p *Process) {
   217  	fp.mutex.Lock()
   218  	if p == nil {
   219  		panic("nil p in (fp *foregroundProc) Set(p *Process)")
   220  	}
   221  	fp.p = p
   222  	//debug.Json("fp.Set", p)
   223  	//debug.Json("fp.p", fp.p)
   224  	fp.mutex.Unlock()
   225  }