-
Notifications
You must be signed in to change notification settings - Fork 204
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
Handle idempotent retry after error during initializing phase #1406
Changes from 4 commits
c1afd24
0b3cf58
cbb093e
3f2a21f
9e32841
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 |
---|---|---|
|
@@ -34,10 +34,11 @@ func (am *assetManager) GetTokenApprovals(ctx context.Context, filter ffapi.AndF | |
} | ||
|
||
type approveSender struct { | ||
mgr *assetManager | ||
approval *core.TokenApprovalInput | ||
resolved bool | ||
msgSender syncasync.Sender | ||
mgr *assetManager | ||
approval *core.TokenApprovalInput | ||
resolved bool | ||
msgSender syncasync.Sender | ||
idempotentSubmit bool | ||
} | ||
|
||
func (s *approveSender) Prepare(ctx context.Context) error { | ||
|
@@ -58,8 +59,9 @@ func (s *approveSender) setDefaults() { | |
|
||
func (am *assetManager) NewApproval(approval *core.TokenApprovalInput) syncasync.Sender { | ||
sender := &approveSender{ | ||
mgr: am, | ||
approval: approval, | ||
mgr: am, | ||
approval: approval, | ||
idempotentSubmit: approval.IdempotencyKey != "", | ||
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. Not sure we really need to store this separate bool here, as we can always compute it from 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 would have to do more investigation to convince myself of that.. and if I did so, I'd probably want to rename 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. So... if it's ok, think I'd like to leave it as is for this PR 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. But yes, the thing we store is the input DTO, so possible it's named incorrectly. |
||
} | ||
sender.setDefaults() | ||
return sender | ||
|
@@ -107,20 +109,28 @@ func (s *approveSender) resolve(ctx context.Context) (opResubmitted bool, err er | |
if err != nil { | ||
// Check if we've clashed on idempotency key. There might be operations still in "Initialized" state that need | ||
// submitting to their handlers | ||
resubmitWholeTX := false | ||
if idemErr, ok := err.(*sqlcommon.IdempotencyError); ok { | ||
resubmitted, resubmitErr := s.mgr.operations.ResubmitOperations(ctx, idemErr.ExistingTXID) | ||
total, resubmitted, resubmitErr := s.mgr.operations.ResubmitOperations(ctx, idemErr.ExistingTXID) | ||
if resubmitErr != nil { | ||
// Error doing resubmit, return the new error | ||
return false, resubmitErr | ||
} | ||
if len(resubmitted) > 0 { | ||
if total == 0 { | ||
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. This logic feels slightly confusing to me. I guess what we're trying to differentiate is these 3 cases:
We're expressing these 3 states with a combination of two integer returns... wonder if there's a cleaner way to express it? 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. Yes - agreed. I think pushing all this into a combined operation+transaction manager as a function that has plug points for the differentiation, rather than the whole of this section being boilerplate copied in lots of places would be good. Choice is whether this little bit of extension to the boilerplate is the point to take on the bigger refactor. I could merge 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. Right, point taken - it may not be worth continuing to iterate on this interface until we have the cycles to do a larger refactor. We should capture that next step as a task though. 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. Raised #1410 |
||
// We didn't do anything last time - just start again | ||
txid = idemErr.ExistingTXID | ||
resubmitWholeTX = true | ||
err = nil | ||
} else if len(resubmitted) > 0 { | ||
// We resubmitted something - translate the status code to 200 (true return) | ||
s.approval.TX.ID = idemErr.ExistingTXID | ||
s.approval.TX.Type = core.TransactionTypeTokenApproval | ||
return true, nil | ||
} | ||
} | ||
return false, err | ||
if !resubmitWholeTX { | ||
return false, err | ||
} | ||
} | ||
s.approval.TX.ID = txid | ||
s.approval.TX.Type = core.TransactionTypeTokenApproval | ||
|
@@ -194,7 +204,7 @@ func (s *approveSender) sendInternal(ctx context.Context, method sendMethod) (er | |
} | ||
} | ||
|
||
_, err = s.mgr.operations.RunOperation(ctx, opApproval(op, pool, &s.approval.TokenApproval)) | ||
_, err = s.mgr.operations.RunOperation(ctx, opApproval(op, pool, &s.approval.TokenApproval), s.idempotentSubmit) | ||
return err | ||
} | ||
|
||
|
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 are these transfer operations all considered "initializing"? If we get a successful return from the plugin, doesn't that mean the transaction has made it all the way through the token connector to the blockchain connector?
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.
It's the phase we're in as a result of the error in this layer of the code, rather than the operation state.
e.g. this error occurred before we submitted the operation to the connector, so the calling code can decide if that means we don't store the operation at all, or we store it in state
Initializing
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 have not analyzed very scenario to know if the error path is such, that it could never succeed.
If I had done that (quite large) piece of work everywhere, I could have introduced a phase of
OpPhaseRejected
to mean - "this is never going to work, tell them it's broken... without even storing the operation at all".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.
OK - so the phase is more meaningful in an error return case in order to determine how to respond to the error (not so much in a success case)?
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 noticed in some managers you used
operations.ErrTernary
, but not here.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.
Sorry - yes, this should be
operations.ErrTernary
almost certainly - I'd thought this was an error return.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.
ok - added a commit for that