Let users specify unique function keys using delay.MustRegister (#268)

diff --git a/v2/delay/delay.go b/v2/delay/delay.go
index 8afb7f6..7194b6c 100644
--- a/v2/delay/delay.go
+++ b/v2/delay/delay.go
@@ -3,52 +3,29 @@
 // license that can be found in the LICENSE file.
 
 /*
-Package delay provides a way to execute code outside the scope of a
-user request by using the taskqueue API.
-
-To declare a function that may be executed later, call Func
-in a top-level assignment context, passing it an arbitrary string key
-and a function whose first argument is of type context.Context.
-The key is used to look up the function so it can be called later.
-	var laterFunc = delay.Func("key", myFunc)
-It is also possible to use a function literal.
-	var laterFunc = delay.Func("key", func(c context.Context, x string) {
-		// ...
-	})
-
-To call a function, invoke its Call method.
-	laterFunc.Call(c, "something")
-A function may be called any number of times. If the function has any
-return arguments, and the last one is of type error, the function may
-return a non-nil error to signal that the function should be retried.
-
-The arguments to functions may be of any type that is encodable by the gob
-package. If an argument is of interface type, it is the client's responsibility
-to register with the gob package whatever concrete type may be passed for that
-argument; see http://golang.org/pkg/gob/#Register for details.
-
-Any errors during initialization or execution of a function will be
-logged to the application logs. Error logs that occur during initialization will
-be associated with the request that invoked the Call method.
-
-The state of a function invocation that has not yet successfully
-executed is preserved by combining the file name in which it is declared
-with the string key that was passed to the Func function. Updating an app
-with pending function invocations should safe as long as the relevant
-functions have the (filename, key) combination preserved. The filename is
-parsed according to these rules:
-  * Paths in package main are shortened to just the file name (github.com/foo/foo.go -> foo.go)
-  * Paths are stripped to just package paths (/go/src/github.com/foo/bar.go -> github.com/foo/bar.go)
-  * Module versions are stripped (/go/pkg/mod/github.com/foo/bar@v0.0.0-20181026220418-f595d03440dc/baz.go -> github.com/foo/bar/baz.go)
-
-There is some inherent risk of pending function invocations being lost during
-an update that contains large changes. For example, switching from using GOPATH
-to go.mod is a large change that may inadvertently cause file paths to change.
-
-The delay package uses the Task Queue API to create tasks that call the
-reserved application path "/_ah/queue/go/delay".
-This path must not be marked as "login: required" in app.yaml;
-it must be marked as "login: admin" or have no access restriction.
+Package delay provides a way to execute code outside of the scope of
+a user request by using the Task Queue API.
+To use a deferred function, you must register the function to be
+deferred as a top-level var. For example,
+    ```
+    var laterFunc = delay.MustRegister("key", myFunc)
+    func myFunc(ctx context.Context, a, b string) {...}
+    ```
+You can also inline with a function literal:
+    ```
+    var laterFunc = delay.MustRegister("key", func(ctx context.Context, a, b string) {...})
+    ```
+In the above example, "key" is a logical name for the function.
+The key needs to be globally unique across your entire application.
+To invoke the function in a deferred fashion, call the top-level item:
+    ```
+    laterFunc(ctx, "aaa", "bbb")
+    ```
+This will queue a task and return quickly; the function will be actually
+run in a new request. The delay package uses the Task Queue API to create
+tasks that call the reserved application path "/_ah/queue/go/delay".
+This path may only be marked as "login: admin" or have no access
+restriction; it will fail if marked as "login: required".
 */
 package delay // import "google.golang.org/appengine/v2/delay"
 
@@ -151,17 +128,20 @@
 	return modVersionPat.ReplaceAllString(file, ""), nil
 }
 
-// Func declares a new Function. The second argument must be a function with a
-// first argument of type context.Context.
-// This function must be called at program initialization time. That means it
-// must be called in a global variable declaration or from an init function.
-// This restriction is necessary because the instance that delays a function
-// call may not be the one that executes it. Only the code executed at program
-// initialization time is guaranteed to have been run by an instance before it
-// receives a request.
+// Func declares a new function that can be called in a deferred fashion.
+// The second argument i must be a function with the first argument of
+// type context.Context.
+// To make the key globally unique, the SDK code will combine "key" with
+// the filename of the file in which myFunc is defined
+// (e.g., /some/path/myfile.go). This is convenient, but can lead to
+// failed deferred tasks if you refactor your code, or change from
+// GOPATH to go.mod, and then re-deploy with in-flight deferred tasks.
+//
+// This function Func must be called in a global scope to properly
+// register the function with the framework.
+//
+// Deprecated: Use MustRegister instead.
 func Func(key string, i interface{}) *Function {
-	f := &Function{fv: reflect.ValueOf(i)}
-
 	// Derive unique, somewhat stable key for this func.
 	_, file, _, _ := runtime.Caller(1)
 	fk, err := fileKey(file)
@@ -169,16 +149,51 @@
 		// Not fatal, but log the error
 		stdlog.Printf("delay: %v", err)
 	}
-	f.key = fk + ":" + key
+	key = fk + ":" + key
+	f, err := registerFunction(key, i)
+	if err != nil {
+		return f
+	}
+	if old := funcs[f.key]; old != nil {
+		old.err = fmt.Errorf("multiple functions registered for %s", key)
+	}
+	funcs[f.key] = f
+	return f
+}
+
+// MustRegister declares a new function that can be called in a deferred fashion.
+// The second argument i must be a function with the first argument of
+// type context.Context.
+// MustRegister requires the key to be globally unique.
+//
+// This function MustRegister must be called in a global scope to properly
+// register the function with the framework.
+// See the package notes above for more details.
+func MustRegister(key string, i interface{}) *Function {
+	f, err := registerFunction(key, i)
+	if err != nil {
+		panic(err)
+	}
+
+	if old := funcs[f.key]; old != nil {
+		panic(fmt.Errorf("multiple functions registered for %q", key))
+	}
+	funcs[f.key] = f
+	return f
+}
+
+func registerFunction(key string, i interface{}) (*Function, error) {
+	f := &Function{fv: reflect.ValueOf(i)}
+	f.key = key
 
 	t := f.fv.Type()
 	if t.Kind() != reflect.Func {
 		f.err = errors.New("not a function")
-		return f
+		return f, f.err
 	}
 	if t.NumIn() == 0 || !isContext(t.In(0)) {
 		f.err = errFirstArg
-		return f
+		return f, errFirstArg
 	}
 
 	// Register the function's arguments with the gob package.
@@ -194,12 +209,7 @@
 		}
 		gob.Register(reflect.Zero(t.In(i)).Interface())
 	}
-
-	if old := funcs[f.key]; old != nil {
-		old.err = fmt.Errorf("multiple functions registered for %s in %s", key, file)
-	}
-	funcs[f.key] = f
-	return f
+	return f, nil
 }
 
 type invocation struct {
diff --git a/v2/delay/delay_test.go b/v2/delay/delay_test.go
index ed26742..7708bef 100644
--- a/v2/delay/delay_test.go
+++ b/v2/delay/delay_test.go
@@ -42,17 +42,17 @@
 }
 
 var (
-	invalidFunc = Func("invalid", func() {})
+	regFRuns = 0
+	regFMsg  = ""
+	regF     = func(c context.Context, arg string) {
+		regFRuns++
+		regFMsg = arg
+	}
+	regFunc     = Func("regFunc", regF)
+	regRegister = MustRegister("regRegister", regF)
 
-	regFuncRuns = 0
-	regFuncMsg  = ""
-	regFunc     = Func("reg", func(c context.Context, arg string) {
-		regFuncRuns++
-		regFuncMsg = arg
-	})
-
-	custFuncTally = 0
-	custFunc      = Func("cust", func(c context.Context, ct *CustomType, ci CustomInterface) {
+	custFTally = 0
+	custF      = func(c context.Context, ct *CustomType, ci CustomInterface) {
 		a, b := 2, 3
 		if ct != nil {
 			a = ct.N
@@ -60,56 +60,68 @@
 		if ci != nil {
 			b = ci.N()
 		}
-		custFuncTally += a + b
+		custFTally += a + b
+	}
+	custFunc     = Func("custFunc", custF)
+	custRegister = MustRegister("custRegister", custF)
+
+	anotherCustFunc = Func("custFunc2", func(c context.Context, n int, ct *CustomType, ci CustomInterface) {
 	})
 
-	anotherCustFunc = Func("cust2", func(c context.Context, n int, ct *CustomType, ci CustomInterface) {
-	})
-
-	varFuncMsg = ""
-	varFunc    = Func("variadic", func(c context.Context, format string, args ...int) {
+	varFMsg = ""
+	varF    = func(c context.Context, format string, args ...int) {
 		// convert []int to []interface{} for fmt.Sprintf.
 		as := make([]interface{}, len(args))
 		for i, a := range args {
 			as[i] = a
 		}
-		varFuncMsg = fmt.Sprintf(format, as...)
-	})
+		varFMsg = fmt.Sprintf(format, as...)
+	}
+	varFunc     = Func("variadicFunc", varF)
+	varRegister = MustRegister("variadicRegister", varF)
 
-	errFuncRuns = 0
-	errFuncErr  = errors.New("error!")
-	errFunc     = Func("err", func(c context.Context) error {
-		errFuncRuns++
-		if errFuncRuns == 1 {
+	errFRuns = 0
+	errFErr  = errors.New("error!")
+	errF     = func(c context.Context) error {
+		errFRuns++
+		if errFRuns == 1 {
 			return nil
 		}
-		return errFuncErr
-	})
+		return errFErr
+	}
+	errFunc     = Func("errFunc", errF)
+	errRegister = MustRegister("errRegister", errF)
 
 	dupeWhich = 0
-	dupe1Func = Func("dupe", func(c context.Context) {
+	dupe1F    = func(c context.Context) {
 		if dupeWhich == 0 {
 			dupeWhich = 1
 		}
-	})
-	dupe2Func = Func("dupe", func(c context.Context) {
+	}
+	dupe1Func = Func("dupe", dupe1F)
+	dupe2F    = func(c context.Context) {
 		if dupeWhich == 0 {
 			dupeWhich = 2
 		}
-	})
+	}
+	dupe2Func = Func("dupe", dupe2F)
 
-	reqFuncRuns    = 0
-	reqFuncHeaders *taskqueue.RequestHeaders
-	reqFuncErr     error
-	reqFunc        = Func("req", func(c context.Context) {
-		reqFuncRuns++
-		reqFuncHeaders, reqFuncErr = RequestHeaders(c)
-	})
+	requestFuncRuns    = 0
+	requestFuncHeaders *taskqueue.RequestHeaders
+	requestFuncErr     error
+	requestF           = func(c context.Context) {
+		requestFuncRuns++
+		requestFuncHeaders, requestFuncErr = RequestHeaders(c)
+	}
+	requestFunc     = Func("requestFunc", requestF)
+	requestRegister = MustRegister("requestRegister", requestF)
 
 	stdCtxRuns = 0
-	stdCtxFunc = Func("stdctx", func(c stdctx.Context) {
+	stdCtxF    = func(c stdctx.Context) {
 		stdCtxRuns++
-	})
+	}
+	stdCtxFunc     = Func("stdctxFunc", stdCtxF)
+	stdCtxRegister = MustRegister("stdctxRegister", stdCtxF)
 )
 
 type fakeContext struct {
@@ -136,6 +148,7 @@
 
 func TestInvalidFunction(t *testing.T) {
 	c := newFakeContext()
+	invalidFunc := Func("invalid", func() {})
 
 	if got, want := invalidFunc.Call(c.ctx), fmt.Errorf("delay: func is invalid: %s", errFirstArg); got.Error() != want.Error() {
 		t.Errorf("Incorrect error: got %q, want %q", got, want)
@@ -144,7 +157,6 @@
 
 func TestVariadicFunctionArguments(t *testing.T) {
 	// Check the argument type validation for variadic functions.
-
 	c := newFakeContext()
 
 	calls := 0
@@ -153,15 +165,19 @@
 		return t, nil
 	}
 
-	varFunc.Call(c.ctx, "hi")
-	varFunc.Call(c.ctx, "%d", 12)
-	varFunc.Call(c.ctx, "%d %d %d", 3, 1, 4)
-	if calls != 3 {
-		t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
-	}
+	for _, testTarget := range []*Function{varFunc, varRegister} {
+		// reset state
+		calls = 0
+		testTarget.Call(c.ctx, "hi")
+		testTarget.Call(c.ctx, "%d", 12)
+		testTarget.Call(c.ctx, "%d %d %d", 3, 1, 4)
+		if calls != 3 {
+			t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
+		}
 
-	if got, want := varFunc.Call(c.ctx, "%d %s", 12, "a string is bad"), errors.New("delay: argument 3 has wrong type: string is not assignable to int"); got.Error() != want.Error() {
-		t.Errorf("Incorrect error: got %q, want %q", got, want)
+		if got, want := testTarget.Call(c.ctx, "%d %s", 12, "a string is bad"), errors.New("delay: argument 3 has wrong type: string is not assignable to int"); got.Error() != want.Error() {
+			t.Errorf("Incorrect error: got %q, want %q", got, want)
+		}
 	}
 }
 
@@ -187,17 +203,18 @@
 			wantErr: "delay: argument 1 has wrong type: int is not assignable to string",
 		},
 	}
-	for i, tc := range tests {
-		got := regFunc.Call(c.ctx, tc.args...)
-		if got.Error() != tc.wantErr {
-			t.Errorf("Call %v: got %q, want %q", i, got, tc.wantErr)
+	for _, testTarget := range []*Function{regFunc, regRegister} {
+		for i, tc := range tests {
+			got := testTarget.Call(c.ctx, tc.args...)
+			if got.Error() != tc.wantErr {
+				t.Errorf("Call %v: got %q, want %q", i, got, tc.wantErr)
+			}
 		}
 	}
 }
 
 func TestRunningFunction(t *testing.T) {
 	c := newFakeContext()
-
 	// Fake out the adding of a task.
 	var task *taskqueue.Task
 	taskqueueAdder = func(_ context.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
@@ -208,23 +225,25 @@
 		return tk, nil
 	}
 
-	regFuncRuns, regFuncMsg = 0, "" // reset state
-	const msg = "Why, hello!"
-	regFunc.Call(c.ctx, msg)
+	for _, testTarget := range []*Function{regFunc, regRegister} {
+		regFRuns, regFMsg = 0, "" // reset state
+		const msg = "Why, hello!"
+		testTarget.Call(c.ctx, msg)
 
-	// Simulate the Task Queue service.
-	req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-	if err != nil {
-		t.Fatalf("Failed making http.Request: %v", err)
-	}
-	rw := httptest.NewRecorder()
-	runFunc(c.ctx, rw, req)
+		// Simulate the Task Queue service.
+		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+		if err != nil {
+			t.Fatalf("Failed making http.Request: %v", err)
+		}
+		rw := httptest.NewRecorder()
+		runFunc(c.ctx, rw, req)
 
-	if regFuncRuns != 1 {
-		t.Errorf("regFuncRuns: got %d, want 1", regFuncRuns)
-	}
-	if regFuncMsg != msg {
-		t.Errorf("regFuncMsg: got %q, want %q", regFuncMsg, msg)
+		if regFRuns != 1 {
+			t.Errorf("regFuncRuns: got %d, want 1", regFRuns)
+		}
+		if regFMsg != msg {
+			t.Errorf("regFuncMsg: got %q, want %q", regFMsg, msg)
+		}
 	}
 }
 
@@ -241,36 +260,38 @@
 		return tk, nil
 	}
 
-	custFuncTally = 0 // reset state
-	custFunc.Call(c.ctx, &CustomType{N: 11}, CustomImpl(13))
+	for _, testTarget := range []*Function{custFunc, custRegister} {
+		custFTally = 0 // reset state
+		testTarget.Call(c.ctx, &CustomType{N: 11}, CustomImpl(13))
 
-	// Simulate the Task Queue service.
-	req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-	if err != nil {
-		t.Fatalf("Failed making http.Request: %v", err)
-	}
-	rw := httptest.NewRecorder()
-	runFunc(c.ctx, rw, req)
+		// Simulate the Task Queue service.
+		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+		if err != nil {
+			t.Fatalf("Failed making http.Request: %v", err)
+		}
+		rw := httptest.NewRecorder()
+		runFunc(c.ctx, rw, req)
 
-	if custFuncTally != 24 {
-		t.Errorf("custFuncTally = %d, want 24", custFuncTally)
-	}
+		if custFTally != 24 {
+			t.Errorf("custFTally = %d, want 24", custFTally)
+		}
 
-	// Try the same, but with nil values; one is a nil pointer (and thus a non-nil interface value),
-	// and the other is a nil interface value.
-	custFuncTally = 0 // reset state
-	custFunc.Call(c.ctx, (*CustomType)(nil), nil)
+		// Try the same, but with nil values; one is a nil pointer (and thus a non-nil interface value),
+		// and the other is a nil interface value.
+		custFTally = 0 // reset state
+		testTarget.Call(c.ctx, (*CustomType)(nil), nil)
 
-	// Simulate the Task Queue service.
-	req, err = http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-	if err != nil {
-		t.Fatalf("Failed making http.Request: %v", err)
-	}
-	rw = httptest.NewRecorder()
-	runFunc(c.ctx, rw, req)
+		// Simulate the Task Queue service.
+		req, err = http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+		if err != nil {
+			t.Fatalf("Failed making http.Request: %v", err)
+		}
+		rw = httptest.NewRecorder()
+		runFunc(c.ctx, rw, req)
 
-	if custFuncTally != 5 {
-		t.Errorf("custFuncTally = %d, want 5", custFuncTally)
+		if custFTally != 5 {
+			t.Errorf("custFTally = %d, want 5", custFTally)
+		}
 	}
 }
 
@@ -287,20 +308,22 @@
 		return tk, nil
 	}
 
-	varFuncMsg = "" // reset state
-	varFunc.Call(c.ctx, "Amiga %d has %d KB RAM", 500, 512)
+	for _, testTarget := range []*Function{varFunc, varRegister} {
+		varFMsg = "" // reset state
+		testTarget.Call(c.ctx, "Amiga %d has %d KB RAM", 500, 512)
 
-	// Simulate the Task Queue service.
-	req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-	if err != nil {
-		t.Fatalf("Failed making http.Request: %v", err)
-	}
-	rw := httptest.NewRecorder()
-	runFunc(c.ctx, rw, req)
+		// Simulate the Task Queue service.
+		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+		if err != nil {
+			t.Fatalf("Failed making http.Request: %v", err)
+		}
+		rw := httptest.NewRecorder()
+		runFunc(c.ctx, rw, req)
 
-	const expected = "Amiga 500 has 512 KB RAM"
-	if varFuncMsg != expected {
-		t.Errorf("varFuncMsg = %q, want %q", varFuncMsg, expected)
+		const expected = "Amiga 500 has 512 KB RAM"
+		if varFMsg != expected {
+			t.Errorf("varFMsg = %q, want %q", varFMsg, expected)
+		}
 	}
 }
 
@@ -317,39 +340,44 @@
 		return tk, nil
 	}
 
-	errFunc.Call(c.ctx)
+	for _, testTarget := range []*Function{errFunc, errRegister} {
+		// reset state
+		c.logging = [][]interface{}{}
+		errFRuns = 0
+		testTarget.Call(c.ctx)
 
-	// Simulate the Task Queue service.
-	// The first call should succeed; the second call should fail.
-	{
-		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-		if err != nil {
-			t.Fatalf("Failed making http.Request: %v", err)
+		// Simulate the Task Queue service.
+		// The first call should succeed; the second call should fail.
+		{
+			req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+			if err != nil {
+				t.Fatalf("Failed making http.Request: %v", err)
+			}
+			rw := httptest.NewRecorder()
+			runFunc(c.ctx, rw, req)
 		}
-		rw := httptest.NewRecorder()
-		runFunc(c.ctx, rw, req)
-	}
-	{
-		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-		if err != nil {
-			t.Fatalf("Failed making http.Request: %v", err)
-		}
-		rw := httptest.NewRecorder()
-		runFunc(c.ctx, rw, req)
-		if rw.Code != http.StatusInternalServerError {
-			t.Errorf("Got status code %d, want %d", rw.Code, http.StatusInternalServerError)
-		}
+		{
+			req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+			if err != nil {
+				t.Fatalf("Failed making http.Request: %v", err)
+			}
+			rw := httptest.NewRecorder()
+			runFunc(c.ctx, rw, req)
+			if rw.Code != http.StatusInternalServerError {
+				t.Errorf("Got status code %d, want %d", rw.Code, http.StatusInternalServerError)
+			}
 
-		wantLogging := [][]interface{}{
-			{"ERROR", "delay: func failed (will retry): %v", errFuncErr},
-		}
-		if !reflect.DeepEqual(c.logging, wantLogging) {
-			t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+			wantLogging := [][]interface{}{
+				{"ERROR", "delay: func failed (will retry): %v", errFErr},
+			}
+			if !reflect.DeepEqual(c.logging, wantLogging) {
+				t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+			}
 		}
 	}
 }
 
-func TestDuplicateFunction(t *testing.T) {
+func TestFuncDuplicateFunction(t *testing.T) {
 	c := newFakeContext()
 
 	// Fake out the adding of a task.
@@ -390,48 +418,80 @@
 	}
 }
 
-func TestGetRequestHeadersFromContext(t *testing.T) {
-	c := newFakeContext()
-
-	// Outside a delay.Func should return an error.
-	headers, err := RequestHeaders(c.ctx)
-	if headers != nil {
-		t.Errorf("RequestHeaders outside Func, got %v, want nil", headers)
-	}
-	if err != errOutsideDelayFunc {
-		t.Errorf("RequestHeaders outside Func err, got %v, want %v", err, errOutsideDelayFunc)
-	}
-
-	// Fake out the adding of a task.
-	var task *taskqueue.Task
-	taskqueueAdder = func(_ context.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
-		if queue != "" {
-			t.Errorf(`Got queue %q, expected ""`, queue)
+func TestMustRegisterDuplicateFunction(t *testing.T) {
+	MustRegister("dupe", dupe1F)
+	defer func() {
+		err := recover()
+		if err == nil {
+			t.Error("MustRegister did not panic")
 		}
-		task = tk
-		return tk, nil
-	}
+		got := fmt.Sprintf("%s", err)
+		want := fmt.Sprintf("multiple functions registered for %q", "dupe")
+		if got != want {
+			t.Errorf("Incorrect error: got %q, want %q", got, want)
+		}
+	}()
+	MustRegister("dupe", dupe2F)
+}
 
-	reqFunc.Call(c.ctx)
+func TestInvalidFunction_MustRegister(t *testing.T) {
+	defer func() {
+		err := recover()
+		if err == nil {
+			t.Error("MustRegister did not panic")
+		}
+		if err != errFirstArg {
+			t.Errorf("Incorrect error: got %q, want %q", err, errFirstArg)
+		}
+	}()
+	MustRegister("invalid", func() {})
+}
 
-	reqFuncRuns, reqFuncHeaders = 0, nil // reset state
-	// Simulate the Task Queue service.
-	req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-	req.Header.Set("x-appengine-taskname", "foobar")
-	if err != nil {
-		t.Fatalf("Failed making http.Request: %v", err)
-	}
-	rw := httptest.NewRecorder()
-	runFunc(c.ctx, rw, req)
 
-	if reqFuncRuns != 1 {
-		t.Errorf("reqFuncRuns: got %d, want 1", reqFuncRuns)
-	}
-	if reqFuncHeaders.TaskName != "foobar" {
-		t.Errorf("reqFuncHeaders.TaskName: got %v, want 'foobar'", reqFuncHeaders.TaskName)
-	}
-	if reqFuncErr != nil {
-		t.Errorf("reqFuncErr: got %v, want nil", reqFuncErr)
+func TestGetRequestHeadersFromContext(t *testing.T) {
+	for _, testTarget := range []*Function{requestFunc, requestRegister} {
+		c := newFakeContext()
+
+		// Outside a delay.Func should return an error.
+		headers, err := RequestHeaders(c.ctx)
+		if headers != nil {
+			t.Errorf("RequestHeaders outside Func, got %v, want nil", headers)
+		}
+		if err != errOutsideDelayFunc {
+			t.Errorf("RequestHeaders outside Func err, got %v, want %v", err, errOutsideDelayFunc)
+		}
+
+		// Fake out the adding of a task.
+		var task *taskqueue.Task
+		taskqueueAdder = func(_ context.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
+			if queue != "" {
+				t.Errorf(`Got queue %q, expected ""`, queue)
+			}
+			task = tk
+			return tk, nil
+		}
+
+		testTarget.Call(c.ctx)
+
+		requestFuncRuns, requestFuncHeaders = 0, nil // reset state
+		// Simulate the Task Queue service.
+		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+		req.Header.Set("x-appengine-taskname", "foobar")
+		if err != nil {
+			t.Fatalf("Failed making http.Request: %v", err)
+		}
+		rw := httptest.NewRecorder()
+		runFunc(c.ctx, rw, req)
+
+		if requestFuncRuns != 1 {
+			t.Errorf("requestFuncRuns: got %d, want 1", requestFuncRuns)
+		}
+		if requestFuncHeaders.TaskName != "foobar" {
+			t.Errorf("requestFuncHeaders.TaskName: got %v, want 'foobar'", requestFuncHeaders.TaskName)
+		}
+		if requestFuncErr != nil {
+			t.Errorf("requestFuncErr: got %v, want nil", requestFuncErr)
+		}
 	}
 }
 
@@ -446,22 +506,24 @@
 		return tk, nil
 	}
 
-	c := newFakeContext()
-	stdCtxRuns = 0 // reset state
-	if err := stdCtxFunc.Call(c.ctx); err != nil {
-		t.Fatal("Function.Call:", err)
-	}
+	for _, testTarget := range []*Function{stdCtxFunc, stdCtxRegister} {
+		c := newFakeContext()
+		stdCtxRuns = 0 // reset state
+		if err := testTarget.Call(c.ctx); err != nil {
+			t.Fatal("Function.Call:", err)
+		}
 
-	// Simulate the Task Queue service.
-	req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
-	if err != nil {
-		t.Fatalf("Failed making http.Request: %v", err)
-	}
-	rw := httptest.NewRecorder()
-	runFunc(c.ctx, rw, req)
+		// Simulate the Task Queue service.
+		req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+		if err != nil {
+			t.Fatalf("Failed making http.Request: %v", err)
+		}
+		rw := httptest.NewRecorder()
+		runFunc(c.ctx, rw, req)
 
-	if stdCtxRuns != 1 {
-		t.Errorf("stdCtxRuns: got %d, want 1", stdCtxRuns)
+		if stdCtxRuns != 1 {
+			t.Errorf("stdCtxRuns: got %d, want 1", stdCtxRuns)
+		}
 	}
 }