| 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); |
| } |
| }, |