Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions api/src/unraid-api/nginx/nginx.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { execa } from 'execa';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { NginxService } from '@app/unraid-api/nginx/nginx.service.js';

vi.mock('execa', () => ({
execa: vi.fn(),
}));

describe('NginxService', () => {
const mockExeca = vi.mocked(execa);

beforeEach(() => {
vi.clearAllMocks();
});

it('coalesces concurrent reload requests', async () => {
const firstService = new NginxService();
const secondService = new NginxService();

const results = await Promise.all([firstService.reload(), secondService.reload()]);

expect(results).toEqual([true, true]);
expect(mockExeca).toHaveBeenCalledTimes(1);
expect(mockExeca).toHaveBeenCalledWith('/etc/rc.d/rc.nginx', ['reload']);
});

it('allows a new reload after the previous one completes', async () => {
const service = new NginxService();

await service.reload();
await service.reload();

expect(mockExeca).toHaveBeenCalledTimes(2);
});
});
20 changes: 19 additions & 1 deletion api/src/unraid-api/nginx/nginx.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@ import { Injectable, Logger } from '@nestjs/common';

import { execa } from 'execa';

let activeReload: Promise<boolean> | undefined;

@Injectable()
export class NginxService {
private readonly logger = new Logger(NginxService.name);

/** reloads nginx via its rc script */
async reload() {
async reload(): Promise<boolean> {
if (activeReload) {
this.logger.debug('Nginx reload already in progress; waiting for it to complete');
return activeReload;
Comment on lines +13 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Queue a reload instead of dropping concurrent requests

When an enable and disable transition overlap, the second handler updates connect.config.wanaccess (wan-access.events.ts:18-29) but this branch merely joins the reload already in progress. Because rc.nginx reads that setting during execution (rc-nginx.modification.ts:51-54), a reload that already consumed the earlier value can finish successfully while the later value is never applied—for example, leaving WAN access enabled after a disable request. Preserve serialization by scheduling a trailing reload after the active command rather than coalescing the request into it.

Useful? React with 👍 / 👎.

}

const reload = this.executeReload();
activeReload = reload;

try {
return await reload;
} finally {
activeReload = undefined;
}
}

private async executeReload(): Promise<boolean> {
try {
await execa('/etc/rc.d/rc.nginx', ['reload']);
this.logger.log('Nginx reloaded');
Expand Down
Loading