fix: add macOS Homebrew Opus fallback and fix shutdown dict iteration

- Add Homebrew library path fallback when ctypes.util.find_library fails
  on macOS (Apple Silicon + Intel paths, guarded by platform check)
- Fix RuntimeError in gateway stop() by iterating over dict copy
- Update Opus tests to verify find_library-first + conditional fallback
This commit is contained in:
0xbyt4 2026-03-13 16:59:03 +03:00
parent c797314fcf
commit 44abe852fb
3 changed files with 33 additions and 11 deletions

View file

@ -409,6 +409,19 @@ class DiscordAdapter(BasePlatformAdapter):
if not discord.opus.is_loaded(): if not discord.opus.is_loaded():
import ctypes.util import ctypes.util
opus_path = ctypes.util.find_library("opus") opus_path = ctypes.util.find_library("opus")
# ctypes.util.find_library fails on macOS with Homebrew-installed libs,
# so fall back to known Homebrew paths if needed.
if not opus_path:
import sys
_homebrew_paths = (
"/opt/homebrew/lib/libopus.dylib", # Apple Silicon
"/usr/local/lib/libopus.dylib", # Intel Mac
)
if sys.platform == "darwin":
for _hp in _homebrew_paths:
if os.path.isfile(_hp):
opus_path = _hp
break
if opus_path: if opus_path:
try: try:
discord.opus.load_opus(opus_path) discord.opus.load_opus(opus_path)

View file

@ -754,7 +754,7 @@ class GatewayRunner:
logger.info("Stopping gateway...") logger.info("Stopping gateway...")
self._running = False self._running = False
for platform, adapter in self.adapters.items(): for platform, adapter in list(self.adapters.items()):
try: try:
await adapter.disconnect() await adapter.disconnect()
logger.info("%s disconnected", platform.value) logger.info("%s disconnected", platform.value)

View file

@ -4,22 +4,31 @@ import inspect
class TestOpusFindLibrary: class TestOpusFindLibrary:
"""Opus loading must use ctypes.util.find_library, not hardcoded paths.""" """Opus loading must try ctypes.util.find_library first, with platform fallback."""
def test_no_hardcoded_opus_path(self): def test_uses_find_library_first(self):
from gateway.platforms.discord import DiscordAdapter """find_library must be the primary lookup strategy."""
source = inspect.getsource(DiscordAdapter.connect)
assert "/opt/homebrew" not in source, \
"Opus loading must not use hardcoded /opt/homebrew path"
assert "libopus.so.0" not in source, \
"Opus loading must not use hardcoded libopus.so.0 path"
def test_uses_find_library(self):
from gateway.platforms.discord import DiscordAdapter from gateway.platforms.discord import DiscordAdapter
source = inspect.getsource(DiscordAdapter.connect) source = inspect.getsource(DiscordAdapter.connect)
assert "find_library" in source, \ assert "find_library" in source, \
"Opus loading must use ctypes.util.find_library" "Opus loading must use ctypes.util.find_library"
def test_homebrew_fallback_is_conditional(self):
"""Homebrew paths must only be tried when find_library returns None."""
from gateway.platforms.discord import DiscordAdapter
source = inspect.getsource(DiscordAdapter.connect)
# Homebrew fallback must exist
assert "/opt/homebrew" in source or "homebrew" in source, \
"Opus loading should have macOS Homebrew fallback"
# find_library must appear BEFORE any Homebrew path
fl_idx = source.index("find_library")
hb_idx = source.index("/opt/homebrew")
assert fl_idx < hb_idx, \
"find_library must be tried before Homebrew fallback paths"
# Fallback must be guarded by platform check
assert "sys.platform" in source or "darwin" in source, \
"Homebrew fallback must be guarded by macOS platform check"
def test_opus_decode_error_logged(self): def test_opus_decode_error_logged(self):
"""Opus decode failure must log the error, not silently return.""" """Opus decode failure must log the error, not silently return."""
from gateway.platforms.discord import VoiceReceiver from gateway.platforms.discord import VoiceReceiver