Describe the current behavior
After a long investigation I am filing this bug report.
When a custom widget calls grist.docApi.applyUserActions() and the action changes a table used by an Access Rules user attribute, Grist may send a docShutdown event to the originating client before sending the successful RPC response.
The action is committed successfully, but the document pane is reloaded and the custom-widget iframe is disposed before the widget receives the result.
As a consequence, code after the awaited call is not executed.
Steps to reproduce
This is reproducible on getgrist.com using the official Grist Labs Action button widget.
See also a simple example reproducing the behaviour with a custom Actionbuttons widget:
https://docs.getgrist.com/51vUFZcC5aqa/useractions-race-condition
- Open the public reproduction document.
- Select a row in the People table.
- Click the
+ action button to add a new People record.
The button executes:
[
["AddRecord", "People", null, {}]
]
The record is added successfully, but the document pane reloads before the widget's applyUserActions() call completes.
The same problem can be reproduced by deleting a row using:
[
["BulkRemoveRecord", "People", [rowId]]
]
Describe the expected behavior
The People table contains:
Email;
Name;
Actions;
Records.
The Records formula is:
People.lookupRecords()
The document defines the following user attribute:
{
"name": "people",
"tableId": "People",
"lookupColId": "Email",
"charId": "Email"
}
An access rule references the attribute record and its Records field:
user.people in user.people.Records
Adding or removing a People record changes the value of:
user.people.Records
This correctly causes Grist to determine that the current user's attributes changed and that the document must be reloaded.
The bug concerns the ordering of that reload relative to the originating RPC response.
For a BulkRemoveRecord action, the captured WebSocket sequence is:
- The client submits the action
{
"reqId": 222,
"method": "applyUserActions",
"args": [
0,
[
["BulkRemoveRecord", "People", [4]]
],
{}
]
}
- Grist sends docShutdown
{
"type": "docShutdown",
"data": null,
"docFD": 0,
"fromSelf": true,
"seqId": 293
}
- Grist sends the successful RPC response a little later:
{
"reqId": 222,
"data": {
"actionNum": 66,
"retValues": [null],
"isModification": true
},
"seqId": 294
}
The action is therefore successful. The removed row is absent after the document reopens.
Actual behavior:
- The custom widget awaits applyUserActions().
- The server applies and commits the action.
- The action changes a user-attribute record.
- granular-access filtering determines that the document needs to reload.
- docShutdown is sent to the originating client.
- the client schedules a document-pane reload.
- the successful applyUserActions RPC response is sent afterward.
- the document pane disposes the custom-widget iframe.
- the widget never processes the successful response.
- its Promise continuation, catch, and finally do not execute.
Expected behavior:
The action may legitimately require the document to reload because the current user's access attributes changed.
However, the RPC request initiated by the custom widget should settle before the originating widget is destroyed.
At least one of these outcomes should occur:
- applyUserActions() resolves successfully before the reload;
- applyUserActions() rejects with a documented reload-related error before the reload;
- Grist delays reloading the originating client until its pending RPC response has been delivered.
The action should not be committed while leaving the initiating widget's Promise permanently unsettled.
Where have you encountered this bug?
Instance information (when self-hosting only)
- Grist instance on getgrist
Describe the current behavior
After a long investigation I am filing this bug report.
When a custom widget calls
grist.docApi.applyUserActions()and the action changes a table used by an Access Rules user attribute, Grist may send a docShutdown event to the originating client before sending the successful RPC response.The action is committed successfully, but the document pane is reloaded and the custom-widget iframe is disposed before the widget receives the result.
As a consequence, code after the awaited call is not executed.
Steps to reproduce
This is reproducible on getgrist.com using the official Grist Labs Action button widget.
See also a simple example reproducing the behaviour with a custom Actionbuttons widget:
https://docs.getgrist.com/51vUFZcC5aqa/useractions-race-condition
+action button to add a new People record.The button executes:
The record is added successfully, but the document pane reloads before the widget's applyUserActions() call completes.
The same problem can be reproduced by deleting a row using:
Describe the expected behavior
The People table contains:
The Records formula is:
People.lookupRecords()The document defines the following user attribute:
An access rule references the attribute record and its Records field:
user.people in user.people.RecordsAdding or removing a People record changes the value of:
user.people.RecordsThis correctly causes Grist to determine that the current user's attributes changed and that the document must be reloaded.
The bug concerns the ordering of that reload relative to the originating RPC response.
For a BulkRemoveRecord action, the captured WebSocket sequence is:
The action is therefore successful. The removed row is absent after the document reopens.
Actual behavior:
Expected behavior:
The action may legitimately require the document to reload because the current user's access attributes changed.
However, the RPC request initiated by the custom widget should settle before the originating widget is destroyed.
At least one of these outcomes should occur:
The action should not be committed while leaving the initiating widget's Promise permanently unsettled.
Where have you encountered this bug?
Instance information (when self-hosting only)