기술 · 실용 가이드
Python cannot schedule new futures after shutdown? 먼저 끝난 수명 주기를 증명하세요
Executor 인스턴스 종료, 인터프리터 종료, asyncio 기본 풀 종료를 구분한 뒤 재시도나 교체를 결정합니다.

RuntimeError: cannot schedule new futures after shutdown은 작업자 수를 늘리라는 신호가 아닙니다. 특정 Executor, asyncio 기본 실행기 또는 인터프리터 전체의 수명 주기가 끝난 뒤 제출자가 경계를 넘었다는 증거입니다. 정확한 문구와 전체 submit 스택으로 소유자를 찾습니다.
재시도를 추가하기 전에 증거를 보존하세요. 각 실행기 세대에는 한 소유자만 두고, 새 생산자를 먼저 멈추고, 수락된 작업을 의도대로 완료하거나 취소한 뒤 shutdown을 한 번만 호출합니다. asyncio.run이 루프를 소유하면 기본 실행기 종료도 맡깁니다.
1. 정확한 종료 신호를 분류합니다
전체 트레이스백을 저장하고 after shutdown과 after interpreter shutdown을 구분하세요. submit, map, 스케줄러, 라이브러리 도우미, run_in_executor 중 어디서 왔는지도 기록합니다.
생성, 제출 수락, 종료 시작, 종료 완료, 최초 거부 시 실행기 세대 ID, PID, 스레드 또는 작업 이름과 단조 시간을 기록합니다. 공개된 소유권 계약을 기준으로 판단합니다.
2. 인스턴스 종료 후 재사용을 고칩니다
with 블록을 벗어나면 실행기가 닫힙니다. 장기 객체가 같은 인스턴스를 저장하면 첫 실행만 성공하고 두 번째가 실패합니다. 각 독립 실행마다 새로 만들거나 전체 반복 사용 기간의 소유자로 with를 옮기세요.
각 호출자가 RuntimeError를 잡아 몰래 새 풀을 만들면 세대가 경쟁하고 작업이 중복될 수 있습니다. 소유자가 잠금이나 상태 기계로 현재 세대를 게시하고 draining 이후 제출을 거부한 뒤 교체해야 합니다.
3. 인터프리터 종료와 daemon 경쟁을 고칩니다
interpreter shutdown 문구는 프로세스 종료가 시작되었다는 뜻입니다. main이 반환된 뒤 daemon 컨트롤러, 스케줄러, 콜백이 계속 제출하거나 주 스레드 예외가 처리되지 않을 때 흔합니다.
명시적 중지 신호로 생산자를 멈추고, 새 제출을 막고, 제어 스레드와 futures를 기다리고, 실행기를 닫은 뒤 main을 반환하세요. 필요한 작업의 유일한 소유자를 daemon으로 두지 마세요.
4. asyncio 기본 실행기 소유권을 지킵니다
run_in_executor(None, ...)는 루프의 기본 실행기를 사용합니다. shutdown_default_executor() 뒤에는 다시 제출할 수 없습니다. asyncio.run은 기본 실행기와 루프 종료를 관리하므로 이중 종료하지 않습니다.
수동 루프에서는 실행기 작업을 만드는 서버와 콜백을 먼저 멈추고 관련 작업을 기다린 뒤 shutdown_default_executor()를 await하고 루프를 닫습니다. 다른 스레드의 콜백도 경계 전에 배출합니다.
5. 재시작과 종료 시험으로 증명합니다
연속 두 번 실행, 큐가 있는 종료, 생산자 활성 중 재시작, 주 스레드 실패를 시험하세요. 수락된 작업은 하나의 종결 상태만 가져야 하고 draining 뒤 제출이 없어야 합니다.
대기 작업을 완료할지 취소할지 정책을 정하고 단언하세요. 생산자 중지가 실행기 종료보다 앞선 시간선과 함께 중복 부작용, 고아 스레드, 누락 예외, 늦은 제출이 없음을 남깁니다.
