diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go
index 3a028fe180aa..ee6e47dac755 100644
--- a/cmd/XDC/main.go
+++ b/cmd/XDC/main.go
@@ -89,10 +89,11 @@ var (
//utils.LightServFlag,
//utils.LightPeersFlag,
//utils.LightKDFFlag,
- //utils.CacheFlag,
- //utils.CacheDatabaseFlag,
+ utils.CacheFlag,
+ utils.CacheDatabaseFlag,
//utils.CacheGCFlag,
//utils.TrieCacheGenFlag,
+ utils.FDLimitFlag,
utils.ListenPortFlag,
utils.MaxPeersFlag,
utils.MaxPendingPeersFlag,
diff --git a/cmd/XDC/usage.go b/cmd/XDC/usage.go
index f0e567bd555f..a49da82bb581 100644
--- a/cmd/XDC/usage.go
+++ b/cmd/XDC/usage.go
@@ -123,15 +123,16 @@ var AppHelpFlagGroups = []flagGroup{
// utils.TxPoolLifetimeFlag,
// },
//},
- //{
- // Name: "PERFORMANCE TUNING",
- // Flags: []cli.Flag{
- // utils.CacheFlag,
- // utils.CacheDatabaseFlag,
- // utils.CacheGCFlag,
- // utils.TrieCacheGenFlag,
- // },
- //},
+ {
+ Name: "PERFORMANCE TUNING",
+ Flags: []cli.Flag{
+ utils.CacheFlag,
+ utils.CacheDatabaseFlag,
+ // utils.CacheGCFlag,
+ // utils.TrieCacheGenFlag,
+ utils.FDLimitFlag,
+ },
+ },
{
Name: "ACCOUNT",
Flags: []cli.Flag{
diff --git a/cmd/gc/main.go b/cmd/gc/main.go
index 6d3b2bdf546e..b017b90bedc5 100644
--- a/cmd/gc/main.go
+++ b/cmd/gc/main.go
@@ -52,7 +52,7 @@ type ResultProcessNode struct {
func main() {
flag.Parse()
- db, _ := leveldb.New(*dir, ethconfig.Defaults.DatabaseCache, utils.MakeDatabaseHandles(), "")
+ db, _ := leveldb.New(*dir, ethconfig.Defaults.DatabaseCache, utils.MakeDatabaseHandles(0), "")
lddb := rawdb.NewDatabase(db)
head := core.GetHeadBlockHash(lddb)
currentHeader := core.GetHeader(lddb, head, core.GetBlockNumber(lddb, head))
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 310a3f12e9b6..ea97a16bcd36 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -20,10 +20,12 @@ package utils
import (
"crypto/ecdsa"
"fmt"
+ "math"
"math/big"
"os"
"path/filepath"
"runtime"
+ godebug "runtime/debug"
"strconv"
"strings"
@@ -53,6 +55,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/netutil"
"github.com/XinFinOrg/XDPoSChain/params"
whisper "github.com/XinFinOrg/XDPoSChain/whisper/whisperv6"
+ gopsutil "github.com/shirou/gopsutil/mem"
"gopkg.in/urfave/cli.v1"
)
@@ -313,6 +316,10 @@ var (
Usage: "Percentage of cache memory allowance to use for trie pruning",
Value: 25,
}
+ FDLimitFlag = cli.IntFlag{
+ Name: "fdlimit",
+ Usage: "Raise the open file descriptor resource limit (default = system fd limit)",
+ }
// Miner settings
StakingEnabledFlag = cli.BoolFlag{
Name: "mine",
@@ -816,20 +823,29 @@ func setPrefix(ctx *cli.Context, cfg *node.Config) {
// MakeDatabaseHandles raises out the number of allowed file handles per process
// for XDC and returns half of the allowance to assign to the database.
-func MakeDatabaseHandles() int {
- limit, err := fdlimit.Current()
+func MakeDatabaseHandles(max int) int {
+ limit, err := fdlimit.Maximum()
if err != nil {
Fatalf("Failed to retrieve file descriptor allowance: %v", err)
}
- if limit < 2048 {
- if err := fdlimit.Raise(2048); err != nil {
- Fatalf("Failed to raise file descriptor allowance: %v", err)
- }
- }
- if limit > 2048 { // cap database file descriptors even if more is available
- limit = 2048
+ switch {
+ case max == 0:
+ // User didn't specify a meaningful value, use system limits
+ case max < 128:
+ // User specified something unhealthy, just use system defaults
+ log.Error("File descriptor limit invalid (<128)", "had", max, "updated", limit)
+ case max > limit:
+ // User requested more than the OS allows, notify that we can't allocate it
+ log.Warn("Requested file descriptors denied by OS", "req", max, "limit", limit)
+ default:
+ // User limit is meaningful and within allowed range, use that
+ limit = max
+ }
+ raised, err := fdlimit.Raise(uint64(limit))
+ if err != nil {
+ Fatalf("Failed to raise file descriptor allowance: %v", err)
}
- return limit / 2 // Leave half for networking and other stuff
+ return int(raised / 2) // Leave half for networking and other stuff
}
// MakeAddress converts an account specified directly as a hex encoded string or
@@ -1161,6 +1177,26 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setTxPool(ctx, &cfg.TxPool)
setEthash(ctx, cfg)
+ // Cap the cache allowance and tune the garbage collector
+ mem, err := gopsutil.VirtualMemory()
+ if err == nil {
+ if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
+ log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024)
+ mem.Total = 2 * 1024 * 1024 * 1024
+ }
+ allowance := int(mem.Total / 1024 / 1024 / 3)
+ if cache := ctx.Int(CacheFlag.Name); cache > allowance {
+ log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
+ ctx.Set(CacheFlag.Name, strconv.Itoa(allowance))
+ }
+ }
+ // Ensure Go's GC ignores the database cache for trigger percentage
+ cache := ctx.Int(CacheFlag.Name)
+ gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))
+
+ log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
+ godebug.SetGCPercent(int(gogc))
+
switch {
case ctx.GlobalIsSet(SyncModeFlag.Name):
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
@@ -1182,7 +1218,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) {
cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
}
- cfg.DatabaseHandles = MakeDatabaseHandles()
+ cfg.DatabaseHandles = MakeDatabaseHandles(ctx.GlobalInt(FDLimitFlag.Name))
if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
@@ -1271,7 +1307,7 @@ func SetupNetwork(ctx *cli.Context) {
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
var (
cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
- handles = MakeDatabaseHandles()
+ handles = MakeDatabaseHandles(ctx.GlobalInt(FDLimitFlag.Name))
)
name := "chaindata"
if ctx.GlobalBool(LightModeFlag.Name) {
diff --git a/common/fdlimit/fdlimit_freebsd.go b/common/fdlimit/fdlimit_bsd.go
similarity index 86%
rename from common/fdlimit/fdlimit_freebsd.go
rename to common/fdlimit/fdlimit_bsd.go
index c126b0c26583..a3a6902c0925 100644
--- a/common/fdlimit/fdlimit_freebsd.go
+++ b/common/fdlimit/fdlimit_bsd.go
@@ -14,23 +14,24 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-// +build freebsd
+//go:build freebsd || dragonfly
+// +build freebsd dragonfly
package fdlimit
import "syscall"
// This file is largely identical to fdlimit_unix.go,
-// but Rlimit fields have type int64 on FreeBSD so it needs
+// but Rlimit fields have type int64 on *BSD so it needs
// an extra conversion.
// Raise tries to maximize the file descriptor allowance of this process
// to the maximum hard-limit allowed by the OS.
-func Raise(max uint64) error {
+func Raise(max uint64) (uint64, error) {
// Get the current limit
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
- return err
+ return 0, err
}
// Try to update the limit to the max allowance
limit.Cur = limit.Max
@@ -38,9 +39,12 @@ func Raise(max uint64) error {
limit.Cur = int64(max)
}
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
- return err
+ return 0, err
+ }
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
}
- return nil
+ return uint64(limit.Cur), nil
}
// Current retrieves the number of file descriptors allowed to be opened by this
diff --git a/common/fdlimit/fdlimit_darwin.go b/common/fdlimit/fdlimit_darwin.go
new file mode 100644
index 000000000000..88dd0f56cbc3
--- /dev/null
+++ b/common/fdlimit/fdlimit_darwin.go
@@ -0,0 +1,71 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package fdlimit
+
+import "syscall"
+
+// hardlimit is the number of file descriptors allowed at max by the kernel.
+const hardlimit = 10240
+
+// Raise tries to maximize the file descriptor allowance of this process
+// to the maximum hard-limit allowed by the OS.
+// Returns the size it was set to (may differ from the desired 'max')
+func Raise(max uint64) (uint64, error) {
+ // Get the current limit
+ var limit syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
+ }
+ // Try to update the limit to the max allowance
+ limit.Cur = limit.Max
+ if limit.Cur > max {
+ limit.Cur = max
+ }
+ if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
+ }
+ // MacOS can silently apply further caps, so retrieve the actually set limit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
+ }
+ return limit.Cur, nil
+}
+
+// Current retrieves the number of file descriptors allowed to be opened by this
+// process.
+func Current() (int, error) {
+ var limit syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
+ }
+ return int(limit.Cur), nil
+}
+
+// Maximum retrieves the maximum number of file descriptors this process is
+// allowed to request for itself.
+func Maximum() (int, error) {
+ // Retrieve the maximum allowed by dynamic OS limits
+ var limit syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
+ }
+ // Cap it to OPEN_MAX (10240) because macos is a special snowflake
+ if limit.Max > hardlimit {
+ limit.Max = hardlimit
+ }
+ return int(limit.Max), nil
+}
diff --git a/common/fdlimit/fdlimit_test.go b/common/fdlimit/fdlimit_test.go
index a9ee9ab36a9b..9fd5e9fc3cbd 100644
--- a/common/fdlimit/fdlimit_test.go
+++ b/common/fdlimit/fdlimit_test.go
@@ -17,7 +17,6 @@
package fdlimit
import (
- "fmt"
"testing"
)
@@ -30,13 +29,13 @@ func TestFileDescriptorLimits(t *testing.T) {
t.Fatal(err)
}
if hardlimit < target {
- t.Skip(fmt.Sprintf("system limit is less than desired test target: %d < %d", hardlimit, target))
+ t.Skipf("system limit is less than desired test target: %d < %d", hardlimit, target)
}
if limit, err := Current(); err != nil || limit <= 0 {
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
}
- if err := Raise(uint64(target)); err != nil {
+ if _, err := Raise(uint64(target)); err != nil {
t.Fatalf("failed to raise file allowance")
}
if limit, err := Current(); err != nil || limit < target {
diff --git a/common/fdlimit/fdlimit_unix.go b/common/fdlimit/fdlimit_unix.go
index a258132353cd..a1f388ebb78d 100644
--- a/common/fdlimit/fdlimit_unix.go
+++ b/common/fdlimit/fdlimit_unix.go
@@ -14,7 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-// +build linux darwin netbsd openbsd solaris
+//go:build linux || netbsd || openbsd || solaris
+// +build linux netbsd openbsd solaris
package fdlimit
@@ -22,11 +23,12 @@ import "syscall"
// Raise tries to maximize the file descriptor allowance of this process
// to the maximum hard-limit allowed by the OS.
-func Raise(max uint64) error {
+// Returns the size it was set to (may differ from the desired 'max')
+func Raise(max uint64) (uint64, error) {
// Get the current limit
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
- return err
+ return 0, err
}
// Try to update the limit to the max allowance
limit.Cur = limit.Max
@@ -34,9 +36,13 @@ func Raise(max uint64) error {
limit.Cur = max
}
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
- return err
+ return 0, err
+ }
+ // MacOS can silently apply further caps, so retrieve the actually set limit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
+ return 0, err
}
- return nil
+ return limit.Cur, nil
}
// Current retrieves the number of file descriptors allowed to be opened by this
diff --git a/common/fdlimit/fdlimit_windows.go b/common/fdlimit/fdlimit_windows.go
index 863c58bedfab..f472153662e6 100644
--- a/common/fdlimit/fdlimit_windows.go
+++ b/common/fdlimit/fdlimit_windows.go
@@ -16,28 +16,31 @@
package fdlimit
-import "errors"
+import "fmt"
+
+// hardlimit is the number of file descriptors allowed at max by the kernel.
+const hardlimit = 16384
// Raise tries to maximize the file descriptor allowance of this process
// to the maximum hard-limit allowed by the OS.
-func Raise(max uint64) error {
+func Raise(max uint64) (uint64, error) {
// This method is NOP by design:
// * Linux/Darwin counterparts need to manually increase per process limits
// * On Windows Go uses the CreateFile API, which is limited to 16K files, non
// changeable from within a running process
// This way we can always "request" raising the limits, which will either have
// or not have effect based on the platform we're running on.
- if max > 16384 {
- return errors.New("file descriptor limit (16384) reached")
+ if max > hardlimit {
+ return hardlimit, fmt.Errorf("file descriptor limit (%d) reached", hardlimit)
}
- return nil
+ return max, nil
}
// Current retrieves the number of file descriptors allowed to be opened by this
// process.
func Current() (int, error) {
// Please see Raise for the reason why we use hard coded 16K as the limit
- return 16384, nil
+ return hardlimit, nil
}
// Maximum retrieves the maximum number of file descriptors this process is