github.com/unirita/cuto@v0.9.8-0.20160830082821-aa6652f877b7/realtime/network/exec.go (about)

     1  package network
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/unirita/cuto/util"
    15  )
    16  
    17  // Command represents a master command which executes realtime network.
    18  type Command struct {
    19  	cmd         *exec.Cmd
    20  	pid         int
    21  	networkName string
    22  }
    23  
    24  // NewCommand creates Command object with unique network name.
    25  // Network name is generate from realtimeName and current timestamp.
    26  func NewCommand(realtimeName string) *Command {
    27  	c := new(Command)
    28  	timestamp := time.Now().Format("20060102150405")
    29  	if realtimeName == "" {
    30  		c.networkName = fmt.Sprintf("realtime_%s", timestamp)
    31  	} else {
    32  		c.networkName = fmt.Sprintf("realtime_%s_%s", realtimeName, timestamp)
    33  	}
    34  
    35  	masterPath := filepath.Join(util.GetRootPath(), "bin", "master")
    36  	configPath := filepath.Join(util.GetRootPath(), "bin", "master.ini")
    37  	c.cmd = exec.Command(masterPath, "-n", c.networkName, "-s", "-c", configPath)
    38  	return c
    39  }
    40  
    41  // GetNetworkName returns network name.
    42  func (c *Command) GetNetworkName() string {
    43  	return c.networkName
    44  }
    45  
    46  // GetPID returns master process ID.
    47  // If Run is not called, returns 0.
    48  func (c *Command) GetPID() int {
    49  	return c.pid
    50  }
    51  
    52  // Run runs the master command and gets its instance id.
    53  func (c *Command) Run() (int, error) {
    54  	stdoutReader, err := c.cmd.StdoutPipe()
    55  	if err != nil {
    56  		return 0, err
    57  	}
    58  	if err := c.cmd.Start(); err != nil {
    59  		return 0, err
    60  	}
    61  	c.pid = c.cmd.Process.Pid
    62  
    63  	lineCh := make(chan string, 1)
    64  	waitCh := make(chan struct{}, 1)
    65  	idCh := make(chan string, 1)
    66  	errCh := make(chan string, 1)
    67  
    68  	go c.monitorStdout(lineCh, stdoutReader)
    69  	go c.waitProcess(waitCh)
    70  	go c.waitID(idCh, errCh, lineCh, waitCh)
    71  
    72  	select {
    73  	case idStr := <-idCh:
    74  		id, err := strconv.Atoi(idStr)
    75  		if err != nil {
    76  			return 0, fmt.Errorf("Invalid instance ID[%s] received.", idStr)
    77  		}
    78  		return id, nil
    79  	case errMsg := <-errCh:
    80  		return 0, fmt.Errorf("Master error: %s", errMsg)
    81  	}
    82  }
    83  
    84  // Release releases any resources associated with the master command process.
    85  // It is recommended that call this function if you do not wait end of process.
    86  func (c *Command) Release() error {
    87  	return c.cmd.Process.Release()
    88  }
    89  
    90  func (c *Command) monitorStdout(lineCh chan<- string, reader io.Reader) {
    91  	scanner := bufio.NewScanner(reader)
    92  	for scanner.Scan() {
    93  		line := scanner.Text()
    94  		if line != "" {
    95  			lineCh <- line
    96  		}
    97  	}
    98  }
    99  
   100  func (c *Command) waitProcess(waitCh chan<- struct{}) {
   101  	c.cmd.Wait()
   102  	close(waitCh)
   103  }
   104  
   105  func (c *Command) waitID(idCh, errCh chan<- string, lineCh <-chan string, waitCh <-chan struct{}) {
   106  	matcher := regexp.MustCompile(`INSTANCE \[\d+`)
   107  	output := ""
   108  	for {
   109  		select {
   110  		case line := <-lineCh:
   111  			id := matcher.FindString(line)
   112  			if id != "" {
   113  				id = strings.Replace(id, "INSTANCE [", "", 1)
   114  				idCh <- id
   115  				return
   116  			}
   117  			output += line + "\n"
   118  		case <-waitCh:
   119  			errCh <- output
   120  			return
   121  		}
   122  	}
   123  }