Audio changes (#5593)

* Squash tested commits

* remove the code jack is concerned about

* Apply suggestions from code review

* more log lines

* more log lines

* format

* formatting

* style(Rename Xms and Xmx mentions): Rename Xms and Xmx to more use friendly names

- Change Xms to "Initial Heapsize"
- Change Xmx to "Max Heapsize"

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>
This commit is contained in:
Draper
2022-03-28 16:23:30 +01:00
committed by GitHub
parent 5a5b22003f
commit 9ec85d4819
28 changed files with 1629 additions and 599 deletions

View File

@@ -6,18 +6,16 @@ from red_commons.logging import getLogger
from redbot.core import data_manager
from redbot.core.i18n import Translator
from ...errors import LavalinkDownloadFailed
from ...manager import ServerManager
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
from ...utils import task_callback_debug
log = getLogger("red.cogs.Audio.cog.Tasks.lavalink")
_ = Translator("Audio", Path(__file__))
class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
def lavalink_restart_connect(self) -> None:
def lavalink_restart_connect(self, manual: bool = False) -> None:
lavalink.unregister_event_listener(self.lavalink_event_handler)
lavalink.unregister_update_listener(self.lavalink_update_handler)
if self.lavalink_connect_task:
@@ -28,93 +26,106 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
self._restore_task = None
lavalink.register_event_listener(self.lavalink_event_handler)
lavalink.register_update_listener(self.lavalink_update_handler)
self.lavalink_connect_task = asyncio.create_task(self.lavalink_attempt_connect())
self.lavalink_connect_task.add_done_callback(task_callback_debug)
self.lavalink_connect_task = asyncio.create_task(
self.lavalink_attempt_connect(manual=manual)
)
async def lavalink_attempt_connect(self, timeout: int = 50) -> None:
async def lavalink_attempt_connect(self, timeout: int = 50, manual: bool = False) -> None:
self.lavalink_connection_aborted = False
max_retries = 5
retry_count = 0
if nodes := lavalink.get_all_nodes():
for node in nodes:
await node.disconnect()
# This ensures that the restore task is ended before this connect attempt is started up.
if self._restore_task:
self._restore_task.cancel()
if self.managed_node_controller is not None:
if not self.managed_node_controller._shutdown:
await self.managed_node_controller.shutdown()
await asyncio.sleep(5)
await lavalink.close(self.bot)
while retry_count < max_retries:
configs = await self.config.all()
external = configs["use_external_lavalink"]
java_exec = configs["java_exc_path"]
if external is False:
settings = self._default_lavalink_settings
host = settings["host"]
password = settings["password"]
ws_port = settings["ws_port"]
if self.player_manager is not None:
await self.player_manager.shutdown()
self.player_manager = ServerManager()
# Change these values to use whatever is set on the YAML
host = configs["yaml"]["server"]["address"]
port = configs["yaml"]["server"]["port"]
password = configs["yaml"]["lavalink"]["server"]["password"]
secured = False
# Make this timeout customizable for lower powered machines?
self.managed_node_controller = ServerManager(self.config, timeout=60, cog=self)
try:
await self.player_manager.start(java_exec)
except LavalinkDownloadFailed as exc:
await asyncio.sleep(1)
if exc.should_retry:
log.exception(
"Exception whilst starting internal Lavalink server, retrying...",
exc_info=exc,
await self.managed_node_controller.start(java_exec)
# timeout is the same as ServerManager.timeout -
# 60s in case of ServerManager(self.config, timeout=60)
await self.managed_node_controller.wait_until_ready()
except asyncio.TimeoutError:
if self.managed_node_controller is not None:
await self.managed_node_controller.shutdown()
if self.lavalink_connection_aborted is not True:
log.critical(
"Managed node startup timeout, aborting managed node startup."
)
retry_count += 1
continue
else:
log.exception(
"Fatal exception whilst starting internal Lavalink server, "
"aborting...",
exc_info=exc,
)
self.lavalink_connection_aborted = True
return
except asyncio.CancelledError:
log.critical("Invalid machine architecture, cannot run Lavalink.")
self.lavalink_connection_aborted = True
return
except Exception as exc:
log.exception(
"Unhandled exception whilst starting internal Lavalink server, "
"Unhandled exception whilst starting managed Lavalink node, "
"aborting...",
exc_info=exc,
)
self.lavalink_connection_aborted = True
if self.managed_node_controller is not None:
await self.managed_node_controller.shutdown()
return
else:
break
else:
host = configs["host"]
password = configs["password"]
ws_port = configs["ws_port"]
port = configs["ws_port"]
secured = configs["secured_ws"]
break
else:
log.critical(
"Setting up the Lavalink server failed after multiple attempts. "
"See above tracebacks for details."
"Setting up the managed Lavalink node failed after multiple attempts. "
"See above logs for details."
)
self.lavalink_connection_aborted = True
if self.managed_node_controller is not None:
await self.managed_node_controller.shutdown()
return
log.debug("Attempting to initialize Red-Lavalink")
retry_count = 0
while retry_count < max_retries:
if lavalink.node._nodes:
await lavalink.node.disconnect()
try:
await lavalink.initialize(
bot=self.bot,
host=host,
password=password,
ws_port=ws_port,
ws_port=port,
timeout=timeout,
resume_key=f"Red-Core-Audio-{self.bot.user.id}-{data_manager.instance_name}",
secured=secured,
)
except lavalink.AbortingNodeConnection:
await lavalink.close(self.bot)
log.warning("Connection attempt to Lavalink node aborted")
return
except asyncio.TimeoutError:
log.warning("Connecting to Lavalink server timed out, retrying...")
if external is False and self.player_manager is not None:
await self.player_manager.shutdown()
await lavalink.close(self.bot)
log.warning("Connecting to Lavalink node timed out, retrying...")
retry_count += 1
await asyncio.sleep(1) # prevent busylooping
except Exception as exc:
log.exception(
"Unhandled exception whilst connecting to Lavalink, aborting...", exc_info=exc
"Unhandled exception whilst connecting to Lavalink node, aborting...",
exc_info=exc,
)
await lavalink.close(self.bot)
self.lavalink_connection_aborted = True
return
else:
@@ -122,11 +133,11 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
else:
self.lavalink_connection_aborted = True
log.critical(
"Connecting to the Lavalink server failed after multiple attempts. "
"Connecting to the Lavalink node failed after multiple attempts. "
"See above tracebacks for details."
)
await lavalink.close(self.bot)
return
if external:
await asyncio.sleep(5)
self._restore_task = asyncio.create_task(self.restore_players())
self._restore_task.add_done_callback(task_callback_debug)

View File

@@ -5,6 +5,7 @@ from pathlib import Path
from typing import Optional
import lavalink
from lavalink import NodeNotFound, PlayerNotFound
from red_commons.logging import getLogger
from redbot.core.data_manager import cog_data_path
@@ -15,7 +16,6 @@ from redbot.core.utils.dbtools import APSWConnectionWrapper
from ...apis.interface import AudioAPIInterface
from ...apis.playlist_wrapper import PlaylistWrapper
from ...errors import DatabaseError, TrackEnqueueError
from ...utils import task_callback_debug
from ..abc import MixinMeta
from ..cog_utils import _SCHEMA_VERSION, CompositeMetaClass
@@ -30,7 +30,6 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
# as initial load happens before the bot can ever be ready.
lavalink.set_logging_level(self.bot._cli_flags.logging_level)
self.cog_init_task = asyncio.create_task(self.initialize())
self.cog_init_task.add_done_callback(task_callback_debug)
async def initialize(self) -> None:
await self.bot.wait_until_red_ready()
@@ -54,7 +53,6 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
await self._build_bundled_playlist()
self.lavalink_restart_connect()
self.player_automated_timer_task = asyncio.create_task(self.player_automated_timer())
self.player_automated_timer_task.add_done_callback(task_callback_debug)
except Exception as exc:
log.critical("Audio failed to start up, please report this issue.", exc_info=exc)
return
@@ -62,14 +60,27 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
self.cog_ready_event.set()
async def restore_players(self):
log.debug("Starting new restore player task")
tries = 0
tracks_to_restore = await self.api_interface.persistent_queue_api.fetch_all()
while not lavalink.get_all_nodes():
await asyncio.sleep(1)
log.trace("Waiting for node to be available")
tries += 1
if tries > 60:
log.warning("Unable to restore players, couldn't connect to Lavalink.")
if tries > 600: # Give 10 minutes from node creation date.
log.warning("Unable to restore players, couldn't connect to Lavalink node.")
return
try:
for node in lavalink.get_all_nodes():
if not node.ready:
log.trace("Waiting for node: %r", node)
await node.wait_until_ready(timeout=60) # In theory this should be instant.
except asyncio.TimeoutError:
log.error(
"Restoring player task aborted due to a timeout waiting for Lavalink node to be ready."
)
log.warning("Audio will attempt queue restore on next restart.")
return
metadata = {}
all_guilds = await self.config.all_guilds()
async for guild_id, guild_data in AsyncIter(all_guilds.items(), steps=100):
@@ -77,7 +88,9 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
if guild_data["currently_auto_playing_in"]:
notify_channel, vc_id = guild_data["currently_auto_playing_in"]
metadata[guild_id] = (notify_channel, vc_id)
if self.lavalink_connection_aborted:
log.warning("Aborting player restore due to Lavalink connection being aborted.")
return
for guild_id, track_data in itertools.groupby(tracks_to_restore, key=lambda x: x.guild_id):
await asyncio.sleep(0)
tries = 0
@@ -86,22 +99,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
track_data = list(track_data)
guild = self.bot.get_guild(guild_id)
if not guild:
log.verbose(
"Skipping player restore - Bot is no longer in Guild (%s)", guild_id
)
continue
persist_cache = self._persist_queue_cache.setdefault(
guild_id, await self.config.guild(guild).persist_queue()
)
if not persist_cache:
log.verbose(
"Skipping player restore - Guild (%s) does not have a persist cache",
guild_id,
)
await self.api_interface.persistent_queue_api.drop(guild_id)
continue
if self.lavalink_connection_aborted:
try:
player = lavalink.get_player(guild_id)
except (NodeNotFound, PlayerNotFound):
player = None
else:
try:
player = lavalink.get_player(guild_id)
except IndexError:
player = None
except KeyError:
player = None
vc = 0
guild_data = await self.config.guild_from_id(guild.id).all()
shuffle = guild_data["shuffle"]
@@ -126,7 +141,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
player = await lavalink.connect(vc, deafen=auto_deafen)
player.store("notify_channel", notify_channel_id)
break
except IndexError:
except NodeNotFound:
await asyncio.sleep(5)
tries += 1
except Exception as exc:
@@ -139,7 +154,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
else:
await asyncio.sleep(1)
if tries >= 5 or guild is None or vc is None or player is None:
if tries >= 5 or vc is None or player is None:
if tries >= 5:
log.verbose(
"Skipping player restore - Guild (%s), 5 attempts to restore player failed.",
guild_id,
)
elif vc is None:
log.verbose(
"Skipping player restore - Guild (%s), VC (%s) does not exist.",
guild_id,
vc_id,
)
else:
log.verbose(
"Skipping player restore - Guild (%s), Unable to create player for VC (%s).",
guild_id,
vc_id,
)
await self.api_interface.persistent_queue_api.drop(guild_id)
continue
@@ -156,7 +188,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
await player.play()
log.debug("Restored %r", player)
except Exception as exc:
log.debug("Error restoring player in %d", guild_id, exc_info=exc)
log.debug("Error restoring player in %s", guild_id, exc_info=exc)
await self.api_interface.persistent_queue_api.drop(guild_id)
for guild_id, (notify_channel_id, vc_id) in metadata.items():
@@ -171,9 +203,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
else:
try:
player = lavalink.get_player(guild_id)
except IndexError:
player = None
except KeyError:
except (NodeNotFound, PlayerNotFound):
player = None
if player is None:
guild_data = await self.config.guild_from_id(guild.id).all()
@@ -195,7 +225,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
player = await lavalink.connect(vc, deafen=auto_deafen)
player.store("notify_channel", notify_channel_id)
break
except IndexError:
except NodeNotFound:
await asyncio.sleep(5)
tries += 1
except Exception as exc:
@@ -205,7 +235,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
break
else:
await asyncio.sleep(1)
if tries >= 5 or guild is None or vc is None or player is None:
if tries >= 5 or vc is None or player is None:
if tries >= 5:
log.verbose(
"Skipping player restore - Guild (%s), 5 attempts to restore player failed.",
guild_id,
)
elif vc is None:
log.verbose(
"Skipping player restore - Guild (%s), VC (%s) does not exist.",
guild_id,
vc_id,
)
else:
log.verbose(
"Skipping player restore - Guild (%s), Unable to create player for VC (%s).",
guild_id,
vc_id,
)
continue
player.repeat = repeat
@@ -220,23 +267,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
try:
await self.api_interface.autoplay(player, self.playlist_api)
except DatabaseError:
notify_channel = self.bot.get_channel(notify_channel)
notify_channel = guild.get_channel(notify_channel)
if notify_channel:
await self.send_embed_msg(
notify_channel, title=_("Couldn't get a valid track.")
)
return
except TrackEnqueueError:
notify_channel = self.bot.get_channel(notify_channel)
notify_channel = guild.get_channel(notify_channel)
if notify_channel:
await self.send_embed_msg(
notify_channel,
title=_("Unable to Get Track"),
description=_(
"I'm unable to get a track from Lavalink at the moment, "
"I'm unable to get a track from the Lavalink node at the moment, "
"try again in a few minutes."
),
)
return
del metadata
del all_guilds
log.debug("Player restore task completed successfully")