diff --git a/containers/long-running-task-go/Dockerfile b/containers/long-running-task-go/Dockerfile new file mode 100644 index 0000000..0bdec72 --- /dev/null +++ b/containers/long-running-task-go/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.23-alpine3.21 AS builder + +WORKDIR /app + +COPY go.* ./ +RUN go mod download + +COPY . ./ + +RUN go build -o server ./cmd/server + +FROM alpine:3.21 + +COPY --from=builder /app/server /app/server + +CMD ["/app/server"] diff --git a/containers/long-running-task-go/README.md b/containers/long-running-task-go/README.md new file mode 100644 index 0000000..7035d47 --- /dev/null +++ b/containers/long-running-task-go/README.md @@ -0,0 +1,11 @@ +# Long Running Task + +This example showcases running a long background task in a container during the 15m retention period. + +## Deploying + +This example can be deployed using the Scaleway CLI: + +```bash +scw container deploy region=pl-waw +``` diff --git a/containers/long-running-task-go/cmd/server/main.go b/containers/long-running-task-go/cmd/server/main.go new file mode 100644 index 0000000..22ceefe --- /dev/null +++ b/containers/long-running-task-go/cmd/server/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "context" + "errors" + "log/slog" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/google/uuid" +) + +var ( + signalChan = make(chan os.Signal, 1) + wg sync.WaitGroup + + disableGracefulShutdown = os.Getenv("DISABLE_GRACEFUL_SHUTDOWN") != "" + + longRunningJobDuration = os.Getenv("LONG_RUNNING_JOB_DURATION") + parsedLongRunningJobDuration time.Duration + defaultLongRunningJobDuration = 14 * time.Minute +) + +func main() { + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + }))) + + if longRunningJobDuration != "" { + var err error + + parsedLongRunningJobDuration, err = time.ParseDuration(longRunningJobDuration) + if err != nil { + slog.Error("failed to parse LONG_RUNNING_JOB_DURATION", "error", err) + os.Exit(1) + } + + slog.Info("LONG_RUNNING_JOB_DURATION set", "duration", parsedLongRunningJobDuration) + } else { + parsedLongRunningJobDuration = defaultLongRunningJobDuration + slog.Info("LONG_RUNNING_JOB_DURATION not set, using default", "duration", parsedLongRunningJobDuration) + } + + srv := &http.Server{ + Addr: ":8080", + Handler: http.HandlerFunc(handler), + } + + // Handle SIGTERM. + if !disableGracefulShutdown { + signal.Notify(signalChan, syscall.SIGTERM) + } + + // Start HTTP server. + if disableGracefulShutdown { + slog.Info("server started") + + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + slog.Error("server failed to start", "error", err) + os.Exit(1) + } + } else { + go func() { + slog.Info("server started") + + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + slog.Error("server failed to start", "error", err) + os.Exit(1) + } + }() + + sig := <-signalChan + slog.Info("received signal", "signal", sig) + + // This should not take a lot of time because the handler is non-blocking. + if err := srv.Shutdown(context.Background()); err != nil { + slog.Error("server failed to shutdown", "error", err) + } + + // This is where we wait for the long running tasks to finish. + wg.Wait() + } +} + +func handler(w http.ResponseWriter, _ *http.Request) { + wg.Add(1) + + taskID := uuid.NewString() + go longBackgroundTask(taskID, parsedLongRunningJobDuration) + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Started long background task: " + taskID)) +} + +func longBackgroundTask(taskID string, duration time.Duration) { + defer wg.Done() + + slog.Info("long background task started", "taskID", taskID, "duration", duration) + + sleepTime := 1 * time.Second + numIterations := int(duration / sleepTime) + + for i := range numIterations { + slog.Info("long background task running...", + "iteration", i, + "total", numIterations, + "taskID", taskID) + + time.Sleep(sleepTime) + } + + slog.Info("long background task finished", "taskID", taskID) +} diff --git a/containers/long-running-task-go/go.mod b/containers/long-running-task-go/go.mod new file mode 100644 index 0000000..3ce8764 --- /dev/null +++ b/containers/long-running-task-go/go.mod @@ -0,0 +1,7 @@ +module github.com/scaleway/serverless-examples/containers/long-running-task-go + +go 1.23 + +toolchain go1.23.4 + +require github.com/google/uuid v1.6.0 diff --git a/containers/long-running-task-go/go.sum b/containers/long-running-task-go/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/containers/long-running-task-go/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=