[V3 Audio] Harden Lavalink boot sequence (#1498)

* Do a bit of hardening

* Loop not asyncio

* Don't use new asyncio coolness

* I hate you all

* Muck up everything

* Fix version comparisons
This commit is contained in:
Will
2018-04-15 18:01:56 -04:00
committed by palmtree5
parent d9fa875d84
commit 5be967e8c5
6 changed files with 122 additions and 60 deletions

View File

@@ -1,18 +1,17 @@
from pathlib import Path
from aiohttp import ClientSession
import shutil
import asyncio
from .audio import Audio
from .manager import start_lavalink_server
from discord.ext import commands
from redbot.core.data_manager import cog_data_path
import redbot.core
LAVALINK_BUILD = 3112
LAVALINK_BUILD_URL = (
"https://ci.fredboat.com/repository/download/"
"Lavalink_Build/{}:id/Lavalink.jar?guest=1"
).format(LAVALINK_BUILD)
LAVALINK_DOWNLOAD_URL = (
"https://github.com/Cog-Creators/Red-DiscordBot/"
"releases/download/{}/Lavalink.jar"
).format(redbot.core.__version__)
LAVALINK_DOWNLOAD_DIR = cog_data_path(raw_name="Audio")
LAVALINK_JAR_FILE = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
@@ -23,7 +22,7 @@ BUNDLED_APP_YML_FILE = Path(__file__).parent / "application.yml"
async def download_lavalink(session):
with LAVALINK_JAR_FILE.open(mode='wb') as f:
async with session.get(LAVALINK_BUILD_URL) as resp:
async with session.get(LAVALINK_DOWNLOAD_URL) as resp:
while True:
chunk = await resp.content.read(512)
if not chunk:
@@ -33,24 +32,25 @@ async def download_lavalink(session):
async def maybe_download_lavalink(loop, cog):
jar_exists = LAVALINK_JAR_FILE.exists()
current_build = await cog.config.current_build()
current_build = redbot.core.VersionInfo(*await cog.config.current_build())
if not jar_exists or current_build < LAVALINK_BUILD:
session = ClientSession(loop=loop)
if not jar_exists or current_build < redbot.core.version_info:
LAVALINK_DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
with ClientSession(loop=loop) as session:
await download_lavalink(session)
await cog.config.current_build.set(LAVALINK_BUILD)
await download_lavalink(session)
await cog.config.current_build.set(redbot.core.version_info.to_json())
session.close()
shutil.copyfile(str(BUNDLED_APP_YML_FILE), str(APP_YML_FILE))
async def setup(bot: commands.Bot):
cog = Audio(bot)
await maybe_download_lavalink(bot.loop, cog)
await start_lavalink_server(bot.loop)
if not await cog.config.use_external_lavalink():
await maybe_download_lavalink(bot.loop, cog)
await start_lavalink_server(bot.loop)
async def _finish():
await cog.init_config()
bot.add_cog(cog)
bot.loop.create_task(_finish())
bot.add_cog(cog)
bot.loop.create_task(cog.init_config())

View File

@@ -16,7 +16,7 @@ __author__ = ["aikaterna", "billy/bollo/ati"]
class Audio:
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2711759128, force_registration=True)
self.config = Config.get_conf(self, 2711759130, force_registration=True)
default_global = {
"host": 'localhost',
@@ -24,7 +24,8 @@ class Audio:
"ws_port": '2332',
"password": 'youshallnotpass',
"status": False,
"current_build": 0
"current_build": [3, 0, 0, 'alpha', 0],
"use_external_lavalink": False
}
default_guild = {

View File

@@ -1,12 +1,22 @@
import shlex
import shutil
import asyncio
from subprocess import Popen, DEVNULL
from subprocess import Popen, DEVNULL, PIPE
import os
import logging
log = logging.getLogger('red.audio.manager')
proc = None
SHUTDOWN = asyncio.Event()
def has_java_error(pid):
from . import LAVALINK_DOWNLOAD_DIR
poss_error_file = LAVALINK_DOWNLOAD_DIR / 'hs_err_pid{}.log'.format(pid)
return poss_error_file.exists()
async def monitor_lavalink_server(loop):
while not SHUTDOWN.is_set():
if proc.poll() is not None:
@@ -14,13 +24,55 @@ async def monitor_lavalink_server(loop):
await asyncio.sleep(0.5)
if not SHUTDOWN.is_set():
print("Lavalink jar shutdown, restarting.")
await start_lavalink_server(loop)
log.info("Lavalink jar shutdown.")
if not has_java_error(proc.pid):
log.info("Restarting Lavalink jar.")
await start_lavalink_server(loop)
else:
log.error("Your Java is borked. Please find the hs_err_pid{}.log file"
" in the Audio data folder and report this issue.".format(
proc.pid
))
async def has_java(loop):
java_available = shutil.which('java') is not None
if not java_available:
return False
version = await get_java_version(loop)
return version >= (1, 8), version
async def get_java_version(loop):
"""
This assumes we've already checked that java exists.
"""
proc = Popen(
shlex.split("java -version", posix=os.name == 'posix'),
stdout=PIPE, stderr=PIPE
)
_, err = proc.communicate()
version_info = str(err, encoding='utf-8')
version_line = version_info.split('\n')[0]
version_start = version_line.find('"')
version_string = version_line[version_start + 1:-1]
major, minor = version_string.split('.')[:2]
return int(major), int(minor)
async def start_lavalink_server(loop):
java_available, java_version = await has_java(loop)
if not java_available:
raise RuntimeError("You must install Java 1.8+ for Lavalink to run.")
extra_flags = ""
if java_version == (1, 8):
extra_flags = "-Dsun.zip.disableMemoryMapping=true"
from . import LAVALINK_DOWNLOAD_DIR, LAVALINK_JAR_FILE
start_cmd = "java -jar {}".format(LAVALINK_JAR_FILE.resolve())
start_cmd = "java {} -jar {}".format(extra_flags, LAVALINK_JAR_FILE.resolve())
global proc
proc = Popen(
@@ -29,13 +81,15 @@ async def start_lavalink_server(loop):
stdout=DEVNULL, stderr=DEVNULL
)
print("Lavalink jar started. PID: {}".format(proc.pid))
log.info("Lavalink jar started. PID: {}".format(proc.pid))
loop.create_task(monitor_lavalink_server(loop))
def shutdown_lavalink_server():
print("Shutting down lavalink server.")
log.info("Shutting down lavalink server.")
SHUTDOWN.set()
global proc
if proc is not None:
proc.terminate()
proc = None