Skip to content

Commit 5bee14b

Browse files
authored
internal: for Flex, use req.Context instead of a context map (#107)
api_pre17.go is a copy of the previous api.go, kept for backwards compatibility. Some other clean-up, too, like using a sync.Once to initialize the background context.
1 parent 9d8544a commit 5bee14b

File tree

5 files changed

+750
-81
lines changed

5 files changed

+750
-81
lines changed

aetest/instance_vm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (i *instance) NewRequest(method, urlStr string, body io.Reader) (*http.Requ
7171
}
7272

7373
// Associate this request.
74-
release := internal.RegisterTestRequest(req, i.apiURL, func(ctx context.Context) context.Context {
74+
req, release := internal.RegisterTestRequest(req, i.apiURL, func(ctx context.Context) context.Context {
7575
ctx = internal.WithAppIDOverride(ctx, "dev~"+i.appID)
7676
return ctx
7777
})

appengine.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func IsDevAppServer() bool {
6363
// NewContext returns a context for an in-flight HTTP request.
6464
// This function is cheap.
6565
func NewContext(req *http.Request) context.Context {
66-
return WithContext(context.Background(), req)
66+
return internal.ReqContext(req)
6767
}
6868

6969
// WithContext returns a copy of the parent context

internal/api.go

Lines changed: 62 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// license that can be found in the LICENSE file.
44

55
// +build !appengine
6+
// +build go1.7
67

78
package internal
89

@@ -62,8 +63,10 @@ var (
6263
},
6364
}
6465

65-
defaultTicketOnce sync.Once
66-
defaultTicket string
66+
defaultTicketOnce sync.Once
67+
defaultTicket string
68+
backgroundContextOnce sync.Once
69+
backgroundContext netcontext.Context
6770
)
6871

6972
func apiURL() *url.URL {
@@ -87,16 +90,10 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) {
8790
outHeader: w.Header(),
8891
apiURL: apiURL(),
8992
}
90-
stopFlushing := make(chan int)
93+
r = r.WithContext(withContext(r.Context(), c))
94+
c.req = r
9195

92-
ctxs.Lock()
93-
ctxs.m[r] = c
94-
ctxs.Unlock()
95-
defer func() {
96-
ctxs.Lock()
97-
delete(ctxs.m, r)
98-
ctxs.Unlock()
99-
}()
96+
stopFlushing := make(chan int)
10097

10198
// Patch up RemoteAddr so it looks reasonable.
10299
if addr := r.Header.Get(userIPHeader); addr != "" {
@@ -195,18 +192,6 @@ func renderPanic(x interface{}) string {
195192
return string(buf)
196193
}
197194

198-
var ctxs = struct {
199-
sync.Mutex
200-
m map[*http.Request]*context
201-
bg *context // background context, lazily initialized
202-
// dec is used by tests to decorate the netcontext.Context returned
203-
// for a given request. This allows tests to add overrides (such as
204-
// WithAppIDOverride) to the context. The map is nil outside tests.
205-
dec map[*http.Request]func(netcontext.Context) netcontext.Context
206-
}{
207-
m: make(map[*http.Request]*context),
208-
}
209-
210195
// context represents the context of an in-flight HTTP request.
211196
// It implements the appengine.Context and http.ResponseWriter interfaces.
212197
type context struct {
@@ -227,6 +212,32 @@ type context struct {
227212

228213
var contextKey = "holds a *context"
229214

215+
// jointContext joins two contexts in a superficial way.
216+
// It takes values and timeouts from a base context, and only values from another context.
217+
type jointContext struct {
218+
base netcontext.Context
219+
valuesOnly netcontext.Context
220+
}
221+
222+
func (c jointContext) Deadline() (time.Time, bool) {
223+
return c.base.Deadline()
224+
}
225+
226+
func (c jointContext) Done() <-chan struct{} {
227+
return c.base.Done()
228+
}
229+
230+
func (c jointContext) Err() error {
231+
return c.base.Err()
232+
}
233+
234+
func (c jointContext) Value(key interface{}) interface{} {
235+
if val := c.base.Value(key); val != nil {
236+
return val
237+
}
238+
return c.valuesOnly.Value(key)
239+
}
240+
230241
// fromContext returns the App Engine context or nil if ctx is not
231242
// derived from an App Engine context.
232243
func fromContext(ctx netcontext.Context) *context {
@@ -253,23 +264,15 @@ func IncomingHeaders(ctx netcontext.Context) http.Header {
253264
return nil
254265
}
255266

256-
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
257-
ctxs.Lock()
258-
c := ctxs.m[req]
259-
d := ctxs.dec[req]
260-
ctxs.Unlock()
261-
262-
if d != nil {
263-
parent = d(parent)
264-
}
267+
func ReqContext(req *http.Request) netcontext.Context {
268+
return req.Context()
269+
}
265270

266-
if c == nil {
267-
// Someone passed in an http.Request that is not in-flight.
268-
// We panic here rather than panicking at a later point
269-
// so that stack traces will be more sensible.
270-
log.Panic("appengine: NewContext passed an unknown http.Request")
271+
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
272+
return jointContext{
273+
base: parent,
274+
valuesOnly: req.Context(),
271275
}
272-
return withContext(parent, c)
273276
}
274277

275278
// DefaultTicket returns a ticket used for background context or dev_appserver.
@@ -291,60 +294,40 @@ func DefaultTicket() string {
291294
}
292295

293296
func BackgroundContext() netcontext.Context {
294-
ctxs.Lock()
295-
defer ctxs.Unlock()
296-
297-
if ctxs.bg != nil {
298-
return toContext(ctxs.bg)
299-
}
300-
301-
// Compute background security ticket.
302-
ticket := DefaultTicket()
303-
304-
ctxs.bg = &context{
305-
req: &http.Request{
306-
Header: http.Header{
307-
ticketHeader: []string{ticket},
297+
backgroundContextOnce.Do(func() {
298+
// Compute background security ticket.
299+
ticket := DefaultTicket()
300+
301+
c := &context{
302+
req: &http.Request{
303+
Header: http.Header{
304+
ticketHeader: []string{ticket},
305+
},
308306
},
309-
},
310-
apiURL: apiURL(),
311-
}
307+
apiURL: apiURL(),
308+
}
309+
backgroundContext = toContext(c)
312310

313-
// TODO(dsymonds): Wire up the shutdown handler to do a final flush.
314-
go ctxs.bg.logFlusher(make(chan int))
311+
// TODO(dsymonds): Wire up the shutdown handler to do a final flush.
312+
go c.logFlusher(make(chan int))
313+
})
315314

316-
return toContext(ctxs.bg)
315+
return backgroundContext
317316
}
318317

319318
// RegisterTestRequest registers the HTTP request req for testing, such that
320319
// any API calls are sent to the provided URL. It returns a closure to delete
321320
// the registration.
322321
// It should only be used by aetest package.
323-
func RegisterTestRequest(req *http.Request, apiURL *url.URL, decorate func(netcontext.Context) netcontext.Context) func() {
322+
func RegisterTestRequest(req *http.Request, apiURL *url.URL, decorate func(netcontext.Context) netcontext.Context) (*http.Request, func()) {
324323
c := &context{
325324
req: req,
326325
apiURL: apiURL,
327326
}
328-
ctxs.Lock()
329-
defer ctxs.Unlock()
330-
if _, ok := ctxs.m[req]; ok {
331-
log.Panic("req already associated with context")
332-
}
333-
if _, ok := ctxs.dec[req]; ok {
334-
log.Panic("req already associated with context")
335-
}
336-
if ctxs.dec == nil {
337-
ctxs.dec = make(map[*http.Request]func(netcontext.Context) netcontext.Context)
338-
}
339-
ctxs.m[req] = c
340-
ctxs.dec[req] = decorate
341-
342-
return func() {
343-
ctxs.Lock()
344-
delete(ctxs.m, req)
345-
delete(ctxs.dec, req)
346-
ctxs.Unlock()
347-
}
327+
ctx := withContext(decorate(req.Context()), c)
328+
req = req.WithContext(ctx)
329+
c.req = req
330+
return req, func() {}
348331
}
349332

350333
var errTimeout = &CallError{

internal/api_classic.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ func IncomingHeaders(ctx netcontext.Context) http.Header {
5959
return nil
6060
}
6161

62+
func ReqContext(req *http.Request) netcontext.Context {
63+
return WithContext(netcontext.Background(), req)
64+
}
65+
6266
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
6367
c := appengine.NewContext(req)
6468
return withContext(parent, c)

0 commit comments

Comments
 (0)