blob: 707018d95bf506f6533e2fff18bcca7b455988a2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 openwhisk
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"time"
)
// OutputGuard constant string
const OutputGuard = "XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX\n"
// DefaultTimeoutStart to wait for a process to start
var DefaultTimeoutStart = 5 * time.Millisecond
// Executor is the container and the guardian of a child process
// It starts a command, feeds input and output, read logs and control its termination
type Executor struct {
cmd *exec.Cmd
input io.WriteCloser
output *bufio.Reader
exited chan bool
}
// NewExecutor creates a child subprocess using the provided command line,
// writing the logs in the given file.
// You can then start it getting a communication channel
func NewExecutor(logout *os.File, logerr *os.File, command string, env map[string]string, args ...string) (proc *Executor) {
cmd := exec.Command(command, args...)
cmd.Stdout = logout
cmd.Stderr = logerr
cmd.Env = []string{}
for k, v := range env {
cmd.Env = append(cmd.Env, k+"="+v)
}
Debug("env: %v", cmd.Env)
if Debugging {
cmd.Env = append(cmd.Env, "OW_DEBUG=/tmp/action.log")
}
input, err := cmd.StdinPipe()
if err != nil {
return nil
}
pipeOut, pipeIn, err := os.Pipe()
if err != nil {
return nil
}
cmd.ExtraFiles = []*os.File{pipeIn}
output := bufio.NewReader(pipeOut)
return &Executor{
cmd,
input,
output,
make(chan bool),
}
}
// Interact interacts with the underlying process
func (proc *Executor) Interact(in []byte) ([]byte, error) {
// input to the subprocess
proc.input.Write(in)
proc.input.Write([]byte("\n"))
out, err := proc.output.ReadBytes('\n')
proc.cmd.Stdout.Write([]byte(OutputGuard))
proc.cmd.Stderr.Write([]byte(OutputGuard))
return out, err
}
// Exited checks if the underlying command exited
func (proc *Executor) Exited() bool {
select {
case <-proc.exited:
return true
default:
return false
}
}
// Start execution of the command
// wait a bit to check if the command exited
// returns an error if the program fails
func (proc *Executor) Start() error {
// start the underlying executable
Debug("Start:")
err := proc.cmd.Start()
if err != nil {
Debug("run: early exit")
proc.cmd = nil // no need to kill
return fmt.Errorf("command exited")
}
Debug("pid: %d", proc.cmd.Process.Pid)
go func() {
proc.cmd.Wait()
proc.exited <- true
}()
select {
case <-proc.exited:
return fmt.Errorf("command exited")
case <-time.After(DefaultTimeoutStart):
return nil
}
}
// Stop will kill the process
// and close the channels
func (proc *Executor) Stop() {
Debug("stopping")
if proc.cmd != nil {
proc.cmd.Process.Kill()
proc.cmd = nil
}
}