Skip to content

Commit

Permalink
event::Emitter: fix reentrant retraction edgecase
Browse files Browse the repository at this point in the history
  • Loading branch information
afri committed Jul 9, 2018
1 parent 84c2d2e commit 3126abf
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 1 deletion.
13 changes: 12 additions & 1 deletion modules/event.sjs
Expand Up @@ -100,7 +100,18 @@ function Emitter() {
listeners.push(resume);
}
retract {
__js listeners.splice(listeners.indexOf(resume), 1);
__js {
// the listeners array gets exchanged from 'emit',
// so if this is a concurrent retract, we must take care not to try
// to accidentally call splice(-1,1) on listeners when our listener isn't found.
// i.e., we must not do:
// listeners.splice(listeners.indexOf(resume), 1);
// see also event-tests.sjs:concurrent retract edgecase
var i = listeners.indexOf(resume);
if (i >= 0)
listeners.splice(i, 1);
} // __js

}
receiver(val);
}
Expand Down
39 changes: 39 additions & 0 deletions test/unit/event-tests.sjs
Expand Up @@ -441,3 +441,42 @@ context('wait()') {||
rv .. assert.eq(2);
}
}

test('concurrent retract edgecase') {||
var rv = '';

var E = event.Emitter();
var R;

waitfor {
E .. event.wait();
waitfor {
E .. event.wait(); // this listener used to be erroneously removed by the listener
// retraction triggered by R(), causing the test to hang forever
rv += 'C';
}
and {
R(); // this call will abort the event listener in the clause below
rv += 'B';
}

rv += 'D';
}
and {
waitfor() {
R = resume;
// R() will be called before we receive the event, causing the wait() call to be
// aborted
E .. event.wait();
}
rv += 'A';
}
and {
hold(0);
E.emit();
hold(0);
E.emit();
rv += 'E';
}
rv .. assert.eq('ABCDE');
}

0 comments on commit 3126abf

Please sign in to comment.