816 lines
19 KiB
Go
816 lines
19 KiB
Go
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.Debugw("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.Debugw("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.Debugw("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.Debugw("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.Debugw("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.Debugw("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
|
|
}
|