Concurrency

a bug story πŸ›

(realtime concepts in async programming)
Node v.20 upgrade deployment:

Lots of websocket pods restarts...πŸ’₯

Trying to write a response after it has been sent. ☠️

Somewhere in here:

  handle: function(request, response) {
      var requestUrl    = url.parse(request.url, true),
          requestMethod = request.method,
          self          = this;
  
      request.originalUrl = request.url;
  
      request.on('error', function(error) { self._returnError(response, error) });
      response.on('error', function(error) { self._returnError(null, error) });
  
      if (this._static.test(requestUrl.pathname))
        return this._static.call(request, response);
  
      // http://groups.google.com/group/faye-users/browse_thread/thread/4a01bb7d25d3636a
      if (requestMethod === 'OPTIONS' || request.headers['access-control-request-method'] === 'POST')
        return this._handleOptions(request, response);
  
      if (EventSource.isEventSource(request))
        return this.handleEventSource(request, response);
  
      if (requestMethod === 'GET')
        return this._callWithParams(request, response, requestUrl.query);
  
      if (requestMethod === 'POST')
        return this._concatStream(request, function(data) {
          var type   = (request.headers['content-type'] || '').split(';')[0],
              params = (type === 'application/json')
                     ? { message: data }
                     : querystring.parse(data);
  
          request.body = data;
          this._callWithParams(request, response, params);
        }, this);
  
      this._returnError(response, { message: 'Unrecognized request type' });
    },
  

   _callWithParams: function(request, response, params) {
        //...

        if (origin) {
          headers['Access-Control-Allow-Origin'] = origin;
        }
        headers['Cache-Control'] = 'no-cache, no-store';
  
        this._server.process(message, request, function(replies) {
          // ...
          headers['Content-Length'] = new Buffer(body, 'utf8').length.toString();
          headers['Connection'] = 'close';
  
          response.writeHead(200, headers);
          response.end(body);
        }, this);
    },
  

Why did it happen?

Deep dive

  • web sdk opens long-polling connection, quickly closes it
  • error handler on 114 fires, writes 400 response (ignored)
  • subsequently, server tries to respond to long-polling connection and fails to write to response
  • in node 16 β†’ error handled on line 115
  • in node 20 β†’ error bubbles up differently for some reason; unhandled

"for some reason"

  • This is a good view to take as a developer
  • Things can happen for reasons you don't know
  • Defensive programming
  • NASA: The Power of Ten
Check response.isWritable
before writing to the response
How does this relate to

"realtime"


What is real-time?

                      your answer here
  
Realtime Systems
  • Event-driven - respond within time guarantee (ms)
  • Often used in safety-critical apps (fly-by-wire, braking systems, etc)
  • Time guarantees cannot allow system load to interfere with deadlines
    • Shed load to maintain time guarantees
    • New requests are shed so the system can honour requests it already accepted
    • Dropping older jobs β†’ nothing ever gets processed
Realtime Systems
  • multi-threaded - for parallelism
  • concurrent - different parts of code simultaneously







Real-time systems are

event-driven


        request.on ('error', function ... );
        response.on ('error', function ... );
    

Node apps

  • βœ… Realtime: Event-driven
  • βœ… Concurrent: Asynchronous
  • βœ… Parallel: Multi-threaded ℹ️
Node is Multi-Threaded
Node is Multi-Threaded

Concurrency in Node

  • In async tasks triggered by thread pool
  • No parallel execution of user code, but...
  • There is concurrency of async tasks
  • Event handling code can be interleaved

  bool strike (int weapon, int strength) {
    static int hitpoints = 100;
    int damage = getDamage(weapon, strength);
    bool rc = false;

    if (hitpoints && damage) {
      hitpoints = hitpoints - totalDamage;
      if (hitpoints < 0) hitpoints = 0;
      rc = true;
    }

    return rc;
  }
  
This code is not re-entrant :-(

  async function handlerOne (req) {
    await blahblah();
    req.end(400, "your bad");
  }

  async function handlerTwo (req) {
    await yadayada();
    req.end(500, "my bad");
  }        
  
multi-threaded (C++)
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
multi-threaded (C++)
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
multi-threaded (C++)
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
nodejs
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 
β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– β– 

Take-aways

  • Concurrency in node !== parallelism
  • multi-threading is hidden in the thread pool (libuv)
  • Be mindful of shared state in event-driven code
  • Design for unexpected things "for some reason"
  • (bonus) pure functions with no side effects may avoid critical sections

Credits

  • Gabriel PΓ©riard
  • Julian Garritano
  • Alexandre Brun
  • Mike Spensieri
  • Alex "AJ" Sincennes
  • #sunco-f-node-20-upgrade peeps
Thanks! πŸ›