Skip to content

Commit fe7881b

Browse files
committed
fix: avoid overlapping timeout durations
1 parent 8bc27a7 commit fe7881b

2 files changed

Lines changed: 99 additions & 5 deletions

File tree

internal/lambda/rie/handlers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ func formatInitDuration(initStart time.Time, initEnd time.Time, timeoutDuration
101101
float64(timeoutDuration.Nanoseconds())) / float64(time.Millisecond)
102102
return fmt.Sprintf("Init Duration: %.2f ms\t", initTimeMS)
103103
}
104-
105104
func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox, bs interop.Bootstrap) {
106105
log.Debugf("invoke: -> %s %s %v", r.Method, r.URL, r.Header)
107106
bodyBytes, err := ioutil.ReadAll(r.Body)
@@ -214,6 +213,9 @@ func InvokeHandler(w http.ResponseWriter, r *http.Request, sandbox Sandbox, bs i
214213
case rapidcore.ErrInvokeTimeout:
215214
initEnd := sandbox.AwaitInitCompletion()
216215
initDuration := formatInitDuration(initStart, initEnd, timeoutDuration)
216+
if !initStart.IsZero() && initEnd.After(invokeStart) {
217+
invokeStart = initEnd
218+
}
217219
printEndReports(invokePayload.ID, initDuration, memorySize, invokeStart, timeoutDuration)
218220

219221
w.Write([]byte(fmt.Sprintf("Task timed out after %d.00 seconds", timeout)))

internal/lambda/rie/handlers_test.go

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import (
1919
)
2020

2121
type delayedInitSandbox struct {
22-
delay time.Duration
22+
initDelay time.Duration
23+
invokeDelay time.Duration
2324
invokeCalled bool
2425
initCompletedAt time.Time
2526
invokeErr error
@@ -36,8 +37,9 @@ func (s *delayedInitSandbox) AwaitInitCompletion() time.Time {
3637

3738
func (s *delayedInitSandbox) Invoke(http.ResponseWriter, *interop.Invoke) error {
3839
s.invokeCalled = true
39-
time.Sleep(s.delay)
40+
time.Sleep(s.initDelay)
4041
s.initCompletedAt = time.Now()
42+
time.Sleep(s.invokeDelay)
4143
return s.invokeErr
4244
}
4345

@@ -88,7 +90,7 @@ func TestInvokeHandlerReportsRuntimeInitDuration(t *testing.T) {
8890

8991
request := httptest.NewRequest(http.MethodPost, "/2015-03-31/functions/function/invocations", nil)
9092
response := httptest.NewRecorder()
91-
sandbox := &delayedInitSandbox{delay: 50 * time.Millisecond}
93+
sandbox := &delayedInitSandbox{initDelay: 50 * time.Millisecond}
9294

9395
reader, writer, err := os.Pipe()
9496
require.NoError(t, err)
@@ -129,7 +131,7 @@ func TestInvokeHandlerReportsInitDurationWhenInitTimesOut(t *testing.T) {
129131
request := httptest.NewRequest(http.MethodPost, "/2015-03-31/functions/function/invocations", nil)
130132
response := httptest.NewRecorder()
131133
sandbox := &delayedInitSandbox{
132-
delay: 10 * time.Millisecond,
134+
initDelay: 50 * time.Millisecond,
133135
invokeErr: rapidcore.ErrInvokeTimeout,
134136
}
135137

@@ -155,4 +157,94 @@ func TestInvokeHandlerReportsInitDurationWhenInitTimesOut(t *testing.T) {
155157

156158
matches = regexp.MustCompile(`\tDuration: ([0-9.]+) ms`).FindStringSubmatch(string(output))
157159
require.Len(t, matches, 2)
160+
durationMilliseconds, err := strconv.ParseFloat(matches[1], 64)
161+
require.NoError(t, err)
162+
require.Less(t, durationMilliseconds, float64(40))
163+
require.LessOrEqual(t, initDurationMilliseconds+durationMilliseconds, float64(1020))
164+
}
165+
166+
func TestInvokeHandlerSeparatesInitFromTimedOutInvocation(t *testing.T) {
167+
initMutex.Lock()
168+
initDone = false
169+
initMutex.Unlock()
170+
t.Cleanup(func() {
171+
initMutex.Lock()
172+
initDone = false
173+
initMutex.Unlock()
174+
})
175+
t.Setenv("AWS_LAMBDA_FUNCTION_TIMEOUT", "1")
176+
177+
request := httptest.NewRequest(http.MethodPost, "/2015-03-31/functions/function/invocations", nil)
178+
response := httptest.NewRecorder()
179+
sandbox := &delayedInitSandbox{
180+
initDelay: 50 * time.Millisecond,
181+
invokeDelay: 50 * time.Millisecond,
182+
invokeErr: rapidcore.ErrInvokeTimeout,
183+
}
184+
185+
reader, writer, err := os.Pipe()
186+
require.NoError(t, err)
187+
originalStdout := os.Stdout
188+
os.Stdout = writer
189+
t.Cleanup(func() { os.Stdout = originalStdout })
190+
191+
InvokeHandler(response, request, sandbox, nil)
192+
require.NoError(t, writer.Close())
193+
output, err := io.ReadAll(reader)
194+
require.NoError(t, err)
195+
require.NoError(t, reader.Close())
196+
197+
require.Equal(t, "Task timed out after 1.00 seconds", response.Body.String())
198+
initMatches := regexp.MustCompile(`Init Duration: ([0-9.]+) ms`).FindStringSubmatch(string(output))
199+
require.Len(t, initMatches, 2)
200+
initDurationMilliseconds, err := strconv.ParseFloat(initMatches[1], 64)
201+
require.NoError(t, err)
202+
require.GreaterOrEqual(t, initDurationMilliseconds, float64(40))
203+
204+
durationMatches := regexp.MustCompile(`\tDuration: ([0-9.]+) ms`).FindStringSubmatch(string(output))
205+
require.Len(t, durationMatches, 2)
206+
durationMilliseconds, err := strconv.ParseFloat(durationMatches[1], 64)
207+
require.NoError(t, err)
208+
require.GreaterOrEqual(t, durationMilliseconds, float64(40))
209+
require.Less(t, durationMilliseconds, float64(90))
210+
require.LessOrEqual(t, initDurationMilliseconds+durationMilliseconds, float64(1020))
211+
}
212+
213+
func TestInvokeHandlerReportsWarmTimeoutWithoutInitDuration(t *testing.T) {
214+
initMutex.Lock()
215+
initDone = true
216+
initMutex.Unlock()
217+
t.Cleanup(func() {
218+
initMutex.Lock()
219+
initDone = false
220+
initMutex.Unlock()
221+
})
222+
t.Setenv("AWS_LAMBDA_FUNCTION_TIMEOUT", "1")
223+
224+
request := httptest.NewRequest(http.MethodPost, "/2015-03-31/functions/function/invocations", nil)
225+
response := httptest.NewRecorder()
226+
sandbox := &delayedInitSandbox{
227+
invokeDelay: 50 * time.Millisecond,
228+
invokeErr: rapidcore.ErrInvokeTimeout,
229+
}
230+
231+
reader, writer, err := os.Pipe()
232+
require.NoError(t, err)
233+
originalStdout := os.Stdout
234+
os.Stdout = writer
235+
t.Cleanup(func() { os.Stdout = originalStdout })
236+
237+
InvokeHandler(response, request, sandbox, nil)
238+
require.NoError(t, writer.Close())
239+
output, err := io.ReadAll(reader)
240+
require.NoError(t, err)
241+
require.NoError(t, reader.Close())
242+
243+
require.Equal(t, "Task timed out after 1.00 seconds", response.Body.String())
244+
require.NotContains(t, string(output), "Init Duration:")
245+
durationMatches := regexp.MustCompile(`\tDuration: ([0-9.]+) ms`).FindStringSubmatch(string(output))
246+
require.Len(t, durationMatches, 2)
247+
durationMilliseconds, err := strconv.ParseFloat(durationMatches[1], 64)
248+
require.NoError(t, err)
249+
require.GreaterOrEqual(t, durationMilliseconds, float64(40))
158250
}

0 commit comments

Comments
 (0)