You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Apareció al desplegar #34 el 2026-09-12. El despliegue funcionó, pero el
apagado del proceso anterior agotó TimeoutStopSec=15, systemd lo mató con
SIGKILL, y eso disparó OnFailure= — una alerta de Discord causada por el
propio despliegue, no por un fallo.
Sep 12 04:08:41 Stopping loopify-bot.service...
(ningún "Shutting down (KeyboardInterrupt)" — nunca llegó al handler)
Sep 12 04:08:56 loopify-bot.service: Failed with result 'timeout'.
Sep 12 04:08:56 Main process exited, code=killed, status=9/KILL
Sep 12 04:08:56 Failed to kill control group ...: Invalid argument
Sep 12 04:08:56 Triggering OnFailure= dependencies.
Sep 12 04:08:56 Consumed 7min 24.747s CPU time, 555.3M memory peak
Lo que más dice es lo que no está: no hay línea Shutting down (KeyboardInterrupt). El SIGINT se entregó pero el proceso no salió del asyncio.run() en 15 segundos.
No es un problema general del apagado
Los 8 apagados anteriores de los últimos 14 días fueron todos limpios, y tras el
despliegue un reinicio de control también lo fue, en el mismo segundo:
Sep 12 04:12:17 Stopping loopify-bot.service...
Sep 12 04:12:17 INFO loopify: Shutting down (KeyboardInterrupt).
Sep 12 04:12:17 loopify-bot.service: Deactivated successfully.
Lo que distingue al que falló: llevaba 20 h en marcha, 7 min de CPU y 555 MB
de pico de memoria, o sea que había reproducido música de verdad. Todos los
apagados limpios fueron de procesos que estaban idle o llevaban poco tiempo. El
reinicio de control de las 04:12 también fue sobre un proceso idle, así que no reproduce la condición.
Hipótesis a investigar
La sospecha es algo en la ruta de reproducción que no responde a la cancelación
dentro de la ventana de 15 s, o que la bloquea. Candidatos, por orden:
MusicPlayer.destroy() durante el cierre. Hace run_in_executor(None, stream.close), y AudioStream.close() espera al hijo
hasta _REAP_TIMEOUT = 5.0 s. Con un stream activo más un prefetch, son dos
cierres de 5 s cada uno, y el executor por defecto se apaga al final de asyncio.run() — un run_in_executor lanzado durante el teardown puede
quedarse sin nadie que lo espere, o retrasarlo.
BufferedAudioSource.cleanup() drena una cola de hasta 250 frames y
luego llama a cleanup() del FFmpegPCMAudio envuelto, que a su vez espera
a FFmpeg.
El hilo de read-ahead es daemon=True, así que no debería impedir la
salida — hay que confirmarlo, no asumirlo.
bot.close() con un VoiceClient conectado: la desconexión de voz hace
ida y vuelta al gateway, y si el websocket ya no responde puede bloquearse
hasta su propio timeout.
Por qué importa, aunque el bot se recupere
Alertas falsas. Cada despliegue o reinicio de un bot que haya estado
sonando manda un aviso de fallo a Discord. Eso entrena a ignorar el canal de
alertas, que es exactamente lo que hizo útil al drop-in.
Failed to kill control group: Invalid argument sugiere que algo del
cgroup estaba en un estado raro al matarlo; merece una mirada aparte.
Cómo reproducirlo
La condición es haber reproducido, no estar reproduciendo:
# En Discord: !play <algo>, dejarlo sonar un rato, luego !stop
sudo systemctl restart loopify-bot
journalctl -u loopify-bot -n 20 --no-pager
# ¿aparece "Shutting down (KeyboardInterrupt)" y "Deactivated successfully",# o "result 'timeout'" y status=9/KILL?
Probar también con el bot reproduciendo activamente al reiniciar, y con la cola
llena para que haya un prefetch vivo además del stream actual.
Criterio de aceptación
Un reinicio tras haber reproducido apaga limpio, dentro de TimeoutStopSec, y
registra Shutting down (KeyboardInterrupt).
Un test que cierre un MusicPlayer con un stream y un prefetch vivos y afirme
que el teardown termina en un plazo acotado.
Ningún hijo de yt-dlp ni de FFmpeg sobrevive al apagado.
Apareció al desplegar #34 el 2026-09-12. El despliegue funcionó, pero el
apagado del proceso anterior agotó
TimeoutStopSec=15, systemd lo mató conSIGKILL, y eso disparó
OnFailure=— una alerta de Discord causada por elpropio despliegue, no por un fallo.
Lo que más dice es lo que no está: no hay línea
Shutting down (KeyboardInterrupt). El SIGINT se entregó pero el proceso no salió delasyncio.run()en 15 segundos.No es un problema general del apagado
Los 8 apagados anteriores de los últimos 14 días fueron todos limpios, y tras el
despliegue un reinicio de control también lo fue, en el mismo segundo:
Lo que distingue al que falló: llevaba 20 h en marcha, 7 min de CPU y 555 MB
de pico de memoria, o sea que había reproducido música de verdad. Todos los
apagados limpios fueron de procesos que estaban idle o llevaban poco tiempo. El
reinicio de control de las 04:12 también fue sobre un proceso idle, así que
no reproduce la condición.
Hipótesis a investigar
La sospecha es algo en la ruta de reproducción que no responde a la cancelación
dentro de la ventana de 15 s, o que la bloquea. Candidatos, por orden:
MusicPlayer.destroy()durante el cierre. Hacerun_in_executor(None, stream.close), yAudioStream.close()espera al hijohasta
_REAP_TIMEOUT = 5.0 s. Con un stream activo más un prefetch, son doscierres de 5 s cada uno, y el executor por defecto se apaga al final de
asyncio.run()— unrun_in_executorlanzado durante el teardown puedequedarse sin nadie que lo espere, o retrasarlo.
BufferedAudioSource.cleanup()drena una cola de hasta 250 frames yluego llama a
cleanup()delFFmpegPCMAudioenvuelto, que a su vez esperaa FFmpeg.
daemon=True, así que no debería impedir lasalida — hay que confirmarlo, no asumirlo.
bot.close()con unVoiceClientconectado: la desconexión de voz haceida y vuelta al gateway, y si el websocket ya no responde puede bloquearse
hasta su propio timeout.
Por qué importa, aunque el bot se recupere
sonando manda un aviso de fallo a Discord. Eso entrena a ignorar el canal de
alertas, que es exactamente lo que hizo útil al drop-in.
kill -9no deja correrAudioStream.close(), así que los hijos de yt-dlp yFFmpeg pueden quedar sin cosechar. Tras este despliegue concreto no quedaron
huérfanos (
pgrep -af 'yt_dlp|ffmpeg'salió vacío), pero es por suerte delcgroup, no por diseño.
Failed to kill control group: Invalid argumentsugiere que algo delcgroup estaba en un estado raro al matarlo; merece una mirada aparte.
Cómo reproducirlo
La condición es haber reproducido, no estar reproduciendo:
Probar también con el bot reproduciendo activamente al reiniciar, y con la cola
llena para que haya un prefetch vivo además del stream actual.
Criterio de aceptación
TimeoutStopSec, yregistra
Shutting down (KeyboardInterrupt).MusicPlayercon un stream y un prefetch vivos y afirmeque el teardown termina en un plazo acotado.