Index: lib/io.js |
=================================================================== |
--- a/lib/io.js |
+++ b/lib/io.js |
@@ -65,84 +65,131 @@ let IO = exports.IO = |
* Reads strings from a file asynchronously, calls listener.process() with |
* each line read and with a null parameter once the read operation is done. |
* The callback will be called when the operation is done. |
*/ |
readFromFile: function(/**nsIFile|nsIURI*/ file, /**Boolean*/ decode, /**Object*/ listener, /**Function*/ callback, /**String*/ timeLineID) |
{ |
try |
{ |
+ let processing = false; |
let buffer = ""; |
+ let loadEvent = null; |
+ let errorEvent = null; |
let uri = file instanceof Ci.nsIFile ? Services.io.newFileURI(file) : file; |
let request = new XMLHttpRequest(); |
request.mozBackgroundRequest = true; |
request.open("GET", uri.spec); |
request.responseType = "moz-chunked-text"; |
request.overrideMimeType("text/plain" + (decode ? "? charset=utf-8" : "")); |
- request.addEventListener("progress", function(event) |
+ let onProgress = function(event) |
{ |
if (timeLineID) |
{ |
TimeLine.asyncStart(timeLineID); |
} |
let data = event.target.response; |
- let index = Math.max(data.lastIndexOf("\n"), data.lastIndexOf("\r")); |
+ let index = (processing ? -1 : Math.max(data.lastIndexOf("\n"), data.lastIndexOf("\r"))); |
Felix Dahlke
2014/03/14 16:10:45
This doesn't really seem reentrant to me. What if
Wladimir Palant
2014/03/14 18:04:51
JavaScript is a single-threaded language, reentran
Felix Dahlke
2014/03/14 21:32:49
Ah, thought this was actually proper multithreadin
|
if (index >= 0) |
{ |
- let oldBuffer = buffer; |
- buffer = data.substr(index + 1); |
- data = data.substr(0, index + 1); |
- let lines = data.split(/[\r\n]+/); |
- lines.pop(); |
- lines[0] = oldBuffer + lines[0]; |
- for (let i = 0; i < lines.length; i++) |
- listener.process(lines[i]); |
+ // Protect against reentrance in case the listener processes events. |
Felix Dahlke
2014/03/14 16:10:45
"processes multiple events" I suppose?
Wladimir Palant
2014/03/14 18:04:51
No, "processes events" = "spins the event loop"
|
+ processing = true; |
+ try |
+ { |
+ let oldBuffer = buffer; |
+ buffer = data.substr(index + 1); |
Felix Dahlke
2014/03/14 16:10:45
Is reference assignment atomic in Gecko? Otherwise
Wladimir Palant
2014/03/14 18:04:51
As I said, the only place where we might get reent
|
+ data = data.substr(0, index + 1); |
+ let lines = data.split(/[\r\n]+/); |
+ lines.pop(); |
+ lines[0] = oldBuffer + lines[0]; |
+ for (let i = 0; i < lines.length; i++) |
+ listener.process(lines[i]); |
+ } |
+ finally |
+ { |
+ processing = false; |
+ let e = { |
+ target: {response: buffer} |
+ }; |
+ buffer = ""; |
+ onProgress(e); |
+ |
+ if (loadEvent) |
+ { |
+ loadEvent = null; |
+ onLoad(loadEvent); |
Felix Dahlke
2014/03/14 16:10:45
onLoad(null) doesn't seem to be what's desired her
Wladimir Palant
2014/03/14 18:04:51
True, we need to save the original event before nu
|
+ } |
+ |
+ if (errorEvent) |
+ { |
+ errorEvent = null; |
+ onError(errorEvent); |
+ } |
+ } |
} |
else |
buffer += data; |
if (timeLineID) |
{ |
TimeLine.asyncEnd(timeLineID); |
} |
- }, false); |
+ }; |
- request.addEventListener("load", function(event) |
+ let onLoad = function(event) |
{ |
+ if (processing) |
+ { |
+ // Still processing data, delay processing this event. |
+ loadEvent = event; |
+ return; |
+ } |
+ |
if (timeLineID) |
{ |
TimeLine.asyncStart(timeLineID); |
} |
if (buffer !== "") |
listener.process(buffer); |
listener.process(null); |
if (timeLineID) |
{ |
TimeLine.asyncEnd(timeLineID); |
TimeLine.asyncDone(timeLineID); |
} |
callback(null); |
- }, false); |
+ }; |
- request.addEventListener("error", function() |
+ let onError = function(event) |
{ |
+ if (processing) |
+ { |
+ // Still processing data, delay processing this event. |
+ errorEvent = event; |
+ return; |
+ } |
+ |
let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException); |
e.initialize("File read operation failed", result, null, Components.stack, file, null); |
callback(e); |
if (timeLineID) |
{ |
TimeLine.asyncDone(timeLineID); |
} |
- }, false); |
+ }; |
+ |
+ request.addEventListener("progress", onProgress, false); |
+ request.addEventListener("load", onLoad, false); |
+ request.addEventListener("error", onError, false); |
request.send(null); |
} |
catch (e) |
{ |
callback(e); |
} |
}, |