-
Notifications
You must be signed in to change notification settings - Fork 226
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 floating promises in run-protocol #5502
Conversation
.increaseLiquidationShortfall(accounting.shortfall) | ||
.catch(reason => | ||
console.error( | ||
'liquidateAndRemove fialed to increaseLiquidationShortfall', |
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.
typo
@@ -391,7 +391,7 @@ test('amm doubleSwap', async t => { | |||
|
|||
const metricsSub = await E(amm.ammPublicFacet).getMetrics(); | |||
const m = await subscriptionTracker(t, metricsSub); | |||
m.assertInitial({ XYK: [] }); | |||
await m.assertInitial({ XYK: [] }); |
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.
void
here would say "don't wait, and ignore it if a broken promise isn't handled". await
here says "don't proceed until we have the results from the call".
I think not having a marker means that it doesn't wait, and the test will fail if a broken promise isn't handled. Is that closer to what we want in a test? Or am I misunderstanding how the alternatives work?
I think the empty case does a worse job of telling us where the issue was, but are the others better at 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.
I think not having a marker means that it doesn't wait, and the test will fail if a broken promise isn't handled. Is that closer to what we want in a test? Or am I misunderstanding how the alternatives work?
I think there's some misunderstanding. void
does not change the handling of the promise; it serves to tell the linter that the non-handling is intentional.
This code works exactly the same without void
,
test.only('unhandled', async t => {
const makeRejector = async () => {
assert.fail('problem');
};
void makeRejector();
t.pass();
});
With this Ava output,
Unhandled rejection in test/amm/vpool-xyk-amm/test-xyk-amm-swap.js
Error: problem
at makeRejector (packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js:1174:12)
at packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js:1176:3
at async Promise.all (index 0)
› makeRejector (packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js:1174:12)
› packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js:1176:3
─
1 test passed [16:12:26]
1 unhandled rejection
I contend this code (as above) is better,
test.only('unhandled', async t => {
const makeRejector = async () => {
assert.fail('problem');
};
await makeRejector();
t.pass();
});
because it handles the rejection and pretty-prints the error:
Rejected promise returned by test. Reason:
Error {
message: 'problem',
}
› makeRejector (packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js:1174:12)
› packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js:1176:9
─
1 test failed [16:13:22]
tick: async (ticks = 1) => { | ||
for (let i = 0; i < ticks; i += 1) { | ||
timer.tick(); | ||
// eslint-disable-next-line no-await-in-loop | ||
await timer.tick(); |
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 seems like a case where tick()
doesn't need to be async
, as long as all the callers await
the results. Alternatively, this could await the last or a Promise.all()
for the group of them.
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 seems like a case where tick() doesn't need to be async, as long as all the callers await the results.
The callers can't await the result of tick:
(line 443) unless it's async (as here) or returns a Promise. So yeah, this could instead be:
tick: (ticks = 1) => Promise.all(Array(ticks).map(() => timer.tick());
Do you think that'd be better?
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.
@@ -1,3 +1,4 @@ | |||
/* eslint-disable no-await-in-loop */ |
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.
Let's not do this unless we're adding an issue to fix 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.
I've been thinking the other direction, to disable the rule in tests. The rule was created because,
performing an await as part of each operation is an indication that the program is not taking full advantage of the parallelization benefits of async/await
but that's less pertinent to test code. In test code clarity of what's happening is more important than small perf wins. Also we often use loops like this to do data-driven coding that isn't necessarily parallelizable. E.g. in the await on 115.
So I don't think there's anything to fix about allowing an await in that loop. If pressed we could refactor like so,
async function checkInterest(i, charge) {
await testJig.advanceRecordingPeriod();
interest += charge;
t.is(
vault.getCurrentDebt().value,
startingDebt + interest,
`interest charge ${i} should have been ${charge}`,
);
t.is(vault.getNormalizedDebt().value, startingDebt);
};
await checkInterest(1, 3n);
await checkInterest(2, 4n);
await checkInterest(3, 4n);
await checkInterest(4, 4n);
but makes room for errors in the iteration number.
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'm not the one that turned on no-await-in-loop
, so I don't feel like I can approve that choice. An issue to discuss whether the rule is superfluous in tests would satisfy me.
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! #5527
@@ -1,3 +1,4 @@ | |||
/* eslint-disable no-await-in-loop */ |
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.
Let's find a different solution to multiple ticks than calling await
on every one.
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, would tickN
on ManualTimer be sufficient?
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.
await manualTimer.tick(); | ||
await waitForPromisesToSettle(); | ||
|
||
shortfallBalance += 42n; | ||
shortfallBalance += 46n; |
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 want to understand what changed here. Changing the number of ticks shouldn't change what the shortfall balance is on the next state change.
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'd like to as well but am balancing that against other priorities. Are you requesting that I figure out what happened and report here before this can be approved?
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 do consider that a blocking issue. I can probably make time to investigate in the next few days if you want to assign the PR to me.
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, I'll assign to you and pull it back if I find time.
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 get these improvements in sooner I made the tests pass without changing numbers and left comments on what changes to tick awaiting result in number changing with FIXME test result relies on awaiting each tick
: aa16d77
6ad610c
to
d80ac48
Compare
d80ac48
to
46bfcb5
Compare
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.
Thanks for the changes. This looks good.
I'm not sure why mergify landed this PR before integration tests ran successfully, but this PR is somehow |
Description
#5495 revealed where promises weren't being handled.
This resolves all the warnings within run-protocol. Classes of changes separated by commit.
Security Considerations
--
Documentation Considerations
--
Testing Considerations
Note that some tests changed due only to more awaits within tests: a6275e7 (no changes required for changes to promise handling within application code)