apache / nano

Nano is now part of Apache CouchDB. Repo moved to https://GitHub.com/apache/couchdb-nano
https://github.com/apache/couchdb-nano
Other
1.13k stars 157 forks source link

Insert overwriting previous insert #275

Closed ghost closed 9 years ago

ghost commented 9 years ago

I have code that is trying to concurrently update a document to reserve a free port. I followed the guide to get the document and then do an insert. If the insert fails it'll retry do a recursive call on the method and gets the document and trys inserting again.

It works for the most part, but on a few occasions...a double insert happens so it overwrites the an entry.

Here is my calling code:

   var port = new PortList(dbname, "test");
   port.init().then(function (test){
       for(var i = 0; i < 6; i++){
           port.reservePort("test_" + i);
       }
    });

and for the the PortList.js:

PortList.prototype.reservePort = function reservePort(id, numRetry){
    var self = this;
    return new Promise(function (resolve, reject){
        var key = self.key;
        var tries;
        var myid = id;
        if(numRetry == null){
            var x = 10;
            tries = x; //defaults to 10 retries
            logger.info("Defaulting to %s retries", x);
        }else{
            tries = numRetry;

        }
        logger.info('Reserving port for %s, at key %s', myid, key);
        logger.info("tries = ", tries);
        self.flowDb.get(key,function(err,doc){
            if (doc) {
                var rev = doc._rev;
                var freeone = null;
                for(var p in doc.free){
                    if(doc.free.hasOwnProperty(p)){
                        freeone= p;
                        break;
                    }
                }
                doc.inuse[freeone]= myid;
                delete doc.free[freeone];
                --doc.available;

                logger.info('Reserving port for %s, at key %s : free port = %s', myid, key, freeone);
                logger.info('Doc to insert = ', doc);
                self.flowDb.insert(doc, function(err, body) {
                    if (!err){
                        logger.info(body);
                        logger.info('Successfully updated doc: %s for key %s', freeone, self.key);
                        resolve(freeone);
                    }else{
                        if(err.statusCode === 409){
                            --tries;
                            if(tries <= 0){
                                logger.error('out of tries');
                                reject('out of tries');
                                return;
                            }else{
                                logger.warn('trying again ' + tries);
                                return self.reservePort(myid, tries);
                            }

                        }
                        logger.error('Failed to update port list:', err);
                        reject('Failed to update port list');
                    }
                });
            }else{
             logger.error('Failed to update port list:'); 
             reject('Failed to update port list');
            }
        });
    });
}

When it executes, the document before inserting has the same _rev id as another another doc: [2015-04-21 13:43:04.477] [INFO] portlist - Doc to insert = { _id: 'test/portlist', _rev: '265-1c0ca8a768551c43a8043c4c4ae071f6', start: 1, end: 50, available: 44, inuse: { '1': 'test_3', '2': 'test_1', '3': 'test_5', '4': 'test_2', '5': 'test_0' },

[2015-04-21 13:43:04.432] [INFO] portlist - Doc to insert = { _id: 'test/portlist', _rev: '265-1c0ca8a768551c43a8043c4c4ae071f6', start: 1, end: 50, available: 44, inuse: { '1': 'test_3', '2': 'test_1', '3': 'test_5', '4': 'test_2', '5': 'test_4' }, free:

but both of them are are inserted:

[2015-04-21 13:43:05.475] [INFO] portlist - { ok: true, id: 'test/portlist', rev: '266-0d56476019fc3d3eca8315e8f7954bc5' } [2015-04-21 13:43:05.479] [INFO] portlist - Successfully updated doc: 5 for key test/portlist [2015-04-21 13:43:05.484] [INFO] portlist - { ok: true, id: 'test/portlist', rev: '266-488e8bd6a767a8fef06ef33883f5f2f4' } [2015-04-21 13:43:05.487] [INFO] portlist - Successfully updated doc: 5 for key test/portlist

and in the resultant doc in the database:

{ "_id": "test/portlist", "_rev": "266-488e8bd6a767a8fef06ef33883f5f2f4", "start": 1, "end": 50, "available": 44, "inuse": { "1": "test_3", "2": "test_1", "3": "test_5", "4": "test_2", "5": "test_0" },

so rev 266-488e8bd6a767a8fef06ef33883f5f2f4 overwrote 266-0d56476019fc3d3eca8315e8f7954bc5

jo commented 9 years ago

Hi @noonedoes can you please post a simplified version of the code, which consists only of logic needed to reproduce the problem?

ghost commented 9 years ago

@jo I did some additional testing. I believe this issue is related to the the the 202 return code during a create of the cloudant document.

https://docs.cloudant.com/api.html#documentCreate32

202 - Accepted

Request has been accepted, but the corresponding operation may not have completed. This is used for background operations, such as database compaction or for bulk operations where some updates might have led to a conflict. This code can also be returned following an attempt to create or update a document.

It appears the insert request was successful but the operation wasn't completed, so when a subsequent insert request happens with the same rev id, it overwrites it. I might be able to get around it by using the header returned during the insert.

Thanks

jo commented 9 years ago

Closing now, feel free to reopen.