mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
fix(server-storage): a slow server does not reload a page started from memory forever
The start probe gives /v1/instance 2.5 s. A remembered server slower than that started the page from memory; the recovery check (with the request timeout) then got an answer and reloaded, and the reloaded page timed out again: an endless reload loop for a server answering in 2.5 to 20 s. The reload now leaves a one-time mark in sessionStorage, and the next probe waits as long as requests do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a2a19b5e7e
commit
ad465a8fbe
7 files changed
+67
-3
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { markSlowStart } from '@iptvnator/services';
|
||||
import { probeServerInstance } from './server-instance-probe';
|
||||
|
||||
const instance = {
|
||||
@@ -55,6 +56,28 @@ describe('probeServerInstance', () => {
|
||||
expect(fetchFn.mock.calls[0][0]).toBe('/api/v1/instance');
|
||||
});
|
||||
|
||||
it('waits longer once after a reload for a server that answered late', async () => {
|
||||
const timeout = jest.spyOn(AbortSignal, 'timeout');
|
||||
try {
|
||||
markSlowStart();
|
||||
await probeServerInstance({
|
||||
...config,
|
||||
fetchFn: answer(instance) as never,
|
||||
});
|
||||
await probeServerInstance({
|
||||
...config,
|
||||
fetchFn: answer(instance) as never,
|
||||
});
|
||||
|
||||
// A slow server otherwise times out again and reloads forever.
|
||||
expect(timeout.mock.calls.map(([ms]) => ms)).toEqual([
|
||||
20_000, 2_500,
|
||||
]);
|
||||
} finally {
|
||||
timeout.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('reports no server storage for an answer without it', async () => {
|
||||
for (const fetchFn of [
|
||||
answer({ ...instance, storage: 'none' }),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { RuntimeCapabilitiesService } from '@iptvnator/services';
|
||||
import { RuntimeCapabilitiesService, takeSlowStart } from '@iptvnator/services';
|
||||
import type { ServerInstanceInfo } from '@iptvnator/shared/interfaces';
|
||||
import type { IptvnatorRuntimeConfig } from '../services/runtime-config';
|
||||
|
||||
const PROBE_TIMEOUT_MS = 2_500;
|
||||
/** After a reload for a server that answered later: as long as requests. */
|
||||
const SLOW_PROBE_TIMEOUT_MS = 20_000;
|
||||
|
||||
/**
|
||||
* - `skipped`: no runtime backend URL, or Electron.
|
||||
@@ -47,7 +49,11 @@ export async function probeServerInstance(
|
||||
cache: 'no-store',
|
||||
signal:
|
||||
typeof AbortSignal.timeout === 'function'
|
||||
? AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
||||
? AbortSignal.timeout(
|
||||
takeSlowStart()
|
||||
? SLOW_PROBE_TIMEOUT_MS
|
||||
: PROBE_TIMEOUT_MS
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
} catch {
|
||||
|
||||
@@ -347,7 +347,9 @@ settings and guards read them in every build.
|
||||
database (backend URL or instance id) than the one it remembers; sign-in
|
||||
therefore remembers the server along with the token, or reports storage
|
||||
trouble. A page started from memory first checks that the instance at the
|
||||
address is the remembered one (`ensureVerified`), and reloads if not,
|
||||
address is the remembered one (`ensureVerified`), and reloads if not
|
||||
(a reload after a server answered late gives the next start probe as
|
||||
long as requests take, once, so a slow server does not reload forever),
|
||||
before sign-in sends the token. So does a page that went offline (any
|
||||
backend call can tell: the storage API with its page-hide `keepalive`
|
||||
writes, `HttpClient` calls through the interceptor and `PwaService`'s raw
|
||||
|
||||
@@ -35,3 +35,4 @@ export * from './lib/translation-tick';
|
||||
export * from './lib/user-data-storage';
|
||||
export * from './lib/server-storage-status.service';
|
||||
export * from './lib/server-access-token';
|
||||
export * from './lib/server-slow-start';
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* A page started from memory reloads once the server answers
|
||||
* (`ServerStorageSession`), but a server slower than the start probe would
|
||||
* time it out again and reload forever: the reloaded page's probe waits as
|
||||
* long as requests do, once.
|
||||
*/
|
||||
const SLOW_START_KEY = 'iptvnator-server:slow-start';
|
||||
|
||||
export function markSlowStart(): void {
|
||||
try {
|
||||
sessionStorage.setItem(SLOW_START_KEY, '1');
|
||||
} catch {
|
||||
// Without session storage the next probe keeps its short wait.
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether this page load follows such a reload; asks only once. */
|
||||
export function takeSlowStart(): boolean {
|
||||
try {
|
||||
const slow = sessionStorage.getItem(SLOW_START_KEY) !== null;
|
||||
sessionStorage.removeItem(SLOW_START_KEY);
|
||||
return slow;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { PlaylistStorageBackend } from '../playlist-storage-backend';
|
||||
import { RuntimeCapabilitiesService } from '../runtime-capabilities.service';
|
||||
import { writeServerAccessToken } from '../server-access-token';
|
||||
import { takeSlowStart } from '../server-slow-start';
|
||||
import { ServerStorageStatusService } from '../server-storage-status.service';
|
||||
import { SERVER_STORAGE_CONFIG } from './server-storage.config';
|
||||
import { ServerStorageSession } from './server-storage-session';
|
||||
@@ -370,6 +371,8 @@ describe('ServerStorageSession', () => {
|
||||
await session.refresh();
|
||||
|
||||
expect(reload).toHaveBeenCalledTimes(1);
|
||||
// The reloaded page's probe waits as long as this request did.
|
||||
expect(takeSlowStart()).toBe(true);
|
||||
});
|
||||
|
||||
it('sends no write before the reload checks the instance', async () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
createBrowserSessionStorage,
|
||||
createBrowserUserDataStorage,
|
||||
} from '../user-data-storage';
|
||||
import { markSlowStart } from '../server-slow-start';
|
||||
import { ServerApiClient, type ServerApiError } from './server-api-client';
|
||||
import { ServerPlaylistDocumentStore } from './server-playlist-document-store';
|
||||
import { SERVER_STORAGE_CONFIG } from './server-storage.config';
|
||||
@@ -150,6 +151,8 @@ export class ServerStorageSession {
|
||||
}
|
||||
if (!this.reloading) {
|
||||
this.reloading = true;
|
||||
// It answers, maybe slower than the start probe waits.
|
||||
markSlowStart();
|
||||
this.reloadPage();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user