-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bitswap(client/messageque): expose donthavetimeout config #703
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,33 +10,41 @@ | |
"github.com/libp2p/go-libp2p/p2p/protocol/ping" | ||
) | ||
|
||
const ( | ||
// dontHaveTimeout is used to simulate a DONT_HAVE when communicating with | ||
func defaultDontHaveTimeoutConfig() *DontHaveTimeoutConfig { | ||
cfg := &DontHaveTimeoutConfig{ | ||
DontHaveTimeout: 5 * time.Second, | ||
MaxExpectedWantProcessTime: 2 * time.Second, | ||
PingLatencyMultiplier: 3, | ||
MessageLatencyAlpha: 0.5, | ||
MessageLatencyMultiplier: 2, | ||
} | ||
|
||
cfg.MaxTimeout = cfg.DontHaveTimeout + cfg.MaxExpectedWantProcessTime | ||
return cfg | ||
} | ||
|
||
type DontHaveTimeoutConfig struct { | ||
// DontHaveTimeout is used to simulate a DONT_HAVE when communicating with | ||
// a peer whose Bitswap client doesn't support the DONT_HAVE response, | ||
// or when the peer takes too long to respond. | ||
// If the peer doesn't respond to a want-block within the timeout, the | ||
// local node assumes that the peer doesn't have the block. | ||
dontHaveTimeout = 5 * time.Second | ||
|
||
// maxExpectedWantProcessTime is the maximum amount of time we expect a | ||
DontHaveTimeout time.Duration | ||
// MaxExpectedWantProcessTime is the maximum amount of time we expect a | ||
// peer takes to process a want and initiate sending a response to us | ||
maxExpectedWantProcessTime = 2 * time.Second | ||
|
||
// maxTimeout is the maximum allowed timeout, regardless of latency | ||
maxTimeout = dontHaveTimeout + maxExpectedWantProcessTime | ||
|
||
// pingLatencyMultiplier is multiplied by the average ping time to | ||
MaxExpectedWantProcessTime time.Duration | ||
// MaxTimeout is the maximum allowed timeout, regardless of latency | ||
MaxTimeout time.Duration | ||
// PingLatencyMultiplier is multiplied by the average ping time to | ||
// get an upper bound on how long we expect to wait for a peer's response | ||
// to arrive | ||
pingLatencyMultiplier = 3 | ||
|
||
// messageLatencyAlpha is the alpha supplied to the message latency EWMA | ||
messageLatencyAlpha = 0.5 | ||
|
||
PingLatencyMultiplier int | ||
// MessageLatencyAlpha is the alpha supplied to the message latency EWMA | ||
MessageLatencyAlpha float64 | ||
// To give a margin for error, the timeout is calculated as | ||
// messageLatencyMultiplier * message latency | ||
messageLatencyMultiplier = 2 | ||
) | ||
// MessageLatencyMultiplier * message latency | ||
MessageLatencyMultiplier int | ||
} | ||
|
||
// PeerConnection is a connection to a peer that can be pinged, and the | ||
// average latency measured | ||
|
@@ -61,16 +69,12 @@ | |
// we ping the peer to estimate latency. If we receive a response from the | ||
// peer we use the response latency. | ||
type dontHaveTimeoutMgr struct { | ||
clock clock.Clock | ||
ctx context.Context | ||
shutdown func() | ||
peerConn PeerConnection | ||
onDontHaveTimeout func([]cid.Cid) | ||
defaultTimeout time.Duration | ||
maxTimeout time.Duration | ||
pingLatencyMultiplier int | ||
messageLatencyMultiplier int | ||
maxExpectedWantProcessTime time.Duration | ||
clock clock.Clock | ||
ctx context.Context | ||
shutdown func() | ||
peerConn PeerConnection | ||
onDontHaveTimeout func([]cid.Cid, time.Duration) | ||
config *DontHaveTimeoutConfig | ||
|
||
// All variables below here must be protected by the lock | ||
lk sync.RWMutex | ||
|
@@ -92,39 +96,33 @@ | |
|
||
// newDontHaveTimeoutMgr creates a new dontHaveTimeoutMgr | ||
// onDontHaveTimeout is called when pending keys expire (not cancelled before timeout) | ||
func newDontHaveTimeoutMgr(pc PeerConnection, onDontHaveTimeout func([]cid.Cid), clock clock.Clock) *dontHaveTimeoutMgr { | ||
return newDontHaveTimeoutMgrWithParams(pc, onDontHaveTimeout, dontHaveTimeout, maxTimeout, | ||
pingLatencyMultiplier, messageLatencyMultiplier, maxExpectedWantProcessTime, clock, nil) | ||
func newDontHaveTimeoutMgr(pc PeerConnection, onDontHaveTimeout func([]cid.Cid, time.Duration), cfg *DontHaveTimeoutConfig, clock clock.Clock) *dontHaveTimeoutMgr { | ||
if cfg == nil { | ||
cfg = defaultDontHaveTimeoutConfig() | ||
} | ||
return newDontHaveTimeoutMgrWithParams(pc, onDontHaveTimeout, cfg, clock, nil) | ||
} | ||
|
||
// newDontHaveTimeoutMgrWithParams is used by the tests | ||
func newDontHaveTimeoutMgrWithParams( | ||
pc PeerConnection, | ||
onDontHaveTimeout func([]cid.Cid), | ||
defaultTimeout time.Duration, | ||
maxTimeout time.Duration, | ||
pingLatencyMultiplier int, | ||
messageLatencyMultiplier int, | ||
maxExpectedWantProcessTime time.Duration, | ||
onDontHaveTimeout func([]cid.Cid, time.Duration), | ||
cfg *DontHaveTimeoutConfig, | ||
clock clock.Clock, | ||
timeoutsTriggered chan struct{}, | ||
) *dontHaveTimeoutMgr { | ||
ctx, shutdown := context.WithCancel(context.Background()) | ||
mqp := &dontHaveTimeoutMgr{ | ||
clock: clock, | ||
ctx: ctx, | ||
shutdown: shutdown, | ||
peerConn: pc, | ||
activeWants: make(map[cid.Cid]*pendingWant), | ||
timeout: defaultTimeout, | ||
messageLatency: &latencyEwma{alpha: messageLatencyAlpha}, | ||
defaultTimeout: defaultTimeout, | ||
maxTimeout: maxTimeout, | ||
pingLatencyMultiplier: pingLatencyMultiplier, | ||
messageLatencyMultiplier: messageLatencyMultiplier, | ||
maxExpectedWantProcessTime: maxExpectedWantProcessTime, | ||
onDontHaveTimeout: onDontHaveTimeout, | ||
timeoutsTriggered: timeoutsTriggered, | ||
clock: clock, | ||
ctx: ctx, | ||
shutdown: shutdown, | ||
peerConn: pc, | ||
activeWants: make(map[cid.Cid]*pendingWant), | ||
timeout: cfg.DontHaveTimeout, | ||
messageLatency: &latencyEwma{alpha: cfg.MessageLatencyAlpha}, | ||
onDontHaveTimeout: onDontHaveTimeout, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the question is: "Do we want users to register a custom handler there?". I would be opposed to that unless there is a prominent use case. |
||
config: cfg, | ||
timeoutsTriggered: timeoutsTriggered, | ||
} | ||
|
||
return mqp | ||
|
@@ -189,7 +187,7 @@ | |
// measurePingLatency measures the latency to the peer by pinging it | ||
func (dhtm *dontHaveTimeoutMgr) measurePingLatency() { | ||
// Wait up to defaultTimeout for a response to the ping | ||
ctx, cancel := context.WithTimeout(dhtm.ctx, dhtm.defaultTimeout) | ||
ctx, cancel := context.WithTimeout(dhtm.ctx, dhtm.config.DontHaveTimeout) | ||
defer cancel() | ||
|
||
// Ping the peer | ||
|
@@ -252,7 +250,7 @@ | |
|
||
// Fire the timeout event for the expired wants | ||
if len(expired) > 0 { | ||
go dhtm.fireTimeout(expired) | ||
go dhtm.fireTimeout(expired, dhtm.timeout) | ||
} | ||
|
||
if len(dhtm.wantQueue) == 0 { | ||
|
@@ -340,14 +338,14 @@ | |
} | ||
|
||
// fireTimeout fires the onDontHaveTimeout method with the timed out keys | ||
func (dhtm *dontHaveTimeoutMgr) fireTimeout(pending []cid.Cid) { | ||
func (dhtm *dontHaveTimeoutMgr) fireTimeout(pending []cid.Cid, timeout time.Duration) { | ||
// Make sure the timeout manager has not been shut down | ||
if dhtm.ctx.Err() != nil { | ||
return | ||
} | ||
|
||
// Fire the timeout | ||
dhtm.onDontHaveTimeout(pending) | ||
dhtm.onDontHaveTimeout(pending, timeout) | ||
|
||
// signal a timeout fired | ||
if dhtm.timeoutsTriggered != nil { | ||
|
@@ -360,18 +358,18 @@ | |
// The maximum expected time for a response is | ||
// the expected time to process the want + (latency * multiplier) | ||
// The multiplier is to provide some padding for variable latency. | ||
timeout := dhtm.maxExpectedWantProcessTime + time.Duration(dhtm.pingLatencyMultiplier)*latency | ||
if timeout > dhtm.maxTimeout { | ||
timeout = dhtm.maxTimeout | ||
timeout := dhtm.config.MaxExpectedWantProcessTime + time.Duration(dhtm.config.PingLatencyMultiplier)*latency | ||
if timeout > dhtm.config.MaxTimeout { | ||
timeout = dhtm.config.MaxTimeout | ||
} | ||
return timeout | ||
} | ||
|
||
// calculateTimeoutFromMessageLatency calculates a timeout derived from message latency | ||
func (dhtm *dontHaveTimeoutMgr) calculateTimeoutFromMessageLatency() time.Duration { | ||
timeout := dhtm.messageLatency.latency * time.Duration(dhtm.messageLatencyMultiplier) | ||
if timeout > dhtm.maxTimeout { | ||
timeout = dhtm.maxTimeout | ||
timeout := dhtm.messageLatency.latency * time.Duration(dhtm.config.MessageLatencyMultiplier) | ||
if timeout > dhtm.config.MaxTimeout { | ||
timeout = dhtm.config.MaxTimeout | ||
} | ||
return timeout | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two concerns with this approach:
My mental model for makign things like this configurable is to
WithFoo
configuration options which allow overriding each knob individuallyDefaultFoo
to allow people to programmatically refer to implicit default, rather than hardcoding them in their own code.This may be more work in this PR upfront, but will save us from headache in the future. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we expose the default struct for people to override necessary knobs this way:
This solves your concern without creating "options hell". This is equivalent to having multi-level options, yet it's cleaner. Also, it doesn't require forwarding all the options from
client
tobitswap
in case you want to add more options horizontally, which I tried initially, and got lost.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, the compromise I propose it expose the default configuration solving your concern while keeping things straightforward.