Skip to content

Commit 3cac6da

Browse files
committed
fix: Ensure the LambdaRuntimeAPI server is up before launching INIT phase
1 parent 9be92e0 commit 3cac6da

File tree

2 files changed

+68
-3
lines changed

2 files changed

+68
-3
lines changed

cmd/localstack/main.go

Lines changed: 20 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.")
@@ -239,6 +255,7 @@ func main() {
239255
return
240256
}
241257

258+
// ping the runtime server until ready
242259
log.Debugln("Completed initialization of runtime init. Sending status ready to LocalStack.")
243260
if err := interopServer.localStackAdapter.SendStatus(Ready, []byte{}); err != nil {
244261
log.Fatalln("Failed to send status ready to LocalStack " + err.Error() + ". Exiting.")

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(500 * 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)