Skip to content

Commit db84dd8

Browse files
Merge branch 'main' into pr-log-context
2 parents 6ffbb99 + 92571fa commit db84dd8

2 files changed

Lines changed: 28 additions & 4 deletions

File tree

components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import concurrent.futures
2121
import contextlib
2222
import datetime
23+
import multiprocessing
2324
import pathlib
2425
import sys
2526
from abc import ABC, abstractmethod
@@ -94,6 +95,8 @@
9495

9596
reducer_connection_queue: asyncio.Queue | None = None
9697

98+
_MULTIPROCESSING_START_METHOD = "spawn"
99+
97100

98101
class DispatchExecutor:
99102
# Globals for dispatch executor pool
@@ -436,6 +439,10 @@ async def handle_cancelling_search_jobs(db_conn_pool) -> None:
436439
else:
437440
logger.error(f"Failed to cancel job {job_id}.")
438441

442+
# Yield to the event loop between jobs so cancellation batches do not
443+
# monopolize the scheduler when many jobs are being processed.
444+
await asyncio.sleep(0)
445+
439446

440447
def insert_query_tasks_into_db(db_conn, job_id, archive_ids: list[str]) -> list[int]:
441448
task_ids = []
@@ -1131,6 +1138,11 @@ async def handle_jobs(
11311138
async def main(argv: list[str]) -> int:
11321139
global reducer_connection_queue
11331140

1141+
# The scheduler accepts reducer TCP connections in the parent process. Using "spawn" prevents
1142+
# child processes from inheriting those sockets and keeping cancelled reducer jobs alive after
1143+
# the parent closes its copy.
1144+
multiprocessing.set_start_method(_MULTIPROCESSING_START_METHOD)
1145+
11341146
args_parser = argparse.ArgumentParser(description="Wait for and run query jobs.")
11351147
args_parser.add_argument("--config", "-c", required=True, help="CLP configuration file.")
11361148

components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import contextlib
23
import enum
34
from enum import Enum
45
from typing import Any
@@ -110,11 +111,22 @@ async def _send_msg_to_reducer(msg: bytes, writer: asyncio.StreamWriter):
110111
await writer.drain()
111112

112113

114+
async def _clean_up_task(task: asyncio.Task | None) -> None:
115+
if task is None:
116+
return
117+
if not task.done():
118+
task.cancel()
119+
with contextlib.suppress(asyncio.CancelledError, asyncio.IncompleteReadError):
120+
await task
121+
122+
113123
async def handle_reducer_connection(
114124
reader: asyncio.StreamReader,
115125
writer: asyncio.StreamWriter,
116126
reducer_connection_queue: asyncio.Queue,
117127
):
128+
recv_listener_msg_task: asyncio.Task | None = None
129+
recv_reducer_msg_task: asyncio.Task | None = None
118130
try:
119131
message_bytes = await _recv_msg_from_reducer(reader)
120132
if message_bytes is None:
@@ -138,10 +150,8 @@ async def handle_reducer_connection(
138150
# Transition to next state
139151
"""
140152
current_wait_state: _ReducerHandlerWaitState = _ReducerHandlerWaitState.JOB_CONFIG
141-
recv_listener_msg_task: asyncio.Task | None = asyncio.create_task(
142-
msg_queues.get_from_listeners()
143-
)
144-
recv_reducer_msg_task: asyncio.Task | None = asyncio.create_task(reader.readexactly(1))
153+
recv_listener_msg_task = asyncio.create_task(msg_queues.get_from_listeners())
154+
recv_reducer_msg_task = asyncio.create_task(reader.readexactly(1))
145155
while True:
146156
pending = [recv_listener_msg_task, recv_reducer_msg_task]
147157
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
@@ -225,5 +235,7 @@ async def handle_reducer_connection(
225235
await msg_queues.put_to_listeners(msg)
226236
break
227237
finally:
238+
await _clean_up_task(recv_listener_msg_task)
239+
await _clean_up_task(recv_reducer_msg_task)
228240
writer.close()
229241
await writer.wait_closed()

0 commit comments

Comments
 (0)