Self-hosting Fluxer: findings, bugs, and patches from a real deployment

(#1037) Feature Shipped self-hosting

Thread

Comment by @mgabor3141
RexSystem 1 vote originally by @mgabor3141 on GitHub OP

3: Voice states missing from READY payload (likely upstream bug)

Symptom: After connecting (or refreshing the page), the channel sidebar shows 0 users in voice channels. You only see who's in a voice channel after you join it yourself. The client logs confirm: Initialized voice states from connection open {guildCount: 2, totalVoiceStates: 0}. Root cause: guild_voice_server.erl is a separate process that holds the authoritative voice state. It's always started for every guild (guild.erl:76). All voice mutations (join, leave, confirm) go through resolve_voice_pid() which routes to this process. But guild_data:get_guild_state/2 — which builds the READY payload — runs inside the guild process and reads voice states from its own state:
VoiceStates = guild_voice:get_voice_states_list(State),
The guild process's voice_states map is always empty because all mutations go to the voice server process. The fallback handler in guild_voice_handler.erl is effectively dead code since resolve_voice_pid always finds the voice server via ETS. This is likely an upstream bug too — the voice server process was presumably split out from the guild process to reduce contention, but get_guild_state was never updated to fetch from the new location. Fix in fluxer_gateway/src/guild/guild_data.erl:
- VoiceStates = guild_voice:get_voice_states_list(State),
+ VoiceStates = fetch_voice_states_from_server(State),
Add the new function:
-spec fetch_voice_states_from_server(guild_state()) -> [map()].
fetch_voice_states_from_server(State) ->
    case maps:get(voice_server_pid, State, undefined) of
        Pid when is_pid(Pid) ->
            try gen_server:call(Pid, {get_voice_states_list}, 5000) of
                VoiceStates when is_list(VoiceStates) -> VoiceStates;
                _ -> []
            catch
                exit:{timeout, _} -> [];
                exit:{noproc, _} -> [];
                exit:{normal, _} -> []
            end;
        _ ->
            guild_voice:get_voice_states_list(State)
    end.
The voice server already has a {get_voice_states_list} handler (guild_voice_server.erl:203), so this just wires it up.