Skip to content

Instantly share code, notes, and snippets.

@pilucdes
Last active July 12, 2026 09:34
Show Gist options
  • Select an option

  • Save pilucdes/3cd4809d543ece25388944ee4640d8d2 to your computer and use it in GitHub Desktop.

Select an option

Save pilucdes/3cd4809d543ece25388944ee4640d8d2 to your computer and use it in GitHub Desktop.
GeForce NOW native Linux app stuck at 'Analyzing network' — root cause + fix (unhandled EPERM on thread priority in NCT SDK)

[Linux] Stuck forever at "Analyzing network" root cause: unhandled exception in NCT SDK when setting thread priority fails (EPERM)

Client: GeForce NOW native Linux app (Flatpak com.nvidia.geforcenow), version 2.0.86.133 OS: CachyOS (Arch-based, rolling), kernel 7.1.3-2-cachyos, COSMIC desktop Hardware: Ryzen 7 5800X, RTX 5070 Ti, wired Ethernet Network: No proxy, no VPN, DNS resolves all NVIDIA endpoints correctly, all HTTPS API calls succeed (login, PNS, etc.)

Symptom

Launching any game hangs indefinitely at "Analyzing network". The spinner never completes and never errors out in the UI.

What actually happens (debugged with an LD_PRELOAD exception tracer)

The network test plugin fails ~100 ms after starting, but the failure never reaches the UI, which waits forever.

CxNative_GeForceNOW.log:

[I] libNetwo TASK NetworkTestPlugin.cpp:52   Starting network test
[I] libNetwo TASK NetworkTestModule.cpp:232  Calling HandleNctClient
[E] libNetwo TASK  PluginModuleBase.cpp:452  0x8000ffff[0]: System exception
[E] libNetwo TASK  PluginModuleBase.cpp:498  Skipping telemetry report of error 0x8000ffff[0]: System exception from method Start

Hooking __cxa_throw and dumping the Poco exception messages shows the real error inside libNetworkTestSDK.so:

THROW Poco::FileNotFoundException msg="/app/cef/NCTClientConfig.xml"     <- thrown during nctCreateClient (caught internally?)
THROW Poco::SystemException msg="cannot set thread priority (Operation not permitted)"
    libNetworkTestSDK.so(...)
    libNetworkTestSDK.so(nctRegisterClientCallbackStruct+0x15)
    libNetworkTest.so(...)

The SystemException escapes the SDK, NetworkTest::Start returns 0x8000ffff, the JS layer logs policy: "NetworkTestFailed" with all metrics zero, and the UI spins forever.

Root cause

The NCT SDK promotes its measurement thread to a realtime scheduling class. On Windows SetThreadPriority effectively never fails, but on Linux pthread_setschedparam(SCHED_RR/FIFO) returns EPERM on any system where unprivileged realtime scheduling is not permitted. The resulting Poco::SystemException is unhandled and kills the entire network test.

Note this is not distro-specific: denying unprivileged RT scheduling is the default posture on most desktop Linux, including Ubuntu 24.04 LTS (RLIMIT_RTPRIO = 0 for regular users), so this failure mode should be reproducible on the officially supported configuration. It was observed here on CachyOS (Arch-based), but nothing in the failing code path involves anything distro-specific.

The test does not need RT priority to function: masking the failed call (LD_PRELOAD shim that turns a failed pthread_setschedparam into a no-op) makes the network test complete successfully and streaming works normally.

Suggested fix

Treat thread-priority elevation as best-effort on Linux: catch the exception (or check the return code) around the pthread_setschedparam call in the NCT SDK and continue with default priority, optionally logging a warning.

Also worth fixing (found while debugging, currently masked by fallbacks):

  • SDK probes <app-data>/config\nvstreamer\settings.txt — Windows path separators on Linux.
  • SDK probes hardcoded /opt/nvidia/config/NVIDIA_Corporation.conf, which cannot exist inside the Flatpak sandbox.
  • libShare looks for config.json at /app/cef/plugins/GeForceNOW/../../mall/... but the file ships at /app/mall/... (AutoHighlightsHandler construction fails with 0x8000ffff at every startup).
  • /app/cef/NCTClientConfig.xml is not shipped in the Flatpak.

Workaround for affected users

An LD_PRELOAD shim (prioshim.c, included in this gist) that intercepts pthread_setschedparam / pthread_setschedprio / sched_setscheduler / setpriority and returns success when the real call fails. The network test does not need realtime priority to work with the failure masked, it completes normally and streaming works.

1. Build the shim (needs gcc)

gcc -shared -fPIC -O2 -o prioshim.so prioshim.c -ldl
mkdir -p ~/.var/app/com.nvidia.geforcenow/fix
cp prioshim.so ~/.var/app/com.nvidia.geforcenow/fix/

(~/.var/app/com.nvidia.geforcenow/ is the app's own data dir, so it is visible inside the Flatpak sandbox and survives app updates.)

2. Launch with the shim

NVIDIA's launcher script inside the Flatpak explicitly clears LD_PRELOAD, so the client binary must be started directly:

flatpak run --command=sh \
  --env=LD_PRELOAD=$HOME/.var/app/com.nvidia.geforcenow/fix/prioshim.so \
  com.nvidia.geforcenow -c 'cd /app/cef && exec ./GeForceNOW'

To make it permanent, put that command in a small script (see launch.sh in this gist) and point the Exec= line of your GeForce NOW .desktop entry at it.

Notes:

  • Bypassing the stock launcher skips the in-app self-update flow; update with flatpak update com.nvidia.geforcenow instead.
  • The shim is forward-compatible: it only masks failed priority calls, so once NVIDIA fixes the bug it becomes a no-op.
  • Optional debugging: set GFN_PRIO_LOG=/path/to/log to log which priority calls were masked.
#!/usr/bin/env bash
# Launcher for GeForce NOW on Linux with the thread-priority workaround.
# See the accompanying write-up for the root cause analysis.
#
# Setup:
# gcc -shared -fPIC -O2 -o prioshim.so prioshim.c -ldl
# mkdir -p ~/.var/app/com.nvidia.geforcenow/fix
# cp prioshim.so launch.sh ~/.var/app/com.nvidia.geforcenow/fix/
# chmod +x ~/.var/app/com.nvidia.geforcenow/fix/launch.sh
# Then point the Exec= line of your GeForce NOW .desktop entry at launch.sh.
#
# Updating the app: bypassing NVIDIA's stock launcher skips the in-app
# self-update flow, so run `flatpak update com.nvidia.geforcenow` instead.
SHIM="$HOME/.var/app/com.nvidia.geforcenow/fix/prioshim.so"
if [ -f "$SHIM" ]; then
# NVIDIA's bin/GeForceNOW wrapper clears LD_PRELOAD, so exec the real
# binary directly. If a future update moves it, fall back to the stock
# launcher (app works, shim inactive).
exec flatpak run --command=sh --env=LD_PRELOAD="$SHIM" com.nvidia.geforcenow -c \
'if [ -x /app/cef/GeForceNOW ]; then cd /app/cef && exec ./GeForceNOW; else exec "/app/bin/GeForceNOW"; fi'
fi
# Shim missing: plain stock launch.
exec flatpak run com.nvidia.geforcenow
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <sys/resource.h>
static void logline(const char *fmt, ...)
{
static int logfd = -2;
if (logfd == -2) {
const char *p = getenv("GFN_PRIO_LOG");
logfd = p ? open(p, O_WRONLY | O_CREAT | O_APPEND, 0644) : -1;
}
if (logfd < 0) return;
char buf[512];
__builtin_va_list ap;
__builtin_va_start(ap, fmt);
int n = vsnprintf(buf, sizeof buf, fmt, ap);
__builtin_va_end(ap);
if (n > 0) {
ssize_t r = write(logfd, buf, (size_t)n);
(void)r;
}
}
int pthread_setschedparam(pthread_t t, int policy, const struct sched_param *p)
{
static int (*real)(pthread_t, int, const struct sched_param *) = NULL;
if (!real) real = dlsym(RTLD_NEXT, "pthread_setschedparam");
int r = real(t, policy, p);
if (r != 0) {
logline("pthread_setschedparam policy=%d prio=%d -> err=%d (masked)\n",
policy, p ? p->sched_priority : -1, r);
return 0;
}
return r;
}
int pthread_setschedprio(pthread_t t, int prio)
{
static int (*real)(pthread_t, int) = NULL;
if (!real) real = dlsym(RTLD_NEXT, "pthread_setschedprio");
int r = real(t, prio);
if (r != 0) {
logline("pthread_setschedprio prio=%d -> err=%d (masked)\n", prio, r);
return 0;
}
return r;
}
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *p)
{
static int (*real)(pid_t, int, const struct sched_param *) = NULL;
if (!real) real = dlsym(RTLD_NEXT, "sched_setscheduler");
int r = real(pid, policy, p);
if (r != 0) {
logline("sched_setscheduler pid=%d policy=%d prio=%d -> errno=%d (masked)\n",
(int)pid, policy, p ? p->sched_priority : -1, errno);
return 0;
}
return r;
}
int setpriority(__priority_which_t which, id_t who, int prio)
{
static int (*real)(__priority_which_t, id_t, int) = NULL;
if (!real) real = dlsym(RTLD_NEXT, "setpriority");
int r = real(which, who, prio);
if (r != 0) {
logline("setpriority which=%d who=%d prio=%d -> errno=%d (masked)\n",
(int)which, (int)who, prio, errno);
return 0;
}
return r;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment