@otpjs/gen
Version:
The gen module for otpjs
219 lines (218 loc) • 8.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.registerName = exports.cast = exports.call = exports.Symbols = void 0;
exports.reply = reply;
exports.start = start;
exports.unregisterName = void 0;
var otp = _interopRequireWildcard(require("@otpjs/core"));
var match = _interopRequireWildcard(require("@otpjs/matching"));
var _types = require("@otpjs/types");
var proc_lib = _interopRequireWildcard(require("@otpjs/proc_lib"));
var Symbols = _interopRequireWildcard(require("./symbols"));
exports.Symbols = Symbols;
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const {
monitor,
link,
nolink,
$gen_call,
$gen_cast,
already_started
} = Symbols;
function log(ctx, ...args) {
return ctx.log.extend('gen')(...args);
}
const {
ok,
error,
nodedown,
DOWN
} = otp.Symbols;
const {
_
} = match.Symbols;
const DEFAULT_TIMEOUT = 5000;
const isLocalName = match.compile((0, _types.t)('local', _));
const isUndefined = match.compile(undefined);
const isPid = _types.Pid.isPid;
function where(ctx, name) {
if (isLocalName(name)) {
const localName = getName(name);
log(ctx, 'where(name: %o, localName: %o)', name, localName);
return ctx.whereis(localName);
} else {
log(ctx, 'where(name: %o, notLocal)', name);
return undefined;
}
}
async function start(ctx, linking, name, init_it, options = {}) {
const response = where(ctx, name);
switch (true) {
case isUndefined(response):
return doSpawn(ctx, linking, name, init_it, options);
case isPid(response):
default:
return (0, _types.t)(error, (0, _types.t)(already_started, response));
}
}
const doSpawn = match.clauses(function routeSpawn(route) {
route(link, _, _, _).to(doSpawnLink);
route(link, _, _, _, _).to(doSpawnLink);
route(monitor, _, _, _).to(doSpawnMonitor);
route(monitor, _, _, _, _).to(doSpawnMonitor);
route(nolink, _, _, _).to(doSpawnNoLink);
route(nolink, _, _, _, _).to(doSpawnNoLink);
route(_, _, _, _).to(doSpawnNoLink);
route(_, _, _, _, _).to(doSpawnNoLink);
async function doSpawnLink(ctx, linking, name, init_it, options) {
const timeout = 'timeout' in options ? options.timeout : Infinity;
log(ctx, 'doSpawn() : proc_lib.startLink()');
return await proc_lib.startLink(ctx, initializer(name, init_it, options), timeout);
}
/* istanbul ignore next */
function doSpawnMonitor(ctx, linking, name, init_it, options) {
log(ctx, 'doSpawn() : proc_lib.startMonitor()');
throw new _types.OTPError((0, _types.t)('not_yet_implemented', link));
}
function doSpawnNoLink(ctx, linking, name, init_it, options) {
const timeout = 'timeout' in options ? options.timeout : Infinity;
log(ctx, 'doSpawn() : proc_lib.start()');
return proc_lib.start(ctx, initializer(name, init_it, options), timeout);
}
});
function initializer(name, initIt, options) {
const decision = match.buildCase(is => {
is(true, success);
is((0, _types.t)(false, _types.Pid.isPid), alreadyStarted);
});
return async function initialize(ctx, starter) {
const registration = registerName(ctx, name);
const next = decision.for(registration);
return next(ctx, registration, initIt, starter);
};
function success(ctx, _result, initIt, starter) {
log(ctx, 'initialize() : initIt(%o)', starter);
return initIt(ctx, starter);
}
function alreadyStarted(ctx, [, pid], _initIt, starter) {
return proc_lib.initAck(ctx, starter, (0, _types.t)(error, (0, _types.t)(already_started, pid)));
}
}
const registerName = match.clauses(function routeRegisterName(route) {
route(isLocalName).to(registerLocalName);
route(_).to(() => true);
function registerLocalName(ctx, name) {
try {
ctx.register(getName(name));
return true;
} catch (err) {
const pid = where(ctx, name);
log(ctx, 'registerName(name: %o, error: %o, pid: %o)', name, err, pid);
return (0, _types.t)(false, pid);
}
}
});
exports.registerName = registerName;
const unregisterName = match.clauses(route => {
route(isLocalName).to(unregisterLocal);
route(_types.Pid.isPid).to(doNothing);
function unregisterLocal(ctx, [, name]) {
try {
ctx.unregister(name);
} finally {
return ok;
}
}
function doNothing() {
return ok;
}
});
exports.unregisterName = unregisterName;
const getName = match.clauses(function routeGetName(route) {
route((0, _types.t)('local', _)).to(([, name]) => name);
route(_types.Pid.isPid).to(pid => pid);
});
const callReplyPattern = ref => match.compile((0, _types.t)(ref, _));
const downPattern = (mref, pid) => match.compile((0, _types.t)(DOWN, mref, _, pid, _));
const call = match.clauses(function routeCall(route) {
route(_types.Pid.isPid, _).to(doCall);
route(_types.Pid.isPid, _, _).to(doCall);
route(_, _).to(doRemoteCall);
route(_, _, _).to(doRemoteCall);
});
exports.call = call;
function doRemoteCall(ctx, pid, message, timeout = DEFAULT_TIMEOUT) {
const fun = pid => doCall(ctx, pid, message, timeout);
log(ctx, 'call(%o) : isNotPid', pid);
return doForProcess(ctx, pid, fun);
}
async function doCall(ctx, pid, message, timeout = DEFAULT_TIMEOUT) {
const self = ctx.self();
const ref = ctx.ref();
const mref = ctx.monitor(pid);
const isReply = callReplyPattern(ref);
const isDown = downPattern(mref, pid);
ctx.send(pid, (0, _types.t)($gen_call, (0, _types.t)(self, ref), message));
log(ctx, 'doCall(%o, %o) : receive(%o, %o)', pid, ref, isReply, isDown);
return ctx.receiveBlock((given, after) => {
given(isReply).then(([ref, response]) => {
ctx.demonitor(mref);
log(ctx, 'doCall(%o, %o) : response : %o', pid, ref, response);
return response;
});
given(isDown).then(([, ref,, pid, reason]) => {
log(ctx, 'doCall(%o, %o) : throw OTPError(%o)', pid, ref, reason);
throw new _types.OTPError(reason);
});
after(timeout).then(() => {
throw (0, _types.OTPError)(otp.Symbols.timeout);
});
});
}
const cast = match.clauses(function routeCast(route) {
route(_types.Pid.isPid, _).to(doCast);
route(_, _).to(doRemoteCast);
function doCast(ctx, pid, message) {
return ctx.send(pid, (0, _types.t)($gen_cast, message));
}
function doRemoteCast(ctx, pid, message) {
const fun = pid => doCast(ctx, pid, message);
return doForProcess(ctx, pid, fun);
}
});
exports.cast = cast;
const isKeyedSymbol = v => typeof v === 'symbol' && Symbol.keyFor(v) !== undefined;
function doForProcess(ctx, process, fun) {
// TODO: look up process (which is not a Pid)
// As of the time of this comment, core/node handles routing remote messages
const compare = match.caseOf(process);
if (compare(isKeyedSymbol)) {
const result = ctx.whereis(process);
log(ctx, 'doForProcess(%o) : found : %o', process, result);
if (result === undefined) {
throw (0, _types.OTPError)('noproc');
} else {
log(ctx, 'fun(%o)', result);
return fun(result);
}
} else if (compare((0, _types.t)(_, _))) {
const [name, node] = process;
if (ctx.nodes().includes(node)) {
return fun(process);
} else {
throw (0, _types.OTPError)((0, _types.t)(nodedown, node));
}
} else {
const error = new _types.OTPError('not_implemented');
log(ctx, 'doForProcess(%o) : not_found', process);
log(ctx, 'doForProcess(%o) : error : %o', error);
throw error;
}
}
function reply(ctx, [pid, ref], reply) {
log(ctx, 'ctx.send(%o, %o)', pid, (0, _types.t)(ref, reply));
ctx.send(pid, (0, _types.t)(ref, reply));
}