Skip to content

Commit dc08737

Browse files
authored
fix: Ensure the LambdaRuntimeAPI server is up before launching INIT phase (#42)
1 parent 9be92e0 commit dc08737

File tree

3 files changed

+73
-8
lines changed

3 files changed

+73
-8
lines changed

cmd/localstack/custom_interop.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ import (
88
"encoding/json"
99
"errors"
1010
"fmt"
11+
"io"
12+
"net/http"
13+
"strconv"
14+
"strings"
15+
"time"
16+
1117
"github.com/go-chi/chi"
1218
log "github.com/sirupsen/logrus"
1319
"go.amzn.com/lambda/core/statejson"
1420
"go.amzn.com/lambda/interop"
1521
"go.amzn.com/lambda/rapidcore"
1622
"go.amzn.com/lambda/rapidcore/standalone"
17-
"io"
18-
"net/http"
19-
"strconv"
20-
"strings"
21-
"time"
2223
)
2324

2425
type CustomInteropServer struct {

cmd/localstack/main.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ package main
44

55
import (
66
"context"
7-
log "github.com/sirupsen/logrus"
8-
"go.amzn.com/lambda/interop"
9-
"go.amzn.com/lambda/rapidcore"
107
"os"
118
"runtime/debug"
129
"strconv"
1310
"strings"
11+
"time"
12+
13+
log "github.com/sirupsen/logrus"
14+
"go.amzn.com/lambda/interop"
15+
"go.amzn.com/lambda/rapidcore"
1416
)
1517

1618
type LsOpts struct {
@@ -188,6 +190,12 @@ func main() {
188190
SetLogsEgressAPI(localStackLogsEgressApi).
189191
SetTracer(tracer)
190192

193+
// Corresponds to the 'AWS_LAMBDA_RUNTIME_API' environment variable.
194+
// We need to ensure the runtime server is up before the INIT phase,
195+
// but this envar is only set after the InitHandler is called.
196+
runtimeAPIAddress := "127.0.0.1:9001"
197+
sandbox.SetRuntimeAPIAddress(runtimeAPIAddress)
198+
191199
// xray daemon
192200
endpoint := "http://" + lsOpts.LocalstackIP + ":" + lsOpts.EdgePort
193201
xrayConfig := initConfig(endpoint, xRayLogLevel)
@@ -225,6 +233,14 @@ func main() {
225233
}
226234
go RunHotReloadingListener(interopServer, lsOpts.HotReloadingPaths, fileWatcherContext, lsOpts.FileWatcherStrategy)
227235

236+
log.Debugf("Awaiting initialization of runtime api at %s.", runtimeAPIAddress)
237+
// Fixes https://github.com/localstack/localstack/issues/12680
238+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
239+
if err := waitForRuntimeAPI(ctx, runtimeAPIAddress); err != nil {
240+
log.Fatalf("Lambda Runtime API server at %s did not come up in 30s, with error %s", runtimeAPIAddress, err.Error())
241+
}
242+
cancel()
243+
228244
// start runtime init. It is important to start `InitHandler` synchronously because we need to ensure the
229245
// notification channels and status fields are properly initialized before `AwaitInitialized`
230246
log.Debugln("Starting runtime init.")

cmd/localstack/runtime.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"strings"
9+
"time"
10+
)
11+
12+
func waitForRuntimeAPI(ctx context.Context, targetAddress string) error {
13+
if !strings.HasPrefix(targetAddress, "http://") {
14+
targetAddress = fmt.Sprintf("http://%s", targetAddress)
15+
}
16+
17+
healthEndpoint, err := url.JoinPath(targetAddress, "2018-06-01", "ping")
18+
if err != nil {
19+
return err
20+
}
21+
22+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, healthEndpoint, nil)
23+
if err != nil {
24+
return err
25+
}
26+
client := &http.Client{
27+
Timeout: 5 * time.Second,
28+
}
29+
30+
ticker := time.NewTicker(50 * time.Millisecond)
31+
defer ticker.Stop()
32+
33+
for {
34+
resp, err := client.Do(req)
35+
if err == nil {
36+
defer resp.Body.Close()
37+
if resp.StatusCode == http.StatusOK {
38+
return nil
39+
}
40+
}
41+
42+
select {
43+
case <-ctx.Done():
44+
return ctx.Err()
45+
case <-ticker.C:
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)