webworker
Version:
An implementation of the HTML5 Web Worker API
186 lines (152 loc) • 5.42 kB
JavaScript
// Utilies and other common gook shared between the WebWorker master and
// its constituent Workers.
var events = require('events');
var path = require('path');
var sys = require('sys');
var urllib = require('url');
// Some debugging functions
var debugLevel = parseInt(process.env.NODE_DEBUG, 16);
var debug = (debugLevel & 0x8) ?
function() { sys.error.apply(this, arguments); } :
function() {};
exports.debug = debug;
// Extract meaning from stack traces
var STACK_FRAME_RE = /.* \(?(.+:\d+:\d+)\)?$/;
// Symbolic names for our messages types
exports.MSGTYPE_NOOP = 0;
exports.MSGTYPE_ERROR = 1;
exports.MSGTYPE_CLOSE = 2;
exports.MSGTYPE_USER = 100;
// Is the given message well-formed?
var isValidMessage = function(msg) {
return (Array.isArray(msg) && msg.length == 2);
}
exports.isValidMessage = isValidMessage;
// A simple messaging stream.
//
// This class is constructed around an existing stream net.Stream. This class
// emits 'msg' events when a message is received. Each emitted 'msg' event
// may come with a second 'fd' parameter if the message was sent with file
// descriptor. A sent file descriptor is guaranteed to be received with the
// message with which it was sent.
//
// Sending messages is done with the send() method.
var MsgStream = function(s) {
var self = this;
events.EventEmitter.call(self);
// Sequence numbers for outgoing and incoming FDs
var fds_seqno_sent = 0;
var fds_seqno_recvd = 0;
// Collections of messages waiting for FDs and vice-versa. These
// are keyed by FD seqno.
var msg_waiting_for_fd = {};
var fd_waiting_for_msg = {};
// Get the JS object representing message 'v' with fd 'fd'.
var getMsgObj = function(v, fd) {
return [(fd != undefined) ? ++fds_seqno_sent : 0, v];
};
self.send = function(v, fd) {
var ms = getMsgObj(v, fd);
debug('Process ' + process.pid + ' sending message: ' + sys.inspect(ms));
s.write(JSON.stringify(ms), fd)
};
s.addListener('message', function(ms) {
debug('Process ' + process.pid + ' received message: ' + ms);
var mo = JSON.parse(ms);
// Ignore invalid messages; this is probably worth an error, though
if (!isValidMessage(mo)) {
return;
}
var fd = undefined;
var fd_seq = mo[0];
var msg = mo[1];
// If our message has an associated file descriptor that we
// have not yet received, queue it for later delivery.
if (fd_seq) {
if (!(fd = fd_waiting_for_msg[fd_seq])) {
msg_waiting_for_fd[fd_seq] = msg;
return;
}
delete fd_waiting_for_msg[fd_seq];
}
// We're complete; emit
self.emit('msg', msg, fd);
});
s.addListener('fd', function(fd) {
// Look for a message that's waiting for our arrival. If we don't
// have one, enqueu the received FD for later delivery.
var msg = msg_waiting_for_fd[++fds_seqno_recvd];
if (!msg) {
fd_waiting_for_msg[fds_seqno_recvd] = fd;
return;
}
// There was a message waiting for us; emit
delete msg_waiting_for_fd[fds_seqno_recvd];
self.emit('msg', msg, fd);
});
};
sys.inherits(MsgStream, events.EventEmitter);
exports.MsgStream = MsgStream;
// Implement the WorkerLocation interface described in
// http://www.whatwg.org/specs/web-workers/current-work/#dom-workerlocation-href
//
// XXX: None of these properties are readonly as required by the spec.
var WorkerLocation = function(url) {
var u = urllib.parse(url);
var portForProto = function(proto) {
switch (proto) {
case 'http':
return 80;
case 'https':
return 443;
case 'file':
return undefined;
default:
sys.debug(
'Unknown protocol \'' + proto + '\'; returning undefined'
);
return undefined;
};
};
this.href = u.href;
this.protocol = u.protocol.substring(0, u.protocol.length - 1);
this.host = u.host;
this.hostname = u.hostname;
this.port = (u.port) ? u.port : portForProto(this.protocol);
this.pathname = (u.pathname) ? path.normalize(u.pathname) : '/';
this.search = (u.search) ? u.search : '';
this.hash = (u.hash) ? u.hash : '';
};
exports.WorkerLocation = WorkerLocation;
// Get the error message for a given exception
//
// The first line of the stack trace seems to always be the message itself.
exports.getErrorMessage = function(e) {
try {
return e.message || e.stack.split('\n')[0].trim();
} catch (e) {
return 'WebWorkers: failed to get error message';
}
};
// Get the filename for a given exception
exports.getErrorFilename = function(e) {
try {
var m = e.stack.split('\n')[1].match(STACK_FRAME_RE);
return m[1].substring(
0,
m[1].lastIndexOf(':', m[1].lastIndexOf(':') - 1)
);
} catch (e) {
return 'WebWorkers: failed to get error filename';
}
};
// Get the line number for a given exception
exports.getErrorLine = function(e) {
try {
var m = e.stack.split('\n')[1].match(STACK_FRAME_RE);
var parts = m[1].split(':');
return parseInt(parts[parts.length - 2]);
} catch (e) {
return -1;
}
};