mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
dev: hooks and sequencing functions for ServiceManager
This commit is contained in:
parent
2f6f2b7d7c
commit
bc5d09fe31
@ -1,4 +1,5 @@
|
||||
const { AdvancedBase } = require("../AdvancedBase");
|
||||
const ServiceFeature = require("../features/ServiceFeature");
|
||||
|
||||
const NOOP = async () => {};
|
||||
|
||||
@ -11,6 +12,10 @@ const TService = Symbol('TService');
|
||||
* becoming the common base for both and a useful utility in general.
|
||||
*/
|
||||
class Service extends AdvancedBase {
|
||||
static FEATURES = [
|
||||
ServiceFeature,
|
||||
];
|
||||
|
||||
async __on (id, args) {
|
||||
const handler = this.__get_event_handler(id);
|
||||
|
||||
@ -48,7 +53,10 @@ class Service extends AdvancedBase {
|
||||
return this._construct(o);
|
||||
},
|
||||
get_depends () {
|
||||
return this.get_depends?.() ?? [];
|
||||
return [
|
||||
...(this.constructor.DEPENDS ?? []),
|
||||
...(this.get_depends?.() ?? []),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,19 +36,17 @@ module.exports = {
|
||||
};
|
||||
instance._.properties[k] = state;
|
||||
|
||||
let spec = null;
|
||||
if ( typeof properties[k] === 'object' ) {
|
||||
// This will be supported in the future.
|
||||
throw new Error(`Property ${k} in ${instance.constructor.name} ` +
|
||||
`is not a supported property specification.`);
|
||||
spec = properties[k];
|
||||
} else if ( typeof properties[k] === 'function' ) {
|
||||
spec = {};
|
||||
spec.value = properties[k]();
|
||||
}
|
||||
|
||||
let value = (() => {
|
||||
if ( typeof properties[k] === 'function' ) {
|
||||
return properties[k]();
|
||||
}
|
||||
|
||||
return properties[k];
|
||||
})();
|
||||
if ( spec === null ) {
|
||||
throw new Error('this will never happen');
|
||||
}
|
||||
|
||||
Object.defineProperty(instance, k, {
|
||||
get: () => {
|
||||
@ -60,11 +58,17 @@ module.exports = {
|
||||
old_value: instance[k],
|
||||
});
|
||||
}
|
||||
const old_value = instance[k];
|
||||
state.value = value;
|
||||
if ( spec.post_set ) {
|
||||
spec.post_set.call(instance, value, {
|
||||
old_value,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
state.value = value;
|
||||
state.value = spec.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
src/putility/src/features/ServiceFeature.js
Normal file
28
src/putility/src/features/ServiceFeature.js
Normal file
@ -0,0 +1,28 @@
|
||||
const { TTopics } = require("../traits/traits");
|
||||
|
||||
module.exports = {
|
||||
install_in_instance: (instance, { parameters }) => {
|
||||
// Convenient definition of listeners between services,
|
||||
// which also makes these connections able to be understood as data
|
||||
// without processing any code.
|
||||
const hooks = instance._get_merged_static_array('HOOKS');
|
||||
instance._.init_hooks = instance._.init_hooks ?? [];
|
||||
|
||||
for ( const spec of hooks ) {
|
||||
|
||||
// We need to wait for the service to be initialized, because
|
||||
// that's when the dependency services have already been
|
||||
// initialized and are ready to accept listeners.
|
||||
instance._.init_hooks.push(() => {
|
||||
const service_entry =
|
||||
instance._.context.services.info(spec.service);
|
||||
const service_instance = service_entry.instance;
|
||||
|
||||
service_instance.as(TTopics).sub(
|
||||
spec.event,
|
||||
spec.do.bind(instance),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
const { AdvancedBase } = require("../AdvancedBase");
|
||||
const { TService } = require("../concepts/Service");
|
||||
const { TeePromise } = require("../libs/promise");
|
||||
|
||||
const mkstatus = name => {
|
||||
const c = class {
|
||||
@ -34,21 +35,26 @@ class ServiceManager extends AdvancedBase {
|
||||
return `running (since ${this.start_ts})`;
|
||||
}
|
||||
}
|
||||
constructor () {
|
||||
constructor ({ context }) {
|
||||
super();
|
||||
|
||||
this.context = context;
|
||||
|
||||
this.services_l_ = [];
|
||||
this.services_m_ = {};
|
||||
this.service_infos_ = {};
|
||||
|
||||
this.init_listeners_ = {};
|
||||
this.init_listeners_ = [];
|
||||
// services which are waiting for dependency servicces to be
|
||||
// initialized; mapped like: waiting_[dependency] = Set(dependents)
|
||||
this.waiting_ = {};
|
||||
}
|
||||
async register (name, factory, options = {}) {
|
||||
await new Promise(rslv => setTimeout(rslv, 0));
|
||||
|
||||
const ins = factory.create({
|
||||
parameters: options.parameters ?? {},
|
||||
context: this.context,
|
||||
});
|
||||
const entry = {
|
||||
name,
|
||||
@ -63,10 +69,51 @@ class ServiceManager extends AdvancedBase {
|
||||
info (name) {
|
||||
return this.services_m_[name];
|
||||
}
|
||||
get (name) {
|
||||
const info = this.services_m_[name];
|
||||
if ( ! info ) throw new Error(`Service not registered: ${name}`);
|
||||
if ( ! (info.status instanceof this.constructor.StatusRunning ) ) {
|
||||
return undefined;
|
||||
}
|
||||
return info.instance;
|
||||
}
|
||||
|
||||
async maybe_init_ (name) {
|
||||
const entry = this.services_m_[name];
|
||||
const depends = entry.instance.as(TService).get_depends();
|
||||
/**
|
||||
* Wait for the specified list of services to be initialized.
|
||||
* @param {*} depends - list of services to wait for
|
||||
*/
|
||||
async wait_for_init (depends) {
|
||||
let check;
|
||||
|
||||
await new Promise(rslv => {
|
||||
check = () => {
|
||||
// Get the list of required services that are not
|
||||
// yet initialized
|
||||
const waiting_for = this.get_waiting_for_(depends);
|
||||
|
||||
console.log('CHECK --- ', waiting_for, new Error());
|
||||
|
||||
// If there's nothing to wait for, remove the listener
|
||||
// on service initializations and resolve
|
||||
if ( waiting_for.length === 0 ) {
|
||||
const i = this.init_listeners_.indexOf(check);
|
||||
if ( i !== -1 ) {
|
||||
this.init_listeners_.splice(i, 1);
|
||||
}
|
||||
rslv();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Services might already be registered
|
||||
if ( check() ) return;
|
||||
|
||||
this.init_listeners_.push(check);
|
||||
});
|
||||
};
|
||||
|
||||
get_waiting_for_ (depends) {
|
||||
const waiting_for = [];
|
||||
for ( const depend of depends ) {
|
||||
const depend_entry = this.services_m_[depend];
|
||||
@ -78,6 +125,13 @@ class ServiceManager extends AdvancedBase {
|
||||
waiting_for.push(depend);
|
||||
}
|
||||
}
|
||||
return waiting_for;
|
||||
}
|
||||
|
||||
async maybe_init_ (name) {
|
||||
const entry = this.services_m_[name];
|
||||
const depends = entry.instance.as(TService).get_depends();
|
||||
const waiting_for = this.get_waiting_for_(depends);
|
||||
|
||||
if ( waiting_for.length === 0 ) {
|
||||
await this.init_service_(name);
|
||||
@ -97,7 +151,7 @@ class ServiceManager extends AdvancedBase {
|
||||
|
||||
// called when a service has all of its dependencies initialized
|
||||
// and is ready to be initialized itself
|
||||
async init_service_ (name) {
|
||||
async init_service_ (name, modifiers = {}) {
|
||||
const entry = this.services_m_[name];
|
||||
entry.status = new this.constructor.StatusInitializing();
|
||||
|
||||
@ -111,10 +165,18 @@ class ServiceManager extends AdvancedBase {
|
||||
const promises = [];
|
||||
if ( maybe_ready_set ) {
|
||||
for ( const dependent of maybe_ready_set.values() ) {
|
||||
promises.push(this.maybe_init_(dependent));
|
||||
promises.push(this.maybe_init_(dependent, {
|
||||
no_init_listeners: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
if ( ! modifiers.no_init_listeners ) {
|
||||
for ( const lis of this.init_listeners_ ) {
|
||||
await lis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user