Skip to content

docShutdown races successful applyUserActions response and destroys custom widget before its Promise settles #2494

Description

@emanuelegissi

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

  1. Open the public reproduction document.
  2. Select a row in the People table.
  3. 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:

  1. The client submits the action
{
  "reqId": 222,
  "method": "applyUserActions",
  "args": [
    0,
    [
      ["BulkRemoveRecord", "People", [4]]
    ],
    {}
  ]
}
  1. Grist sends docShutdown
{
  "type": "docShutdown",
  "data": null,
  "docFD": 0,
  "fromSelf": true,
  "seqId": 293
}
  1. 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:

  1. The custom widget awaits applyUserActions().
  2. The server applies and commits the action.
  3. The action changes a user-attribute record.
  4. granular-access filtering determines that the document needs to reload.
  5. docShutdown is sent to the originating client.
  6. the client schedules a document-pane reload.
  7. the successful applyUserActions RPC response is sent afterward.
  8. the document pane disposes the custom-widget iframe.
  9. the widget never processes the successful response.
  10. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions