github.com/matrixorigin/matrixone@v1.2.0/pkg/udf/pythonservice/service.go (about)

     1  // Copyright 2023 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package pythonservice
    16  
    17  import (
    18  	"fmt"
    19  	"github.com/matrixorigin/matrixone/pkg/logutil"
    20  	"io"
    21  	"os"
    22  	"os/exec"
    23  	"path"
    24  	"path/filepath"
    25  	"strconv"
    26  	"sync"
    27  	"sync/atomic"
    28  )
    29  
    30  type service struct {
    31  	cfg     Config
    32  	process *os.Process
    33  	log     io.WriteCloser
    34  	mutex   sync.Mutex
    35  }
    36  
    37  func NewService(cfg Config) (PythonUdfServer, error) {
    38  	err := cfg.Validate()
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return &service{cfg: cfg}, nil
    43  }
    44  
    45  var severNo int32 = 0
    46  
    47  func (s *service) Start() error {
    48  	s.mutex.Lock()
    49  	defer s.mutex.Unlock()
    50  
    51  	if s.process == nil {
    52  		var err error
    53  		var file string
    54  
    55  		ex, err := os.Executable()
    56  		if err != nil {
    57  			return err
    58  		}
    59  		exePath := filepath.Dir(ex)
    60  
    61  		if path.IsAbs(file) {
    62  			file = path.Join(s.cfg.Path, "server.py")
    63  		} else {
    64  			file = path.Join(exePath, s.cfg.Path, "server.py")
    65  		}
    66  		_, err = os.Stat(file)
    67  		if err != nil {
    68  			return err
    69  		}
    70  
    71  		no := strconv.Itoa(int(atomic.AddInt32(&severNo, 1)))
    72  		s.log, err = os.OpenFile(path.Join(exePath, "pyserver"+no+".log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
    73  		if err != nil {
    74  			return err
    75  		}
    76  		defer func() {
    77  			if err != nil {
    78  				s.log.Close()
    79  			}
    80  		}()
    81  		executable := s.cfg.Python
    82  		if executable == "" {
    83  			executable = "python"
    84  		}
    85  
    86  		cmd := exec.Command(executable, "-u", file, "--address="+s.cfg.Address)
    87  		cmd.Stdout = s.log
    88  		cmd.Stderr = s.log
    89  		go func(cmd *exec.Cmd) {
    90  			logutil.Infof("start python server process, command: %s", cmd.String())
    91  			err := cmd.Run()
    92  			// panic the launch process if python server exit with an error
    93  			if err != nil {
    94  				panic(fmt.Sprintf("python server exit with error: %v", err))
    95  			}
    96  		}(cmd)
    97  
    98  		s.process = cmd.Process
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  func (s *service) Close() error {
   105  	s.mutex.Lock()
   106  	defer func() {
   107  		s.mutex.Unlock()
   108  		if s.log != nil {
   109  			s.log.Close()
   110  		}
   111  	}()
   112  
   113  	if s.process != nil {
   114  		err := s.process.Kill()
   115  		if err != nil {
   116  			return err
   117  		}
   118  		s.process = nil
   119  	}
   120  
   121  	return nil
   122  }