


























@@ -104,6 +104,26 @@ function createDeferred(): { promise: Promise<void>; resolve: () => void } {
104104return { promise, resolve: resolvePromise };
105105}
106106107+async function flushMicrotasks(times = 8): Promise<void> {
108+for (let i = 0; i < times; i += 1) {
109+await Promise.resolve();
110+}
111+}
112+113+async function waitForMicrotaskCondition(
114+check: () => boolean,
115+message: string,
116+attempts = 100,
117+): Promise<void> {
118+for (let i = 0; i < attempts; i += 1) {
119+if (check()) {
120+return;
121+}
122+await Promise.resolve();
123+}
124+throw new Error(message);
125+}
126+107127function installTestRegistry(
108128 ...plugins: Array<
109129ChannelPlugin<TestAccount> | { plugin: ChannelPlugin<TestAccount>; origin: string }
@@ -618,6 +638,106 @@ describe("server-channels auto restart", () => {
618638);
619639});
620640641+it("limits whole-channel account startup fanout to four", async () => {
642+const accountIds = ["one", "two", "three", "four", "five", "six"];
643+const releases: Array<() => void> = [];
644+let active = 0;
645+let maxActive = 0;
646+const isConfigured = vi.fn(async () => {
647+active += 1;
648+maxActive = Math.max(maxActive, active);
649+await new Promise<void>((resolve) => {
650+releases.push(resolve);
651+});
652+active -= 1;
653+return true;
654+});
655+const startAccount = vi.fn(
656+async ({ abortSignal }: { abortSignal: AbortSignal }) =>
657+await new Promise<void>((resolve) => {
658+abortSignal.addEventListener("abort", () => resolve(), { once: true });
659+}),
660+);
661+installTestRegistry(
662+createTestPlugin({
663+listAccountIds: () => accountIds,
664+ isConfigured,
665+ startAccount,
666+}),
667+);
668+const manager = createManager();
669+670+const start = manager.startChannel("discord");
671+await flushMicrotasks();
672+673+expect(isConfigured).toHaveBeenCalledTimes(4);
674+expect(maxActive).toBe(4);
675+expect(startAccount).not.toHaveBeenCalled();
676+677+releases.splice(0, 4).forEach((release) => release());
678+await waitForMicrotaskCondition(
679+() => isConfigured.mock.calls.length === 6,
680+"expected second account startup wave",
681+);
682+683+expect(isConfigured).toHaveBeenCalledTimes(6);
684+expect(maxActive).toBe(4);
685+686+releases.splice(0).forEach((release) => release());
687+await start;
688+expect(startAccount).toHaveBeenCalledTimes(6);
689+690+await manager.stopChannel("discord");
691+});
692+693+it("limits channel plugin startup fanout to four", async () => {
694+const channelIds = Array.from({ length: 6 }, (_, index) => `test-${index}` as ChannelId);
695+const releases: Array<() => void> = [];
696+let active = 0;
697+let maxActive = 0;
698+const plugins = channelIds.map((id, index) =>
699+createTestPlugin({
700+ id,
701+order: index,
702+isConfigured: async () => {
703+active += 1;
704+maxActive = Math.max(maxActive, active);
705+await new Promise<void>((resolve) => {
706+releases.push(resolve);
707+});
708+active -= 1;
709+return true;
710+},
711+startAccount: async ({ abortSignal }) =>
712+await new Promise<void>((resolve) => {
713+abortSignal.addEventListener("abort", () => resolve(), { once: true });
714+}),
715+}),
716+);
717+installTestRegistry(...plugins);
718+const manager = createManager({ channelIds });
719+720+const start = manager.startChannels();
721+await flushMicrotasks();
722+723+expect(releases).toHaveLength(4);
724+expect(maxActive).toBe(4);
725+726+releases.splice(0, 4).forEach((release) => release());
727+await waitForMicrotaskCondition(
728+() => releases.length === 2,
729+"expected second channel startup wave",
730+);
731+732+expect(releases).toHaveLength(2);
733+expect(maxActive).toBe(4);
734+735+releases.splice(0).forEach((release) => release());
736+await start;
737+738+await Promise.all(channelIds.map((id) => manager.stopChannel(id)));
739+});
740+621741it("evicts stale account lifecycle state during whole-channel reload", async () => {
622742let accountIds = [DEFAULT_ACCOUNT_ID];
623743const startAccount = vi.fn(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。