119 lines
4.6 KiB
JavaScript
119 lines
4.6 KiB
JavaScript
|
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||
|
|
import { logger as rootLogger } from "../logger.js";
|
||
|
|
import { sleep } from "../utils.js";
|
||
|
|
import { MembershipActionType } from "./MembershipManager.js";
|
||
|
|
|
||
|
|
/** @internal */
|
||
|
|
|
||
|
|
/** @internal */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* This scheduler tracks the state of the current membership participation
|
||
|
|
* and runs one central timer that wakes up a handler callback with the correct action + state
|
||
|
|
* whenever necessary.
|
||
|
|
*
|
||
|
|
* It can also be awakened whenever a new action is added which is
|
||
|
|
* earlier then the current "next awake".
|
||
|
|
* @internal
|
||
|
|
*/
|
||
|
|
export class ActionScheduler {
|
||
|
|
constructor(/** This is the callback called for each scheduled action (`this.addAction()`) */
|
||
|
|
membershipLoopHandler, parentLogger) {
|
||
|
|
_defineProperty(this, "logger", void 0);
|
||
|
|
/**
|
||
|
|
* This is tracking the state of the scheduler loop.
|
||
|
|
* Only used to prevent starting the loop twice.
|
||
|
|
*/
|
||
|
|
_defineProperty(this, "running", false);
|
||
|
|
// function for the wakeup mechanism (in case we add an action externally and need to leave the current sleep)
|
||
|
|
_defineProperty(this, "wakeup", update => {
|
||
|
|
this.logger.error("Cannot call wakeup before calling `startWithJoin()`");
|
||
|
|
});
|
||
|
|
_defineProperty(this, "_actions", []);
|
||
|
|
this.membershipLoopHandler = membershipLoopHandler;
|
||
|
|
this.logger = (parentLogger ?? rootLogger).getChild(`[NewMembershipActionScheduler]`);
|
||
|
|
}
|
||
|
|
get actions() {
|
||
|
|
return this._actions;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* This starts the main loop of the membership manager that handles event sending, delayed event sending and delayed event restarting.
|
||
|
|
* @param initialActions The initial actions the manager will start with. It should be enough to pass: DelayedLeaveActionType.Initial
|
||
|
|
* @returns Promise that resolves once all actions have run and no more are scheduled.
|
||
|
|
* @throws This throws an error if one of the actions throws.
|
||
|
|
* In most other error cases the manager will try to handle any server errors by itself.
|
||
|
|
*/
|
||
|
|
async startWithJoin() {
|
||
|
|
if (this.running) {
|
||
|
|
this.logger.error("Cannot call startWithJoin() on NewMembershipActionScheduler while already running");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.running = true;
|
||
|
|
this._actions = [{
|
||
|
|
ts: Date.now(),
|
||
|
|
type: MembershipActionType.SendDelayedEvent
|
||
|
|
}];
|
||
|
|
try {
|
||
|
|
while (this._actions.length > 0) {
|
||
|
|
// Sort so next (smallest ts) action is at the beginning
|
||
|
|
this._actions.sort((a, b) => a.ts - b.ts);
|
||
|
|
const nextAction = this._actions[0];
|
||
|
|
let wakeupUpdate = undefined;
|
||
|
|
|
||
|
|
// while we await for the next action, wakeup has to resolve the wakeupPromise
|
||
|
|
const wakeupPromise = new Promise(resolve => {
|
||
|
|
this.wakeup = update => {
|
||
|
|
wakeupUpdate = update;
|
||
|
|
resolve();
|
||
|
|
};
|
||
|
|
});
|
||
|
|
if (nextAction.ts > Date.now()) await Promise.race([wakeupPromise, sleep(nextAction.ts - Date.now())]);
|
||
|
|
let handlerResult = {};
|
||
|
|
if (!wakeupUpdate) {
|
||
|
|
this.logger.debug(`Current MembershipManager processing: ${nextAction.type}\nQueue:`, this._actions, `\nDate.now: "${Date.now()}`);
|
||
|
|
try {
|
||
|
|
// `this.wakeup` can also be called and sets the `wakeupUpdate` object while we are in the handler.
|
||
|
|
handlerResult = await this.membershipLoopHandler(nextAction.type);
|
||
|
|
} catch (e) {
|
||
|
|
// Preserve the original error as `cause`.
|
||
|
|
throw new Error(`The MembershipManager shut down because of the end condition: ${e}`, {
|
||
|
|
cause: e
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// remove the processed action only after we are done processing
|
||
|
|
this._actions.splice(0, 1);
|
||
|
|
// The wakeupUpdate always wins since that is a direct external update.
|
||
|
|
const actionUpdate = wakeupUpdate ?? handlerResult;
|
||
|
|
if ("replace" in actionUpdate) {
|
||
|
|
this._actions = actionUpdate.replace;
|
||
|
|
} else if ("insert" in actionUpdate) {
|
||
|
|
this._actions.push(...actionUpdate.insert);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
// Set the rtc session running state since we cannot recover from here and the consumer user of the
|
||
|
|
// MatrixRTCSession class needs to manually rejoin.
|
||
|
|
this.running = false;
|
||
|
|
}
|
||
|
|
this.logger.debug("Leave MembershipManager ActionScheduler loop (no more actions)");
|
||
|
|
}
|
||
|
|
initiateJoin() {
|
||
|
|
this.wakeup?.({
|
||
|
|
replace: [{
|
||
|
|
ts: Date.now(),
|
||
|
|
type: MembershipActionType.SendDelayedEvent
|
||
|
|
}]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
initiateLeave() {
|
||
|
|
this.wakeup?.({
|
||
|
|
replace: [{
|
||
|
|
ts: Date.now(),
|
||
|
|
type: MembershipActionType.SendScheduledDelayedLeaveEvent
|
||
|
|
}]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=MembershipManagerActionScheduler.js.map
|