Skip to content

Commit

Permalink
Add RGA#destroy() method and use it to fix a bug where a server res…
Browse files Browse the repository at this point in the history
…tart would cause multiple RGAs to be tied to a single editor, within a single browser window, leading to a kaleidoscope of possible bugs.
  • Loading branch information
jorendorff committed Apr 12, 2016
1 parent 70f1de0 commit 9cb8707
Showing 1 changed file with 61 additions and 8 deletions.
69 changes: 61 additions & 8 deletions lib/rga.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function RGA(id, history, queue) {
this._nextTimestamp = id;
this._subscribers = [];
this._queue = queue || RGA._browserQueue;
this._onDestroy = [];

var self = this;
this.downstream = function (sender, op) {
Expand Down Expand Up @@ -143,6 +144,19 @@ RGA.prototype = {
return s;
},

// Disconnect this RGA from anything it was connected to.
destroy: function () {
this._subscribers = undefined;
for (var i = 0; i < this._onDestroy.length; i++)
this._onDestroy[i]();
this._onDestroy = undefined;
},

// Returns true if this.destroy() has been called.
wasDestroyed: function () {
return this._subscribers === undefined;
},

// Add an event listener. An RGA only emits one kind of event: "op".
on: function (type, callback) {
if (type === "op")
Expand Down Expand Up @@ -174,24 +188,52 @@ RGA.tie = function tie(a, b) {

a.on("op", b.downstream);
b.on("op", a.downstream);

// If either RGA is destroyed, untie it from the other.
function untie() {
if (a !== undefined) {
a.off("op", b.downstream);
b.off("op", a.downstream);
a = b = undefined;
}
}
b._onDestroy.push(untie);
a._onDestroy.push(untie);
};

// Cause an RGA object to communicate via socket.io to update an RGA object
// tied to the other end of the socket. The two RGA objects must initially
// contain the same history.
RGA.tieToSocket = function tieToSocket(a, s) {
var a_s = function (sender, op) {
s.emit("downstream", op);
if (s !== undefined) {
//a._log("forwarding from RGA " + a.id + " to socket: ", op);
s.emit("downstream", op);
}
};
a.on("op", a_s);
s.on("downstream", function (op) {
a.downstream(a_s, op);
});

// Cleanup.
s.on("disconnect", function () {
a.off("op", a_s);
});
var s_a = function (op) {
if (a !== undefined) {
//a._log("forwarding from socket to RGA " + a.id + ": ", op);
a.downstream(a_s, op);
}
};
s.on("downstream", s_a);

// If the RGA is destroyed or the socket gets disconnected, untie them from
// one another. Amazingly, socket.io offers no way to unsubscribe callbacks.
// To ensure no further events are delievered and no unused GC-edges remain,
// we null out the variables, and the callbacks above check for this state.
function untie() {
if (a !== undefined) {
//a._log("untying RGA " + a.id + " from socket");
a.off("op", a_s);
a = s = a_s = s_a = untie = undefined;
}
}
a._onDestroy.push(untie);
s.on("disconnect", untie);
};

// Return a delta to turn the string s0 into s1.
Expand Down Expand Up @@ -302,6 +344,9 @@ RGA.AceEditorRGA = function AceEditorRGA(id, editor, history, queue) {
var self = this;
this._changeCallback = function () { self._takeUserEdits() };
editor.getSession().on("change", this._changeCallback);
this._onDestroy.push(function () {
self.editor.getSession().off("change", self._changeCallback);
});

// Now for the other direction. Replace the callback that receives changes
// from other RGAs with a new one that also updates the editor.
Expand Down Expand Up @@ -517,6 +562,14 @@ Object.assign(RGA.AceEditorRGA.prototype, {
RGA.AceEditorRGA.setup = function (editor, socket) {
var local = undefined;
socket.on("welcome", function (event) {
if (local !== undefined) {
// If the server is restarted, we'll receive a second welcome event.
// Don't try to keep the old RGA in sync with the new server instance,
// and *definitely* don't try to tie two RGAs to the same editor!
local.destroy();
local = undefined;
}

local = new RGA.AceEditorRGA(event.id, editor, event.history);
RGA.tieToSocket(local, socket);
editor.focus();
Expand Down

0 comments on commit 9cb8707

Please sign in to comment.