Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 56 additions & 6 deletions main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
"errors"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/spf13/pflag"
"golang.org/x/term"

"github.com/ava-labs/avalanchego/app"
"github.com/ava-labs/avalanchego/config"
"github.com/ava-labs/avalanchego/node"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/version"
)

Expand All @@ -24,7 +27,6 @@ func main() {
if errors.Is(err, pflag.ErrHelp) {
os.Exit(0)
}

if err != nil {
fmt.Printf("couldn't configure flags: %s\n", err)
os.Exit(1)
Expand Down Expand Up @@ -58,15 +60,63 @@ func main() {
}

if term.IsTerminal(int(os.Stdout.Fd())) {
fmt.Println(app.Header)
fmt.Println(Header)
}

nodeApp, err := app.New(nodeConfig)
runner, err := node.NewRunner(nodeConfig)
if err != nil {
fmt.Printf("couldn't start node: %s\n", err)
os.Exit(1)
}

exitCode := app.Run(nodeApp)
os.Exit(exitCode)
os.Exit(Run(runner))
}

const Header = ` _____ .__ .__
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typically consts live at the top of the file

/ _ \___ _______ | | _____ ____ ____ | |__ ____ ,_ o
/ /_\ \ \/ /\__ \ | | \__ \ / \_/ ___\| | \_/ __ \ / //\,
/ | \ / / __ \| |__/ __ \| | \ \___| Y \ ___/ \>> |
\____|__ /\_/ (____ /____(____ /___| /\___ >___| /\___ > \\
\/ \/ \/ \/ \/ \/ \/`

func Run(app *node.Runner) int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we even need this function - we only use it once anyways. Should we inline this into main?

// 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
}
97 changes: 13 additions & 84 deletions app/app.go → node/runner.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,30 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package app
package node

import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"

"go.uber.org/zap"

"github.com/ava-labs/avalanchego/node"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/perms"
"github.com/ava-labs/avalanchego/utils/ulimit"

nodeconfig "github.com/ava-labs/avalanchego/config/node"
)

const Header = ` _____ .__ .__
/ _ \___ _______ | | _____ ____ ____ | |__ ____ ,_ o
/ /_\ \ \/ /\__ \ | | \__ \ / \_/ ___\| | \_/ __ \ / //\,
/ | \ / / __ \| |__/ __ \| | \ \___| Y \ ___/ \>> |
\____|__ /\_/ (____ /____(____ /___| /\___ >___| /\___ > \\
\/ \/ \/ \/ \/ \/ \/`

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
// Runner is a wrapper around a *Node that runs in this process
type Runner struct {
node *Node
log logging.Logger
logFactory logging.Factory
exitWG sync.WaitGroup
}

func New(config nodeconfig.Config) (App, error) {
func NewRunner(config nodeconfig.Config) (*Runner, error) {
Copy link
Contributor

@joshua-kim joshua-kim Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in main owns how to actually start a node/when to shutdown... so I think this code belongs in main more than it belongs in node.

Taking a step back... I'm not sure why we needed the old App type to begin with (now called Runner). There seems to be a clear abstraction boundary between node (our business logic/an avax node), and main (the runner/how we spin up a node), but App/Runner doesn't seem to have a clear boundary of what it owns. I think it might be most reasonable to just in-line all of this code into main and remove Runner altogether, or keep the abstraction but unexport it and leave it under main/.

// 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)
Expand All @@ -71,74 +50,24 @@ func New(config nodeconfig.Config) (App, error) {
return nil, err
}

n, err := node.New(&config, logFactory, log)
n, err := New(&config, logFactory, log)
Copy link

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function call New(&config, logFactory, log) appears to be calling a function that doesn't exist in the current context. This was previously node.New() but the import for the node package was removed. This will cause a compilation error.

Copilot uses AI. Check for mistakes.
if err != nil {
log.Fatal("failed to initialize node", zap.Error(err))
log.Stop()
logFactory.Close()
return nil, fmt.Errorf("failed to initialize node: %w", err)
}

return &app{
return &Runner{
node: n,
log: log,
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 logging.Logger
logFactory logging.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() {
func (a *Runner) Start() {
// [p.ExitCode] will block until [p.exitWG.Done] is called
a.exitWG.Add(1)
go func() {
Expand Down Expand Up @@ -166,13 +95,13 @@ func (a *app) Start() {

// Stop attempts to shutdown the currently running node. This function will
// block until Shutdown returns.
func (a *app) Stop() {
func (a *Runner) 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 {
func (a *Runner) ExitCode() int {
a.exitWG.Wait()
return a.node.ExitCode()
}
Loading