-
Notifications
You must be signed in to change notification settings - Fork 303
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
Add a Backofff Handler utils #499
Conversation
fbe0648
to
5b73089
Compare
pkg/neg/syncers/backoff.go
Outdated
var MaxRetryError = fmt.Errorf("maximum retry exceeded") | ||
|
||
// backoffHanlder handles delays for back off retry | ||
type backoffHanlder interface { |
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.
spelling
pkg/neg/syncers/backoff.go
Outdated
} | ||
|
||
// boundedExponentialBackendOffHandler is a back off handler that implements bounded exponential back off. | ||
type boundedExponentialBackendOffHandler struct { |
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.
"bounded" is probably extraneous (I would think every implementation has a bound)
pkg/neg/syncers/backoff.go
Outdated
return handler.lastRetryDelay, nil | ||
} | ||
|
||
func (handler *boundedExponentialBackendOffHandler) ResetRetryDelay() { |
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.
why not just create a new one instead of reset?
pkg/neg/syncers/backoff.go
Outdated
"time" | ||
) | ||
|
||
var MaxRetryError = fmt.Errorf("maximum retry exceeded") |
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.
ErrRetriesExceeded
// NextRetryDelay returns the delay for next retry or error if maximum number of retries exceeded. | ||
NextRetryDelay() (time.Duration, error) | ||
// ResetRetryDelay resets the retry delay | ||
ResetRetryDelay() |
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.
Alternative: construct a new backoffHandler instead of reset
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.
That basically push more load on Golang GC. Imagine I need to create a new backoffHandler object after every successful NEG sync.
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.
This is usually not the case for a modern GC (efficiency issues due to GC), unless you are creating an incredible # of objects.
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.
Not having reset() usually makes it easier to reason about the object.
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.
Imagine there are multiple go routines running NEG API calls. And when they finished succesfully, every one will call Reset. Or, they need to create duplicated objects.
pkg/neg/syncers/backoff.go
Outdated
} | ||
|
||
// NextRetryDelay returns the next back off delay for retry. | ||
func (handler *boundedExponentialBackOffHandler) NextRetryDelay() (time.Duration, error) { |
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.
We should probably add jitter. Does the code vendored in from client-go offer any resuable bits?
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.
Added jitter.
79b87b7
to
c3f0147
Compare
Fixed the comments. Ready for review |
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.
Do the tests pass if we run them with -parallel
?
handler.lock.Lock() | ||
defer handler.lock.Unlock() | ||
handler.retryCount += 1 | ||
if handler.maxRetries > 0 && handler.retryCount > handler.maxRetries { |
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.
nit - don't need the > 0
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.
> 0
is for the case where one do not want to have a maxRetries limit
|
||
func TestStartAndStopBackoffRetryHandler(t *testing.T) { | ||
helper := &retryHandlerTestHelper{} | ||
handler := NewDelayRetryHandler(helper.incrementCount, NewExponentialBackendOffHandler(15, testMinRetryDelay, testMaxRetryDelay)) |
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.
This and line 88 - could we put the max retries in a variable at the top? const testMaxRetries = 10
?
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.
ack
retryFunc func() | ||
} | ||
|
||
func NewDelayRetryHandler(retryFunc func(), backoff backoffHandler) *backoffRetryHandler { |
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.
To make sure I understand it correctly:
- Each NEG can get one retry handler.
handler.Run()
starts before any NE operation for this NEG is made.- Each NEG can trigger multiple go routines that handle NE operations in parallel. Some of them may fail at different time.
- All failed operations will attempt to call
handler.Retry()
. Conceptually all calls tohandler.Retry()
will be merged into one until the next retry runs. - Each
retryFunc()
can trigger go routines that execute NE operations necessary for the entire NEG based on its current state.
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.
Yes.
One more thing, For successful operations, they will call Reset().
If some are successful and some failed, the successful operations will not be retried. Eventually, only the failed ones will be retries plus back off exponentially.
Ready for another round |
The retry handler part LGTM. |
Can I get this merged? |
Ping |
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: freehan, MrHohn The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Extract the backoff retry logic from
batchSyncer
and write 2 util package that can be reused by different syncers, plus unit testing:backoffHandler
calculates exponential backoff delaysbackoffRetryHandler
triggersretryFunc
after back off delay.These utils are building blocks for the new
transactionSyncer
cc: @agau4779