The profiling server typically shouldn't be exposed to the public, so this doesn't make a huge difference, but it's still good practice for HTTP servers to have read timeouts.
293 lines
8.2 KiB
Go
293 lines
8.2 KiB
Go
// Copyright (c) 2013-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2022 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"runtime/pprof"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/internal/blockchain"
|
|
"github.com/decred/dcrd/internal/blockchain/indexers"
|
|
"github.com/decred/dcrd/internal/limits"
|
|
"github.com/decred/dcrd/internal/version"
|
|
)
|
|
|
|
var cfg *config
|
|
|
|
// winServiceMain is only invoked on Windows. It detects when dcrd is running
|
|
// as a service and reacts accordingly.
|
|
var winServiceMain func() (bool, error)
|
|
|
|
// serviceStartOfDayChan is only used by Windows when the code is running as a
|
|
// service. It signals the service code that startup has completed. Notice
|
|
// that it uses a buffered channel so the caller will not be blocked when the
|
|
// service is not running.
|
|
var serviceStartOfDayChan = make(chan *config, 1)
|
|
|
|
// dcrdMain is the real main function for dcrd. It is necessary to work around
|
|
// the fact that deferred functions do not run when os.Exit() is called.
|
|
func dcrdMain() error {
|
|
// Load configuration and parse command line. This function also
|
|
// initializes logging and configures it accordingly.
|
|
appName := filepath.Base(os.Args[0])
|
|
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
|
tcfg, _, err := loadConfig(appName)
|
|
if err != nil {
|
|
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
|
|
fmt.Fprintln(os.Stderr, err)
|
|
var e errSuppressUsage
|
|
if !errors.As(err, &e) {
|
|
fmt.Fprintln(os.Stderr, usageMessage)
|
|
}
|
|
return err
|
|
}
|
|
cfg = tcfg
|
|
defer func() {
|
|
if logRotator != nil {
|
|
logRotator.Close()
|
|
}
|
|
}()
|
|
|
|
// Get a context that will be canceled when a shutdown signal has been
|
|
// triggered either from an OS signal such as SIGINT (Ctrl+C) or from
|
|
// another subsystem such as the RPC server.
|
|
ctx := shutdownListener()
|
|
defer dcrdLog.Info("Shutdown complete")
|
|
|
|
// Show version and home dir at startup.
|
|
dcrdLog.Infof("Version %s (Go version %s %s/%s)", version.String(),
|
|
runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
|
dcrdLog.Infof("Home dir: %s", cfg.HomeDir)
|
|
if cfg.NoFileLogging {
|
|
dcrdLog.Info("File logging disabled")
|
|
}
|
|
|
|
// Block and transaction processing can cause bursty allocations. This
|
|
// limits the garbage collector from excessively overallocating during
|
|
// bursts. It does this by tweaking the target GC percent and soft memory
|
|
// limit depending on the version of the Go runtime.
|
|
//
|
|
// Starting with Go 1.19, a soft upper memory limit is imposed that leaves
|
|
// plenty of headroom for the minimum recommended value and the target GC
|
|
// percentage is left at the default value to significantly reduce the
|
|
// number of GC cycles thereby reducing the amount of CPU time spent doing
|
|
// garbage collection.
|
|
//
|
|
// For versions of Go prior to 1.19, the ability to set a soft upper memory
|
|
// limit was not available, so the GC percentage is lowered instead which
|
|
// has the effect of preventing overallocations at the expense of more
|
|
// frequent GC cycles.
|
|
//
|
|
// These values were arrived at with the help of profiling live usage.
|
|
if limits.SupportsMemoryLimit {
|
|
// Enforce a soft memory limit for a base amount along with any extra
|
|
// utxo cache over and above the default max cache size.
|
|
const memLimitBase = (15 * (1 << 30)) / 10 // 1.5 GiB
|
|
softMemLimit := int64(memLimitBase)
|
|
if cfg.UtxoCacheMaxSize > defaultUtxoCacheMaxSize {
|
|
extra := int64(cfg.UtxoCacheMaxSize) - defaultUtxoCacheMaxSize
|
|
softMemLimit += extra * (1 << 20)
|
|
}
|
|
limits.SetMemoryLimit(softMemLimit)
|
|
dcrdLog.Infof("Soft memory limit: %s", humanizeBytes(softMemLimit))
|
|
} else {
|
|
debug.SetGCPercent(20)
|
|
}
|
|
|
|
// Enable http profiling server if requested.
|
|
if cfg.Profile != "" {
|
|
go func() {
|
|
listenAddr := cfg.Profile
|
|
dcrdLog.Infof("Creating profiling server listening on %s",
|
|
listenAddr)
|
|
profileRedirect := http.RedirectHandler("/debug/pprof",
|
|
http.StatusSeeOther)
|
|
http.Handle("/", profileRedirect)
|
|
server := &http.Server{
|
|
Addr: listenAddr,
|
|
ReadHeaderTimeout: time.Second * 3,
|
|
}
|
|
err := server.ListenAndServe()
|
|
if err != nil {
|
|
fatalf(err.Error())
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Write cpu profile if requested.
|
|
if cfg.CPUProfile != "" {
|
|
f, err := os.Create(cfg.CPUProfile)
|
|
if err != nil {
|
|
dcrdLog.Errorf("Unable to create cpu profile: %v", err.Error())
|
|
return err
|
|
}
|
|
pprof.StartCPUProfile(f)
|
|
defer f.Close()
|
|
defer pprof.StopCPUProfile()
|
|
}
|
|
|
|
// Write mem profile if requested.
|
|
if cfg.MemProfile != "" {
|
|
f, err := os.Create(cfg.MemProfile)
|
|
if err != nil {
|
|
dcrdLog.Errorf("Unable to create mem profile: %v", err)
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
defer pprof.WriteHeapProfile(f)
|
|
}
|
|
|
|
var lifetimeNotifier lifetimeEventServer
|
|
if cfg.LifetimeEvents {
|
|
lifetimeNotifier = newLifetimeEventServer(outgoingPipeMessages)
|
|
}
|
|
|
|
if cfg.PipeRx != 0 {
|
|
go serviceControlPipeRx(uintptr(cfg.PipeRx))
|
|
}
|
|
if cfg.PipeTx != 0 {
|
|
go serviceControlPipeTx(uintptr(cfg.PipeTx))
|
|
} else {
|
|
go drainOutgoingPipeMessages()
|
|
}
|
|
|
|
// Return now if a shutdown signal was triggered.
|
|
if shutdownRequested(ctx) {
|
|
return nil
|
|
}
|
|
|
|
// Load the block database.
|
|
lifetimeNotifier.notifyStartupEvent(lifetimeEventDBOpen)
|
|
db, err := loadBlockDB(cfg.params.Params)
|
|
if err != nil {
|
|
dcrdLog.Errorf("%v", err)
|
|
return err
|
|
}
|
|
defer func() {
|
|
// Ensure the database is sync'd and closed on shutdown.
|
|
lifetimeNotifier.notifyShutdownEvent(lifetimeEventDBOpen)
|
|
dcrdLog.Infof("Gracefully shutting down the block database...")
|
|
db.Close()
|
|
}()
|
|
|
|
// Return now if a shutdown signal was triggered.
|
|
if shutdownRequested(ctx) {
|
|
return nil
|
|
}
|
|
|
|
// Load the UTXO database.
|
|
utxoDb, err := blockchain.LoadUtxoDB(ctx, cfg.params.Params, cfg.DataDir)
|
|
if err != nil {
|
|
dcrdLog.Errorf("%v", err)
|
|
return err
|
|
}
|
|
defer func() {
|
|
// Ensure the database is sync'd and closed on shutdown.
|
|
dcrdLog.Infof("Gracefully shutting down the UTXO database...")
|
|
utxoDb.Close()
|
|
}()
|
|
|
|
// Return now if a shutdown signal was triggered.
|
|
if shutdownRequested(ctx) {
|
|
return nil
|
|
}
|
|
|
|
// Always drop the legacy address index if needed and drop any other indexes
|
|
// and exit if requested.
|
|
//
|
|
// NOTE: The order is important here because dropping the tx index also
|
|
// drops the address index since it relies on it.
|
|
if err := indexers.DropAddrIndex(ctx, db); err != nil {
|
|
dcrdLog.Errorf("%v", err)
|
|
return err
|
|
}
|
|
if cfg.DropTxIndex {
|
|
if err := indexers.DropTxIndex(ctx, db); err != nil {
|
|
dcrdLog.Errorf("%v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
if cfg.DropExistsAddrIndex {
|
|
if err := indexers.DropExistsAddrIndex(ctx, db); err != nil {
|
|
dcrdLog.Errorf("%v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Drop the legacy v1 committed filter index if needed.
|
|
if err := indexers.DropCfIndex(ctx, db); err != nil {
|
|
dcrdLog.Errorf("%v", err)
|
|
return err
|
|
}
|
|
|
|
// Create server.
|
|
lifetimeNotifier.notifyStartupEvent(lifetimeEventP2PServer)
|
|
svr, err := newServer(ctx, cfg.Listeners, db, utxoDb, cfg.params.Params,
|
|
cfg.DataDir)
|
|
if err != nil {
|
|
dcrdLog.Errorf("Unable to start server: %v", err)
|
|
return err
|
|
}
|
|
|
|
if shutdownRequested(ctx) {
|
|
return nil
|
|
}
|
|
|
|
lifetimeNotifier.notifyStartupComplete()
|
|
defer lifetimeNotifier.notifyShutdownEvent(lifetimeEventP2PServer)
|
|
|
|
// Signal the Windows service (if running) that startup has completed.
|
|
serviceStartOfDayChan <- cfg
|
|
|
|
// Run the server. This will block until the context is cancelled which
|
|
// happens when the interrupt signal is received from an OS signal or
|
|
// shutdown is requested through one of the subsystems such as the RPC
|
|
// server.
|
|
svr.Run(ctx)
|
|
srvrLog.Infof("Server shutdown complete")
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
// Up some limits.
|
|
if err := limits.SetLimits(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to set limits: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Call serviceMain on Windows to handle running as a service. When
|
|
// the return isService flag is true, exit now since we ran as a
|
|
// service. Otherwise, just fall through to normal operation.
|
|
if runtime.GOOS == "windows" {
|
|
isService, err := winServiceMain()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
if isService {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
// Work around defer not working after os.Exit()
|
|
if err := dcrdMain(); err != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|