Files
node/app/app.go
T

185 lines
4.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package app
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/luxfi/log"
"github.com/luxfi/node/node"
"github.com/luxfi/node/utils"
"github.com/luxfi/filesystem/perms"
"github.com/luxfi/sys/ulimit"
nodeconfig "github.com/luxfi/node/config/node"
)
const Header = `
▼▼▼▼▼
▼▼▼
`
var _ App = (*app)(nil)
type App interface {
// Start kicks off the application and returns immediately.
// Start should only be called once.
Start()
// Stop notifies the application to exit and returns immediately.
// Stop should only be called after [Start].
// It is safe to call Stop multiple times.
Stop()
// ExitCode should only be called after [Start] returns. It
// should block until the application finishes
ExitCode() int
}
func New(config nodeconfig.Config) (App, error) {
// Set the data directory permissions to be read write.
if err := perms.ChmodR(config.DatabaseConfig.Path, true, perms.ReadWriteExecute); err != nil {
return nil, fmt.Errorf("failed to restrict the permissions of the database directory with: %w", err)
}
// LoggingConfig removed from node.Config
// if err := perms.ChmodR(config.LoggingConfig.Directory, true, perms.ReadWriteExecute); err != nil {
// return nil, fmt.Errorf("failed to restrict the permissions of the log directory with: %w", err)
// }
// Create a logger and log factory
// Use info level by default to avoid excessive logging (debug level causes massive log spam)
infoLevel, _ := log.ToLevel("info")
logFactory := log.NewFactoryWithConfig(log.Config{
RotatingWriterConfig: log.RotatingWriterConfig{
MaxSize: 8, // 8MB per log file (reasonable for standard profile)
MaxFiles: 5, // Keep 5 rotated files
MaxAge: 7, // 7 days retention
Directory: config.DatabaseConfig.Path,
},
DisplayLevel: infoLevel,
LogLevel: infoLevel, // Use info level, not debug (prevents log spam)
})
logger, err := logFactory.Make("main")
if err != nil {
return nil, fmt.Errorf("failed to create logger: %w", err)
}
// update fd limit
fdLimit := config.FdLimit
if err := ulimit.Set(fdLimit, logger); err != nil {
logger.Error("failed to set fd-limit",
"error", err,
)
return nil, err
}
n, err := node.New(&config, logFactory, logger)
if err != nil {
return nil, fmt.Errorf("failed to initialize node: %w", err)
}
return &app{
node: n,
log: logger,
logFactory: logFactory,
}, nil
}
func Run(app App) int {
// start running the application
app.Start()
// register terminationSignals to kill the application
terminationSignals := make(chan os.Signal, 1)
signal.Notify(terminationSignals, syscall.SIGINT, syscall.SIGTERM)
stackTraceSignal := make(chan os.Signal, 1)
signal.Notify(stackTraceSignal, syscall.SIGABRT)
// start up a new go routine to handle attempts to kill the application
go func() {
for range terminationSignals {
app.Stop()
return
}
}()
// start a goroutine to listen on SIGABRT signals,
// to print the stack trace to standard error.
go func() {
for range stackTraceSignal {
fmt.Fprint(os.Stderr, utils.GetStacktrace(true))
}
}()
// wait for the app to exit and get the exit code response
exitCode := app.ExitCode()
// shut down the termination signal go routine
signal.Stop(terminationSignals)
close(terminationSignals)
// shut down the stack trace go routine
signal.Stop(stackTraceSignal)
close(stackTraceSignal)
// return the exit code that the application reported
return exitCode
}
// app is a wrapper around a node that runs in this process
type app struct {
node *node.Node
log log.Logger
logFactory log.Factory
exitWG sync.WaitGroup
}
// Start the business logic of the node (as opposed to config reading, etc).
// Does not block until the node is done.
func (a *app) Start() {
// [p.ExitCode] will block until [p.exitWG.Done] is called
a.exitWG.Add(1)
go func() {
defer func() {
if r := recover(); r != nil {
a.log.Error("caught panic", "panic", r)
}
// a.log.Stop() // Not available in new log module
// a.logFactory.Close() // Not available
a.exitWG.Done()
}()
defer func() {
// If [p.node.Dispatch()] panics, then we should log the panic and
// then re-raise the panic. This is why the above defer is broken
// into two parts.
// a.log.StopOnPanic() // Not available
}()
err := a.node.Dispatch()
a.log.Debug("dispatch returned",
log.Reflect("error", err),
)
}()
}
// Stop attempts to shutdown the currently running node. This function will
// block until Shutdown returns.
func (a *app) Stop() {
a.node.Shutdown(0)
}
// ExitCode returns the exit code that the node is reporting. This function
// blocks until the node has been shut down.
func (a *app) ExitCode() int {
a.exitWG.Wait()
return a.node.ExitCode()
}