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

Invalid op submitted. Op version newer than current snapshot #574

Open
PodviaznikovPavlo opened this issue Sep 6, 2022 · 10 comments
Open

Comments

@PodviaznikovPavlo
Copy link

From time to time I receive this error on .submitOp.
There are no specific actions. I can submit same action but change value from 1 to 2 fox example and will get this error. Also, it is not possible to reproduce, because the error can appear when i change 1 to 2, 2 to 3, n to m. You never know when this error appears.

Can someone clarify, in what cases this error appears? what exactly happening, so sharedb just says - no, not this time. And is there a way to workaround?

@alecgibson
Copy link
Collaborator

Hmm according to code comments, this should never happen.

What does your code look like? Hard to say how this could have happened without a repro case. Are you doing anything exotic in middleware? Which DB driver are you using?

@PodviaznikovPavlo
Copy link
Author

PodviaznikovPavlo commented Sep 6, 2022

thanks for response. so, here is what i have:

  1. the sharedb version is 1.9.2. maybe in 1.9.2 it was possible to get this error. if so, i will update sharedb version;
  2. here is my client code
const context = this.subscriptions[docId];
const ops = [];
ops.push({
  p: ["data", rowId, colId],
  od: oldValue,
  oi: newValue
});

context.submitOp(ops, (error) => {
  error ? resolve({ error }) : resolve({ success: true });
});

this can be called quickly many times. like, at the same time i can call this function 200 times. which means, 200 times context.submitOp will be triggered ( with different coords and data of course ). these ops will go in parallel, and not one-by-one ( maybe there might be an issue with this, i don't know )

  1. this is server side part
class SharedbService {
  constructor() {
    this.shareDBInstance = undefined;
  }

  init() {
    const db = DbService.dbConnection;
    const sharedbRedis = RedisService.sharedbRedis;
    const wss = WebsocketService.websocketServer;

    this.shareDBInstance = new ShareDB({ db, pubsub: sharedbRedis });
    this._setShareDBApplyMiddleware();
    
    wss.on('connection', (ws, req) => this._onConnection(ws, req));
    wss.on('error', (error) => this._onError({error, errorCode: 4002}));
  }

  async _onConnection(ws, req) {
    const token = HelperService.getTokenFromURL({ urlString: req.url });
    if(!token) return this._onError({ ws, errorCode: 4000 });

    const { user, error } = await AuthService.verify(token);
    if(error) return this._onError({ ws, errorCode: 4001 });

    const stream = new WebSocketJSONStream(ws);
    const agent = this.shareDBInstance.listen(stream);
    agent.custom.user = user;
  }

  _setShareDBApplyMiddleware() {
    this.shareDBInstance
      .use('receive', (req, next) => {
        const data = req.data || {};

        switch(data.a){
          case 'ping': break;
          default: next(); break;
        }
       })
      .use('commit', (context, next) => {
        const ops = context.op.op;
        const { user } = context.agent.custom;

        context.maxRetries = 5;

        if(context.retries >= 5) {
          return next(
            new Error(
              JSON.stringify(ErrorCodes["4003"])
            )
          );
        }

        if(!user) {
          return this._onError({ ws, errorCode: 4001 });
        }

        ops.forEach(op => {
          op.user = { username: user };
        });

        ops.push(...[
          { p: ["updated_by"], oi: user },
          { p: ['updated'], oi: new Date().toISOString() }
        ]);

        next();
      })
      .use('afterWrite', (context, next) => {
        const ops = context.op.op;

        LoggerService.logOps(JSON.stringify(ops));
        next();
      });
  }

  _onError({ws, error, errorCode}) {
    if(ws) {
      ws.send(JSON.stringify({ error: ErrorCodes[errorCode] }));
      ws.close(errorCode, ErrorCodes[errorCode].message);
    }
    
    LoggerService.onError(`${ErrorCodes[errorCode].message}: ${error || errorCode}`);
  }
}

answering your question about middleware, i think the main thing is that i modify ops there with some info about current user. don't know if this is something "exotic" :)

  1. i'm using sharedb-mongo lib. So, using mongo driver. And redis pub sub.
    "sharedb-mongo": "^1.0.0", "sharedb-redis-pubsub": "^1.0.0",

the biggest problem is that the issue is not reproducible. it can be one op goes well, and on second i get the versions error.

if what i provided is not enough, maybe you can explain what exactly this error is about, how it can be trigerred and maybe how can i avoid it. Or, maybe it is possible to ignore it, don't know. for the moment this specific issue is very painful.

@PodviaznikovPavlo
Copy link
Author

updated sharedb to latest - still can see in some cases this error. so not related to the version

@PodviaznikovPavlo
Copy link
Author

also, we have JAVA API, which works with Mongo. so, there are cases, when action happens, and i send request to API and calling sharedb submitOp. I know, that you cannot udpate mongo model from 2 ends at the same time. maybe there is some issue with this. also, since API updates model in mongo, API doesn't change model version. maybe there is some conflict with this. so, it can be that local snapshot might be outdated because API made some changes to this model

@alecgibson
Copy link
Collaborator

also, we have JAVA API, which works with Mongo. so, there are cases, when action happens, and i send request to API and calling sharedb submitOp. I know, that you cannot udpate mongo model from 2 ends at the same time. maybe there is some issue with this. also, since API updates model in mongo, API doesn't change model version. maybe there is some conflict with this. so, it can be that local snapshot might be outdated because API made some changes to this model

I'm not sure I follow. You mean you're manually changing ShareDB documents outside of ShareDB by directly updating the underlying Mongo document?

@PodviaznikovPavlo
Copy link
Author

I'm not sure I follow. You mean you're manually changing ShareDB documents outside of ShareDB by directly updating the underlying Mongo document?

yes. we have complex system. and the model in Mongo is updated from different sides:

  1. sharedb. this is my code. i subscribe the document, make changes and update document through sharedb
  2. java. these guys update model in mongo directly.

since there is no sharedb for java ( at least it didn't exist, maybe it is now ), java has to update model directly.

so, it can be such situation
there can be paste of data ( ~20 ops ). it means, i will call sharedb submitOp 20 times and these will go in parallel. But, at the same time, for each change i will call Java API, for some purpose and Java API will make some additional changes to the model, i'm saving data through sharedb at the moment. So, at the same time, one model can be updated many times and from different sides.

@alecgibson
Copy link
Collaborator

I'm not sure ShareDB's behaviour is well-defined if you're mutating its document without its knowledge

@PodviaznikovPavlo
Copy link
Author

okay, so that's might be a problem, right? in this case we can't be sure sharedb can handle it? it was designed to be the only source working with model, right?

@alecgibson
Copy link
Collaborator

Yes that would be my first guess. ShareDB assumes that it has full control over its own documents. The issue could be – for example – the Java API accidentally writing a modified version of an old snapshot, essentially rewinding the version number. But there's bound to be a wide number of edge cases when doing this sort of thing, which is beyond the scope of this library to debug. As I've said, ShareDB's behaviour in this case is undefined.

My recommendation is that your Java API make a call to a Node.js API and submit ops "properly" via ShareDB. Or your Java API needs to work on a separate collection that's just a "dumb" MongoDB collection (not ShareDB).

@PodviaznikovPavlo
Copy link
Author

great, thanks for your response. NodeJS API is one of the ideas i have. i jsut want to be sure, that the case i described is something, that can lead to sharedb behaves the way i don't expect. and looks like this is the case. if you see any workaround how to avoid this error, any param can be provided or something, please, let me know. if no, then i got everything. thanks!

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

No branches or pull requests

2 participants