This is an automated email from the ASF dual-hosted git repository.

rabbah pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwhisk-runtime-go.git


The following commit(s) were added to refs/heads/master by this push:
     new 23ab27f  Enhance proxy for asynchornous handshake on action init (#121)
23ab27f is described below

commit 23ab27f7e607377eef68fcb39104807d43f2e657
Author: Michele Sciabarra <[email protected]>
AuthorDate: Tue Feb 18 17:04:35 2020 +0100

    Enhance proxy for asynchornous handshake on action init (#121)
    
    This commit add an explicit "ack" between the proxy and the action runner. 
The new protocol requires explicit opt in via environment variables set 
accordingly. This change allows the proxy to reliabliy detect functions that 
fail at init time without using timed heuristics (that don't work for slow to 
come up runtimes, leading to errors at the "run" step instead). The 
enhancements also better separate logs from the action runtime.
---
 CHANGES.md                                         |  5 ++
 README.md                                          |  2 +-
 docs/ACTION.md                                     |  8 ++-
 docs/ENVVARS.md                                    | 50 ++++++++++++++
 examples/golang-hello-vendor/src/hello/Gopkg.toml  | 15 +++++
 examples/golang-main-vendor/src/main/Gopkg.toml    | 15 +++++
 .../main/Gopkg.toml => openwhisk/_test/badack.sh   | 38 +----------
 .../main/Gopkg.toml => openwhisk/_test/badack2.sh  | 38 +----------
 .../Gopkg.toml => openwhisk/_test/badcompile.sh    | 38 +----------
 openwhisk/_test/build.sh                           |  3 +
 .../Gopkg.toml => openwhisk/_test/helloack/exec    | 43 +++---------
 openwhisk/actionProxy.go                           | 30 ++++++++-
 openwhisk/actionProxy_test.go                      | 46 +++++++++++++
 openwhisk/compiler.go                              |  9 ++-
 openwhisk/executor.go                              | 78 ++++++++++++++++++++--
 openwhisk/executor_test.go                         | 65 ++++++++++++++++--
 openwhisk/initHandler.go                           | 18 ++++-
 openwhisk/initHandler_test.go                      |  6 +-
 openwhisk/version.go                               |  2 +-
 19 files changed, 344 insertions(+), 165 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 4af8485..9e0526a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -16,6 +16,11 @@
 # limitations under the License.
 #
 -->
+# 1.16.0 (next release)
+- added OW_WAIT_FOR_ACK such at if true, the proxy waits for an 
acknowledgement from the action on startup
+- added OW_EXECUTION_ENV to validate the execution environment before starting 
an action
+- write compilation logs to standard out
+
 # 1.15.0
 - added OW_ACTION_VERSION to action environment (PR#113)
 - propagate API_HOST from parent to child process (PR#115)
diff --git a/README.md b/README.md
index 960d0a2..7009c42 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ This repository contains both the OpenWhisk runtime for 
Golang Actions, as well
 - Writing [Generic](docs/ACTION.md#generic) actions, in bash or as a generic 
linux binary
 - Deployment for [Generic](docs/DEPLOY.md#generic) actions
 - The [ActionLoop](docs/ACTION.md#actionloop) protocol for generic actions
+- Environment [Variables](docs/ENVVARS.md) to configure the proxy
 
 # Change Log
 
@@ -45,4 +46,3 @@ This repository contains both the OpenWhisk runtime for 
Golang Actions, as well
 
 # License
 [Apache 2.0](LICENSE.txt)
-
diff --git a/docs/ACTION.md b/docs/ACTION.md
index 840feb2..8fc639f 100644
--- a/docs/ACTION.md
+++ b/docs/ACTION.md
@@ -75,7 +75,9 @@ The `actionloop` runtime can execute  generic Linux 
executable in an efficient w
 
 ### The Action Loop Protocol
 
-The protocol can be specified informally as follow.
+The protocol can be specified informally as follows.
+
+- Send an acknowledgement after initialization when required. If the 
environment variable `__OW_WAIT_FOR_ACK` is not empty, write on file descriptor 
3 the string `{ "ok": true }`.
 
 - Read one line from standard input (file descriptor 0).
 - Parse the line as a JSON object. Currently the object will be in currently 
in the format:
@@ -108,6 +110,10 @@ In the current actionloop image there is `bash` and the 
`jq` command, so you can
 
 ```bash
 #!/bin/bash
+# send an ack if required
+if test -n "$__OW_WAIT_FOR_ACK"
+  then echo '{"ok":true}' >&3
+fi
 # read input forever line by line
 while read line
 do
diff --git a/docs/ENVVARS.md b/docs/ENVVARS.md
new file mode 100644
index 0000000..22210b1
--- /dev/null
+++ b/docs/ENVVARS.md
@@ -0,0 +1,50 @@
+<!--
+#
+# 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.
+#
+-->
+
+# Environment Variables
+
+## Enviroment variables that control behaviour of the proxy
+
+The following variables are usually set in the Dockerfile
+
+`OW_COMPILER` points to the compiler script to use to compile actions.
+
+`OW_SAVE_JAR` enables checking that an upload file is a jar (that is itself a 
zip file) and it will not expand it if there is a subdirectory names "META-INF" 
(so it is a jar file). Used to support uploading of Java jars.
+
+`OW_WAIT_FOR_ACK` enables waiting for an acknowledgement in the actionloop 
protocol. It should be enabled in all the newer runtimes. Do not enable in 
existing runtimes as it would break existing actions built for that runtime.
+
+`OW_EXECUTION_ENV` enables detection and verification of the compilation 
environent. The compiler is expected to create a file named `exec.env` in the 
same folder as the `exec` file to be run. If this variable is set, before 
starting an action, the init will check that the content of the `exec.env`, 
trimmed of spaces and new lines, is the same, to ensure an action is executed 
in the right execution environment.
+
+`OW_LOG_INIT_ERROR` enables logging of compilation error; the default 
behaviour is to return errors in the answer of the init.
+
+## Environment variables propagated to actions and to the compilation script
+
+The proxy itself sets the following environment variables:
+
+`__OW_EXECUTION_ENV` is the same value that the proxy receive as 
`OW_EXECUTION_ENV`
+
+`__OW_WAIT_FOR_ACK` is set if the proxy has the variable `OW_WAIT_FOR_ACK` set.
+
+Any other environment variable set in the Dockerfile that starts with `__OW_` 
are propagated to the proxy and can override also the values set by the proxy.
+
+Furthermore, actions receive their own environment variables and the values 
set overrides the variables set from the proxy and in the environment.
+
+
+
+
diff --git a/examples/golang-hello-vendor/src/hello/Gopkg.toml 
b/examples/golang-hello-vendor/src/hello/Gopkg.toml
index 3fc5f20..4dad4be 100644
--- a/examples/golang-hello-vendor/src/hello/Gopkg.toml
+++ b/examples/golang-hello-vendor/src/hello/Gopkg.toml
@@ -17,6 +17,21 @@
 
 # Gopkg.toml example
 #
+# 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.
+#
 # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
 # for detailed Gopkg.toml documentation.
 #
diff --git a/examples/golang-main-vendor/src/main/Gopkg.toml 
b/examples/golang-main-vendor/src/main/Gopkg.toml
index 48e74a1..c3dfb0e 100644
--- a/examples/golang-main-vendor/src/main/Gopkg.toml
+++ b/examples/golang-main-vendor/src/main/Gopkg.toml
@@ -17,6 +17,21 @@
 
 # Gopkg.toml example
 #
+# 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.
+#
 # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
 # for detailed Gopkg.toml documentation.
 #
diff --git a/examples/golang-main-vendor/src/main/Gopkg.toml 
b/openwhisk/_test/badack.sh
old mode 100644
new mode 100755
similarity index 51%
copy from examples/golang-main-vendor/src/main/Gopkg.toml
copy to openwhisk/_test/badack.sh
index 48e74a1..524122b
--- a/examples/golang-main-vendor/src/main/Gopkg.toml
+++ b/openwhisk/_test/badack.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -14,38 +15,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-# Gopkg.toml example
-#
-# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
-# for detailed Gopkg.toml documentation.
-#
-# required = ["github.com/user/thing/cmd/thing"]
-# ignored = ["github.com/user/project/pkgX", 
"bitbucket.org/user/project/pkgA/pkgY"]
-#
-# [[constraint]]
-#   name = "github.com/user/project"
-#   version = "1.0.0"
-#
-# [[constraint]]
-#   name = "github.com/user/project2"
-#   branch = "dev"
-#   source = "github.com/myfork/project2"
-#
-# [[override]]
-#   name = "github.com/x/y"
-#   version = "2.4.0"
-#
-# [prune]
-#   non-go = false
-#   go-tests = true
-#   unused-packages = true
-
-
-[[constraint]]
-  name = "github.com/rs/zerolog"
-  version = "1.9.1"
-
-[prune]
-  go-tests = true
-  unused-packages = true
+echo "bad ack" >&3
+read
diff --git a/examples/golang-main-vendor/src/main/Gopkg.toml 
b/openwhisk/_test/badack2.sh
old mode 100644
new mode 100755
similarity index 51%
copy from examples/golang-main-vendor/src/main/Gopkg.toml
copy to openwhisk/_test/badack2.sh
index 48e74a1..0412f00
--- a/examples/golang-main-vendor/src/main/Gopkg.toml
+++ b/openwhisk/_test/badack2.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -14,38 +15,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-# Gopkg.toml example
-#
-# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
-# for detailed Gopkg.toml documentation.
-#
-# required = ["github.com/user/thing/cmd/thing"]
-# ignored = ["github.com/user/project/pkgX", 
"bitbucket.org/user/project/pkgA/pkgY"]
-#
-# [[constraint]]
-#   name = "github.com/user/project"
-#   version = "1.0.0"
-#
-# [[constraint]]
-#   name = "github.com/user/project2"
-#   branch = "dev"
-#   source = "github.com/myfork/project2"
-#
-# [[override]]
-#   name = "github.com/x/y"
-#   version = "2.4.0"
-#
-# [prune]
-#   non-go = false
-#   go-tests = true
-#   unused-packages = true
-
-
-[[constraint]]
-  name = "github.com/rs/zerolog"
-  version = "1.9.1"
-
-[prune]
-  go-tests = true
-  unused-packages = true
+echo "{}" >&3
+read
diff --git a/examples/golang-main-vendor/src/main/Gopkg.toml 
b/openwhisk/_test/badcompile.sh
old mode 100644
new mode 100755
similarity index 51%
copy from examples/golang-main-vendor/src/main/Gopkg.toml
copy to openwhisk/_test/badcompile.sh
index 48e74a1..ef5d986
--- a/examples/golang-main-vendor/src/main/Gopkg.toml
+++ b/openwhisk/_test/badcompile.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -14,38 +15,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-# Gopkg.toml example
-#
-# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
-# for detailed Gopkg.toml documentation.
-#
-# required = ["github.com/user/thing/cmd/thing"]
-# ignored = ["github.com/user/project/pkgX", 
"bitbucket.org/user/project/pkgA/pkgY"]
-#
-# [[constraint]]
-#   name = "github.com/user/project"
-#   version = "1.0.0"
-#
-# [[constraint]]
-#   name = "github.com/user/project2"
-#   branch = "dev"
-#   source = "github.com/myfork/project2"
-#
-# [[override]]
-#   name = "github.com/x/y"
-#   version = "2.4.0"
-#
-# [prune]
-#   non-go = false
-#   go-tests = true
-#   unused-packages = true
-
-
-[[constraint]]
-  name = "github.com/rs/zerolog"
-  version = "1.9.1"
-
-[prune]
-  go-tests = true
-  unused-packages = true
+echo "error in stdout"
+echo "error in stderr" >&2
diff --git a/openwhisk/_test/build.sh b/openwhisk/_test/build.sh
index 1922b73..fe17a65 100755
--- a/openwhisk/_test/build.sh
+++ b/openwhisk/_test/build.sh
@@ -57,4 +57,7 @@ cd jar ; zip -q -r ../sample.jar * ; cd ..
 build exec
 test -e exec.zip && rm exec.zip
 zip -q -r exec.zip exec etc dir
+echo exec/env >helloack/exec.env
+zip -j helloack.zip helloack/*
+
 
diff --git a/examples/golang-main-vendor/src/main/Gopkg.toml 
b/openwhisk/_test/helloack/exec
old mode 100644
new mode 100755
similarity index 51%
copy from examples/golang-main-vendor/src/main/Gopkg.toml
copy to openwhisk/_test/helloack/exec
index 48e74a1..7f14074
--- a/examples/golang-main-vendor/src/main/Gopkg.toml
+++ b/openwhisk/_test/helloack/exec
@@ -1,3 +1,4 @@
+#!/bin/bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -14,38 +15,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-# Gopkg.toml example
-#
-# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
-# for detailed Gopkg.toml documentation.
-#
-# required = ["github.com/user/thing/cmd/thing"]
-# ignored = ["github.com/user/project/pkgX", 
"bitbucket.org/user/project/pkgA/pkgY"]
-#
-# [[constraint]]
-#   name = "github.com/user/project"
-#   version = "1.0.0"
-#
-# [[constraint]]
-#   name = "github.com/user/project2"
-#   branch = "dev"
-#   source = "github.com/myfork/project2"
-#
-# [[override]]
-#   name = "github.com/x/y"
-#   version = "2.4.0"
-#
-# [prune]
-#   non-go = false
-#   go-tests = true
-#   unused-packages = true
-
-
-[[constraint]]
-  name = "github.com/rs/zerolog"
-  version = "1.9.1"
-
-[prune]
-  go-tests = true
-  unused-packages = true
+echo '{"ok":true}' >&3
+while read line
+do
+   name="$(echo $line | jq -r .value.name)"
+   echo msg="hello $name"
+   echo '{"hello": "'$name'"}' >&3
+done
diff --git a/openwhisk/actionProxy.go b/openwhisk/actionProxy.go
index 805207c..bceab1a 100644
--- a/openwhisk/actionProxy.go
+++ b/openwhisk/actionProxy.go
@@ -72,6 +72,18 @@ func NewActionProxy(baseDir string, compiler string, outFile 
*os.File, errFile *
 
 //SetEnv sets the environment
 func (ap *ActionProxy) SetEnv(env map[string]interface{}) {
+       // Propagate proxy version
+       ap.env["__OW_PROXY_VERSION"] = Version
+       // propagate OW_EXECUTION_ENV as  __OW_EXECUTION_ENV
+       ee := os.Getenv("OW_EXECUTION_ENV")
+       if ee != "" {
+               ap.env["__OW_EXECUTION_ENV"] = ee
+       }
+       // require an ack
+       wa := os.Getenv("OW_WAIT_FOR_ACK")
+       if wa != "" {
+               ap.env["__OW_WAIT_FOR_ACK"] = wa
+       }
        // propagate all the variables starting with "__OW_"
        for _, v := range os.Environ() {
                if strings.HasPrefix(v, "__OW_") {
@@ -107,6 +119,20 @@ func (ap *ActionProxy) StartLatestAction() error {
                return fmt.Errorf("no valid actions available")
        }
 
+       // check version
+       execEnv := os.Getenv("OW_EXECUTION_ENV")
+       if execEnv != "" {
+               execEnvFile := fmt.Sprintf("%s/%d/bin/exec.env", ap.baseDir, 
highestDir)
+               execEnvData, err := ioutil.ReadFile(execEnvFile)
+               if err != nil {
+                       return err
+               }
+               if strings.TrimSpace(string(execEnvData)) != execEnv {
+                       fmt.Printf("Expected exec.env should start with 
%s\nActual value: %s", execEnv, execEnvData)
+                       return fmt.Errorf("Execution environment version 
mismatch. See logs for details.")
+               }
+       }
+
        // save the current executor
        curExecutor := ap.theExecutor
 
@@ -115,7 +141,9 @@ func (ap *ActionProxy) StartLatestAction() error {
        os.Chmod(executable, 0755)
        newExecutor := NewExecutor(ap.outFile, ap.errFile, executable, ap.env)
        Debug("starting %s", executable)
-       err := newExecutor.Start()
+
+       // start executor
+       err := newExecutor.Start(os.Getenv("OW_WAIT_FOR_ACK") != "")
        if err == nil {
                ap.theExecutor = newExecutor
                if curExecutor != nil {
diff --git a/openwhisk/actionProxy_test.go b/openwhisk/actionProxy_test.go
index 2fdbd06..e28772c 100644
--- a/openwhisk/actionProxy_test.go
+++ b/openwhisk/actionProxy_test.go
@@ -126,6 +126,24 @@ func Example_compile_src() {
        // ./action/c2/out/exec
 }
 
+func Example_badcompile() {
+
+       os.Setenv("OW_LOG_INIT_ERROR", "1")
+       ts, cur, log := startTestServer("_test/badcompile.sh")
+       res, _, _ := doPost(ts.URL+"/init", initBytes([]byte("hello"), "main"))
+       fmt.Print(res)
+       stopTestServer(ts, cur, log)
+       os.Setenv("OW_LOG_INIT_ERROR", "")
+       // Unordered output:
+       // {"error":"The action failed to generate or locate a binary. See logs 
for details."}
+       // error in stdout
+       // error in stderr
+       //
+       // XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+       // XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+
+}
+
 func Example_SetEnv() {
        ap := NewActionProxy("", "", nil, nil)
        fmt.Println(ap.env)
@@ -144,3 +162,31 @@ func Example_SetEnv() {
        // [1,2,3] {"a":1,"b":2} string 123
 
 }
+
+func Example_executionEnv_nocheck() {
+       os.Setenv("OW_EXECUTION_ENV", "")
+       ts, cur, log := startTestServer("")
+       res, _, _ := doPost(ts.URL+"/init", initBinary("_test/helloack.zip", 
"main"))
+       fmt.Print(res)
+       stopTestServer(ts, cur, log)
+       // Output:
+       // {"ok":true}
+}
+
+func Example_executionEnv_check() {
+       os.Setenv("OW_EXECUTION_ENV", "bad/env")
+       ts, cur, log := startTestServer("")
+       res, _, _ := doPost(ts.URL+"/init", initBinary("_test/helloack.zip", 
"main"))
+       fmt.Print(res)
+       os.Setenv("OW_EXECUTION_ENV", "exec/env")
+       res, _, _ = doPost(ts.URL+"/init", initBinary("_test/helloack.zip", 
"main"))
+       fmt.Print(res)
+       stopTestServer(ts, cur, log)
+       // reset value
+       os.Setenv("OW_EXECUTION_ENV", "")
+       // Output:
+       // Expected exec.env should start with bad/env
+       // Actual value: exec/env
+       // {"error":"cannot start action: Execution environment version 
mismatch. See logs for details."}
+       // {"ok":true}
+}
diff --git a/openwhisk/compiler.go b/openwhisk/compiler.go
index 686c5e2..0236be4 100644
--- a/openwhisk/compiler.go
+++ b/openwhisk/compiler.go
@@ -54,15 +54,18 @@ func (ap *ActionProxy) CompileAction(main string, srcDir 
string, binDir string)
        var cmd *exec.Cmd
        cmd = exec.Command(ap.compiler, main, srcDir, binDir)
        cmd.Env = []string{"PATH=" + os.Getenv("PATH")}
+       for k, v := range ap.env {
+               cmd.Env = append(cmd.Env, k+"="+v)
+       }
 
        // gather stdout and stderr
        out, err := cmd.CombinedOutput()
        Debug("compiler out: %s, %v", out, err)
-       if err != nil {
-               return err
-       }
        if len(out) > 0 {
                return fmt.Errorf("%s", out)
        }
+       if err != nil {
+               return err
+       }
        return nil
 }
diff --git a/openwhisk/executor.go b/openwhisk/executor.go
index 707018d..2744738 100644
--- a/openwhisk/executor.go
+++ b/openwhisk/executor.go
@@ -19,6 +19,8 @@ package openwhisk
 
 import (
        "bufio"
+       "encoding/json"
+       "errors"
        "fmt"
        "io"
        "os"
@@ -79,7 +81,26 @@ 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')
+
+       chout := make(chan []byte)
+       go func() {
+               out, err := proc.output.ReadBytes('\n')
+               if err == nil {
+                       chout <- out
+               } else {
+                       chout <- []byte{}
+               }
+       }()
+       var err error
+       var out []byte
+       select {
+       case out = <-chout:
+               if len(out) == 0 {
+                       err = errors.New("no answer from the action")
+               }
+       case <-proc.exited:
+               err = errors.New("command exited")
+       }
        proc.cmd.Stdout.Write([]byte(OutputGuard))
        proc.cmd.Stderr.Write([]byte(OutputGuard))
        return out, err
@@ -95,10 +116,16 @@ func (proc *Executor) Exited() bool {
        }
 }
 
+// ActionAck is the expected data structure for the action acknowledgement
+type ActionAck struct {
+       Ok bool `json:"ok"`
+}
+
 // Start execution of the command
-// wait a bit to check if the command exited
+// if the flag ack is true, wait forever for an acknowledgement
+// if the flag ack is false wait a bit to check if the command exited
 // returns an error if the program fails
-func (proc *Executor) Start() error {
+func (proc *Executor) Start(waitForAck bool) error {
        // start the underlying executable
        Debug("Start:")
        err := proc.cmd.Start()
@@ -108,15 +135,54 @@ func (proc *Executor) Start() error {
                return fmt.Errorf("command exited")
        }
        Debug("pid: %d", proc.cmd.Process.Pid)
+
        go func() {
                proc.cmd.Wait()
                proc.exited <- true
        }()
+
+       // not waiting for an ack, so use a timeout
+       if !waitForAck {
+               select {
+               case <-proc.exited:
+                       return fmt.Errorf("command exited")
+               case <-time.After(DefaultTimeoutStart):
+                       return nil
+               }
+       }
+
+       // wait for acknowledgement
+       Debug("waiting for an ack")
+       ack := make(chan error)
+       go func() {
+               out, err := proc.output.ReadBytes('\n')
+               Debug("received ack %s", out)
+               if err != nil {
+                       ack <- err
+                       return
+               }
+               // parse ack
+               var ackData ActionAck
+               err = json.Unmarshal(out, &ackData)
+               if err != nil {
+                       ack <- err
+                       return
+               }
+               // check ack
+               if !ackData.Ok {
+                       ack <- fmt.Errorf("The action did not initialize 
properly.")
+                       return
+               }
+               ack <- nil
+       }()
+       // wait for ack or unexpected termination
        select {
+       // ack received
+       case err = <-ack:
+               return err
+       // process exited
        case <-proc.exited:
-               return fmt.Errorf("command exited")
-       case <-time.After(DefaultTimeoutStart):
-               return nil
+               return fmt.Errorf("command exited before ack")
        }
 }
 
diff --git a/openwhisk/executor_test.go b/openwhisk/executor_test.go
index 93702ad..bae8962 100644
--- a/openwhisk/executor_test.go
+++ b/openwhisk/executor_test.go
@@ -26,19 +26,19 @@ var m = map[string]string{}
 func ExampleNewExecutor_failed() {
        log, _ := ioutil.TempFile("", "log")
        proc := NewExecutor(log, log, "true", m)
-       err := proc.Start()
+       err := proc.Start(false)
        fmt.Println(err)
        proc.Stop()
        proc = NewExecutor(log, log, "/bin/pwd", m)
-       err = proc.Start()
+       err = proc.Start(false)
        fmt.Println(err)
        proc.Stop()
        proc = NewExecutor(log, log, "donotexist", m)
-       err = proc.Start()
+       err = proc.Start(false)
        fmt.Println(err)
        proc.Stop()
        proc = NewExecutor(log, log, "/etc/passwd", m)
-       err = proc.Start()
+       err = proc.Start(false)
        fmt.Println(err)
        proc.Stop()
        // Output:
@@ -51,7 +51,7 @@ func ExampleNewExecutor_failed() {
 func ExampleNewExecutor_bc() {
        log, _ := ioutil.TempFile("", "log")
        proc := NewExecutor(log, log, "_test/bc.sh", m)
-       err := proc.Start()
+       err := proc.Start(false)
        fmt.Println(err)
        res, _ := proc.Interact([]byte("2+2"))
        fmt.Printf("%s", res)
@@ -67,7 +67,7 @@ func ExampleNewExecutor_bc() {
 func ExampleNewExecutor_hello() {
        log, _ := ioutil.TempFile("", "log")
        proc := NewExecutor(log, log, "_test/hello.sh", m)
-       err := proc.Start()
+       err := proc.Start(false)
        fmt.Println(err)
        res, _ := proc.Interact([]byte(`{"value":{"name":"Mike"}}`))
        fmt.Printf("%s", res)
@@ -84,7 +84,7 @@ func ExampleNewExecutor_hello() {
 func ExampleNewExecutor_env() {
        log, _ := ioutil.TempFile("", "log")
        proc := NewExecutor(log, log, "_test/env.sh", 
map[string]string{"TEST_HELLO": "WORLD", "TEST_HI": "ALL"})
-       err := proc.Start()
+       err := proc.Start(false)
        fmt.Println(err)
        res, _ := proc.Interact([]byte(`{"value":{"name":"Mike"}}`))
        fmt.Printf("%s", res)
@@ -96,3 +96,54 @@ func ExampleNewExecutor_env() {
        // XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
        // XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
 }
+
+func ExampleNewExecutor_ack() {
+       log, _ := ioutil.TempFile("", "log")
+       proc := NewExecutor(log, log, "_test/hi", m)
+       err := proc.Start(true)
+       fmt.Println(err)
+       proc.Stop()
+       dump(log)
+       // Output:
+       // command exited before ack
+       // hi
+}
+
+func ExampleNewExecutor_badack() {
+       log, _ := ioutil.TempFile("", "log")
+       proc := NewExecutor(log, log, "_test/badack.sh", m)
+       err := proc.Start(true)
+       fmt.Println(err)
+       proc.Stop()
+       dump(log)
+       // Output:
+       // invalid character 'b' looking for beginning of value
+}
+
+func ExampleNewExecutor_badack2() {
+       log, _ := ioutil.TempFile("", "log")
+       proc := NewExecutor(log, log, "_test/badack2.sh", m)
+       err := proc.Start(true)
+       fmt.Println(err)
+       proc.Stop()
+       dump(log)
+       // Output:
+       // The action did not initialize properly.
+}
+
+func ExampleNewExecutor_helloack() {
+       log, _ := ioutil.TempFile("", "log")
+       proc := NewExecutor(log, log, "_test/helloack/exec", m)
+       err := proc.Start(true)
+       fmt.Println(err)
+       res, _ := proc.Interact([]byte(`{"value":{"name":"Mike"}}`))
+       fmt.Printf("%s", res)
+       proc.Stop()
+       dump(log)
+       // Output:
+       // <nil>
+       // {"hello": "Mike"}
+       // msg=hello Mike
+       // XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+       // XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
diff --git a/openwhisk/initHandler.go b/openwhisk/initHandler.go
index 0664508..1391c63 100644
--- a/openwhisk/initHandler.go
+++ b/openwhisk/initHandler.go
@@ -117,14 +117,28 @@ func (ap *ActionProxy) initHandler(w http.ResponseWriter, 
r *http.Request) {
        // if a compiler is defined try to compile
        _, err = ap.ExtractAndCompile(&buf, main)
        if err != nil {
-               sendError(w, http.StatusBadGateway, err.Error())
+               if os.Getenv("OW_LOG_INIT_ERROR") == "" {
+                       sendError(w, http.StatusBadGateway, err.Error())
+               } else {
+                       ap.errFile.Write([]byte(err.Error() + "\n"))
+                       ap.outFile.Write([]byte(OutputGuard))
+                       ap.errFile.Write([]byte(OutputGuard))
+                       sendError(w, http.StatusBadGateway, "The action failed 
to generate or locate a binary. See logs for details.")
+               }
                return
        }
 
        // start an action
        err = ap.StartLatestAction()
        if err != nil {
-               sendError(w, http.StatusBadRequest, "cannot start action: 
"+err.Error())
+               if os.Getenv("OW_LOG_INIT_ERROR") == "" {
+                       sendError(w, http.StatusBadGateway, "cannot start 
action: "+err.Error())
+               } else {
+                       ap.errFile.Write([]byte(err.Error() + "\n"))
+                       ap.outFile.Write([]byte(OutputGuard))
+                       ap.errFile.Write([]byte(OutputGuard))
+                       sendError(w, http.StatusBadGateway, "Cannot start 
action. Check logs for details.")
+               }
                return
        }
        ap.initialized = true
diff --git a/openwhisk/initHandler_test.go b/openwhisk/initHandler_test.go
index e18edd7..89e9545 100644
--- a/openwhisk/initHandler_test.go
+++ b/openwhisk/initHandler_test.go
@@ -221,9 +221,9 @@ func Example_badinit_nocompiler() {
        // Output:
        // 500 {"error":"no action defined yet"}
        // 403 {"error":"Missing main/no code to execute."}
-       // 400 {"error":"cannot start action: command exited"}
-       // 400 {"error":"cannot start action: command exited"}
-       // 400 {"error":"cannot start action: command exited"}
+       // 502 {"error":"cannot start action: command exited"}
+       // 502 {"error":"cannot start action: command exited"}
+       // 502 {"error":"cannot start action: command exited"}
        // 500 {"error":"no action defined yet"}
        // hi
 }
diff --git a/openwhisk/version.go b/openwhisk/version.go
index 2bc050c..fd310fe 100644
--- a/openwhisk/version.go
+++ b/openwhisk/version.go
@@ -17,4 +17,4 @@
 package openwhisk
 
 // Version number - internal
-var Version = "1.14.0"
+var Version = "1.16.0"

Reply via email to