package rcmgr

import (
	"fmt"
	"math"
	"math/big"
	"strings"
	"sync"

	"github.com/libp2p/go-libp2p/core/network"
)

// resources tracks the current state of resource consumption
type resources struct {
	limit Limit

	nconnsIn, nconnsOut     int
	nstreamsIn, nstreamsOut int
	nfd                     int

	memory int64
}

// A resourceScope can be a DAG, where a downstream node is not allowed to outlive an upstream node
// (ie cannot call Done in the upstream node before the downstream node) and account for resources
// using a linearized parent set.
// A resourceScope can be a span scope, where it has a specific owner; span scopes create a tree rooted
// at the owner (which can be a DAG scope) and can outlive their parents -- this is important because
// span scopes are the main *user* interface for memory management, and the user may call
// Done in a span scope after the system has closed the root of the span tree in some background
// goroutine.
// If we didn't make this distinction we would have a double release problem in that case.
type resourceScope struct {
	sync.Mutex
	done   bool
	refCnt int

	spanID int

	rc    resources
	owner *resourceScope   // set in span scopes, which define trees
	edges []*resourceScope // set in DAG scopes, it's the linearized parent set

	name    string   // for debugging purposes
	trace   *trace   // debug tracing
	metrics *metrics // metrics collection
}

var _ network.ResourceScope = (*resourceScope)(nil)
var _ network.ResourceScopeSpan = (*resourceScope)(nil)

func newResourceScope(limit Limit, edges []*resourceScope, name string, trace *trace, metrics *metrics) *resourceScope {
	for _, e := range edges {
		e.IncRef()
	}
	r := &resourceScope{
		rc:      resources{limit: limit},
		edges:   edges,
		name:    name,
		trace:   trace,
		metrics: metrics,
	}
	r.trace.CreateScope(name, limit)
	return r
}

func newResourceScopeSpan(owner *resourceScope, id int) *resourceScope {
	r := &resourceScope{
		rc:      resources{limit: owner.rc.limit},
		owner:   owner,
		name:    fmt.Sprintf("%s.span-%d", owner.name, id),
		trace:   owner.trace,
		metrics: owner.metrics,
	}
	r.trace.CreateScope(r.name, r.rc.limit)
	return r
}

// IsSpan will return true if this name was created by newResourceScopeSpan
func IsSpan(name string) bool {
	return strings.Contains(name, ".span-")
}

func addInt64WithOverflow(a int64, b int64) (c int64, ok bool) {
	c = a + b
	return c, (c > a) == (b > 0)
}

// mulInt64WithOverflow checks for overflow in multiplying two int64s. See
// https://groups.google.com/g/golang-nuts/c/h5oSN5t3Au4/m/KaNQREhZh0QJ
func mulInt64WithOverflow(a, b int64) (c int64, ok bool) {
	const mostPositive = 1<<63 - 1
	const mostNegative = -(mostPositive + 1)
	c = a * b
	if a == 0 || b == 0 || a == 1 || b == 1 {
		return c, true
	}
	if a == mostNegative || b == mostNegative {
		return c, false
	}
	return c, c/b == a
}

// Resources implementation
func (rc *resources) checkMemory(rsvp int64, prio uint8) error {
	if rsvp < 0 {
		return fmt.Errorf("can't reserve negative memory. rsvp=%v", rsvp)
	}

	limit := rc.limit.GetMemoryLimit()
	if limit == math.MaxInt64 {
		// Special case where we've set max limits.
		return nil
	}

	newmem, addOk := addInt64WithOverflow(rc.memory, rsvp)

	threshold, mulOk := mulInt64WithOverflow(1+int64(prio), limit)
	if !mulOk {
		thresholdBig := big.NewInt(limit)
		thresholdBig.Mul(thresholdBig, big.NewInt(1+int64(prio)))
		thresholdBig.Rsh(thresholdBig, 8) // Divide 256
		// necessarily a Int64 since we multiplied a int64 != MaxInt64 with
		// a uint8+1 (max 255+1 = 256) and divided by 256
		threshold = thresholdBig.Int64()
	} else {
		threshold = threshold / 256
	}

	if !addOk || newmem > threshold {
		return &ErrMemoryLimitExceeded{
			current:   rc.memory,
			attempted: rsvp,
			limit:     limit,
			priority:  prio,
			err:       network.ErrResourceLimitExceeded,
		}
	}
	return nil
}

func (rc *resources) reserveMemory(size int64, prio uint8) error {
	if err := rc.checkMemory(size, prio); err != nil {
		return err
	}

	rc.memory += size
	return nil
}

func (rc *resources) releaseMemory(size int64) {
	rc.memory -= size

	// sanity check for bugs upstream
	if rc.memory < 0 {
		log.Warn("BUG: too much memory released")
		rc.memory = 0
	}
}

func (rc *resources) addStream(dir network.Direction) error {
	if dir == network.DirInbound {
		return rc.addStreams(1, 0)
	}
	return rc.addStreams(0, 1)
}

func (rc *resources) addStreams(incount, outcount int) error {
	if incount > 0 {
		limit := rc.limit.GetStreamLimit(network.DirInbound)
		if rc.nstreamsIn+incount > limit {
			return &ErrStreamOrConnLimitExceeded{
				current:   rc.nstreamsIn,
				attempted: incount,
				limit:     limit,
				err:       fmt.Errorf("cannot reserve inbound stream: %w", network.ErrResourceLimitExceeded),
			}
		}
	}
	if outcount > 0 {
		limit := rc.limit.GetStreamLimit(network.DirOutbound)
		if rc.nstreamsOut+outcount > limit {
			return &ErrStreamOrConnLimitExceeded{
				current:   rc.nstreamsOut,
				attempted: outcount,
				limit:     limit,
				err:       fmt.Errorf("cannot reserve outbound stream: %w", network.ErrResourceLimitExceeded),
			}
		}
	}

	if limit := rc.limit.GetStreamTotalLimit(); rc.nstreamsIn+incount+rc.nstreamsOut+outcount > limit {
		return &ErrStreamOrConnLimitExceeded{
			current:   rc.nstreamsIn + rc.nstreamsOut,
			attempted: incount + outcount,
			limit:     limit,
			err:       fmt.Errorf("cannot reserve stream: %w", network.ErrResourceLimitExceeded),
		}
	}

	rc.nstreamsIn += incount
	rc.nstreamsOut += outcount
	return nil
}

func (rc *resources) removeStream(dir network.Direction) {
	if dir == network.DirInbound {
		rc.removeStreams(1, 0)
	} else {
		rc.removeStreams(0, 1)
	}
}

func (rc *resources) removeStreams(incount, outcount int) {
	rc.nstreamsIn -= incount
	rc.nstreamsOut -= outcount

	if rc.nstreamsIn < 0 {
		log.Warn("BUG: too many inbound streams released")
		rc.nstreamsIn = 0
	}
	if rc.nstreamsOut < 0 {
		log.Warn("BUG: too many outbound streams released")
		rc.nstreamsOut = 0
	}
}

func (rc *resources) addConn(dir network.Direction, usefd bool) error {
	var fd int
	if usefd {
		fd = 1
	}

	if dir == network.DirInbound {
		return rc.addConns(1, 0, fd)
	}

	return rc.addConns(0, 1, fd)
}

func (rc *resources) addConns(incount, outcount, fdcount int) error {
	if incount > 0 {
		limit := rc.limit.GetConnLimit(network.DirInbound)
		if rc.nconnsIn+incount > limit {
			return &ErrStreamOrConnLimitExceeded{
				current:   rc.nconnsIn,
				attempted: incount,
				limit:     limit,
				err:       fmt.Errorf("cannot reserve inbound connection: %w", network.ErrResourceLimitExceeded),
			}
		}
	}
	if outcount > 0 {
		limit := rc.limit.GetConnLimit(network.DirOutbound)
		if rc.nconnsOut+outcount > limit {
			return &ErrStreamOrConnLimitExceeded{
				current:   rc.nconnsOut,
				attempted: outcount,
				limit:     limit,
				err:       fmt.Errorf("cannot reserve outbound connection: %w", network.ErrResourceLimitExceeded),
			}
		}
	}

	if connLimit := rc.limit.GetConnTotalLimit(); rc.nconnsIn+incount+rc.nconnsOut+outcount > connLimit {
		return &ErrStreamOrConnLimitExceeded{
			current:   rc.nconnsIn + rc.nconnsOut,
			attempted: incount + outcount,
			limit:     connLimit,
			err:       fmt.Errorf("cannot reserve connection: %w", network.ErrResourceLimitExceeded),
		}
	}
	if fdcount > 0 {
		limit := rc.limit.GetFDLimit()
		if rc.nfd+fdcount > limit {
			return &ErrStreamOrConnLimitExceeded{
				current:   rc.nfd,
				attempted: fdcount,
				limit:     limit,
				err:       fmt.Errorf("cannot reserve file descriptor: %w", network.ErrResourceLimitExceeded),
			}
		}
	}

	rc.nconnsIn += incount
	rc.nconnsOut += outcount
	rc.nfd += fdcount
	return nil
}

func (rc *resources) removeConn(dir network.Direction, usefd bool) {
	var fd int
	if usefd {
		fd = 1
	}

	if dir == network.DirInbound {
		rc.removeConns(1, 0, fd)
	} else {
		rc.removeConns(0, 1, fd)
	}
}

func (rc *resources) removeConns(incount, outcount, fdcount int) {
	rc.nconnsIn -= incount
	rc.nconnsOut -= outcount
	rc.nfd -= fdcount

	if rc.nconnsIn < 0 {
		log.Warn("BUG: too many inbound connections released")
		rc.nconnsIn = 0
	}
	if rc.nconnsOut < 0 {
		log.Warn("BUG: too many outbound connections released")
		rc.nconnsOut = 0
	}
	if rc.nfd < 0 {
		log.Warn("BUG: too many file descriptors released")
		rc.nfd = 0
	}
}

func (rc *resources) stat() network.ScopeStat {
	return network.ScopeStat{
		Memory:             rc.memory,
		NumStreamsInbound:  rc.nstreamsIn,
		NumStreamsOutbound: rc.nstreamsOut,
		NumConnsInbound:    rc.nconnsIn,
		NumConnsOutbound:   rc.nconnsOut,
		NumFD:              rc.nfd,
	}
}

// resourceScope implementation
func (s *resourceScope) wrapError(err error) error {
	return fmt.Errorf("%s: %w", s.name, err)
}

func (s *resourceScope) ReserveMemory(size int, prio uint8) error {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.reserveMemory(int64(size), prio); err != nil {
		log.Debug("blocked memory reservation", logValuesMemoryLimit(s.name, "", s.rc.stat(), err)...)
		s.trace.BlockReserveMemory(s.name, prio, int64(size), s.rc.memory)
		s.metrics.BlockMemory(size)
		return s.wrapError(err)
	}

	if err := s.reserveMemoryForEdges(size, prio); err != nil {
		s.rc.releaseMemory(int64(size))
		s.metrics.BlockMemory(size)
		return s.wrapError(err)
	}

	s.trace.ReserveMemory(s.name, prio, int64(size), s.rc.memory)
	s.metrics.AllowMemory(size)
	return nil
}

func (s *resourceScope) reserveMemoryForEdges(size int, prio uint8) error {
	if s.owner != nil {
		return s.owner.ReserveMemory(size, prio)
	}

	var reserved int
	var err error
	for _, e := range s.edges {
		var stat network.ScopeStat
		stat, err = e.ReserveMemoryForChild(int64(size), prio)
		if err != nil {
			log.Debug("blocked memory reservation from constraining edge", logValuesMemoryLimit(s.name, e.name, stat, err)...)
			break
		}

		reserved++
	}

	if err != nil {
		// we failed because of a constraint; undo memory reservations
		for _, e := range s.edges[:reserved] {
			e.ReleaseMemoryForChild(int64(size))
		}
	}

	return err
}

func (s *resourceScope) releaseMemoryForEdges(size int) {
	if s.owner != nil {
		s.owner.ReleaseMemory(size)
		return
	}

	for _, e := range s.edges {
		e.ReleaseMemoryForChild(int64(size))
	}
}

func (s *resourceScope) ReserveMemoryForChild(size int64, prio uint8) (network.ScopeStat, error) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.reserveMemory(size, prio); err != nil {
		s.trace.BlockReserveMemory(s.name, prio, size, s.rc.memory)
		return s.rc.stat(), s.wrapError(err)
	}

	s.trace.ReserveMemory(s.name, prio, size, s.rc.memory)
	return network.ScopeStat{}, nil
}

func (s *resourceScope) ReleaseMemory(size int) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.releaseMemory(int64(size))
	s.releaseMemoryForEdges(size)
	s.trace.ReleaseMemory(s.name, int64(size), s.rc.memory)
}

func (s *resourceScope) ReleaseMemoryForChild(size int64) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.releaseMemory(size)
	s.trace.ReleaseMemory(s.name, size, s.rc.memory)
}

func (s *resourceScope) AddStream(dir network.Direction) error {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.addStream(dir); err != nil {
		log.Debug("blocked stream", logValuesStreamLimit(s.name, "", dir, s.rc.stat(), err)...)
		s.trace.BlockAddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
		return s.wrapError(err)
	}

	if err := s.addStreamForEdges(dir); err != nil {
		s.rc.removeStream(dir)
		return s.wrapError(err)
	}

	s.trace.AddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
	return nil
}

func (s *resourceScope) addStreamForEdges(dir network.Direction) error {
	if s.owner != nil {
		return s.owner.AddStream(dir)
	}

	var err error
	var reserved int
	for _, e := range s.edges {
		var stat network.ScopeStat
		stat, err = e.AddStreamForChild(dir)
		if err != nil {
			log.Debug("blocked stream from constraining edge", logValuesStreamLimit(s.name, e.name, dir, stat, err)...)
			break
		}
		reserved++
	}

	if err != nil {
		for _, e := range s.edges[:reserved] {
			e.RemoveStreamForChild(dir)
		}
	}

	return err
}

func (s *resourceScope) AddStreamForChild(dir network.Direction) (network.ScopeStat, error) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.addStream(dir); err != nil {
		s.trace.BlockAddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
		return s.rc.stat(), s.wrapError(err)
	}

	s.trace.AddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
	return network.ScopeStat{}, nil
}

func (s *resourceScope) RemoveStream(dir network.Direction) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.removeStream(dir)
	s.removeStreamForEdges(dir)
	s.trace.RemoveStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
}

func (s *resourceScope) removeStreamForEdges(dir network.Direction) {
	if s.owner != nil {
		s.owner.RemoveStream(dir)
		return
	}

	for _, e := range s.edges {
		e.RemoveStreamForChild(dir)
	}
}

func (s *resourceScope) RemoveStreamForChild(dir network.Direction) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.removeStream(dir)
	s.trace.RemoveStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
}

func (s *resourceScope) AddConn(dir network.Direction, usefd bool) error {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.addConn(dir, usefd); err != nil {
		log.Debug("blocked connection", logValuesConnLimit(s.name, "", dir, usefd, s.rc.stat(), err)...)
		s.trace.BlockAddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
		return s.wrapError(err)
	}

	if err := s.addConnForEdges(dir, usefd); err != nil {
		s.rc.removeConn(dir, usefd)
		return s.wrapError(err)
	}

	s.trace.AddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
	return nil
}

func (s *resourceScope) addConnForEdges(dir network.Direction, usefd bool) error {
	if s.owner != nil {
		return s.owner.AddConn(dir, usefd)
	}

	var err error
	var reserved int
	for _, e := range s.edges {
		var stat network.ScopeStat
		stat, err = e.AddConnForChild(dir, usefd)
		if err != nil {
			log.Debug("blocked connection from constraining edge", logValuesConnLimit(s.name, e.name, dir, usefd, stat, err)...)
			break
		}
		reserved++
	}

	if err != nil {
		for _, e := range s.edges[:reserved] {
			e.RemoveConnForChild(dir, usefd)
		}
	}

	return err
}

func (s *resourceScope) AddConnForChild(dir network.Direction, usefd bool) (network.ScopeStat, error) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.addConn(dir, usefd); err != nil {
		s.trace.BlockAddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
		return s.rc.stat(), s.wrapError(err)
	}

	s.trace.AddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
	return network.ScopeStat{}, nil
}

func (s *resourceScope) RemoveConn(dir network.Direction, usefd bool) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.removeConn(dir, usefd)
	s.removeConnForEdges(dir, usefd)
	s.trace.RemoveConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
}

func (s *resourceScope) removeConnForEdges(dir network.Direction, usefd bool) {
	if s.owner != nil {
		s.owner.RemoveConn(dir, usefd)
	}

	for _, e := range s.edges {
		e.RemoveConnForChild(dir, usefd)
	}
}

func (s *resourceScope) RemoveConnForChild(dir network.Direction, usefd bool) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.removeConn(dir, usefd)
	s.trace.RemoveConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
}

func (s *resourceScope) ReserveForChild(st network.ScopeStat) error {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return s.wrapError(network.ErrResourceScopeClosed)
	}

	if err := s.rc.reserveMemory(st.Memory, network.ReservationPriorityAlways); err != nil {
		s.trace.BlockReserveMemory(s.name, 255, st.Memory, s.rc.memory)
		return s.wrapError(err)
	}

	if err := s.rc.addStreams(st.NumStreamsInbound, st.NumStreamsOutbound); err != nil {
		s.trace.BlockAddStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
		s.rc.releaseMemory(st.Memory)
		return s.wrapError(err)
	}

	if err := s.rc.addConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD); err != nil {
		s.trace.BlockAddConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)

		s.rc.releaseMemory(st.Memory)
		s.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)
		return s.wrapError(err)
	}

	s.trace.ReserveMemory(s.name, 255, st.Memory, s.rc.memory)
	s.trace.AddStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
	s.trace.AddConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)

	return nil
}

func (s *resourceScope) ReleaseForChild(st network.ScopeStat) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.releaseMemory(st.Memory)
	s.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)
	s.rc.removeConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD)

	s.trace.ReleaseMemory(s.name, st.Memory, s.rc.memory)
	s.trace.RemoveStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
	s.trace.RemoveConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
}

func (s *resourceScope) ReleaseResources(st network.ScopeStat) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return
	}

	s.rc.releaseMemory(st.Memory)
	s.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)
	s.rc.removeConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD)

	if s.owner != nil {
		s.owner.ReleaseResources(st)
	} else {
		for _, e := range s.edges {
			e.ReleaseForChild(st)
		}
	}

	s.trace.ReleaseMemory(s.name, st.Memory, s.rc.memory)
	s.trace.RemoveStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
	s.trace.RemoveConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
}

func (s *resourceScope) nextSpanID() int {
	s.spanID++
	return s.spanID
}

func (s *resourceScope) BeginSpan() (network.ResourceScopeSpan, error) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return nil, s.wrapError(network.ErrResourceScopeClosed)
	}

	s.refCnt++
	return newResourceScopeSpan(s, s.nextSpanID()), nil
}

func (s *resourceScope) Done() {
	s.Lock()
	defer s.Unlock()

	s.doneUnlocked()
}

func (s *resourceScope) doneUnlocked() {
	if s.done {
		return
	}
	stat := s.rc.stat()
	if s.owner != nil {
		s.owner.ReleaseResources(stat)
		s.owner.DecRef()
	} else {
		for _, e := range s.edges {
			e.ReleaseForChild(stat)
			e.DecRef()
		}
	}

	s.rc.nstreamsIn = 0
	s.rc.nstreamsOut = 0
	s.rc.nconnsIn = 0
	s.rc.nconnsOut = 0
	s.rc.nfd = 0
	s.rc.memory = 0

	s.done = true

	s.trace.DestroyScope(s.name)
}

func (s *resourceScope) Stat() network.ScopeStat {
	s.Lock()
	defer s.Unlock()

	return s.rc.stat()
}

func (s *resourceScope) IncRef() {
	s.Lock()
	defer s.Unlock()

	s.refCnt++
}

func (s *resourceScope) DecRef() {
	s.Lock()
	defer s.Unlock()

	s.refCnt--
}

func (s *resourceScope) IsUnused() bool {
	s.Lock()
	defer s.Unlock()

	if s.done {
		return true
	}

	if s.refCnt > 0 {
		return false
	}

	st := s.rc.stat()
	return st.NumStreamsInbound == 0 &&
		st.NumStreamsOutbound == 0 &&
		st.NumConnsInbound == 0 &&
		st.NumConnsOutbound == 0 &&
		st.NumFD == 0
}
