From 0c4fbd089161a3cf79a1f22a6968dbd93fe514a4 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sat, 13 Jun 2026 13:24:56 +0200 Subject: [PATCH] fix: stabilize linux embedded mpv startup --- .../native/src/embedded_mpv_wid_common.h | 65 ++++++++++++++----- .../embedded-mpv-native-source.spec.ts | 47 ++++++++++++-- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/apps/electron-backend/native/src/embedded_mpv_wid_common.h b/apps/electron-backend/native/src/embedded_mpv_wid_common.h index f2995814a..cfe8a873f 100644 --- a/apps/electron-backend/native/src/embedded_mpv_wid_common.h +++ b/apps/electron-backend/native/src/embedded_mpv_wid_common.h @@ -53,27 +53,29 @@ IPTVNATOR_DECLARE_MPV_DYNAMIC_SYMBOL(mpv_wakeup) #include #include +#include #include #include +#include #include #include #include #include #include -#include #include -#include #include #include #include #include #include +#include #include #include #include #include #ifdef __linux__ #include +#include #include #include #include @@ -204,10 +206,19 @@ std::string formatInvariantDouble(double value) return "0"; } - std::ostringstream output; - output.imbue(std::locale::classic()); - output << std::setprecision(17) << value; - return output.str(); + char buffer[64]{}; + const auto result = std::to_chars( + buffer, + buffer + sizeof(buffer), + value, + std::chars_format::general, + 17 + ); + if (result.ec != std::errc()) { + return "0"; + } + + return std::string(buffer, result.ptr); } std::string nowIsoString() @@ -594,18 +605,37 @@ std::vector buildLinuxMpvArguments( return arguments; } -long inheritedFileDescriptorLimit() +void closeInheritedFileDescriptors() { - const long maxOpenFiles = sysconf(_SC_OPEN_MAX); - return maxOpenFiles > 0 ? maxOpenFiles : 1024; -} + DIR* directory = opendir("/proc/self/fd"); + if (directory) { + const int directoryFd = dirfd(directory); + while (dirent* entry = readdir(directory)) { + char* end = nullptr; + const long value = std::strtol(entry->d_name, &end, 10); + if ( + !end || + *end != '\0' || + value <= STDERR_FILENO || + value > INT_MAX + ) { + continue; + } -void closeInheritedFileDescriptors(long fileDescriptorLimit) -{ - for (long fileDescriptor = STDERR_FILENO + 1; - fileDescriptor < fileDescriptorLimit; - fileDescriptor += 1) { - const int descriptor = static_cast(fileDescriptor); + const int descriptor = static_cast(value); + if (descriptor == directoryFd) { + continue; + } + const int flags = fcntl(descriptor, F_GETFD); + if (flags >= 0) { + fcntl(descriptor, F_SETFD, flags | FD_CLOEXEC); + } + } + closedir(directory); + return; + } + + for (int descriptor = STDERR_FILENO + 1; descriptor < 1024; descriptor += 1) { const int flags = fcntl(descriptor, F_GETFD); if (flags >= 0) { fcntl(descriptor, F_SETFD, flags | FD_CLOEXEC); @@ -945,7 +975,6 @@ pid_t spawnLinuxMpvProcess( envp.push_back(nullptr); const bool traceEnabled = std::getenv("IPTVNATOR_TRACE_EMBEDDED_MPV"); - const long fileDescriptorLimit = inheritedFileDescriptorLimit(); const pid_t processId = fork(); if (processId != 0) { return processId; @@ -962,7 +991,7 @@ pid_t spawnLinuxMpvProcess( } } - closeInheritedFileDescriptors(fileDescriptorLimit); + closeInheritedFileDescriptors(); execvpe(argv[0], argv.data(), envp.data()); _exit(127); } diff --git a/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts b/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts index b8fc13171..68e13e601 100644 --- a/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts +++ b/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts @@ -324,14 +324,19 @@ describe('Embedded MPV native source recording invariants', () => { expect(widCommonSource).toContain( 'arguments.push_back("--gpu-context=x11egl");' ); - expect(widCommonSource).toContain('inheritedFileDescriptorLimit()'); expect(widCommonSource).toContain( + 'void closeInheritedFileDescriptors()' + ); + expect(widCommonSource).toContain('opendir("/proc/self/fd")'); + expect(widCommonSource).toContain('readdir(directory)'); + expect(widCommonSource).toContain( + 'fcntl(descriptor, F_SETFD, flags | FD_CLOEXEC)' + ); + expect(widCommonSource).toContain('closeInheritedFileDescriptors();'); + expect(widCommonSource).not.toContain('inheritedFileDescriptorLimit()'); + expect(widCommonSource).not.toContain( 'closeInheritedFileDescriptors(fileDescriptorLimit);' ); - expect(widCommonSource).toContain('flags | FD_CLOEXEC'); - expect(widCommonSource).not.toContain('opendir('); - expect(widCommonSource).not.toContain('readdir('); - expect(widCommonSource).not.toContain('closedir('); }); it('drives Linux out-of-process MPV state and controls over JSON IPC', () => { @@ -370,14 +375,42 @@ describe('Embedded MPV native source recording invariants', () => { expect(refreshBody).not.toContain('clampVolumePercent(*volume)'); }); + it('only marks open file descriptors close-on-exec before Linux MPV exec', () => { + const closeDescriptorsBody = sourceFunctionBody( + widCommonSource, + 'void closeInheritedFileDescriptors(', + 'closeInheritedFileDescriptors' + ); + expect(closeDescriptorsBody).toContain('opendir("/proc/self/fd")'); + expect(closeDescriptorsBody).toContain('readdir(directory)'); + expect(closeDescriptorsBody).toContain('fcntl(descriptor, F_GETFD)'); + expect(closeDescriptorsBody).toContain( + 'fcntl(descriptor, F_SETFD, flags | FD_CLOEXEC)' + ); + expect(widCommonSource).not.toContain('sysconf(_SC_OPEN_MAX)'); + expect(widCommonSource).not.toContain( + 'fileDescriptor < fileDescriptorLimit' + ); + + const spawnBody = sourceFunctionBody( + widCommonSource, + 'pid_t spawnLinuxMpvProcess(', + 'spawnLinuxMpvProcess' + ); + expect(spawnBody).toContain('closeInheritedFileDescriptors();'); + expect(spawnBody).not.toContain('inheritedFileDescriptorLimit()'); + }); + it('formats MPV floating-point values independently from the user locale', () => { const formatterBody = sourceFunctionBody( widCommonSource, 'std::string formatInvariantDouble(', 'formatInvariantDouble' ); - expect(formatterBody).toContain('std::locale::classic()'); - expect(formatterBody).toContain('std::setprecision(17)'); + expect(formatterBody).toContain('std::to_chars('); + expect(formatterBody).toContain('std::chars_format::general'); + expect(formatterBody).not.toContain('std::locale::classic()'); + expect(formatterBody).not.toContain('std::ostringstream'); const linuxArgumentsBody = sourceFunctionBody( widCommonSource,