mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(epg): report retired worker exits as cancellations
This commit is contained in:
1 parent
a7bd81a848
commit
6d406c85e2
3 files changed
+68
-28
No files matched your search
@@ -182,6 +182,29 @@ export class EpgWorkerService {
|
||||
fn();
|
||||
};
|
||||
|
||||
const cancelIfRetired = (exited = false): boolean => {
|
||||
if (generation === epgSourceGeneration(url)) return false;
|
||||
if (this.workers.get(url) === worker) this.workers.delete(url);
|
||||
settle(() => {
|
||||
this.sendProgressToRenderer(
|
||||
url,
|
||||
'cancelled',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
generation
|
||||
);
|
||||
if (exited) resolve();
|
||||
else
|
||||
void this.terminateWorker(worker, 'retired fetch').then(
|
||||
() => resolve()
|
||||
);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
const scheduleFetchTimeout = () => {
|
||||
clearFetchTimeout();
|
||||
timeoutId = setTimeout(() => {
|
||||
@@ -207,6 +230,7 @@ export class EpgWorkerService {
|
||||
};
|
||||
|
||||
const handleFetchTimeout = () => {
|
||||
if (settled || cancelIfRetired()) return;
|
||||
const errorMessage = `EPG fetch timed out after ${
|
||||
this.fetchTimeoutMs / 1000
|
||||
}s without progress`;
|
||||
@@ -313,6 +337,7 @@ export class EpgWorkerService {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
if (settled || cancelIfRetired()) return;
|
||||
console.error(
|
||||
this.loggerLabel,
|
||||
'Error handling message:',
|
||||
@@ -335,6 +360,7 @@ export class EpgWorkerService {
|
||||
});
|
||||
|
||||
worker.on('error', (error) => {
|
||||
if (settled || cancelIfRetired()) return;
|
||||
console.error(this.loggerLabel, 'Worker error event:', error);
|
||||
this.sendProgressToRenderer(
|
||||
url,
|
||||
@@ -351,7 +377,7 @@ export class EpgWorkerService {
|
||||
});
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (settled) return;
|
||||
if (settled || cancelIfRetired(true)) return;
|
||||
const errorMessage = `Worker exited unexpectedly (code ${code})`;
|
||||
console.error(this.loggerLabel, `${errorMessage}: ${url}`);
|
||||
this.sendProgressToRenderer(
|
||||
|
||||
@@ -287,32 +287,45 @@ describe('EpgEvents', () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
it('does not revive a retired source from late worker READY or COMPLETE messages', async () => {
|
||||
const service = new EpgWorkerService('[Test EPG]', 1000);
|
||||
const url = 'https://removed.example/guide.xml';
|
||||
const fetch = service.fetchEpgFromUrl(url).catch(() => undefined);
|
||||
const worker = mockWorkerInstances[0];
|
||||
let finishTermination!: () => void;
|
||||
worker.terminate.mockReturnValue(
|
||||
new Promise<void>((resolve) => {
|
||||
finishTermination = resolve;
|
||||
})
|
||||
);
|
||||
const clear = service.clearEpgDataForSource(url);
|
||||
worker.emit('message', { type: 'READY' });
|
||||
worker.emit('message', { type: 'EPG_COMPLETE' });
|
||||
expect(worker.postMessage).not.toHaveBeenCalled();
|
||||
expect(service.hasFetchedUrl(url)).toBe(false);
|
||||
expect(mockWorkerInstances).toHaveLength(1);
|
||||
worker.emit('exit', 1);
|
||||
finishTermination();
|
||||
await flushPromises();
|
||||
const clearWorker = mockWorkerInstances[1];
|
||||
clearWorker.emit('message', { type: 'READY' });
|
||||
clearWorker.emit('message', { type: 'CLEAR_COMPLETE' });
|
||||
await clear;
|
||||
await fetch;
|
||||
});
|
||||
it.each([
|
||||
['exit', 1],
|
||||
['error', new Error('terminated worker')],
|
||||
] as const)(
|
||||
'cancels a retired source on %s without reviving it from late READY or COMPLETE messages',
|
||||
async (event, payload) => {
|
||||
const service = new EpgWorkerService('[Test EPG]', 1000);
|
||||
const url = 'https://removed.example/guide.xml';
|
||||
const progress = jest.spyOn(service, 'sendProgressToRenderer');
|
||||
const fetch = service.fetchEpgFromUrl(url).then(
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
const worker = mockWorkerInstances[0];
|
||||
let finishTermination!: () => void;
|
||||
worker.terminate.mockReturnValue(
|
||||
new Promise<void>((resolve) => {
|
||||
finishTermination = resolve;
|
||||
})
|
||||
);
|
||||
const clear = service.clearEpgDataForSource(url);
|
||||
worker.emit('message', { type: 'READY' });
|
||||
worker.emit('message', { type: 'EPG_COMPLETE' });
|
||||
expect(worker.postMessage).not.toHaveBeenCalled();
|
||||
expect(service.hasFetchedUrl(url)).toBe(false);
|
||||
expect(mockWorkerInstances).toHaveLength(1);
|
||||
worker.emit(event, payload);
|
||||
finishTermination();
|
||||
await flushPromises();
|
||||
const clearWorker = mockWorkerInstances[1];
|
||||
clearWorker.emit('message', { type: 'READY' });
|
||||
clearWorker.emit('message', { type: 'CLEAR_COMPLETE' });
|
||||
await clear;
|
||||
expect(await fetch).toBe(true);
|
||||
expect(progress.mock.calls.map((call) => call[1])).toEqual([
|
||||
'cancelled',
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
it('does not start a queued source removed while an earlier source imports', async () => {
|
||||
getDatabase.mockRejectedValue(new Error('force stale for test'));
|
||||
|
||||
@@ -1335,7 +1335,8 @@ existing source-clear worker. Successfully cleared request candidates are forgot
|
||||
without resetting their generation fences; failed cleanups remain retryable.
|
||||
Same-URL clears are serialized and replacement imports await the outstanding
|
||||
clear, so an older cleanup cannot erase a newly re-added source.
|
||||
Retired queued imports emit cancellation so progress rows disappear. Programmes are deleted by source; a globally keyed
|
||||
Retired queued and running imports emit generation-scoped cancellation so progress
|
||||
rows disappear without reporting routine worker termination as an import failure. Programmes are deleted by source; a globally keyed
|
||||
channel is retained while another source still has programmes, transferring its
|
||||
legacy owner to that remaining source. Manual mappings are preserved and can
|
||||
resolve another retained source sharing that channel ID. Legacy programmes with
|
||||
|
||||
Reference in new issue
Block a user