@sha1n/fungus
Version:
A dependency based service graph controller library
96 lines (95 loc) • 3.98 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceController = void 0;
const assert = require("assert");
const EventEmitter = require("events");
const logger_1 = require("./logger");
const logger = (0, logger_1.createLogger)('srv-ctrl');
class ServiceController extends EventEmitter {
constructor(service) {
super();
this.service = service;
this.startPendingDependencies = new Set();
this.meta = undefined;
this.start = (ctx) => __awaiter(this, void 0, void 0, function* () {
if (this.isStarted()) {
return;
}
return this.startPromise || (this.startPromise = this.doStart(ctx));
});
this.stop = (ctx) => __awaiter(this, void 0, void 0, function* () {
logger.debug('%s: going to shutdown...', this.id);
if (!this.isStarted() && !this.startPromise) {
return;
}
try {
if (this.startPromise) {
logger.debug('%s: waiting for startup to finish...', this.id);
yield this.startPromise;
}
logger.debug('%s: stopping...', this.id);
yield this.service.stop(ctx);
this.emit('stopped', this.service.id, ctx);
this.meta = undefined;
}
catch (e) {
this.emit('error', e);
throw e;
}
finally {
this.meta = undefined;
}
});
}
get id() {
return this.service.id;
}
addDependency(dependency) {
this.startPendingDependencies.add(dependency.id);
dependency.once('started', (metadata, ctx) => {
// An event emitter should trigger a promise rejection up the stack
this.onDependencyStarted(metadata, ctx).catch(logger.error);
});
}
doStart(ctx) {
return __awaiter(this, void 0, void 0, function* () {
try {
this.meta = yield this.service.start(ctx);
ctx.register(this.meta);
this.emit('started', this.meta, ctx);
}
catch (e) {
const hasListeners = this.emit('error', e);
assert(hasListeners, 'A service controller is expected to have a listener at this point');
throw e;
}
finally {
this.startPromise = undefined;
}
});
}
onDependencyStarted(metadata, ctx) {
return __awaiter(this, void 0, void 0, function* () {
logger.debug('%s: dependency started -> %s', this.id, metadata.id);
this.startPendingDependencies.delete(metadata.id);
assert(!this.isStarted() && !this.startPromise, `Unexpected internal state. starting=${this.startPromise !== undefined}, started=${this.isStarted()}`);
if (this.startPendingDependencies.size === 0 && !ctx.shuttingDown) {
logger.debug('%s: all dependencies are started', this.id);
yield this.start(ctx);
}
});
}
isStarted() {
return this.meta !== undefined;
}
}
exports.ServiceController = ServiceController;