diff --git a/_examples/cron/Makefile b/_examples/cron/Makefile index 019492c..eaa9356 100644 --- a/_examples/cron/Makefile +++ b/_examples/cron/Makefile @@ -1,6 +1,6 @@ .PHONY: dev dev: - wrangler dev + wrangler dev --test-scheduled .PHONY: build build: diff --git a/_examples/cron/README.md b/_examples/cron/README.md index 014e4a7..b31da2f 100644 --- a/_examples/cron/README.md +++ b/_examples/cron/README.md @@ -18,3 +18,12 @@ make dev # run dev server make build # build Go Wasm binary make deploy # deploy worker ``` + +#### Testing cron schedule + +* With curl command below, you can test the cron schedule. + - see: https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#background + +``` +curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" +``` diff --git a/_examples/cron/main.go b/_examples/cron/main.go index 73c3ec9..61f2b08 100644 --- a/_examples/cron/main.go +++ b/_examples/cron/main.go @@ -3,7 +3,9 @@ package main import ( "context" "fmt" + "time" + "github.com/syumai/workers/cloudflare" "github.com/syumai/workers/cloudflare/cron" ) @@ -15,6 +17,11 @@ func task(ctx context.Context) error { fmt.Println(e.ScheduledTime.Unix()) + cloudflare.WaitUntil(func() { + time.Sleep(1 * time.Second) + fmt.Println("Run sub task after returning from main task") + }) + return nil } diff --git a/_examples/multiple-handlers/.gitignore b/_examples/multiple-handlers/.gitignore new file mode 100644 index 0000000..aee7b7e --- /dev/null +++ b/_examples/multiple-handlers/.gitignore @@ -0,0 +1,3 @@ +build +node_modules +.wrangler diff --git a/_examples/multiple-handlers/Makefile b/_examples/multiple-handlers/Makefile new file mode 100644 index 0000000..eaa9356 --- /dev/null +++ b/_examples/multiple-handlers/Makefile @@ -0,0 +1,12 @@ +.PHONY: dev +dev: + wrangler dev --test-scheduled + +.PHONY: build +build: + go run ../../cmd/workers-assets-gen + tinygo build -o ./build/app.wasm -target wasm -no-debug ./... + +.PHONY: deploy +deploy: + wrangler deploy diff --git a/_examples/multiple-handlers/README.md b/_examples/multiple-handlers/README.md new file mode 100644 index 0000000..768d335 --- /dev/null +++ b/_examples/multiple-handlers/README.md @@ -0,0 +1,29 @@ +# multiple-handlers + +* This example shows how to use multiple handlers in a single worker. + +## Development + +### Requirements + +This project requires these tools to be installed globally. + +* wrangler +* tinygo + +### Commands + +``` +make dev # run dev server +make build # build Go Wasm binary +make deploy # deploy worker +``` + +#### Testing cron schedule + +* With curl command below, you can test the cron schedule. + - see: https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#background + +``` +curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" +``` diff --git a/_examples/multiple-handlers/go.mod b/_examples/multiple-handlers/go.mod new file mode 100644 index 0000000..4066289 --- /dev/null +++ b/_examples/multiple-handlers/go.mod @@ -0,0 +1,7 @@ +module github.com/syumai/workers/_examples/multiple-handlers + +go 1.21.3 + +require github.com/syumai/workers v0.0.0 + +replace github.com/syumai/workers => ../../ diff --git a/_examples/multiple-handlers/go.sum b/_examples/multiple-handlers/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/_examples/multiple-handlers/main.go b/_examples/multiple-handlers/main.go new file mode 100644 index 0000000..ff94f6f --- /dev/null +++ b/_examples/multiple-handlers/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "fmt" + "net/http" + + "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare/cron" +) + +func main() { + handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + fmt.Fprint(w, "Hello, world!") + }) + + task := func(ctx context.Context) error { + e, err := cron.NewEvent(ctx) + if err != nil { + return err + } + fmt.Println(e.ScheduledTime.Unix()) + return nil + } + + // set up the worker + workers.ServeNonBlock(handler) + cron.ScheduleTaskNonBlock(task) + + // send a ready signal to the runtime + workers.Ready() + + // block until the handler or task is done + select { + case <-workers.Done(): + case <-cron.Done(): + } +} diff --git a/_examples/multiple-handlers/wrangler.toml b/_examples/multiple-handlers/wrangler.toml new file mode 100644 index 0000000..c0dc4c1 --- /dev/null +++ b/_examples/multiple-handlers/wrangler.toml @@ -0,0 +1,10 @@ +name = "multiple-handlers" +main = "./build/worker.mjs" +compatibility_date = "2023-02-24" +workers_dev = false + +[triggers] +crons = ["* * * * *"] + +[build] +command = "make build" diff --git a/cloudflare/cron/scheduler.go b/cloudflare/cron/scheduler.go index 1f97881..d0485f6 100644 --- a/cloudflare/cron/scheduler.go +++ b/cloudflare/cron/scheduler.go @@ -5,13 +5,17 @@ import ( "fmt" "syscall/js" + "github.com/syumai/workers" "github.com/syumai/workers/internal/jsutil" "github.com/syumai/workers/internal/runtimecontext" ) type Task func(ctx context.Context) error -var scheduledTask Task +var ( + scheduledTask Task + doneCh = make(chan struct{}) +) func runScheduler(eventObj js.Value) error { ctx := runtimecontext.New(context.Background(), eventObj) @@ -46,16 +50,21 @@ func init() { jsutil.Binding.Set("runScheduler", runSchedulerCallback) } -//go:wasmimport workers ready -func ready() - // ScheduleTask sets the Task to be executed func ScheduleTask(task Task) { scheduledTask = task - ready() - select {} + workers.Ready() + <-Done() } // ScheduleTaskNonBlock sets the Task to be executed but does not signal readiness or block // indefinitely. The non-blocking form is meant to be used in conjunction with [workers.Serve]. -func ScheduleTaskNonBlock(task Task) { scheduledTask = task } +func ScheduleTaskNonBlock(task Task) { + scheduledTask = task +} + +// Done returns a channel which is closed when the task is done. +// Currently, this channel is never closed to support cloudflare.WaitUntil feature. +func Done() <-chan struct{} { + return doneCh +} diff --git a/handler.go b/handler.go index edc3f5b..0ce5f66 100644 --- a/handler.go +++ b/handler.go @@ -25,3 +25,15 @@ func Serve(handler http.Handler) { fmt.Fprintln(os.Stderr, "warn: this server is currently running in non-JS mode. to enable JS-related features, please use the make command in the syumai/workers template.") http.ListenAndServe(addr, handler) } + +func ServeNonBlock(http.Handler) { + panic("ServeNonBlock is not supported in non-JS environments") +} + +func Ready() { + panic("Ready is not supported in non-JS environments") +} + +func Done() <-chan struct{} { + panic("Done is not supported in non-JS environments") +} diff --git a/handler_js.go b/handler_js.go index ed026a3..54f1fdf 100644 --- a/handler_js.go +++ b/handler_js.go @@ -16,7 +16,7 @@ import ( var ( httpHandler http.Handler - closeCh = make(chan struct{}) + doneCh = make(chan struct{}) ) func init() { @@ -52,7 +52,7 @@ type appCloser struct { } func (c *appCloser) Close() error { - defer close(closeCh) + defer close(doneCh) return c.ReadCloser.Close() } @@ -84,16 +84,32 @@ func handleRequest(reqObj js.Value) (js.Value, error) { return w.ToJSResponse(), nil } -//go:wasmimport workers ready -func ready() - -// Server serves http.Handler on a JS runtime. +// Serve serves http.Handler on a JS runtime. // if the given handler is nil, http.DefaultServeMux will be used. func Serve(handler http.Handler) { + ServeNonBlock(handler) + Ready() + <-Done() +} + +// ServeNonBlock sets the http.Handler to be served but does not signal readiness or block +// indefinitely. The non-blocking form is meant to be used in conjunction with Ready and WaitForCompletion. +func ServeNonBlock(handler http.Handler) { if handler == nil { handler = http.DefaultServeMux } httpHandler = handler +} + +//go:wasmimport workers ready +func ready() + +// Ready must be called after all setups of the Go side's handlers are done. +func Ready() { ready() - <-closeCh +} + +// Done returns a channel which is closed when the handler is done. +func Done() <-chan struct{} { + return doneCh }