baconjs / bacon.js

Functional reactive programming library for TypeScript and JavaScript

Home Page:https://baconjs.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unexpected Re-entrant behavior

Macil opened this issue · comments

Several days of debugging an issue have finally led me to discovering this:

var Bacon = require('baconjs');

var criticalSectionActive = false;
var someBus = new Bacon.Bus();
someBus.log();

Bacon.later(0, [1,2])
  .flatMap(Bacon.fromArray)
  .onValue(function(x) {
    if (criticalSectionActive) {
      console.error("Re-entrance!", x);
    }
    criticalSectionActive = true;
    someBus.end();
    criticalSectionActive = false;
    console.log('got x', x);
  });

Output:

<end>
Re-entrance! 2
got x 2
got x 1

Expected output:

<end>
got x 1
got x 2

Using Bacon 0.7.53

Somehow changing the event graph (someBus.end()) confuses event delivery:

var Bacon = require("./dist/Bacon.js");

var criticalSectionActive = false;
var someBus = new Bacon.Bus();
someBus.log();

Bacon.later(0, [1,2])
  .flatMap(Bacon.fromArray)
  .onValue(function(x) {
    if (criticalSectionActive) {
      console.error("Re-entrance!", x);
    }
    console.log("point a", x);
    criticalSectionActive = true;
    console.log("point b", x);
    someBus.end();
    console.log("point c", x);
    criticalSectionActive = false;
    console.log("point d", x);
    console.log('got x', x);
  });
point a 1
point b 1
<end>
Re-entrance! 2
point a 2
point b 2
point c 2
point d 2
got x 2
point c 1
point d 1
got x 1

i.e. first we process first value, and in between we process second one.


I'm not sure if this can be fixed without breaking something else. Currently event propagation, side-effects (onValue and co) and stream-network changes can be interleaved, which causes edge cases like that.


Workaround for now is to un-interleave side-effecting manually (if synchronous delivery isn't important):

var Bacon = require("./dist/Bacon.js");

var criticalSectionActive = false;
var someBus = new Bacon.Bus();
someBus.log();

Bacon.later(0, [1,2])
  .flatMap(Bacon.fromArray)
  .onValue(function(x) {
    setTimeout(function () {
      if (criticalSectionActive) {
         console.error("Re-entrance!", x);
      }
      console.log("point a", x);
      criticalSectionActive = true;
      console.log("point b", x);
      someBus.end();
      console.log("point c", x);
      criticalSectionActive = false;
      console.log("point d", x);
      console.log('got x', x);
    }, 0);
  });
point a 1
point b 1
<end>
point c 1
point d 1
got x 1
point a 2
point b 2
point c 2
point d 2
got x 2

Well it is certainly unexpected that ending an unrelated Bus affects the processing order of side-effects. I'll see if this can be avoided. The Bus seems to provided us with a lot of edge cases.

Thanks @agentme for taking the time to produce a test case for us. It helps a lot!