Skip to content
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 updates failing during output of a notebook #144868

Merged
merged 12 commits into from
Mar 21, 2022

Conversation

rchiodo
Copy link
Contributor

@rchiodo rchiodo commented Mar 11, 2022

This PR fixes microsoft/vscode-jupyter#9327

Outputs that contain any data would crash during an update preventing the cell output update from coming through to an extension.

I believe the issue was originally caused by this commit (our tests started failing this morning):
#144679

@@ -32,7 +60,7 @@ export class ExtHostNotebookDocuments implements extHostProtocol.ExtHostNotebook
$acceptModelChanged(uri: UriComponents, event: SerializableObjectWithBuffers<extHostProtocol.NotebookCellsChangedEventDto>, isDirty: boolean, newMetadata?: NotebookDocumentMetadata): void {
const document = this._notebooksAndEditors.getNotebookDocument(URI.revive(uri));
const e = document.acceptModelChanged(event.value, isDirty, newMetadata);
this._onDidChangeNotebookDocument.fire(deepFreeze(e));
this._onDidChangeNotebookDocument.fire(partialFreeze(e));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting this with a full freeze:

ERR Cannot freeze array buffer views with elements: TypeError: Cannot freeze array buffer views with elements
	at Function.freeze (<anonymous>)
	at deepFreeze (d:\Source\vscode\out\vs\base\common\objects.js:36:20)
	at ExtHostNotebookDocuments.$acceptModelChanged (d:\Source\vscode\out\vs\workbench\api\common\extHostNotebookDocuments.js:32:77)
	at RPCProtocol._doInvokeHandler (d:\Source\vscode\out\vs\workbench\services\extensions\common\rpcProtocol.js:399:27)
	at RPCProtocol._invokeHandler (d:\Source\vscode\out\vs\workbench\services\extensions\common\rpcProtocol.js:384:45)
	at RPCProtocol._receiveRequest (d:\Source\vscode\out\vs\workbench\services\extensions\common\rpcProtocol.js:311:32)
	at RPCProtocol._receiveOneMessage (d:\Source\vscode\out\vs\workbench\services\extensions\common\rpcProtocol.js:243:26)
	at d:\Source\vscode\out\vs\workbench\services\extensions\common\rpcProtocol.js:115:52
	at Listener.invoke (d:\Source\vscode\out\vs\base\common\event.js:420:27)
	at Emitter.fire (d:\Source\vscode\out\vs\base\common\event.js:559:34)**
**

because the output.items.data were not freezable.

if (!append) {
output.items.length = 0;
output.items.length = 0; // This crashes without a copy
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also noticed this throwing an exception while running the same code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a stack trace? Is it throwing because the event processing froze it? That wouldn't be good...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I believe it's crashing because of the freeze.

@rchiodo
Copy link
Contributor Author

rchiodo commented Mar 11, 2022

To repro this issue:

  • Open a jupyter notebook
  • Put this code into it:
print("Start")
import time
for i in range(100):
  time.sleep(0.1)
  print(i)

print("End")
  • Exception should be thrown inside of VS code


const hasBuffer = (typeof Buffer !== 'undefined');

export function partialFreeze<T>(obj: T): T {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit messy, hoping you guys have a cleaner way to do this. Maybe modifying the original deepFreeze to do the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe modifying the original deepFreeze to do the same?

💯% All users of deepFreeze should benefit from this goodness. There is already a check to decide to decent on an object or not. This check should be extended.

Also, this shouldn't be checking for Buffer - that's a nodejs only type and where are here in common-land. I also believe the error is coming from the underlying typed array, this fails the same way

> var a = new Uint8Array(12)
undefined
> Object.freeze(a)
Uncaught TypeError: Cannot freeze array buffer views with elements
    at Function.freeze (<anonymous>)

There should be a check iff an object is a typed array - quite a few candidates according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays#typed_array_views. Good news is they are instanceof-able, e.g candidate instanceof Float32Array works etc.

Copy link
Member

@jrieken jrieken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for far. I have added a few comments and suggestions for improvements. I will also push a change that removes the deepFreeze to unblock you.


const hasBuffer = (typeof Buffer !== 'undefined');

export function partialFreeze<T>(obj: T): T {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe modifying the original deepFreeze to do the same?

💯% All users of deepFreeze should benefit from this goodness. There is already a check to decide to decent on an object or not. This check should be extended.

Also, this shouldn't be checking for Buffer - that's a nodejs only type and where are here in common-land. I also believe the error is coming from the underlying typed array, this fails the same way

> var a = new Uint8Array(12)
undefined
> Object.freeze(a)
Uncaught TypeError: Cannot freeze array buffer views with elements
    at Function.freeze (<anonymous>)

There should be a check iff an object is a typed array - quite a few candidates according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays#typed_array_views. Good news is they are instanceof-able, e.g candidate instanceof Float32Array works etc.

if (!append) {
output.items.length = 0;
output.items.length = 0; // This crashes without a copy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a stack trace? Is it throwing because the event processing froze it? That wouldn't be good...

@rchiodo rchiodo requested a review from jrieken March 11, 2022 17:37
Copy link
Member

@jrieken jrieken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to take in the changes for deepFreeze (checking for typed array) but no cloning of outputs or such. Instead, the event itself shouldn't likely be using deepFreeze but some level of shallow freeze.

src/vs/workbench/api/common/extHostNotebookDocument.ts Outdated Show resolved Hide resolved
@rchiodo
Copy link
Contributor Author

rchiodo commented Mar 14, 2022

I suggest to take in the changes for deepFreeze (checking for typed array) but no cloning of outputs or such. Instead, the event itself shouldn't likely be using deepFreeze but some level of shallow freeze.

I modified deepFreeze to support an exclusion list. I then used this at the event location to exclude freezing the outputs.

@jrieken
Copy link
Member

jrieken commented Mar 16, 2022

I modified deepFreeze to support an exclusion list. I then used this at the event location to exclude freezing the outputs.

Not a fan of that tbh - deepFreeze is "deep" and a flat list of key is always ambiguous in that context. Maybe my earlier suggestion was misleading. I like two things from this PR

  1. improve deepFreeze to always ignore typed arrays because it is apparently illegal to freeze them.
  2. don't use deepFreeze when emitting the notebook change event.

It is my bad that I started with deepFreeze here but I came to believe it is wrong to freeze objects that you "don't own", e.g the function that applies the change and emits the event should only shallow freeze the parts of the event it owns, like the event itself, cellChanges, contentChanges etc but not the notebook document or its cells. It's the responsibility of themselves to protect them from outside modifications (which may or may not be the case but that would be a different issue)

@rchiodo
Copy link
Contributor Author

rchiodo commented Mar 16, 2022

it is my bad that I started with deepFreeze here but I came to believe it is wrong to freeze objects that you "don't own", e.g the function that applies the change and emits the event should only shallow freeze the parts of the event it owns, like the event itself, cellChanges, contentChanges etc but not the notebook document or its cells. It's the responsibility of themselves to protect them from outside modifications (which may or may not be the case but that would be a different issue)

Yeah that makes more sense. Only freeze event properties, not the notebook itself.

@rchiodo rchiodo requested a review from jrieken March 17, 2022 18:46
@@ -294,6 +294,10 @@ export class ExtHostNotebookDocument {
}
}

// Freeze event properties so handlers cannot accidentally modify them
deepFreeze(result.cellChanges);
deepFreeze(result.contentChanges);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shallow freeze only, otherwise NotebookCell instances are frozen

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this should just be object.freeze instead of deepFreeze

Copy link
Contributor Author

@rchiodo rchiodo Mar 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it's weird that the output wasn't frozen? I think I need to debug the actual contents here to see what's in them. Object. freeze would certainly work but there might be more that can be frozen.

@rchiodo rchiodo requested a review from jrieken March 18, 2022 20:52
Copy link
Member

@jrieken jrieken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@jrieken
Copy link
Member

jrieken commented Mar 21, 2022

Ops, post merge (while writing the TPI) I realised this was still freezing the notebook itself and its metadata. Pushed another change on top of things

@github-actions github-actions bot locked and limited conversation to collaborators May 5, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Problem with NotebookCell.outputs not updating
4 participants