Skip to content

fix(config): keep working without a file watcher - #141

Open
kakra wants to merge 1 commit into
ralgar:mainfrom
kakra:fix/optional-file-watcher
Open

kakra wants to merge 1 commit into
ralgar:mainfrom
kakra:fix/optional-file-watcher

Conversation

@kakra

@kakra kakra commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

vkShade crashes when the platform file watcher cannot be created.

FileWatcher::create() returns nullptr if creating the watcher fails, for example when the per-user inotify instance limit (fs.inotify.max_user_instances, 128 by default) is exhausted. ConfigStore::update() already copes with a missing watcher, but loading, saving, and clearing a file dereferenced it. With the limit exhausted, vkShade logged Failed to create file watcher: inotify_init1: Too many open files three times and then crashed.

The change guards the four calls and logs a warning instead of an error, since the watcher only reloads changed files automatically:

File watcher unavailable, changed files will not be reloaded automatically: inotify_init1: Too many open files

Verification

  • Reproduced with vkcube in a separate user namespace whose inotify instances were used up. Without the change: the three error lines, then SIGSEGV. With the change: three warnings, the config and preset load, and vkcube runs to the end.
  • The six existing tests pass.
  • Not tested with a real game.

Each process that loads the layer creates three watchers, so a few Vulkan processes at once (a launcher, the game, helpers) can use up the remaining instances on a busy desktop.

FileWatcher::create() returns nullptr when the platform watcher cannot
be created, for example when the per-user inotify instance limit is
reached. ConfigStore only checked for that in update() and dereferenced
the pointer when loading, saving, and clearing a file, which crashed
the application. Skip watching in that case and log a warning instead,
since the watcher only reloads changed files automatically.
@kakra

kakra commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

One thing I'd like your opinion on: instead of guarding every use of m_watcher, FileWatcher::create() could return a no-op watcher when the platform one cannot be created.

As it is, this PR follows the null check that ConfigStore::update() already has and repeats it at the four other call sites. That works, but every future call needs the same guard, and if one is forgotten the crash is back immediately. The contract of create() is the actual problem: it promises a watcher and can return nullptr.

The alternative keeps the interface and the warning, and only touches file_watcher.cpp:

namespace
{
    // Used when the platform watcher cannot be created; nothing is ever reported as changed.
    class NullFileWatcher : public vkShade::Platform::FileWatcher
    {
    public:
        bool watch(const std::filesystem::path&) override { return false; }
        void unwatch() override {}
        bool changed() override { return false; }
    };
}

// in create():
catch (const std::system_error& e) {
    Logger::warn("File watcher unavailable, changed files will not be reloaded automatically: {}", e.what());
    return std::make_unique<NullFileWatcher>();
}

Then config_store.cpp needs no guards, and the m_watcher && in update() becomes redundant but harmless.

The trade-off is that create() never returns nullptr any more, and a caller can no longer ask whether automatic reload is active. Nothing needs that today.

I haven't tested this variant yet. Do you prefer it over the guards? I can switch this PR to it, or leave it as it is.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant