Skip to content

Commit ab4e2d3

Browse files
authored
Let users specify unique function keys using delay.MustRegister (#268)
1 parent 6d50fa8 commit ab4e2d3

File tree

2 files changed

+316
-244
lines changed

2 files changed

+316
-244
lines changed

v2/delay/delay.go

Lines changed: 75 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,29 @@
33
// license that can be found in the LICENSE file.
44

55
/*
6-
Package delay provides a way to execute code outside the scope of a
7-
user request by using the taskqueue API.
8-
9-
To declare a function that may be executed later, call Func
10-
in a top-level assignment context, passing it an arbitrary string key
11-
and a function whose first argument is of type context.Context.
12-
The key is used to look up the function so it can be called later.
13-
var laterFunc = delay.Func("key", myFunc)
14-
It is also possible to use a function literal.
15-
var laterFunc = delay.Func("key", func(c context.Context, x string) {
16-
// ...
17-
})
18-
19-
To call a function, invoke its Call method.
20-
laterFunc.Call(c, "something")
21-
A function may be called any number of times. If the function has any
22-
return arguments, and the last one is of type error, the function may
23-
return a non-nil error to signal that the function should be retried.
24-
25-
The arguments to functions may be of any type that is encodable by the gob
26-
package. If an argument is of interface type, it is the client's responsibility
27-
to register with the gob package whatever concrete type may be passed for that
28-
argument; see http://golang.org/pkg/gob/#Register for details.
29-
30-
Any errors during initialization or execution of a function will be
31-
logged to the application logs. Error logs that occur during initialization will
32-
be associated with the request that invoked the Call method.
33-
34-
The state of a function invocation that has not yet successfully
35-
executed is preserved by combining the file name in which it is declared
36-
with the string key that was passed to the Func function. Updating an app
37-
with pending function invocations should safe as long as the relevant
38-
functions have the (filename, key) combination preserved. The filename is
39-
parsed according to these rules:
40-
* Paths in package main are shortened to just the file name (github.com/foo/foo.go -> foo.go)
41-
* Paths are stripped to just package paths (/go/src/github.com/foo/bar.go -> github.com/foo/bar.go)
42-
* Module versions are stripped (/go/pkg/mod/github.com/foo/[email protected]/baz.go -> github.com/foo/bar/baz.go)
43-
44-
There is some inherent risk of pending function invocations being lost during
45-
an update that contains large changes. For example, switching from using GOPATH
46-
to go.mod is a large change that may inadvertently cause file paths to change.
47-
48-
The delay package uses the Task Queue API to create tasks that call the
49-
reserved application path "/_ah/queue/go/delay".
50-
This path must not be marked as "login: required" in app.yaml;
51-
it must be marked as "login: admin" or have no access restriction.
6+
Package delay provides a way to execute code outside of the scope of
7+
a user request by using the Task Queue API.
8+
To use a deferred function, you must register the function to be
9+
deferred as a top-level var. For example,
10+
```
11+
var laterFunc = delay.MustRegister("key", myFunc)
12+
func myFunc(ctx context.Context, a, b string) {...}
13+
```
14+
You can also inline with a function literal:
15+
```
16+
var laterFunc = delay.MustRegister("key", func(ctx context.Context, a, b string) {...})
17+
```
18+
In the above example, "key" is a logical name for the function.
19+
The key needs to be globally unique across your entire application.
20+
To invoke the function in a deferred fashion, call the top-level item:
21+
```
22+
laterFunc(ctx, "aaa", "bbb")
23+
```
24+
This will queue a task and return quickly; the function will be actually
25+
run in a new request. The delay package uses the Task Queue API to create
26+
tasks that call the reserved application path "/_ah/queue/go/delay".
27+
This path may only be marked as "login: admin" or have no access
28+
restriction; it will fail if marked as "login: required".
5229
*/
5330
package delay // import "google.golang.org/appengine/v2/delay"
5431

@@ -151,34 +128,72 @@ func fileKey(file string) (string, error) {
151128
return modVersionPat.ReplaceAllString(file, ""), nil
152129
}
153130

154-
// Func declares a new Function. The second argument must be a function with a
155-
// first argument of type context.Context.
156-
// This function must be called at program initialization time. That means it
157-
// must be called in a global variable declaration or from an init function.
158-
// This restriction is necessary because the instance that delays a function
159-
// call may not be the one that executes it. Only the code executed at program
160-
// initialization time is guaranteed to have been run by an instance before it
161-
// receives a request.
131+
// Func declares a new function that can be called in a deferred fashion.
132+
// The second argument i must be a function with the first argument of
133+
// type context.Context.
134+
// To make the key globally unique, the SDK code will combine "key" with
135+
// the filename of the file in which myFunc is defined
136+
// (e.g., /some/path/myfile.go). This is convenient, but can lead to
137+
// failed deferred tasks if you refactor your code, or change from
138+
// GOPATH to go.mod, and then re-deploy with in-flight deferred tasks.
139+
//
140+
// This function Func must be called in a global scope to properly
141+
// register the function with the framework.
142+
//
143+
// Deprecated: Use MustRegister instead.
162144
func Func(key string, i interface{}) *Function {
163-
f := &Function{fv: reflect.ValueOf(i)}
164-
165145
// Derive unique, somewhat stable key for this func.
166146
_, file, _, _ := runtime.Caller(1)
167147
fk, err := fileKey(file)
168148
if err != nil {
169149
// Not fatal, but log the error
170150
stdlog.Printf("delay: %v", err)
171151
}
172-
f.key = fk + ":" + key
152+
key = fk + ":" + key
153+
f, err := registerFunction(key, i)
154+
if err != nil {
155+
return f
156+
}
157+
if old := funcs[f.key]; old != nil {
158+
old.err = fmt.Errorf("multiple functions registered for %s", key)
159+
}
160+
funcs[f.key] = f
161+
return f
162+
}
163+
164+
// MustRegister declares a new function that can be called in a deferred fashion.
165+
// The second argument i must be a function with the first argument of
166+
// type context.Context.
167+
// MustRegister requires the key to be globally unique.
168+
//
169+
// This function MustRegister must be called in a global scope to properly
170+
// register the function with the framework.
171+
// See the package notes above for more details.
172+
func MustRegister(key string, i interface{}) *Function {
173+
f, err := registerFunction(key, i)
174+
if err != nil {
175+
panic(err)
176+
}
177+
178+
if old := funcs[f.key]; old != nil {
179+
panic(fmt.Errorf("multiple functions registered for %q", key))
180+
}
181+
funcs[f.key] = f
182+
return f
183+
}
184+
185+
func registerFunction(key string, i interface{}) (*Function, error) {
186+
f := &Function{fv: reflect.ValueOf(i)}
187+
f.key = key
173188

174189
t := f.fv.Type()
175190
if t.Kind() != reflect.Func {
176191
f.err = errors.New("not a function")
177-
return f
192+
return f, f.err
178193
}
179194
if t.NumIn() == 0 || !isContext(t.In(0)) {
180195
f.err = errFirstArg
181-
return f
196+
return f, errFirstArg
182197
}
183198

184199
// Register the function's arguments with the gob package.
@@ -194,12 +209,7 @@ func Func(key string, i interface{}) *Function {
194209
}
195210
gob.Register(reflect.Zero(t.In(i)).Interface())
196211
}
197-
198-
if old := funcs[f.key]; old != nil {
199-
old.err = fmt.Errorf("multiple functions registered for %s in %s", key, file)
200-
}
201-
funcs[f.key] = f
202-
return f
212+
return f, nil
203213
}
204214

205215
type invocation struct {

0 commit comments

Comments
 (0)