-
Notifications
You must be signed in to change notification settings - Fork 305
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
Interactive Window Ordering Fix #8233
Conversation
src/test/datascience/editor-integration/codewatcher.unit.test.ts
Outdated
Show resolved
Hide resolved
I fixed up the Unit tests. Will take a peek at what we currently have in the .vscode tests. If we don't have coverage for this situation there I'll add a test before submission for queuing multiple sets of cells and making sure they come out in order. |
} | ||
} else { | ||
// Failure. Print our failure message and clear the queue | ||
await this.addErrorMessage(file, leftCount); |
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.
Right now I have a failure to execute a cell just clear out the queue, didn't feel worth tracking trying to see which command queued which cells.
debug?: boolean | ||
) { | ||
// Either start up this addCode, or queue it for later | ||
if (this.executingAddCode) { |
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 feel like TS / JS has a bunch of way to do this type of queuing and they all always look a bit janky to me. Speak up if it looks like I'm doing something unreasonable here, but this way felt the cleanest to me. (Had a promise chain thing I was initially working with that felt hard to read / debug).
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 you could just chain the promise. Like the interactive window does itself.
Essentially you have a single promise - addCodePromise that you init to a resolved promise.
addCode changes to do something like so:
// Chain execution promises so that cells are executed in the right order
if (this.addCodePromise) {
this.addCodePromise= this.addCodePromise.then(() =>
this.addCodeImpl(code, fileUri, line, isDebug)
);
} else {
this.addCodePromise= this.addCodeImpl(code, fileUri, line, isDebug);
}
try {
return await this.addCodePromise;
} catch (exc) {
// Rethrow, but clear promise so we can execute again
this.addCodePromise= undefined;
throw exc;
}
Old addCode method becomes addCodeImpl
Actually feels like we could have a method/service to do this cause we do this a lot.
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 would also leave the old code the same as the behavior wouldn't have to account for a queue.
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.
Trying to think of what that generic service might be.
It would look like a function. A function which returns a promise.
I wonder if we could do it with a decorator. That would be even better. Something like so:
@chainable
public addCode(...): Promise
Where the chainable decorator would do the code I pasted above.
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 agree, and would suggest moving all of that into the existing addCode
method, unless i'm reading it incorrectly
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 the addCode method as that the choke point, thats what everything calls today.
Perhaps rename from addCode
to queueAddCode
or the like and make it 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.
Then we could use that in both the interractive window and here (and probably ten other places where we chain stuff)
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 had initially started with the promise chain starting from a resolved promise. Felt like it was getting icky with the promises resolving to false (instead of throwing) and the error handler function having to get tacked on. But I'm not adverse to going back. Let me restart from there and see if I can get it looking 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.
I tried this on the current interactive window 'submitCodeImpl' and it seems to work. Then we can use this to do promise chaining anywhere:
// Turn a method that returns a promise into something that uses a chain of promises
export function chainPromise() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (_target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value;
const chainedKey = `chained${propertyKey}`;
// eslint-disable-next-line , @typescript-eslint/no-explicit-any
descriptor.value = function (...args: any[]) {
// Check for promise in the object.
let currentValue = (this as any)[chainedKey] as Promise<any>;
if (currentValue) {
currentValue = currentValue.then(() => originalMethod.apply(this, args));
} else {
currentValue = originalMethod.apply(this, args);
}
// Save promise in object
(this as any)[chainedKey] = currentValue;
// If promise fails, clear it.
return currentValue
.then((r) => r)
.catch((e) => {
(this as any)[chainedKey] = undefined;
throw e;
});
};
return descriptor;
};
}
This changes submitCodeImpl (or your addCode) to this instead:
@chainPromise()
private async submitCodeImpl(code: string, fileUri: Uri, line: number, isDebug: boolean) {
// Internals don't worry about chaining anymore
}
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.
Hmm, thought I put a comment here. I didn't like the decorator for this (though your implementation is rad clever) it feels like really core functionality to be putting into a decorator (versus timing / telemetry) and not easy to debug and the mild differences between something like the addCode here and submitCode in the interactive window (things like the false return value handling) didn't lend to sharing one decorator.
Codecov Report
@@ Coverage Diff @@
## main #8233 +/- ##
======================================
Coverage 71% 72%
======================================
Files 368 371 +3
Lines 22591 23058 +467
Branches 3418 3507 +89
======================================
+ Hits 16212 16632 +420
- Misses 4984 4986 +2
- Partials 1395 1440 +45
|
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.
.
debug?: boolean | ||
) { | ||
// Either start up this addCode, or queue it for later | ||
if (this.executingAddCode) { |
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 agree, and would suggest moving all of that into the existing addCode
method, unless i'm reading it incorrectly
debug?: boolean | ||
) { | ||
// Either start up this addCode, or queue it for later | ||
if (this.executingAddCode) { |
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 the addCode method as that the choke point, thats what everything calls today.
Perhaps rename from addCode
to queueAddCode
or the like and make it sync
@@ -560,6 +560,81 @@ testing2`; | |||
document.verifyAll(); | |||
}); | |||
|
|||
test('Test two command ordering correct ordering', async () => { |
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.
Usually I like our vscode tests over unit, but one of these tests actually caught an issue in an earlier checkin. And the way they are set up they actually work pretty good for testing this scenario. I put this test in the old bits and it failed with the expected failure (trying to interleave instead of running each in order).
await this.addErrorMessage(file, leftCount); | ||
|
||
// Throw to break out of the promise chain | ||
throw new InteractiveCellResultError(); |
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.
Slight difference from something like submitCodeImpl as we want to abort the entire chain not just for exceptions (which we want to bubble up) but also for a error in the code, which returns as a false value from .addCode.
For #6982
package-lock.json
has been regenerated by runningnpm install
(if dependencies have changed).