-
Notifications
You must be signed in to change notification settings - Fork 90
fix(backend): deadlocks #3320
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
fix(backend): deadlocks #3320
Conversation
- fixes deadlocks by ensuring we dont open new connections before resolving open transactions - forced knex connections to 1 to help find these cases
✅ Deploy Preview for brilliant-pasca-3e80ec ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
- payment returned from createOutgoingPayment previously had walletAddress of undefined (coming from withGraphFetched(quote)). When assigning manually it included wallet address. Updated get methods to include wallet address on quote as well.
// TODO: should getQuote happen inside trx? wasnt in main (was inside but not using trx). | ||
// If so, in the getQuote method, need to not only pass into IlpQuoteDetails but also connector. | ||
// Probably should have IlpQuoteDetails usingt he trx but not sure about the rest (just | ||
// including the IlpQuoteDetails insert would prly require refactor to to that here) | ||
const quote = await deps.paymentMethodHandlerService.getQuote( | ||
paymentMethod, | ||
{ | ||
quoteId, | ||
walletAddress, | ||
receiver, | ||
receiveAmount: options.receiveAmount, | ||
debitAmount: options.debitAmount | ||
} | ||
) |
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.
Something still needs to be done here one way or another.
-
return ilp quote details from get quote and save in transaction with quote insert. This assumes the ilp connector db calls do not need to use the transaction (they are not using it on main). This should be more performant as it greatly reduces the duration of the transaction. However it does leak the ilp payment method concern into quotes.
-
Do this in the transaction. Pass the transaction into the connector and use for all the db calls.
Originally we called this inside the transaction. The transaction was passed in but only used to save the IlpQuoteDetails
. It was not used by the connector. This getQuote
is a heavy operation (for ilp payments) because of all the db/network calls so it would be nice not to include it in the transaction provided its safe.
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.
Another idea for 1. is a new saveAdditionalDetails(trx: Knex.Transction, quote: Quote)
method on the paymentMethodHandlerService
that inserts into IlpQuoteDetails
for the ilp paymnet method, and does nothing for local payments. Keeps the ilp quote details encapsulated in the ilp payment method handler.
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 think
ilp quote details encapsulated in the ilp payment method handler.
is a good approach: Do you think if we start a new transaction before we call this, and then pass in an optional trx
into the .getQuote
method, it be a perf issue?
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.
is a good approach: Do you think if we start a new transaction before we call this, and then pass in an optional trx into the .getQuote method, it be a perf issue?
First, if we do this we need to use this transaction everywhere inside getQuote
(including connector). Currently, and on main, this is not done inside the transaction. This would be required to prevent the same deadlocking issue at max connections.
I think that should determine the direction here. If everything in getQuote
needs to start using the transaction we should pass it into getQuote
and pass into the connector and use it. Otherwise we shouldn't call getQuote
within the lifecycle of the transaction because it unnecessarily elongates the transaction. The extra work would be the ~11 (?) network calls between rafikis in the connector to get the quote. At that point the question is how to ensure the ilpQuoteDetails
are still saved inside the transaction. Directly call the IlpQuoteDetails insert here or
some new paymentMethodHandlerService. saveAdditionalDetails(trx: Knex.Transction, quote: Quote)
to expose it.
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.
As we just discussed in our call, we're fine with the current behavior (not inside trx) but will still need to ensure the IlpQuoteDetails
is happening inside the transaction. I think the new saveAdditionalDetails
method will work fine for that.
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.
After further discussion we came to the conclusion that we dont need with ilp quote details and the quote. If there is a failure between the details insert and the quote insert it will not create any sort of consistency issues and will just exist in our db as something that happened during ilp quoting (although not utilized).
quote: UnfinalizedQuote, | ||
receiver: Receiver | ||
): Date { | ||
const quoteExpiry = new Date(Date.now() + deps.config.quoteLifespan) |
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.
creates an expiry of now, instead of createdAt
(since we are setting ahead of creation). This was to allow us to simply create the quote and not create then update it.
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.
Nice improvement
@@ -347,13 +357,36 @@ function calculateFixedSendQuoteAmounts( | |||
} | |||
} | |||
|
|||
function calculateExpiry( | |||
deps: ServiceDependencies, | |||
quote: UnfinalizedQuote, |
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.
quote
is unused
} | ||
|
||
async function getWalletAddressPage( | ||
deps: ServiceDependencies, | ||
options: ListOptions | ||
): Promise<Quote[]> { | ||
const quotes = await Quote.query(deps.knex) | ||
.list(options) | ||
.withGraphFetched('fee') |
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.
Ah, because we are not using fee
s in the GraphQL resolvers?
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, and if I recall correctly there were some inconsistencies with the centralized getQuote, getPage tests. Which expect the get
methods and create
methods to return the same object. As I recall the fee is no longer returned from the create. Or it's null instead of undefined.
// TODO: should getQuote happen inside trx? wasnt in main (was inside but not using trx). | ||
// If so, in the getQuote method, need to not only pass into IlpQuoteDetails but also connector. | ||
// Probably should have IlpQuoteDetails usingt he trx but not sure about the rest (just | ||
// including the IlpQuoteDetails insert would prly require refactor to to that here) | ||
const quote = await deps.paymentMethodHandlerService.getQuote( | ||
paymentMethod, | ||
{ | ||
quoteId, | ||
walletAddress, | ||
receiver, | ||
receiveAmount: options.receiveAmount, | ||
debitAmount: options.debitAmount | ||
} | ||
) |
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 think
ilp quote details encapsulated in the ilp payment method handler.
is a good approach: Do you think if we start a new transaction before we call this, and then pass in an optional trx
into the .getQuote
method, it be a perf issue?
@@ -22,7 +22,7 @@ export class Quote extends WalletAddressSubresource { | |||
|
|||
public estimatedExchangeRate!: number | |||
public feeId?: string | |||
public fee?: Fee | |||
public fee?: Fee | null |
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.
what is the null used for?
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.
Had to do with how it was populated in different scenarios. If it was joined but there was no fee vs not joined and maybe some other scenarios. Was causing problems in the centralized getPage tests or similar as I recall.
return tracer.startActiveSpan( | ||
'outgoingPaymentService.createOutgoingPayment', | ||
async (span: Span) => { |
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.
good idea
throw err | ||
} finally { | ||
stopTimerOP() | ||
span.end() |
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 we have to explicitly end the span? or does it do it by itself?
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 appears so, yes.
Given this example from the docs:
export function rollTheDice(rolls: number, min: number, max: number) {
// Create a span. A span must be closed.
return tracer.startActiveSpan('rollTheDice', (span: Span) => {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
// Be sure to end the span!
span.end();
return result;
});
}
And some testing. I removed the span.end()
and no longer saw this outgoingPaymentService.createOutgoingPayment
span.
🚀 Performance Test ResultsTest Configuration:
Test Metrics:
📜 Logs
|
Changes proposed in this pull request
Date.now()
before creating instead ofquote.createdAt
)Context
fixes #3319 #3226
Checklist
fixes #number
user-docs
label (if necessary)