Replace asyncio.to_thread with ThreadPoolExecutor.run_in_executor
- Add ThreadPoolExecutor with 3 max workers to SeriesApp - Replace all asyncio.to_thread calls with loop.run_in_executor - Add shutdown() method to properly cleanup executor - Integrate SeriesApp.shutdown() into FastAPI shutdown sequence - Ensures proper resource cleanup on Ctrl+C (SIGINT/SIGTERM)
This commit is contained in:
@@ -12,6 +12,7 @@ Note:
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from events import Events
|
||||
@@ -148,6 +149,9 @@ class SeriesApp:
|
||||
|
||||
self.directory_to_search = directory_to_search
|
||||
|
||||
# Initialize thread pool executor
|
||||
self.executor = ThreadPoolExecutor(max_workers=3)
|
||||
|
||||
# Initialize events
|
||||
self._events = Events()
|
||||
self._events.download_status = None
|
||||
@@ -229,7 +233,9 @@ class SeriesApp:
|
||||
|
||||
async def _init_list(self) -> None:
|
||||
"""Initialize the series list with missing episodes (async)."""
|
||||
self.series_list = await asyncio.to_thread(
|
||||
loop = asyncio.get_running_loop()
|
||||
self.series_list = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.list.GetMissingEpisode
|
||||
)
|
||||
logger.debug(
|
||||
@@ -251,7 +257,12 @@ class SeriesApp:
|
||||
RuntimeError: If search fails
|
||||
"""
|
||||
logger.info("Searching for: %s", words)
|
||||
results = await asyncio.to_thread(self.loader.search, words)
|
||||
loop = asyncio.get_running_loop()
|
||||
results = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.loader.search,
|
||||
words
|
||||
)
|
||||
logger.info("Found %d results", len(results))
|
||||
return results
|
||||
|
||||
@@ -348,7 +359,9 @@ class SeriesApp:
|
||||
|
||||
try:
|
||||
# Perform download in thread to avoid blocking event loop
|
||||
download_success = await asyncio.to_thread(
|
||||
loop = asyncio.get_running_loop()
|
||||
download_success = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.loader.download,
|
||||
self.directory_to_search,
|
||||
serie_folder,
|
||||
@@ -481,7 +494,9 @@ class SeriesApp:
|
||||
try:
|
||||
# Get total items to scan
|
||||
logger.info("Getting total items to scan...")
|
||||
total_to_scan = await asyncio.to_thread(
|
||||
loop = asyncio.get_running_loop()
|
||||
total_to_scan = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.serie_scanner.get_total_to_scan
|
||||
)
|
||||
logger.info("Total folders to scan: %d", total_to_scan)
|
||||
@@ -503,7 +518,10 @@ class SeriesApp:
|
||||
)
|
||||
|
||||
# Reinitialize scanner
|
||||
await asyncio.to_thread(self.serie_scanner.reinit)
|
||||
await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.serie_scanner.reinit
|
||||
)
|
||||
|
||||
def scan_progress_handler(progress_data):
|
||||
"""Handle scan progress events from scanner."""
|
||||
@@ -528,7 +546,10 @@ class SeriesApp:
|
||||
|
||||
try:
|
||||
# Perform scan (file-based, returns results in scanner.keyDict)
|
||||
await asyncio.to_thread(self.serie_scanner.scan)
|
||||
await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.serie_scanner.scan
|
||||
)
|
||||
finally:
|
||||
# Always unsubscribe after scan completes or fails
|
||||
self.serie_scanner.unsubscribe_on_progress(
|
||||
@@ -685,3 +706,14 @@ class SeriesApp:
|
||||
)
|
||||
|
||||
return all_series
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Shutdown the thread pool executor.
|
||||
|
||||
Should be called when the SeriesApp instance is no longer needed
|
||||
to properly clean up resources.
|
||||
"""
|
||||
if hasattr(self, 'executor'):
|
||||
self.executor.shutdown(wait=True)
|
||||
logger.info("ThreadPoolExecutor shut down successfully")
|
||||
|
||||
Reference in New Issue
Block a user