summaryrefslogtreecommitdiff
path: root/internal/app
diff options
context:
space:
mode:
Diffstat (limited to 'internal/app')
-rw-r--r--internal/app/action.go110
-rw-r--r--internal/app/flags.go33
2 files changed, 109 insertions, 34 deletions
diff --git a/internal/app/action.go b/internal/app/action.go
index b579253..78bb251 100644
--- a/internal/app/action.go
+++ b/internal/app/action.go
@@ -4,11 +4,18 @@ import (
"context"
"fmt"
"log/slog"
+ "net"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+ "github.com/ChausseBenjamin/rafta/internal/db"
"github.com/ChausseBenjamin/rafta/internal/logging"
- "github.com/ChausseBenjamin/rafta/internal/server"
- "github.com/ChausseBenjamin/rafta/internal/storage"
+ "github.com/ChausseBenjamin/rafta/internal/pb"
"github.com/urfave/cli/v3"
+ "google.golang.org/grpc"
)
func action(ctx context.Context, cmd *cli.Command) error {
@@ -18,30 +25,99 @@ func action(ctx context.Context, cmd *cli.Command) error {
cmd.String(FlagLogOutput),
)
if err != nil {
- slog.Warn("Error(s) occured during logger initialization", logging.ErrKey, err)
+ slog.WarnContext(ctx, "Error(s) occurred during logger initialization", logging.ErrKey, err)
}
+ slog.InfoContext(ctx, "Starting rafta server")
- slog.Info("Starting rafta server")
+ errAppChan := make(chan error)
+ readyChan := make(chan bool)
+ shutdownDone := make(chan struct{}) // Signals when graceful shutdown is complete
- // TODO: Setup the db
- store, err := storage.Setup(cmd.String(FlagDBPath))
- if err != nil {
- slog.Error("Unable to setup database", logging.ErrKey, err)
+ var once sync.Once
+ gracefulShutdown := func() {}
+ brutalShutdown := func() {}
+
+ application := func() {
+ server, store, err := initApp(ctx, cmd)
+ if err != nil {
+ errAppChan <- err
+ return
+ }
+
+ gracefulShutdown = func() {
+ once.Do(func() { // Ensure brutal shutdown isn't triggered later
+ server.GracefulStop()
+ store.Close()
+ slog.InfoContext(ctx, "Application shutdown")
+ close(shutdownDone) // Signal that graceful shutdown is complete
+ })
+ }
+
+ brutalShutdown = func() {
+ once.Do(func() { // Ensure graceful shutdown isn't re-executed
+ slog.WarnContext(ctx, "Graceful shutdown delay exceeded, shutting down NOW!")
+ server.Stop()
+ store.Close()
+ })
+ }
+
+ port := fmt.Sprintf(":%d", cmd.Int(FlagListenPort))
+ listener, err := net.Listen("tcp", port)
+ if err != nil {
+ errAppChan <- err
+ return
+ }
+ slog.InfoContext(ctx, "Server listening", "port", cmd.Int(FlagListenPort))
+ readyChan <- true
+
+ if err := server.Serve(listener); err != nil {
+ errAppChan <- err
+ }
}
+ go application()
- srv, lis, err := server.Setup(cmd.Int(FlagListenPort), store)
- if err != nil {
- slog.Error("Unable to setup server", logging.ErrKey, err)
+ stopChan := waitForTermChan()
+ running := true
+ for running {
+ select {
+ case errApp := <-errAppChan:
+ if errApp != nil {
+ slog.ErrorContext(ctx, "Application error", logging.ErrKey, errApp)
+ }
+ return errApp
+ case <-stopChan:
+ slog.InfoContext(ctx, "Shutdown requested")
+ go gracefulShutdown()
- return err
+ select {
+ case <-shutdownDone: // If graceful shutdown completes in time, exit normally
+ case <-time.After(cmd.Duration(FlagGraceTimeout)): // Timeout exceeded
+ brutalShutdown()
+ }
+ running = false
+ }
}
+ return nil
+}
- slog.Info(fmt.Sprintf("Listening on port %d", cmd.Int(FlagListenPort)))
- if err := srv.Serve(lis); err != nil {
- slog.Error("Server runtime error", logging.ErrKey, err)
+func waitForTermChan() chan os.Signal {
+ stopChan := make(chan os.Signal, 1)
+ signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM)
+ return stopChan
+}
- return err
+func initApp(ctx context.Context, cmd *cli.Command) (*grpc.Server, *db.Store, error) {
+ store, err := db.Setup(ctx, cmd.String(FlagDBPath))
+ if err != nil {
+ slog.ErrorContext(ctx, "Unable to setup database", logging.ErrKey, err)
+ return nil, nil, err
}
- return nil
+ server, err := pb.Setup(ctx, store)
+ if err != nil {
+ slog.ErrorContext(ctx, "Unable to setup gRPC server", logging.ErrKey, err)
+ return nil, nil, err
+ }
+
+ return server, store, nil
}
diff --git a/internal/app/flags.go b/internal/app/flags.go
index e96b8ef..d83dae8 100644
--- a/internal/app/flags.go
+++ b/internal/app/flags.go
@@ -6,18 +6,20 @@ import (
"log/slog"
"os"
"strings"
+ "time"
"github.com/ChausseBenjamin/rafta/internal/logging"
- "github.com/ChausseBenjamin/rafta/internal/server"
+ "github.com/ChausseBenjamin/rafta/internal/pb"
"github.com/urfave/cli/v3"
)
const (
- FlagListenPort = "port"
- FlagLogLevel = "log-level"
- FlagLogFormat = "log-format"
- FlagLogOutput = "log-output"
- FlagDBPath = "database"
+ FlagListenPort = "port"
+ FlagLogLevel = "log-level"
+ FlagLogFormat = "log-format"
+ FlagLogOutput = "log-output"
+ FlagDBPath = "database"
+ FlagGraceTimeout = "grace-timeout"
)
func flags() []cli.Flag {
@@ -51,9 +53,15 @@ func flags() []cli.Flag {
&cli.IntFlag{
Name: FlagListenPort,
Aliases: []string{"p"},
- Value: 1234,
+ Value: 1157, // list in leetspeek :P
Sources: cli.EnvVars("LISTEN_PORT"),
Action: validateListenPort,
+ },
+ &cli.DurationFlag{
+ Name: FlagGraceTimeout,
+ Aliases: []string{"t"},
+ Value: 5 * time.Second,
+ Sources: cli.EnvVars("GRACEFUL_TIMEOUT"),
}, // }}}
// Database {{{
&cli.StringFlag{
@@ -62,7 +70,6 @@ func flags() []cli.Flag {
Value: "store.db",
Usage: "database file",
Sources: cli.EnvVars("DATABASE_PATH"),
- Action: validateDBPath,
}, // }}}
}
}
@@ -113,15 +120,7 @@ func validateListenPort(ctx context.Context, cmd *cli.Command, p int64) error {
ctx,
fmt.Sprintf("Out-of-bound port provided: %d", p),
)
- return server.ErrOutOfBoundsPort
+ return pb.ErrOutOfBoundsPort
}
return nil
}
-
-func validateDBPath(ctx context.Context, cmd *cli.Command, s string) error {
- // TODO: Ensure the db file is writable.
- // TODO: Ensure the db file is a valid sqlite3 db.
- // TODO: Call db.Reset() if either of the above fail.
- // TODO: Log the error/crash if the db file is not writable.
- return nil
-}