Can node.js code result in race conditions?

后端 未结 6 1744
小鲜肉
小鲜肉 2020-12-02 23:01

From what I read, race conditions occur when different threads try to change a shared variable, which can result in a value that\'s not possible with any serial order of exe

相关标签:
6条回答
  • 2020-12-02 23:42

    Race conditions can still happen as they really have nothing to do with threads, but on making assumptions about event timing and sequence, so threads are just an example of that.

    Node.js is single-threaded, but still concurrent, and race conditions are possible. For example:

    var http = require('http');
    
    var size;
    
    http.createServer(function (req, res) {
      size = 0;
    
      req.on('data', function (data) {
        size += data.length;
      });
    
      req.on('end', function () {
        res.end(size.toString());
      })
    
    }).listen(1337, '127.0.0.1');
    

    This program is supposed to send clients a size of their request. If you test it, will seem to work correct. But it is actually based on implicit assumption, that nothing happens between request start and end events. If there are 2 or more concurrent clients it will not work.

    This happens here because size variable is shared, a lot like when two threads share a variable. You can think about an abstract "asynchrnous context", which is a lot like thread, but it can only be suspended at certain points.

    0 讨论(0)
  • 2020-12-02 23:43

    Yes. Node.js can run into race conditions as soon as you start sharing resources.

    I mistakenly also thought you couldn't get race conditions in Node.js because it's single threaded nature, but as soon as you use a shared resource outside of node (e.g. a file from the file system) you can get into a race condition. I posted an example of this issue in this question when I was trying to understand this: node.js readfile woes

    What is different in Node.js from other environments is that you have a single thread of JavaScript execution so there is only one JavaScript instance running your code (as oppossed to a threaded environment in which there are many threads executing your app code at the same time.)

    0 讨论(0)
  • 2020-12-02 23:45

    Yes, race conditions (in the sense of a shared resource having an inconsistent value due to order of events) can still happen anywhere that there's a point of suspension that could lead to other code being run (with threads its at any line), take for example this piece of async code that is entirely single threaded:

    var accountBalance = 0;
    
    async function getAccountBalance() {
        // Suppose this was asynchronously from a database or something
        return accountBalance;
    };
    
    async function setAccountBalance(value) {
        // Suppose this was asynchronously from a database or something
        accountBalance = value;
    };
    
    async function increment(value, incr) {
        return value + incr;
    };
    
    async function add$50() {
        var balance, newBalance;
        balance = await getAccountBalance();
        newBalance = await increment(balance, 50);
        await setAccountBalance(newBalance);
    };
    
    async function main() {
        var transaction1, transaction2;
        transaction1 = add$50();
        transaction2 = add$50();
        await transaction1;
        await transaction2;
        console.log('$' + await getAccountBalance());
        // Can print either $50 or $100
        // which it prints is dependent on what order
        // things arrived on the message queue, for this very simple
        // dummy implementation it actually prints $50 because
        // all values are added to the message queue immediately
        // so it actually alternates between the two async functions
    };
    
    main();
    

    This code has suspension points at every single await and as such could context switch between the two functions at a bad time producing "$50" rather than the expected "$100", this is essentially the same example as Wikipedia's example for Race Conditions in threads but with explicit points of suspension/re-entry.

    Just like threads though you can solve such race conditions with things like a Lock (aka mutex). So we could prevent the above race condition in the same way as threads:

    var accountBalance = 0;
    
    class Lock {
        constructor() {
            this._locked = false;
            this._waiting = [];
        }
    
        lock() {
            var unlock = () => {
                var nextResolve;
                if (this._waiting.length > 0) {
                    nextResolve = this._waiting.pop(0);
                    nextResolve(unlock);
                } else {
                    this._locked = false;
                }
            };
            if (this._locked) {
                return new Promise((resolve) => {
                    this._waiting.push(resolve);
                });
            } else {
                this._locked = true;
                return new Promise((resolve) => {
                    resolve(unlock);
                });
            }
        }
    }
    
    var account = new Lock();
    
     async function getAccountBalance() {
        // Suppose this was asynchronously from a database or something
        return accountBalance;
    };
    
    async function setAccountBalance(value) {
        // Suppose this was asynchronously from a database or something
        accountBalance = value;
    };
    
    async function increment(value, incr) {
        return value + incr;
    };
    
    async function add$50() {
        var unlock, balance, newBalance;
    
        unlock = await account.lock();
    
        balance = await getAccountBalance();
        newBalance = await increment(balance, 50);
        await setAccountBalance(newBalance);
    
        await unlock();
    };
    
    async function main() {
        var transaction1, transaction2;
        transaction1 = add$50();
        transaction2 = add$50();
        await transaction1;
        await transaction2;
        console.log('$' + await getAccountBalance()); // Now will always be $100 regardless
    };
    
    main();
    
    0 讨论(0)
  • 2020-12-02 23:52

    No. That's true you cannot have race condition on a single threaded, non I/O doing program.

    But node.js is mainly fast because of its non blocking way of programming. Non blocking means that setting a listener to a response event, you can do something else while waiting this response.

    Why ? Because the work for getting the response is done on another thread. Database, filesystem, run on other thread, client obviously runs on another computer and you program workflow can depend on its response.

    So strictly speaking, node.js runs on one thread, but your program workflow, wich include I/O (database, filesystem), client and everything, runs on many thread.

    So there still can be race condition if you do a request to add something to a database, and then just send a request to delete it without waiting for the response of the first request. There would be no race condition if the database was running in the same thread as node.js, and the request was just a function call executed immediatly.

    0 讨论(0)
  • 2020-12-02 23:59

    Yes. It can.

    Race condition in Nodejs is feasible when you use cluster module to initialise multiple workers.

    The case

    var cluster = require('cluster');
    var fs = require('fs');
    if(cluster.isMaster){
        for(var i=0;i<4;i++){
            cluster.fork();
        }
    }else{
        fs.watch('/path/to/file',function(){
            var anotherFile = '/path/to/anotherFile';
            fs.readFile(anotherFile,function(er,data){
                 if(er){
                     throw er;
                 }
                 data = +data+1;
                 fs.writeFile(anotherFile,data,function(er){
                     if(er){
                         throw er;
                     }
                     fs.readFile(anotherFile,function(er,newData){
                         if(er){
                             throw er;
                         }
                         console.log(newData); //newData is now undetermined
                     });
                 });
            });
        });
    }
    

    Whenever you change the watched file, 4 workers will execute the handler at the same time. This behaviour causes the undetermined newData.

    The solution

    if(cluster.isMaster){
        var lock = {};
        var timer = setInterval(function(){
            if(Object.keys(cluster.workers).length >= 4){
                return clearInterval(timer);
            }
            //note that this lock won't 100% work if workers are forked at the same time with loop.
            cluster.fork().on('message',function(id){
                 var isLocked = lock[id];
                 if(isLocked){
                     return console.log('This task has already been handled');
                 }
                 lock[id] = 1;
                 this.send('No one has done it yet');
            });
        },100);
    }else{
         process.on('message',function(){
            //only one worker can execute this task
            fs.watch('/path/to/file',function(){
                var anotherFile = '/path/to/anotherFile';
                fs.readFile(anotherFile,function(er,data){
                     if(er){
                         throw er;
                     }
                     data = +data+1;
                     fs.writeFile(anotherFile,data,function(er){
                         if(er){
                            throw er;
                         }
                         fs.readFile(anotherFile,function(er,newData){
                             if(er){
                                 throw er;
                             }
                             console.log(newData); //newData is now determined
                         });
                     });
                });
            });
         });
         //ask the master for permission
         process.send('watch');
    }
    
    0 讨论(0)
  • 2020-12-03 00:02

    No. Node.js is free of race conditions that would be caused by context switching; however, you can still write a node.js program where asynchronous events happening in an unexpected order result in an inconsistent state.

    For example, suppose you have two functions. The first sends a message through a WebSocket and, in a callback, saves the reply. The second function deletes all saved replies. Calling the functions in order does not guarantee an empty message list. It is important to consider all possible event orderings when doing asynchronous programming.

    EDIT: Here's some example code

    var messages = [];
    
    ...
    
    io.sockets.on('connection', function (socket) {
        socket.emit('ask', { question: 'How many fish do you have?' });
        socket.on('reply', function (data) {
            messages.push(data);
        });
        ...
        wipe();
    });
    
    function wipe() {
        setTimeout(function() {
            messages = [];
        }, 500);
    }
    
    0 讨论(0)
提交回复
热议问题