Skip to content
This repository has been archived by the owner on Sep 6, 2022. It is now read-only.

Network Resource Manager interface #229

Merged
merged 57 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
cfce3f5
add resource manager interfaces
vyzo Dec 16, 2021
044fbea
add scope accessors to streams and conns
vyzo Dec 16, 2021
193ee25
add ResourceManager accessor to the Network interface
vyzo Dec 16, 2021
2afdeee
allow initially unattached streams.
vyzo Dec 16, 2021
46cbcfe
introduce service scopes, canonicalize ownership interface through se…
vyzo Dec 21, 2021
d4cfc93
make system scope explicit
vyzo Dec 21, 2021
586f1d0
make memory stat an int64
vyzo Dec 21, 2021
b3f9e1d
make the system scope a generic resource scope, introduce the DMZ
vyzo Dec 21, 2021
35ec4fd
fix typo
vyzo Dec 21, 2021
081233f
fix typo
vyzo Dec 21, 2021
5767cbb
Merge branch 'master' into feat/rcmgr
vyzo Dec 21, 2021
aeb9233
rename DMZ to transient scope, remove OpenConnection from PeerScope
vyzo Dec 22, 2021
d997bbb
remove ncopy param from GrowBuffer
vyzo Dec 22, 2021
8f759d3
remove protocols from OpenStream
vyzo Dec 22, 2021
dcef937
document nil receiver contract, fix protocol scope protocol accessor …
vyzo Dec 22, 2021
c52807c
remove nil receiver contract requirement
vyzo Dec 23, 2021
76a25c9
flesh out stat struct
vyzo Dec 23, 2021
25d4931
turn resource manager scope accessors into viewers
vyzo Dec 23, 2021
deb1c16
interface refiniments
vyzo Dec 24, 2021
1f5a414
add Buffer interface
vyzo Dec 27, 2021
6400bea
fix typo
vyzo Dec 28, 2021
3357752
fix typo
vyzo Dec 28, 2021
d99bef0
fix typo
vyzo Dec 28, 2021
c452665
rename user scopes to plain names, management scopes as such
vyzo Dec 28, 2021
219e9bc
rename BeginTxn to BeginTransaction
vyzo Dec 28, 2021
3ecf62c
RIP Buffers
vyzo Dec 28, 2021
535c095
make ErrResourceLimitExceeded a temporary error; move rcmgr errors wi…
vyzo Dec 29, 2021
caacd96
unexport TemporaryError
vyzo Dec 30, 2021
a5d2e41
null resource manager stub
vyzo Dec 31, 2021
b948cd7
unexport the null stubs, make entry point a variable
vyzo Dec 31, 2021
48c94b6
don't rely on typed nils but instead use actual null object instances
vyzo Dec 31, 2021
5fcc7b6
add Scope to the CapableConn interface
vyzo Dec 31, 2021
625a159
rename ConnectionScope to ConnScope for consistency
vyzo Dec 31, 2021
f205ca6
fix typo
vyzo Dec 31, 2021
31b6a33
rename ConnectionManagementScope to ConnManagementScope
vyzo Dec 31, 2021
59c3e97
Merge branch 'master' of github.com:libp2p/go-libp2p-core into feat/r…
vyzo Jan 4, 2022
f4f1520
add the ConnManagementScope to Upgrader.Upgrade
marten-seemann Jan 4, 2022
d2ff788
fix argument name
marten-seemann Jan 4, 2022
d1a82f1
godocs for ResourceManager
vyzo Jan 4, 2022
541324d
introduce MemoryStatus indicator in ReserveMemory
vyzo Jan 5, 2022
cafe95a
use uint8 for MemoryStatus
vyzo Jan 5, 2022
84d55d3
rework reservation interface to pass priority instead of returning me…
vyzo Jan 6, 2022
d9136f0
improve comment
vyzo Jan 6, 2022
2192774
fix typo
vyzo Jan 6, 2022
005b84b
export the NullScope
marten-seemann Jan 7, 2022
9d473a8
Stream.SetProtocol can return an error
vyzo Jan 9, 2022
0c1046c
merge the mux package into network
marten-seemann Jan 13, 2022
0391e67
pass the PeerScope to Multiplexer.NetConn
marten-seemann Jan 13, 2022
d3d9ac7
Update network/rcmgr.go
vyzo Jan 13, 2022
140d0ed
Update network/rcmgr.go
vyzo Jan 13, 2022
7a1c47e
Update network/rcmgr.go
vyzo Jan 13, 2022
e2bac5c
Update network/rcmgr.go
vyzo Jan 14, 2022
4bfde8e
remove reference to deprecated mux.MuxedConn
vyzo Jan 14, 2022
68e492f
rename transaction to span
vyzo Jan 14, 2022
953320f
indicate bytes in ReserveMemory
vyzo Jan 14, 2022
06aa6f9
break ResourceManager View methods into Viewer interface.
vyzo Jan 14, 2022
6b8d8bf
add experimental interface warning
vyzo Jan 14, 2022
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
8 changes: 8 additions & 0 deletions network/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Conn interface {
ConnSecurity
ConnMultiaddrs
ConnStat
ConnScoper

// ID returns an identifier that uniquely identifies this Conn within this
// host, during this run. Connection IDs may repeat across restarts.
Expand Down Expand Up @@ -65,3 +66,10 @@ type ConnStat interface {
// Stat stores metadata pertaining to this conn.
Stat() ConnStats
}

// ConnScoper is the interface that one can mix into a connection interface to give it a resource
// management scope
type ConnScoper interface {
// Scope returns the user view of this connection's resource scope
Scope() ConnScope
}
21 changes: 20 additions & 1 deletion network/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
package network

import "errors"
import (
"errors"
"net"
)

type temporaryError string

func (e temporaryError) Error() string { return string(e) }
func (e temporaryError) Temporary() bool { return true }
func (e temporaryError) Timeout() bool { return false }

var _ net.Error = temporaryError("")

// ErrNoRemoteAddrs is returned when there are no addresses associated with a peer during a dial.
var ErrNoRemoteAddrs = errors.New("no remote addresses")
Expand All @@ -12,3 +23,11 @@ var ErrNoConn = errors.New("no usable connection to peer")
// ErrTransientConn is returned when attempting to open a stream to a peer with only a transient
// connection, without specifying the UseTransient option.
var ErrTransientConn = errors.New("transient connection to peer")

// ErrResourceLimitExceeded is returned when attempting to perform an operation that would
// exceed system resource limits.
var ErrResourceLimitExceeded = temporaryError("resource limit exceeded")

// ErrResourceScopeClosed is returned when attemptig to reserve resources in a closed resource
// scope.
var ErrResourceScopeClosed = errors.New("resource scope closed")
3 changes: 3 additions & 0 deletions network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ type Network interface {
// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
// use the known local interfaces.
InterfaceListenAddresses() ([]ma.Multiaddr, error)

// ResourceManager returns the ResourceManager associated with this network
ResourceManager() ResourceManager
}

// Dialer represents a service that can dial out to peers
Expand Down
313 changes: 313 additions & 0 deletions network/rcmgr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
package network

import (
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/protocol"
)

// ResourceManager is the interface to the network resource management subsystem.
// The ResourceManager tracks and accounts for resource usage in the stack, from the internals
// to the application, and provides a mechanism to limit resource usage according to a user
// configurable policy.
//
// Resource Management through the ResourceManager is based on the concept of Resource
// Management Scopes, whereby resource usage is constrained by a DAG of scopes,
// The following diagram illustrates the structure of the resource constraint DAG:
// System
// +------------> Transient.............+................+
// | . .
// +------------> Service------------- . ----------+ .
// | . | .
// +-------------> Protocol----------- . ----------+ .
// | . | .
// +--------------> Peer \ | .
// +------------> Connection | .
// | \ \
// +---------------------------> Stream
//
// The basic resources accounted by the ResourceManager include memory, streams, connections,
// and file descriptors. These account for both space and time used by
// the stack, as each resource has a direct effect on the system
// availability and performance.
//
// The modus operandi of the resource manager is to restrict resource usage at the time of
// reservation. When a component of the stack needs to use a resource, it reserves it in the
// appropriate scope. The resource manager gates the reservation against the scope applicable
// limits; if the limit is exceeded, then an error (wrapping ErrResourceLimitExceeded) and it
// is up the component to act accordingly. At the lower levels of the stack, this will normally
// signal a filure of some sorts, like failing to opening a stream or a connection, which will
vyzo marked this conversation as resolved.
Show resolved Hide resolved
vyzo marked this conversation as resolved.
Show resolved Hide resolved
// propagate to the programmer. Some components may be able to handle resource reservation failure
// more gracefully; for instance a muxer trying to grow a buffer for a window change, will simply
// retain the existing window size and continue to operate normally albeit with some degraded
// throughput.
// All resources reserved in some scope are released when the scope is closed. For low level
// scopes, mainly Connection and Stream scopes, this happens when the connection or stream is
// closed.
//
// Service programmers will typically use the resource manager to reserve memory
// for their subsystem.
// This happens with two avenues: the programmer can attach a stream to a service, whereby
// resources reserved by the stream are automatically accounted in the service budget; or the
// programmer may directly interact with the service scope, by using ViewService through the
// resource manager interface.
//
// Application programmers can also directly reserve memory in some applicable scope. In order
// to failicate control flow delimited resource accounting, all scopes defined in the system
vyzo marked this conversation as resolved.
Show resolved Hide resolved
// allow for the user to create transactions. Transactions are temporary scopes rooted at some
// other scope and release their resources when the programmer is done with them. Transaction
// scopes can form trees, with nested transactions.
//
// Typical Usage:
// - Low level components of the system (transports, muxers) all have access to the resource
// manageer and create connection and stream scopes through it. These scopes are accessible
vyzo marked this conversation as resolved.
Show resolved Hide resolved
// to the user, albeit with a narrower interface, through Conn and Stream objects who have
// a Scope method.
// - Services typically center around streams, where the programmer can attach streams to a
// particular service. They can also directly reserve memory for a service by accessing the
// service scope using the ResourceManager interface.
// - Applications that want to account for their network resource usage can reserve memory,
// typically using a transaction, directly in the System or a Service scope; they can also
// opt to use appropriate steam scopes for streams that they create or own.
//
// User Serviceable Parts: the user has the option to specify their own implementation of the
// interface. We provide a canonical implementation in the go-libp2p-resource-manager package.
// The user of that package can specify limits for the various scopes, which can be static
// or dynamic.
type ResourceManager interface {
vyzo marked this conversation as resolved.
Show resolved Hide resolved
// ViewSystem views the system wide resource scope.
// The system scope is the top level scope that accounts for global
// resource usage at all levels of the system. This scope constrains all
// other scopes and institutes global hard limits.
ViewSystem(func(ResourceScope) error) error

// ViewTransient views the transient (DMZ) resource scope.
// The transient scope accounts for resources that are in the process of
// full establishment. For instance, a new connection prior to the
// handshake does not belong to any peer, but it still needs to be
// constrained as this opens an avenue for attacks in transient resource
// usage. Similarly, a stream that has not negotiated a protocol yet is
// constrained by the transient scope.
ViewTransient(func(ResourceScope) error) error

// ViewService retrieves a service-specific scope.
ViewService(string, func(ServiceScope) error) error
Copy link
Contributor

Choose a reason for hiding this comment

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

Does service deal with transient connections too? How does hole punching figure into this (e.g. when secondary connections associated with the service are made)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, the service does not deal with conns at all, it only deals with streams.
Conns are really shared resources, so there isn't much of a point to have them owned by a service.


// ViewProtocol views the resource management scope for a specific protocol.
ViewProtocol(protocol.ID, func(ProtocolScope) error) error

// ViewPeer views the resource management scope for a specific peer.
ViewPeer(peer.ID, func(PeerScope) error) error
vyzo marked this conversation as resolved.
Show resolved Hide resolved

// OpenConnection creates a new connection scope not yet associated with any peer; the connection
// is scoped at the transient scope.
// The caller owns the returned scope and is responsible for calling Done in order to signify
// the end of th scope's span.
vyzo marked this conversation as resolved.
Show resolved Hide resolved
OpenConnection(dir Direction, usefd bool) (ConnManagementScope, error)

// OpenStream creates a new stream scope, initially unnegotiated.
// An unnegotiated stream will be initially unattached to any protocol scope
// and constrained by the transient scope.
// The caller owns the returned scope and is responsible for calling Done in order to signify
// the end of th scope's span.
OpenStream(p peer.ID, dir Direction) (StreamManagementScope, error)

// Close closes the resource manager
Close() error
}

// MemoryStatus is an indicator of the current level of available memory for scope reservations.
type MemoryStatus int
vyzo marked this conversation as resolved.
Show resolved Hide resolved

const (
// MemoryStatusOK indicates that the scope has sufficient memory.
MemoryStatusOK = iota
// MemoryStatusCaution indicates that the scope is using more than half its available memory.
MemoryStatusCaution
// MemoryStatusCritical indicates that the scope is using more than 80% of its available memory.
MemoryStatusCritical
)

// ResourceScope is the interface for all scopes.
type ResourceScope interface {
// ReserveMemory reserves memory/buffer space in the scope.
//
// If ReserveMemory returns an error, then no memory was reserved and the caller should handle
// the failure condition.
//
// If the error is nil, then the returned MemoryStatus indicates the health of the memory
// subsystem for the scope, _after_ the reservation.
// A MemoryStatus of MemoryStatusCritical (Red) indicates that the scope already uses
// too much memory and it should only proceed with absolutely critical memory allocations.
// A MemoryStatus of MemorStatusCaution (Yellow) indicates that the scope uses a lot
// of memory and the caller should backoff if it is an optional operation (e.g. a window
// buffer increase in a muxer).
// A MemoryStatus of MemoryStatusOK (Green) indicates that the scope has sufficient memory
// available and the caller is free to proceed without concerns.
ReserveMemory(size int) (MemoryStatus, error)
// ReleaseMemory explicitly releases memory previously reserved with ReserveMemory
ReleaseMemory(size int)

// Stat retrieves current resource usage for the scope.
Stat() ScopeStat
vyzo marked this conversation as resolved.
Show resolved Hide resolved

// BeginTransaction creates a new transactional scope rooted at this scope
BeginTransaction() (TransactionalScope, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

Under what kinds of situations are we expecting an error here? Is this just room for growth in case anything comes up, or do we have some in mind?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We currently error if the scope has been closed. But it's good to have room for growth too.

}
vyzo marked this conversation as resolved.
Show resolved Hide resolved

// TransactionalScope is a ResourceScope with transactional semantics.
// Transactional scopes are control flow delimited and release all their associated resources
// when the programmer calls Done.
//
// Example:
// txn, err := someScope.BeginTransaction()
// if err != nil { ... }
// defer txn.Done()
//
// if err := txn.ReserveMemory(...); err != nil { ... }
// // ... use memory
type TransactionalScope interface {
ResourceScope
// Done ends the transaction scope and releases associated resources.
Done()
Copy link
Member

Choose a reason for hiding this comment

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

Do we have any examples of how we think we might want to use these interfaces? We should start with some toy applications before we spend too much time on these APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The most basic hooks will be inside the transport and muxer implementations.
We need to cater to both them, at the bowels of the stack, and higher level users.

Copy link
Member

Choose a reason for hiding this comment

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

Can we add testable examples to the godocs? https://go.dev/blog/examples

}

// ServiceScope is the interface for service resource scopes
type ServiceScope interface {
ResourceScope

// Name returns the name of this service
Name() string
}

// ProtocolScope is the interface for protocol resource scopes.
type ProtocolScope interface {
ResourceScope

// Protocol returns the protocol for this scope
Protocol() protocol.ID
}

// PeerScope is the interface for peer resource scopes.
type PeerScope interface {
ResourceScope

// Peer returns the peer ID for this scope
Peer() peer.ID
}

// ConnManagementScope is the low level interface for connection resource scopes.
// This interface is used by the low level components of the system who create and own
// the span of a connection scope.
type ConnManagementScope interface {
TransactionalScope

// PeerScope returns the peer scope associated with this connection.
// It returns nil if the connection is not yet asociated with any peer.
PeerScope() PeerScope

// SetPeer sets the peer for a previously unassociated connection
SetPeer(peer.ID) error
}

// ConnScope is the user view of a connection scope
type ConnScope interface {
ResourceScope
}

// StreamManagementScope is the interface for stream resource scopes.
// This interface is used by the low level components of the system who create and own
// the span of a stream scope.
type StreamManagementScope interface {
TransactionalScope

// ProtocolScope returns the protocol resource scope associated with this stream.
// It returns nil if the stream is not associated with any protocol scope.
ProtocolScope() ProtocolScope
// SetProtocol sets the protocol for a previously unnegotiated stream
SetProtocol(proto protocol.ID) error
marten-seemann marked this conversation as resolved.
Show resolved Hide resolved

// ServiceScope returns the service owning the stream, if any.
ServiceScope() ServiceScope
// SetService sets the service owning this stream.
SetService(srv string) error

// PeerScope returns the peer resource scope associated with this stream.
PeerScope() PeerScope
}

// StreamScope is the user view of a StreamScope.
type StreamScope interface {
ResourceScope

// SetService sets the service owning this stream.
SetService(srv string) error
}

// ScopeStat is a struct containing resource accounting information.
type ScopeStat struct {
NumStreamsInbound int
NumStreamsOutbound int
NumConnsInbound int
NumConnsOutbound int
Comment on lines +260 to +263
Copy link
Contributor

Choose a reason for hiding this comment

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

How (if at all) are we planning to deal with these in the context of relayed or DCUtR connections?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what do you mean here? Relayed connections are just a virtual connection on top of some other connection and don't use fds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do count them however in stat.

NumFD int

Memory int64
}

// NullResourceManager is a stub for tests and initialization of default values
var NullResourceManager ResourceManager = &nullResourceManager{}

type nullResourceManager struct{}
type nullScope struct{}

var _ ResourceScope = (*nullScope)(nil)
var _ TransactionalScope = (*nullScope)(nil)
var _ ServiceScope = (*nullScope)(nil)
var _ ProtocolScope = (*nullScope)(nil)
var _ PeerScope = (*nullScope)(nil)
var _ ConnManagementScope = (*nullScope)(nil)
var _ ConnScope = (*nullScope)(nil)
var _ StreamManagementScope = (*nullScope)(nil)
var _ StreamScope = (*nullScope)(nil)

var nullScopeObj = &nullScope{}

func (n *nullResourceManager) ViewSystem(f func(ResourceScope) error) error {
return f(nullScopeObj)
}
func (n *nullResourceManager) ViewTransient(f func(ResourceScope) error) error {
return f(nullScopeObj)
}
func (n *nullResourceManager) ViewService(svc string, f func(ServiceScope) error) error {
return f(nullScopeObj)
}
func (n *nullResourceManager) ViewProtocol(p protocol.ID, f func(ProtocolScope) error) error {
return f(nullScopeObj)
}
func (n *nullResourceManager) ViewPeer(p peer.ID, f func(PeerScope) error) error {
return f(nullScopeObj)
}
func (n *nullResourceManager) OpenConnection(dir Direction, usefd bool) (ConnManagementScope, error) {
return nullScopeObj, nil
}
func (n *nullResourceManager) OpenStream(p peer.ID, dir Direction) (StreamManagementScope, error) {
return nullScopeObj, nil
}
func (n *nullResourceManager) Close() error {
return nil
}

func (n *nullScope) ReserveMemory(size int) (MemoryStatus, error) { return MemoryStatusOK, nil }
func (n *nullScope) ReleaseMemory(size int) {}
func (n *nullScope) Stat() ScopeStat { return ScopeStat{} }
func (n *nullScope) BeginTransaction() (TransactionalScope, error) { return nullScopeObj, nil }
func (n *nullScope) Done() {}
func (n *nullScope) Name() string { return "" }
func (n *nullScope) Protocol() protocol.ID { return "" }
func (n *nullScope) Peer() peer.ID { return "" }
func (n *nullScope) PeerScope() PeerScope { return nullScopeObj }
func (n *nullScope) SetPeer(peer.ID) error { return nil }
func (n *nullScope) ProtocolScope() ProtocolScope { return nullScopeObj }
func (n *nullScope) SetProtocol(proto protocol.ID) error { return nil }
func (n *nullScope) ServiceScope() ServiceScope { return nullScopeObj }
func (n *nullScope) SetService(srv string) error { return nil }
3 changes: 3 additions & 0 deletions network/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ type Stream interface {

// Conn returns the connection this stream is part of.
Conn() Conn

// Scope returns the user's view of this stream's resource scope
Scope() StreamScope
}
3 changes: 2 additions & 1 deletion transport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type CapableConn interface {
mux.MuxedConn
network.ConnSecurity
network.ConnMultiaddrs
network.ConnScoper
vyzo marked this conversation as resolved.
Show resolved Hide resolved

// Transport returns the transport to which this connection belongs.
Transport() Transport
Expand Down Expand Up @@ -112,5 +113,5 @@ type Upgrader interface {
// UpgradeListener upgrades the passed multiaddr-net listener into a full libp2p-transport listener.
UpgradeListener(Transport, manet.Listener) Listener
// Upgrade upgrades the multiaddr/net connection into a full libp2p-transport connection.
Upgrade(ctx context.Context, t Transport, maconn manet.Conn, dir network.Direction, p peer.ID) (CapableConn, error)
Upgrade(ctx context.Context, t Transport, maconn manet.Conn, dir network.Direction, p peer.ID, scope network.ConnManagementScope) (CapableConn, error)
}