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

🐛 Unhandled rejection when submitting ops during hard rollback. #692

Merged
merged 2 commits into from
Mar 4, 2025

Conversation

dawidreedsy
Copy link
Contributor

After doing the steps:

  1. Create a doc with rich text type (or any other non irreversible type)
  2. Make op submission fail
  3. Now in the hard rollback we do this._setType(null);
  4. If there is any op comming before the hard rollback fetch is finished, we get the error
Cannot submit op. Document has not been created.

as in the _submit we do:

    if (!this.type) {
      var err = new ShareDBError(
        ERROR_CODE.ERR_DOC_DOES_NOT_EXIST,
        'Cannot submit op. Document has not been created. ' + this.collection + '.' + this.id
      );
      if (callback) return callback(err);
      return this.emit('error', err);
    }

We definitely do not handle this case properly. Possible solutions:

  1. Just throw error whenever that happens, which is easy to implement and it is not really breaking. User would be then able to react on the error or just ignore it.
  2. Store copy of cannonical snapshot in the doc itself, so that we do not have to do fetch for hard rollback. More difficult to implement and has a side effect of storing the doc twice in the memory.

@dawidreedsy dawidreedsy force-pushed the bug-fix/broken-hard-rollback branch 3 times, most recently from 70e8163 to d6842e4 Compare March 3, 2025 16:28
@coveralls
Copy link

coveralls commented Mar 3, 2025

Coverage Status

coverage: 97.491% (+0.004%) from 97.487%
when pulling fd67b16 on bug-fix/broken-hard-rollback
into ca50594 on master.

Copy link
Collaborator

@alecgibson alecgibson left a comment

Choose a reason for hiding this comment

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

Test, please!

Comment on lines +753 to +757
if (this._isInHardRollback) {
var err = new ShareDBError(
ERROR_CODE.ERR_DOC_IN_HARD_ROLLBACK,
'Cannot submit op. Document is performing hard rollback. ' + this.collection + '.' + this.id
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could just move this after the var err = new ShareDBError(ERR_DOC_DOES_NOT_EXIST) to override this "default" error and then avoid having to duplicate the emit() machinery

@dawidreedsy
Copy link
Contributor Author

dawidreedsy commented Mar 4, 2025

Yes i know want to prepare the test today, just need to fiddle a bit to make it work. Just wanted to push it first, so the @ericyhwang get a chance to look into it before the meeting :)

@dawidreedsy dawidreedsy force-pushed the bug-fix/broken-hard-rollback branch 3 times, most recently from a8e8f0c to 814e814 Compare March 4, 2025 15:53
After doing the steps:
1. Create a doc with rich text type (or any other non irreversible type)
2. Make op submission fail
3. Now in the hard rollback we do `this._setType(null);`
4. If there is any op comming before the hard rollback `fetch` is finished, we get the error
```
Cannot submit op. Document has not been created.
```
as in the `_submit` we do:
```typescript
    if (!this.type) {
      var err = new ShareDBError(
        ERROR_CODE.ERR_DOC_DOES_NOT_EXIST,
        'Cannot submit op. Document has not been created. ' + this.collection + '.' + this.id
      );
      if (callback) return callback(err);
      return this.emit('error', err);
    }
```

We definitely do not handle this case properly. Possible solutions:
1. Just throw error whenever that happens, which is easy to implement
   and it is not really breaking. User would be then able to react on
   the error or just ignore it.
2. Store copy of cannonical snapshot in the doc itself, so that we do
   not have to do fetch for hard rollback. More difficult to implement
   and has a side effect of storing the doc twice in the memory.
@dawidreedsy dawidreedsy force-pushed the bug-fix/broken-hard-rollback branch from 814e814 to 6f3869c Compare March 4, 2025 15:57
@dawidreedsy
Copy link
Contributor Author

To be honest i think there might be more to this than just my fix as before fetch we do

  this._setType(null);
  this.version = null;
  this.inflightOp = null;
  this.pendingOps = [];

and after fetch is finished

    if (inflightOp) pendingOps.unshift(inflightOp);
    var allOpsHadCallbacks = !!pendingOps.length;
    for (var i = 0; i < pendingOps.length; i++) {
      allOpsHadCallbacks = util.callEach(pendingOps[i].callbacks, err) && allOpsHadCallbacks;
    }
    if (err && !allOpsHadCallbacks) doc.emit('error', err);
    doc._isInHardRollback = false;
```,

I think it was menat to reject all ops that were added after fetch started. I am not sure just throwing the above error wouldn't break anything. Even though all tests are passing.

@@ -1077,7 +1088,10 @@ Doc.prototype._hardRollback = function(err) {
inflightOp = null;
}

if (!pendingOps.length) return;
if (!pendingOps.length) {
doc._isInHardRollback = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to avoid potential future issues with forgetting to reset _isInHardRollback on early returns, we could reset to false at the top of this _fetch callback

@dawidreedsy dawidreedsy force-pushed the bug-fix/broken-hard-rollback branch from 0857be5 to fd67b16 Compare March 4, 2025 17:26
@dawidreedsy dawidreedsy merged commit 2b38f47 into master Mar 4, 2025
7 checks passed
@dawidreedsy dawidreedsy deleted the bug-fix/broken-hard-rollback branch March 4, 2025 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants