comparison rlgwebd.js @ 165:59e62710cbb5

rlgwebd.js: prevent races when reading ttyrecs. DglSession objects read a 12-byte TTYREC header, extract therefrom the length of the data chunk, and then read the data. In between these two reads, the file watcher could trigger another readchunk() invocation, which might attempt to read a header from the beginning of the data chunk. This usually results in expecting a data chunk of several GB and failing to create a Buffer for it. The race is remedied by setting a flag on the DglSession object whenever readchunk() is called, clearing it when both reads complete, and refusing to read if it is already set.
author John "Elwin" Edwards
date Wed, 07 Jan 2015 13:18:35 -0500
parents 3a97e4ee50f0
children fba1b34e7554
comparison
equal deleted inserted replaced
164:3a97e4ee50f0 165:59e62710cbb5
203 var basename = pathcoms[pathcoms.length - 1]; 203 var basename = pathcoms[pathcoms.length - 1];
204 var firstsep = basename.indexOf(':'); 204 var firstsep = basename.indexOf(':');
205 this.pname = basename.slice(0, firstsep); 205 this.pname = basename.slice(0, firstsep);
206 var fname = basename.slice(firstsep + 1); 206 var fname = basename.slice(firstsep + 1);
207 this.ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname, fname); 207 this.ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname, fname);
208 /* Flag to prevent multiple handlers from reading simultaneously and
209 * getting into a race. */
210 this.reading = false;
208 this.framebuf = new Buffer(1024); 211 this.framebuf = new Buffer(1024);
209 this.frameoff = 0; 212 this.frameoff = 0;
210 this.framepush = function(chunk) { 213 this.framepush = function(chunk) {
211 /* If this chunk resets the screen, discard what preceded it. */ 214 /* If this chunk resets the screen, discard what preceded it. */
212 var cgame = games[this.gname]; 215 var cgame = games[this.gname];
227 } 230 }
228 chunk.copy(this.framebuf, this.frameoff); 231 chunk.copy(this.framebuf, this.frameoff);
229 this.frameoff += chunk.length; 232 this.frameoff += chunk.length;
230 }; 233 };
231 this.readchunk = function () { 234 this.readchunk = function () {
235 if (this.reading)
236 return;
237 this.reading = true;
232 var header = new Buffer(12); 238 var header = new Buffer(12);
233 fs.read(ss.fd, header, 0, 12, null, function (err, n, buf) { 239 fs.read(ss.fd, header, 0, 12, null, function (err, n, buf) {
234 /* Stop recursion if end of file has been reached. */ 240 /* Stop recursion if end of file has been reached. */
235 if (err || n < 12) 241 if (err || n < 12) {
242 ss.reading = false;
236 return; 243 return;
244 }
237 var datalen = buf.readUInt32LE(8); 245 var datalen = buf.readUInt32LE(8);
238 //tslog("Allocating %d bytes", datalen); 246 //tslog("Allocating %d bytes", datalen);
239 var databuf = new Buffer(datalen); 247 var databuf = new Buffer(datalen);
240 fs.read(ss.fd, databuf, 0, datalen, null, function (err, n, buf) { 248 fs.read(ss.fd, databuf, 0, datalen, null, function (err, n, buf) {
241 if (err || n < datalen) 249 ss.reading = false;
250 if (err || n < datalen) {
242 return; 251 return;
252 }
243 /* Process the data */ 253 /* Process the data */
244 ss.framepush(buf); 254 ss.framepush(buf);
245 ss.emit("data", buf); 255 ss.emit("data", buf);
246 tslog("DGL %s: %d bytes", ss.tag(), buf.length); 256 tslog("DGL %s: %d bytes", ss.tag(), buf.length);
247 /* Recurse. */ 257 /* Recurse. */
276 this.tag = function () { 286 this.tag = function () {
277 return this.gname + "/" + this.pname; 287 return this.gname + "/" + this.pname;
278 }; 288 };
279 this.close = function () { 289 this.close = function () {
280 this.watcher.close() 290 this.watcher.close()
291 /* Ensure all data is handled before quitting. */
292 this.readchunk();
281 fs.close(this.fd); 293 fs.close(this.fd);
282 this.emit("close"); 294 this.emit("close");
283 tslog("DGL %s: closed", ss.tag()); 295 tslog("DGL %s: closed", ss.tag());
284 }; 296 };
285 } 297 }