3737 IncorrectProcessFound ,
3838 NoProcessFound ,
3939 NodeUnhealthy ,
40+ PortAlreadyInUse ,
4041)
4142from .utils import (
4243 change_dict_naming_convention ,
@@ -128,6 +129,7 @@ class ServerManager:
128129
129130 def __init__ (self , config : Config , cog : "Audio" , timeout : Optional [int ] = None ) -> None :
130131 self .ready : asyncio .Event = asyncio .Event ()
132+ self .abort_for_unmanaged : asyncio .Event = asyncio .Event ()
131133 self ._config = config
132134 self ._proc : Optional [asyncio .subprocess .Process ] = None # pylint:disable=no-member
133135 self ._node_pid : Optional [int ] = None
@@ -136,6 +138,7 @@ def __init__(self, config: Config, cog: "Audio", timeout: Optional[int] = None)
136138 self .timeout = timeout
137139 self .cog = cog
138140 self ._args = []
141+ self ._current_config = {}
139142
140143 @property
141144 def path (self ) -> Optional [str ]:
@@ -236,6 +239,7 @@ async def process_settings(self):
236239 data ["sentry" ]["tags" ]["rll_version" ] = lavalink .__version__
237240 data ["sentry" ]["tags" ]["red_version" ] = red_version
238241
242+ self ._current_config = data
239243 with open (LAVALINK_APP_YML , "w" ) as f :
240244 yaml .safe_dump (data , f )
241245
@@ -338,6 +342,14 @@ async def _wait_for_launcher(self) -> None:
338342 log .info ("Managed Lavalink node is ready to receive requests." )
339343 break
340344 if _FAILED_TO_START .search (line ):
345+ if (
346+ f"Port { self ._current_config ['server' ]['port' ]} was already in use" .encode ()
347+ in line
348+ ):
349+ raise PortAlreadyInUse (
350+ f"Port { self ._current_config ['server' ]['port' ]} already in use. "
351+ f"Managed Lavalink startup aborted."
352+ )
341353 raise ManagedLavalinkStartFailure (
342354 f"Lavalink failed to start: { line .decode ().strip ()} "
343355 )
@@ -351,6 +363,7 @@ async def _wait_for_launcher(self) -> None:
351363 async def shutdown (self ) -> None :
352364 if self .start_monitor_task is not None :
353365 self .start_monitor_task .cancel ()
366+ self .abort_for_unmanaged .clear ()
354367 await self ._partial_shutdown ()
355368
356369 async def _partial_shutdown (self ) -> None :
@@ -459,8 +472,44 @@ async def maybe_download_jar(self):
459472 if not (LAVALINK_JAR_FILE .exists () and await self ._is_up_to_date ()):
460473 await self ._download_jar ()
461474
475+ @staticmethod
476+ async def get_lavalink_process (
477+ * matches : str , cwd : Optional [str ] = None , lazy_match : bool = False
478+ ):
479+ process_list = []
480+ filter = [cwd ] if cwd else []
481+ async for proc in AsyncIter (psutil .process_iter ()):
482+ try :
483+ if cwd :
484+ if not (proc .cwd () in filter ):
485+ continue
486+ cmdline = proc .cmdline ()
487+ if (matches and all (a in cmdline for a in matches )) or (
488+ lazy_match and any ("lavalink" in arg .lower () for arg in cmdline )
489+ ):
490+ proc_as_dict = proc .as_dict (
491+ attrs = ["pid" , "name" , "create_time" , "status" , "cmdline" , "cwd" ]
492+ )
493+ process_list .append (proc_as_dict )
494+ except (psutil .NoSuchProcess , psutil .AccessDenied , psutil .ZombieProcess ):
495+ pass
496+ return process_list
497+
462498 async def wait_until_ready (self , timeout : Optional [float ] = None ):
463- await asyncio .wait_for (self .ready .wait (), timeout = timeout or self .timeout )
499+ tasks = [
500+ asyncio .create_task (c ) for c in [self .ready .wait (), self .abort_for_unmanaged .wait ()]
501+ ]
502+ done , pending = await asyncio .wait (
503+ tasks , timeout = timeout or self .timeout , return_when = asyncio .FIRST_COMPLETED
504+ )
505+ for task in pending :
506+ task .cancel ()
507+ if done :
508+ done .pop ().result ()
509+ if self .abort_for_unmanaged .is_set ():
510+ raise asyncio .TimeoutError
511+ if not self .ready .is_set ():
512+ raise asyncio .TimeoutError
464513
465514 async def start_monitor (self , java_path : str ):
466515 retry_count = 0
@@ -546,6 +595,12 @@ async def start_monitor(self, java_path: str):
546595 log .critical (exc )
547596 self .cog .lavalink_connection_aborted = True
548597 return await self .shutdown ()
598+ except PortAlreadyInUse as exc :
599+ log .critical (exc )
600+ self .cog .lavalink_connection_aborted = False
601+ self .cog ._runtime_external_node = True
602+ self .abort_for_unmanaged .set ()
603+ return await self .shutdown ()
549604 except ManagedLavalinkNodeException as exc :
550605 delay = backoff .delay ()
551606 log .critical (
0 commit comments