github.com/gogf/gf/v2@v2.7.4/os/gproc/gproc_process.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package gproc
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"os"
    13  	"os/exec"
    14  	"runtime"
    15  	"strings"
    16  
    17  	"go.opentelemetry.io/otel"
    18  	"go.opentelemetry.io/otel/propagation"
    19  	"go.opentelemetry.io/otel/trace"
    20  
    21  	"github.com/gogf/gf/v2"
    22  	"github.com/gogf/gf/v2/errors/gcode"
    23  	"github.com/gogf/gf/v2/errors/gerror"
    24  	"github.com/gogf/gf/v2/internal/intlog"
    25  	"github.com/gogf/gf/v2/net/gtrace"
    26  	"github.com/gogf/gf/v2/os/genv"
    27  	"github.com/gogf/gf/v2/text/gstr"
    28  )
    29  
    30  // Process is the struct for a single process.
    31  type Process struct {
    32  	exec.Cmd
    33  	Manager *Manager
    34  	PPid    int
    35  }
    36  
    37  // NewProcess creates and returns a new Process.
    38  func NewProcess(path string, args []string, environment ...[]string) *Process {
    39  	env := os.Environ()
    40  	if len(environment) > 0 {
    41  		env = append(env, environment[0]...)
    42  	}
    43  	process := &Process{
    44  		Manager: nil,
    45  		PPid:    os.Getpid(),
    46  		Cmd: exec.Cmd{
    47  			Args:       []string{path},
    48  			Path:       path,
    49  			Stdin:      os.Stdin,
    50  			Stdout:     os.Stdout,
    51  			Stderr:     os.Stderr,
    52  			Env:        env,
    53  			ExtraFiles: make([]*os.File, 0),
    54  		},
    55  	}
    56  	process.Dir, _ = os.Getwd()
    57  	if len(args) > 0 {
    58  		// Exclude of current binary path.
    59  		start := 0
    60  		if strings.EqualFold(path, args[0]) {
    61  			start = 1
    62  		}
    63  		process.Args = append(process.Args, args[start:]...)
    64  	}
    65  	return process
    66  }
    67  
    68  // NewProcessCmd creates and returns a process with given command and optional environment variable array.
    69  func NewProcessCmd(cmd string, environment ...[]string) *Process {
    70  	return NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...)
    71  }
    72  
    73  // Start starts executing the process in non-blocking way.
    74  // It returns the pid if success, or else it returns an error.
    75  func (p *Process) Start(ctx context.Context) (int, error) {
    76  	if p.Process != nil {
    77  		return p.Pid(), nil
    78  	}
    79  	// OpenTelemetry for command.
    80  	var (
    81  		span trace.Span
    82  		tr   = otel.GetTracerProvider().Tracer(
    83  			tracingInstrumentName,
    84  			trace.WithInstrumentationVersion(gf.VERSION),
    85  		)
    86  	)
    87  	ctx, span = tr.Start(
    88  		otel.GetTextMapPropagator().Extract(
    89  			ctx,
    90  			propagation.MapCarrier(genv.Map()),
    91  		),
    92  		gstr.Join(os.Args, " "),
    93  		trace.WithSpanKind(trace.SpanKindInternal),
    94  	)
    95  	defer span.End()
    96  	span.SetAttributes(gtrace.CommonLabels()...)
    97  
    98  	// OpenTelemetry propagation.
    99  	tracingEnv := tracingEnvFromCtx(ctx)
   100  	if len(tracingEnv) > 0 {
   101  		p.Env = append(p.Env, tracingEnv...)
   102  	}
   103  	p.Env = append(p.Env, fmt.Sprintf("%s=%d", envKeyPPid, p.PPid))
   104  	p.Env = genv.Filter(p.Env)
   105  
   106  	// On Windows, this works and doesn't work on other platforms
   107  	if runtime.GOOS == "windows" {
   108  		joinProcessArgs(p)
   109  	}
   110  
   111  	if err := p.Cmd.Start(); err == nil {
   112  		if p.Manager != nil {
   113  			p.Manager.processes.Set(p.Process.Pid, p)
   114  		}
   115  		return p.Process.Pid, nil
   116  	} else {
   117  		return 0, err
   118  	}
   119  }
   120  
   121  // Run executes the process in blocking way.
   122  func (p *Process) Run(ctx context.Context) error {
   123  	if _, err := p.Start(ctx); err == nil {
   124  		return p.Wait()
   125  	} else {
   126  		return err
   127  	}
   128  }
   129  
   130  // Pid retrieves and returns the PID for the process.
   131  func (p *Process) Pid() int {
   132  	if p.Process != nil {
   133  		return p.Process.Pid
   134  	}
   135  	return 0
   136  }
   137  
   138  // Send sends custom data to the process.
   139  func (p *Process) Send(data []byte) error {
   140  	if p.Process != nil {
   141  		return Send(p.Process.Pid, data)
   142  	}
   143  	return gerror.NewCode(gcode.CodeInvalidParameter, "invalid process")
   144  }
   145  
   146  // Release releases any resources associated with the Process p,
   147  // rendering it unusable in the future.
   148  // Release only needs to be called if Wait is not.
   149  func (p *Process) Release() error {
   150  	return p.Process.Release()
   151  }
   152  
   153  // Kill causes the Process to exit immediately.
   154  func (p *Process) Kill() (err error) {
   155  	err = p.Process.Kill()
   156  	if err != nil {
   157  		err = gerror.Wrapf(err, `kill process failed for pid "%d"`, p.Process.Pid)
   158  		return err
   159  	}
   160  	if p.Manager != nil {
   161  		p.Manager.processes.Remove(p.Pid())
   162  	}
   163  	if runtime.GOOS != "windows" {
   164  		if err = p.Process.Release(); err != nil {
   165  			intlog.Errorf(context.TODO(), `%+v`, err)
   166  		}
   167  	}
   168  	// It ignores this error, just log it.
   169  	_, err = p.Process.Wait()
   170  	intlog.Errorf(context.TODO(), `%+v`, err)
   171  	return nil
   172  }
   173  
   174  // Signal sends a signal to the Process.
   175  // Sending Interrupt on Windows is not implemented.
   176  func (p *Process) Signal(sig os.Signal) error {
   177  	return p.Process.Signal(sig)
   178  }