Skip to content

Commit 4a4468e

Browse files
authored
Ensure at least one log flush happens before request ends (#164)
Force a log flush before the connection closes. Fixes #163
1 parent 8066335 commit 4a4468e

File tree

2 files changed

+60
-16
lines changed

2 files changed

+60
-16
lines changed

internal/api.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,13 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) {
129129
flushes++
130130
}
131131
c.pendingLogs.Unlock()
132-
go c.flushLog(false)
132+
flushed := make(chan struct{})
133+
go func() {
134+
defer close(flushed)
135+
// Force a log flush, because with very short requests we
136+
// may not ever flush logs.
137+
c.flushLog(true)
138+
}()
133139
w.Header().Set(logFlushHeader, strconv.Itoa(flushes))
134140

135141
// Avoid nil Write call if c.Write is never called.
@@ -139,6 +145,9 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) {
139145
if c.outBody != nil {
140146
w.Write(c.outBody)
141147
}
148+
// Wait for the last flush to complete before returning,
149+
// otherwise the security ticket will not be valid.
150+
<-flushed
142151
}
143152

144153
func executeRequestSafely(c *context, r *http.Request) {

internal/api_test.go

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -250,43 +250,78 @@ func TestDelayedLogFlushing(t *testing.T) {
250250
f, c, cleanup := setup()
251251
defer cleanup()
252252

253-
http.HandleFunc("/quick_log", func(w http.ResponseWriter, r *http.Request) {
253+
http.HandleFunc("/slow_log", func(w http.ResponseWriter, r *http.Request) {
254254
logC := WithContext(netcontext.Background(), r)
255255
fromContext(logC).apiURL = c.apiURL // Otherwise it will try to use the default URL.
256256
Logf(logC, 1, "It's a lovely day.")
257257
w.WriteHeader(200)
258+
time.Sleep(1200 * time.Millisecond)
258259
w.Write(make([]byte, 100<<10)) // write 100 KB to force HTTP flush
259260
})
260261

261262
r := &http.Request{
262263
Method: "GET",
263264
URL: &url.URL{
264265
Scheme: "http",
265-
Path: "/quick_log",
266+
Path: "/slow_log",
266267
},
267268
Header: c.req.Header,
268269
Body: ioutil.NopCloser(bytes.NewReader(nil)),
269270
}
270271
w := httptest.NewRecorder()
271272

272-
// Check that log flushing does not hold up the HTTP response.
273-
start := time.Now()
274-
handleHTTP(w, r)
275-
if d := time.Since(start); d > 10*time.Millisecond {
276-
t.Errorf("handleHTTP took %v, want under 10ms", d)
273+
handled := make(chan struct{})
274+
go func() {
275+
defer close(handled)
276+
handleHTTP(w, r)
277+
}()
278+
// Check that the log flush eventually comes in.
279+
time.Sleep(1200 * time.Millisecond)
280+
if f := atomic.LoadInt32(&f.LogFlushes); f != 1 {
281+
t.Errorf("After 1.2s: f.LogFlushes = %d, want 1", f)
277282
}
283+
284+
<-handled
278285
const hdr = "X-AppEngine-Log-Flush-Count"
279-
if h := w.HeaderMap.Get(hdr); h != "1" {
280-
t.Errorf("%s header = %q, want %q", hdr, h, "1")
286+
if got, want := w.HeaderMap.Get(hdr), "1"; got != want {
287+
t.Errorf("%s header = %q, want %q", hdr, got, want)
281288
}
282-
if f := atomic.LoadInt32(&f.LogFlushes); f != 0 {
283-
t.Errorf("After HTTP response: f.LogFlushes = %d, want 0", f)
289+
if got, want := atomic.LoadInt32(&f.LogFlushes), int32(2); got != want {
290+
t.Errorf("After HTTP response: f.LogFlushes = %d, want %d", got, want)
284291
}
285292

286-
// Check that the log flush eventually comes in.
287-
time.Sleep(100 * time.Millisecond)
288-
if f := atomic.LoadInt32(&f.LogFlushes); f != 1 {
289-
t.Errorf("After 100ms: f.LogFlushes = %d, want 1", f)
293+
}
294+
295+
func TestLogFlushing(t *testing.T) {
296+
f, c, cleanup := setup()
297+
defer cleanup()
298+
299+
http.HandleFunc("/quick_log", func(w http.ResponseWriter, r *http.Request) {
300+
logC := WithContext(netcontext.Background(), r)
301+
fromContext(logC).apiURL = c.apiURL // Otherwise it will try to use the default URL.
302+
Logf(logC, 1, "It's a lovely day.")
303+
w.WriteHeader(200)
304+
w.Write(make([]byte, 100<<10)) // write 100 KB to force HTTP flush
305+
})
306+
307+
r := &http.Request{
308+
Method: "GET",
309+
URL: &url.URL{
310+
Scheme: "http",
311+
Path: "/quick_log",
312+
},
313+
Header: c.req.Header,
314+
Body: ioutil.NopCloser(bytes.NewReader(nil)),
315+
}
316+
w := httptest.NewRecorder()
317+
318+
handleHTTP(w, r)
319+
const hdr = "X-AppEngine-Log-Flush-Count"
320+
if got, want := w.HeaderMap.Get(hdr), "1"; got != want {
321+
t.Errorf("%s header = %q, want %q", hdr, got, want)
322+
}
323+
if got, want := atomic.LoadInt32(&f.LogFlushes), int32(1); got != want {
324+
t.Errorf("After HTTP response: f.LogFlushes = %d, want %d", got, want)
290325
}
291326
}
292327

0 commit comments

Comments
 (0)