2022-07-28 19:46:47 +08:00
|
|
|
|
/*
|
|
|
|
|
Package rcmgr is the resource manager for go-libp2p. This allows you to track
|
|
|
|
|
resources being used throughout your go-libp2p process. As well as making sure
|
|
|
|
|
that the process doesn't use more resources than what you define as your
|
|
|
|
|
limits. The resource manager only knows about things it is told about, so it's
|
|
|
|
|
the responsibility of the user of this library (either go-libp2p or a go-libp2p
|
|
|
|
|
user) to make sure they check with the resource manager before actually
|
|
|
|
|
allocating the resource.
|
|
|
|
|
|
|
|
|
|
Resource Management basics – Scopes
|
|
|
|
|
|
|
|
|
|
The Resource Manager is an object that keeps track of how many resources have
|
|
|
|
|
been allocated and what they have been allocated for. A resource is a stream,
|
|
|
|
|
connection, or memory reservation. The resources can be allocated for the
|
|
|
|
|
system, for a peer, for a protocol, or some combination.
|
|
|
|
|
|
|
|
|
|
The things that are allocating resources are called "Scopes". A scope can have
|
|
|
|
|
a parent scope that limits its resources. A scope can also have child scopes
|
|
|
|
|
and it can limit the resources of the child scopes. Scopes form a directed
|
|
|
|
|
acyclic graph (DAG) representing resource limits. For example, if scope A is
|
|
|
|
|
the parent of scope B, and scope A has a connection limit of 10, then whatever
|
|
|
|
|
limit B sets for connections it can never be greater than 10.
|
|
|
|
|
|
|
|
|
|
The common scopes are:
|
|
|
|
|
|
|
|
|
|
System scope: This is the root scope and represents all the resources that the
|
|
|
|
|
resource manager knows about. It can define the absolute limit of the process.
|
|
|
|
|
|
|
|
|
|
Transient scope: This is a scope for resources that have yet to be assigned a
|
|
|
|
|
peer as an owner. When we first start a connection we are unsure who we're
|
|
|
|
|
connecting to, so these connections are limited by the transient (and system)
|
|
|
|
|
scope.
|
|
|
|
|
|
|
|
|
|
Peer scope: This is a scope defined for a specific peer id.
|
|
|
|
|
|
|
|
|
|
Connection scope: This is a scope for a specific connection.
|
|
|
|
|
|
|
|
|
|
Allowlist system scope: This is a separate root scope for allowlisted peers. It
|
|
|
|
|
lets you define limits for a set of trusted multiaddrs and peers. See
|
|
|
|
|
`WithAllowlistedMultiaddrs` and ./docs/allowlist.md for more information on the
|
|
|
|
|
allowlist.
|
|
|
|
|
|
|
|
|
|
Allowlist transient scope: Similar to the above and the normal transient scope
|
|
|
|
|
but for allowlisted peers.
|
|
|
|
|
|
|
|
|
|
Protocol scope: This is a scope that defines limits for a specific protocol id.
|
|
|
|
|
|
|
|
|
|
There are a couple other scopes that are combination of the above. For example
|
|
|
|
|
there is a ProtocolPeer scope that represents the limits for a specific
|
|
|
|
|
protocol id for a specific peer.
|
|
|
|
|
|
|
|
|
|
Resource Management basics – Limits
|
|
|
|
|
|
|
|
|
|
Limits are what define how much of a resource we are willing to allocate. See
|
|
|
|
|
`BaseLimit` for what the limit looks like. These are attached to a scope so
|
|
|
|
|
that the scope + limit define the resource constraints of the go-libp2p
|
|
|
|
|
process.
|
|
|
|
|
|
|
|
|
|
Limit scaling
|
|
|
|
|
|
|
|
|
|
If the same go-libp2p application is run on various different machines, it's
|
|
|
|
|
helpful to have limits that scale relative to the specs of the machine. This
|
|
|
|
|
is where `ScalingLimitConfig` helps. With `ScalingLimitConfig` and it's
|
|
|
|
|
`ScalingLimitConfig.Scale` method you can define what the minimum resources
|
|
|
|
|
should be and how they scale up with machine size. Consult `limit_test.go` for
|
|
|
|
|
usage examples.
|
|
|
|
|
|
|
|
|
|
Default limits
|
|
|
|
|
|
|
|
|
|
By default the resource manager ships with some reasonable scaling limits and
|
|
|
|
|
makes a reasonable guess at how much system memory you want to dedicate to the
|
|
|
|
|
go-libp2p process. For the default definitions see `DefaultLimits` and
|
|
|
|
|
`ScalingLimitConfig.AutoScale()`.
|
|
|
|
|
|
|
|
|
|
Tweaking Defaults
|
|
|
|
|
|
|
|
|
|
If the defaults seem mostly okay, but you want to adjust one facet you can do
|
|
|
|
|
simply copy the defaults and update the field you want to change. You can
|
|
|
|
|
apply changes to a `BaseLimit`, `BaseLimitIncrease`, and `LimitConfig` with
|
|
|
|
|
`.Apply`.
|
|
|
|
|
|
|
|
|
|
Monitoring
|
|
|
|
|
|
|
|
|
|
Once you have limits set, you'll want to monitor to see if you're running into
|
|
|
|
|
your limits often. This could be a sign that you need to raise your limits
|
|
|
|
|
(your process is more intensive than you originally thought) or that you need
|
|
|
|
|
fix something in your application (surely you don't need over 1000 streams?).
|
|
|
|
|
|
|
|
|
|
There are OpenCensus metrics that can be hooked up to the resource manager. See
|
|
|
|
|
`obs/stats_test.go` for an example on how to enable this, and `DefaultViews` in
|
|
|
|
|
`stats.go` for recommended views. These metrics can be hooked up to Prometheus
|
|
|
|
|
or any other OpenCensus supported platform.
|
|
|
|
|
|
|
|
|
|
There is also an included Grafana dashboard to help kickstart your
|
|
|
|
|
observability into the resource manager. Find more information about it at
|
|
|
|
|
`./obs/grafana-dashboards/README.md`.
|
|
|
|
|
|
|
|
|
|
How to tune your limits
|
|
|
|
|
|
|
|
|
|
Once you've set your limits and monitoring you can now tune your limits better.
|
|
|
|
|
The `blocked_resources` metric will tell you what was blocked and for what
|
|
|
|
|
scope. If you see a steady stream of these blocked requests it means your
|
|
|
|
|
resource limits are too low for your usage. If you see a rare sudden spike,
|
|
|
|
|
this is okay and it means the resource manager protected you from some anamoly.
|
|
|
|
|
|
|
|
|
|
How to disable limits
|
|
|
|
|
|
|
|
|
|
Sometimes disabling all limits is useful when you want to see how much
|
|
|
|
|
resources you use during normal operation. You can then use this information to
|
|
|
|
|
define your initial limits.
|
|
|
|
|
|
|
|
|
|
How to debug "resource limit exceeded" errors
|
|
|
|
|
|
|
|
|
|
If you're seeing a lot of "resource limit exceeded" errors take a look at the
|
|
|
|
|
`blocked_resources` metric for some information on what was blocked. Also take
|
|
|
|
|
a look at the resources used per stream, and per protocol (the Grafana
|
|
|
|
|
Dashboard is ideal for this) and check if you're routinely hitting limits or if
|
|
|
|
|
these are rare (but noisy) spikes.
|
|
|
|
|
|
|
|
|
|
*/
|
2021-12-22 17:39:46 +08:00
|
|
|
|
package rcmgr
|
|
|
|
|
|
2021-12-23 21:28:14 +08:00
|
|
|
|
import (
|
2022-06-12 17:51:55 +08:00
|
|
|
|
"encoding/json"
|
|
|
|
|
"io"
|
|
|
|
|
|
2021-12-23 21:28:14 +08:00
|
|
|
|
"github.com/libp2p/go-libp2p-core/network"
|
2021-12-23 23:46:08 +08:00
|
|
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
|
|
|
|
"github.com/libp2p/go-libp2p-core/protocol"
|
2021-12-23 21:28:14 +08:00
|
|
|
|
)
|
|
|
|
|
|
2022-01-05 22:54:28 +08:00
|
|
|
|
// Limit is an object that specifies basic resource limits.
|
2021-12-22 17:39:46 +08:00
|
|
|
|
type Limit interface {
|
2022-01-09 03:39:42 +08:00
|
|
|
|
// GetMemoryLimit returns the (current) memory limit.
|
2021-12-22 17:39:46 +08:00
|
|
|
|
GetMemoryLimit() int64
|
2022-01-09 03:39:42 +08:00
|
|
|
|
// GetStreamLimit returns the stream limit, for inbound or outbound streams.
|
2021-12-23 21:28:14 +08:00
|
|
|
|
GetStreamLimit(network.Direction) int
|
2022-01-15 02:10:18 +08:00
|
|
|
|
// GetStreamTotalLimit returns the total stream limit
|
|
|
|
|
GetStreamTotalLimit() int
|
2022-01-09 03:39:42 +08:00
|
|
|
|
// GetConnLimit returns the connection limit, for inbound or outbound connections.
|
2021-12-23 21:28:14 +08:00
|
|
|
|
GetConnLimit(network.Direction) int
|
2022-01-15 02:10:18 +08:00
|
|
|
|
// GetConnTotalLimit returns the total connection limit
|
|
|
|
|
GetConnTotalLimit() int
|
2022-01-09 03:39:42 +08:00
|
|
|
|
// GetFDLimit returns the file descriptor limit.
|
2021-12-23 21:28:14 +08:00
|
|
|
|
GetFDLimit() int
|
2021-12-22 17:39:46 +08:00
|
|
|
|
}
|
2021-12-23 23:46:08 +08:00
|
|
|
|
|
2022-01-05 22:54:28 +08:00
|
|
|
|
// Limiter is the interface for providing limits to the resource manager.
|
2021-12-23 23:46:08 +08:00
|
|
|
|
type Limiter interface {
|
|
|
|
|
GetSystemLimits() Limit
|
|
|
|
|
GetTransientLimits() Limit
|
2022-07-02 04:39:13 +08:00
|
|
|
|
GetAllowlistedSystemLimits() Limit
|
|
|
|
|
GetAllowlistedTransientLimits() Limit
|
2021-12-23 23:46:08 +08:00
|
|
|
|
GetServiceLimits(svc string) Limit
|
2022-01-09 04:04:07 +08:00
|
|
|
|
GetServicePeerLimits(svc string) Limit
|
2021-12-23 23:46:08 +08:00
|
|
|
|
GetProtocolLimits(proto protocol.ID) Limit
|
2022-01-14 19:42:08 +08:00
|
|
|
|
GetProtocolPeerLimits(proto protocol.ID) Limit
|
2021-12-23 23:46:08 +08:00
|
|
|
|
GetPeerLimits(p peer.ID) Limit
|
|
|
|
|
GetStreamLimits(p peer.ID) Limit
|
|
|
|
|
GetConnLimits() Limit
|
|
|
|
|
}
|
2021-12-24 18:12:44 +08:00
|
|
|
|
|
2022-06-12 17:51:55 +08:00
|
|
|
|
// NewDefaultLimiterFromJSON creates a new limiter by parsing a json configuration,
|
|
|
|
|
// using the default limits for fallback.
|
|
|
|
|
func NewDefaultLimiterFromJSON(in io.Reader) (Limiter, error) {
|
|
|
|
|
return NewLimiterFromJSON(in, DefaultLimits.AutoScale())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewLimiterFromJSON creates a new limiter by parsing a json configuration.
|
|
|
|
|
func NewLimiterFromJSON(in io.Reader, defaults LimitConfig) (Limiter, error) {
|
|
|
|
|
cfg, err := readLimiterConfigFromJSON(in, defaults)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &fixedLimiter{cfg}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readLimiterConfigFromJSON(in io.Reader, defaults LimitConfig) (LimitConfig, error) {
|
|
|
|
|
var cfg LimitConfig
|
|
|
|
|
if err := json.NewDecoder(in).Decode(&cfg); err != nil {
|
|
|
|
|
return LimitConfig{}, err
|
|
|
|
|
}
|
2022-06-19 15:43:31 +08:00
|
|
|
|
cfg.Apply(defaults)
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return cfg, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
// fixedLimiter is a limiter with fixed limits.
|
|
|
|
|
type fixedLimiter struct {
|
|
|
|
|
LimitConfig
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
var _ Limiter = (*fixedLimiter)(nil)
|
|
|
|
|
|
|
|
|
|
func NewFixedLimiter(conf LimitConfig) Limiter {
|
2022-06-12 18:02:18 +08:00
|
|
|
|
log.Debugw("initializing new limiter with config", "limits", conf)
|
2022-06-10 22:01:03 +08:00
|
|
|
|
return &fixedLimiter{LimitConfig: conf}
|
|
|
|
|
}
|
2021-12-24 18:12:44 +08:00
|
|
|
|
|
2022-01-06 18:57:37 +08:00
|
|
|
|
// BaseLimit is a mixin type for basic resource limits.
|
|
|
|
|
type BaseLimit struct {
|
2022-01-15 02:10:18 +08:00
|
|
|
|
Streams int
|
2022-01-06 18:57:37 +08:00
|
|
|
|
StreamsInbound int
|
|
|
|
|
StreamsOutbound int
|
2022-01-15 02:10:18 +08:00
|
|
|
|
Conns int
|
2022-01-06 18:57:37 +08:00
|
|
|
|
ConnsInbound int
|
|
|
|
|
ConnsOutbound int
|
|
|
|
|
FD int
|
2022-06-10 22:01:03 +08:00
|
|
|
|
Memory int64
|
2022-01-05 22:54:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-19 15:43:31 +08:00
|
|
|
|
// Apply overwrites all zero-valued limits with the values of l2
|
|
|
|
|
// Must not use a pointer receiver.
|
|
|
|
|
func (l *BaseLimit) Apply(l2 BaseLimit) {
|
|
|
|
|
if l.Streams == 0 {
|
|
|
|
|
l.Streams = l2.Streams
|
|
|
|
|
}
|
|
|
|
|
if l.StreamsInbound == 0 {
|
|
|
|
|
l.StreamsInbound = l2.StreamsInbound
|
|
|
|
|
}
|
|
|
|
|
if l.StreamsOutbound == 0 {
|
|
|
|
|
l.StreamsOutbound = l2.StreamsOutbound
|
|
|
|
|
}
|
|
|
|
|
if l.Conns == 0 {
|
|
|
|
|
l.Conns = l2.Conns
|
|
|
|
|
}
|
|
|
|
|
if l.ConnsInbound == 0 {
|
|
|
|
|
l.ConnsInbound = l2.ConnsInbound
|
|
|
|
|
}
|
|
|
|
|
if l.ConnsOutbound == 0 {
|
|
|
|
|
l.ConnsOutbound = l2.ConnsOutbound
|
|
|
|
|
}
|
|
|
|
|
if l.Memory == 0 {
|
|
|
|
|
l.Memory = l2.Memory
|
|
|
|
|
}
|
|
|
|
|
if l.FD == 0 {
|
|
|
|
|
l.FD = l2.FD
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
// BaseLimitIncrease is the increase per GB of system memory.
|
|
|
|
|
type BaseLimitIncrease struct {
|
|
|
|
|
Streams int
|
|
|
|
|
StreamsInbound int
|
|
|
|
|
StreamsOutbound int
|
|
|
|
|
Conns int
|
|
|
|
|
ConnsInbound int
|
|
|
|
|
ConnsOutbound int
|
|
|
|
|
Memory int64
|
|
|
|
|
FDFraction float64
|
2022-01-17 01:37:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-12 17:51:55 +08:00
|
|
|
|
// Apply overwrites all zero-valued limits with the values of l2
|
2022-06-19 15:43:31 +08:00
|
|
|
|
// Must not use a pointer receiver.
|
|
|
|
|
func (l *BaseLimitIncrease) Apply(l2 BaseLimitIncrease) {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
if l.Streams == 0 {
|
|
|
|
|
l.Streams = l2.Streams
|
|
|
|
|
}
|
|
|
|
|
if l.StreamsInbound == 0 {
|
|
|
|
|
l.StreamsInbound = l2.StreamsInbound
|
|
|
|
|
}
|
|
|
|
|
if l.StreamsOutbound == 0 {
|
|
|
|
|
l.StreamsOutbound = l2.StreamsOutbound
|
|
|
|
|
}
|
|
|
|
|
if l.Conns == 0 {
|
|
|
|
|
l.Conns = l2.Conns
|
|
|
|
|
}
|
|
|
|
|
if l.ConnsInbound == 0 {
|
|
|
|
|
l.ConnsInbound = l2.ConnsInbound
|
|
|
|
|
}
|
|
|
|
|
if l.ConnsOutbound == 0 {
|
|
|
|
|
l.ConnsOutbound = l2.ConnsOutbound
|
|
|
|
|
}
|
|
|
|
|
if l.Memory == 0 {
|
|
|
|
|
l.Memory = l2.Memory
|
|
|
|
|
}
|
2022-06-19 15:43:31 +08:00
|
|
|
|
if l.FDFraction == 0 {
|
|
|
|
|
l.FDFraction = l2.FDFraction
|
2022-06-12 17:51:55 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-05 22:54:28 +08:00
|
|
|
|
func (l *BaseLimit) GetStreamLimit(dir network.Direction) int {
|
2021-12-24 18:12:44 +08:00
|
|
|
|
if dir == network.DirInbound {
|
|
|
|
|
return l.StreamsInbound
|
|
|
|
|
} else {
|
|
|
|
|
return l.StreamsOutbound
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-15 02:10:18 +08:00
|
|
|
|
func (l *BaseLimit) GetStreamTotalLimit() int {
|
|
|
|
|
return l.Streams
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-05 22:54:28 +08:00
|
|
|
|
func (l *BaseLimit) GetConnLimit(dir network.Direction) int {
|
2021-12-24 18:12:44 +08:00
|
|
|
|
if dir == network.DirInbound {
|
|
|
|
|
return l.ConnsInbound
|
|
|
|
|
} else {
|
|
|
|
|
return l.ConnsOutbound
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-15 02:10:18 +08:00
|
|
|
|
func (l *BaseLimit) GetConnTotalLimit() int {
|
|
|
|
|
return l.Conns
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-05 22:54:28 +08:00
|
|
|
|
func (l *BaseLimit) GetFDLimit() int {
|
2021-12-24 18:12:44 +08:00
|
|
|
|
return l.FD
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *BaseLimit) GetMemoryLimit() int64 {
|
|
|
|
|
return l.Memory
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *fixedLimiter) GetSystemLimits() Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.System
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetTransientLimits() Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.Transient
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-02 04:39:13 +08:00
|
|
|
|
func (l *fixedLimiter) GetAllowlistedSystemLimits() Limit {
|
|
|
|
|
return &l.AllowlistedSystem
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *fixedLimiter) GetAllowlistedTransientLimits() Limit {
|
|
|
|
|
return &l.AllowlistedTransient
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetServiceLimits(svc string) Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
sl, ok := l.Service[svc]
|
2021-12-24 18:12:44 +08:00
|
|
|
|
if !ok {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.ServiceDefault
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
2022-06-10 22:01:03 +08:00
|
|
|
|
return &sl
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetServicePeerLimits(svc string) Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
pl, ok := l.ServicePeer[svc]
|
2022-01-14 19:42:08 +08:00
|
|
|
|
if !ok {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.ServicePeerDefault
|
2022-01-14 19:42:08 +08:00
|
|
|
|
}
|
2022-06-10 22:01:03 +08:00
|
|
|
|
return &pl
|
2022-01-09 04:04:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetProtocolLimits(proto protocol.ID) Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
pl, ok := l.Protocol[proto]
|
2021-12-24 18:12:44 +08:00
|
|
|
|
if !ok {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.ProtocolDefault
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
2022-06-10 22:01:03 +08:00
|
|
|
|
return &pl
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetProtocolPeerLimits(proto protocol.ID) Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
pl, ok := l.ProtocolPeer[proto]
|
2022-01-14 19:42:08 +08:00
|
|
|
|
if !ok {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.ProtocolPeerDefault
|
2022-01-14 19:42:08 +08:00
|
|
|
|
}
|
2022-06-10 22:01:03 +08:00
|
|
|
|
return &pl
|
2022-01-14 19:42:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetPeerLimits(p peer.ID) Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
pl, ok := l.Peer[p]
|
2021-12-24 18:12:44 +08:00
|
|
|
|
if !ok {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.PeerDefault
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
2022-06-10 22:01:03 +08:00
|
|
|
|
return &pl
|
2021-12-24 18:12:44 +08:00
|
|
|
|
}
|
2022-01-06 18:57:37 +08:00
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetStreamLimits(_ peer.ID) Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.Stream
|
2022-01-06 18:57:37 +08:00
|
|
|
|
}
|
2022-01-13 21:37:02 +08:00
|
|
|
|
|
2022-06-10 22:01:03 +08:00
|
|
|
|
func (l *fixedLimiter) GetConnLimits() Limit {
|
2022-06-12 17:51:55 +08:00
|
|
|
|
return &l.Conn
|
2022-01-13 21:37:02 +08:00
|
|
|
|
}
|