init
This commit is contained in:
138
node_modules/matrix-js-sdk/src/matrixrtc/MembershipManagerActionScheduler.ts
generated
vendored
Normal file
138
node_modules/matrix-js-sdk/src/matrixrtc/MembershipManagerActionScheduler.ts
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
import { type Logger, logger as rootLogger } from "../logger.ts";
|
||||
import { type EmptyObject } from "../matrix.ts";
|
||||
import { sleep } from "../utils.ts";
|
||||
import { MembershipActionType } from "./MembershipManager.ts";
|
||||
|
||||
/** @internal */
|
||||
export interface Action {
|
||||
/**
|
||||
* When this action should be executed
|
||||
*/
|
||||
ts: number;
|
||||
/**
|
||||
* The state of the different loops
|
||||
* can also be thought of as the type of the action
|
||||
*/
|
||||
type: MembershipActionType;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type ActionUpdate =
|
||||
| {
|
||||
/** Replace all existing scheduled actions with this new array */
|
||||
replace: Action[];
|
||||
}
|
||||
| {
|
||||
/** Add these actions to the existing scheduled actions */
|
||||
insert: Action[];
|
||||
}
|
||||
| EmptyObject;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
private logger: Logger;
|
||||
/**
|
||||
* This is tracking the state of the scheduler loop.
|
||||
* Only used to prevent starting the loop twice.
|
||||
*/
|
||||
public running = false;
|
||||
|
||||
public constructor(
|
||||
/** This is the callback called for each scheduled action (`this.addAction()`) */
|
||||
private membershipLoopHandler: (type: MembershipActionType) => Promise<ActionUpdate>,
|
||||
parentLogger?: Logger,
|
||||
) {
|
||||
this.logger = (parentLogger ?? rootLogger).getChild(`[NewMembershipActionScheduler]`);
|
||||
}
|
||||
|
||||
// function for the wakeup mechanism (in case we add an action externally and need to leave the current sleep)
|
||||
private wakeup: (update: ActionUpdate) => void = (update: ActionUpdate): void => {
|
||||
this.logger.error("Cannot call wakeup before calling `startWithJoin()`");
|
||||
};
|
||||
private _actions: Action[] = [];
|
||||
public get actions(): Action[] {
|
||||
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.
|
||||
*/
|
||||
public async startWithJoin(): Promise<void> {
|
||||
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: ActionUpdate | undefined = undefined;
|
||||
|
||||
// while we await for the next action, wakeup has to resolve the wakeupPromise
|
||||
const wakeupPromise = new Promise<void>((resolve) => {
|
||||
this.wakeup = (update: ActionUpdate): void => {
|
||||
wakeupUpdate = update;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
if (nextAction.ts > Date.now()) await Promise.race([wakeupPromise, sleep(nextAction.ts - Date.now())]);
|
||||
|
||||
let handlerResult: ActionUpdate = {};
|
||||
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)");
|
||||
}
|
||||
|
||||
public initiateJoin(): void {
|
||||
this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendDelayedEvent }] });
|
||||
}
|
||||
public initiateLeave(): void {
|
||||
this.wakeup?.({ replace: [{ ts: Date.now(), type: MembershipActionType.SendScheduledDelayedLeaveEvent }] });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user