2025, Oct 02 15:33
WSL/Linux में Unix सिग्नल से Python ThreadPoolExecutor की शेड्यूलिंग रोकें और सुरक्षित शटडाउन करें
WSL/Linux में Python ThreadPoolExecutor की शेड्यूलिंग को UNIX signals (SIGUSR1, SIGTERM) से नियंत्रित करें: setproctitle, ग्रेसफुल शटडाउन subprocess नियंत्रण
चलने के बाद लंबे समय तक चलने वाले subprocess कार्यों के पूल को नियंत्रित करने की जरूरत अक्सर पड़ती है: कुछ देर के लिए शेड्यूलिंग रोकना, जो काम चल रहा है उसे पूरा होने देना, फिर बाद में फिर शुरू करना या साफ-सुथरे ढंग से बाहर निकलना। जब Python में subprocess कॉल भेजने के लिए ThreadPoolExecutor का उपयोग किया जाता है, तो पहला ख़याल अक्सर किसी कीबोर्ड हॉटकी को जोड़ने का आता है। लेकिन WSL टर्मिनलों में pynput या keyboard जैसी हुक-आधारित लाइब्रेरी अपेक्षित की-इवेंट्स प्राप्त नहीं कर पातीं, और input का इस्तेमाल करना मुख्य प्रवाह को ब्लॉक कर देगा—जो स्वीकार्य नहीं है। नीचे दिया गया तरीका हॉटकी की जगह UNIX सिग्नल का उपयोग करता है ताकि रनटाइम नियंत्रण मजबूत और स्क्रिप्टेबल रहे।
समस्या को दिखाने वाला न्यूनतम उदाहरण
ढांचा सरल है: एक पूल subprocess कार्यों को भेजता है, पर जैसे ही रन शुरू हो जाता है, शेड्यूलिंग को रोकने का भरोसेमंद तरीका नहीं बचता।
import concurrent.futures
import subprocess
import sys
import time
def do_work(arg):
    # एक धीमे subprocess कॉल का अनुकरण
    return subprocess.run([
        sys.executable,
        "-c",
        f"import time; time.sleep({arg})"
    ])
def main():
    durations = [5, 4, 6, 3, 5, 4]
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
        future_list = [pool.submit(do_work, d) for d in durations]
        for fut in concurrent.futures.as_completed(future_list):
            _ = fut.result()
if __name__ == "__main__":
    main()
पूल जैसे ही कार्य सबमिट करना शुरू करता है, WSL टर्मिनल में साधारण की-स्ट्रोक से शेड्यूलिंग को गैर-ब्लॉकिंग तरीके से रोकना या बंद करना संभव नहीं रहता। ग्लोबल हॉटकी सुनने वाली लाइब्रेरी उस वातावरण में की-स्ट्रोक पकड़ ही नहीं पातीं, और input का सहारा लेने से मुख्य थ्रेड रुक जाएगा। इस परिदृश्य में फाइल-आधारित सिग्नलिंग भी वांछनीय नहीं है।
असल में समस्या क्या है
मुद्दा एक्जीक्यूटर नहीं, बल्कि कंट्रोल चैनल है। हॉटकी हुक WSL टर्मिनल में चल रही Python प्रक्रिया तक इवेंट पहुँचा ही न सकें, तो बिना ब्लॉकिंग रीड या फाइल पोलिंग के स्टेट टॉगल करने का विश्वसनीय तरीका नहीं बचता। काम एक बार सबमिट होने के बाद ‘फायर-एंड-फॉरगेट’ जैसा हो जाता है।
समाधान: UNIX सिग्नल, प्रोसेस टाइटल और स्पष्ट हैंडलर
व्यवहारिक रास्ता यह है कि Python प्रक्रिया को बाहर से नियंत्रित करने के लिए UNIX सिग्नल अपनाए जाएं। पहले setproctitle की मदद से प्रक्रिया को एक अलग पहचान वाला नाम दें, ताकि उसे नाम से टार्गेट किया जा सके। फिर SIGTERM और SIGUSR1 के हैंडलर जोड़ें। SIGUSR1 के साथ आप शेड्यूलर को नए कार्य कतार में लगाने से रोक सकते हैं, जबकि चल रहे subprocess अपने आप पूरा हो जाएंगे। SIGTERM के साथ आप सुव्यवस्थित तरीके से बाहर निकल सकते हैं। क्योंकि प्रक्रिया का शीर्षक पहचाने जाने योग्य है, pkill -USR1 -f [exe_name] भेजना ही पर्याप्त है—PID ढूंढ़ने की जरूरत नहीं पड़ती।
एंड-टू-एंड इम्प्लीमेंटेशन
नीचे दिया गया कोड एक नामित प्रक्रिया का उपयोग करता है, SIGUSR1 मिलने पर आगे की शेड्यूलिंग रोक देता है, और SIGTERM पर ग्रेसफुल शटडाउन करता है। जो subprocess चल रहे हैं, उन्हें पूरा होने दिया जाता है। यह तर्क ऊपर बताई पद्धति का ही विस्तार है, बस नियंत्रण स्पष्ट और नजर आने योग्य हो जाता है।
import concurrent.futures
import signal
import subprocess
import sys
import threading
import time
from setproctitle import setproctitle
# सिग्नलों द्वारा बदले जाने वाले वैश्विक नियंत्रण फ्लैग
halt_queue = threading.Event()
terminate_run = threading.Event()
def on_usr1(sig, frame):
    # नए कार्यों की शेड्यूलिंग रोकें; चल रहे कार्यों को पूरा होने दें
    halt_queue.set()
def on_term(sig, frame):
    # सौम्य रोक का अनुरोध: नए कार्यों को रोकें और बंद होने की ओर बढ़ें
    halt_queue.set()
    terminate_run.set()
def worker_job(delay_s):
    # नियंत्रण घटनाओं पर subprocess को मारे बिना एक धीमे subprocess कॉल का अनुकरण करें
    return subprocess.run([
        sys.executable,
        "-c",
        f"import time; time.sleep({delay_s})"
    ])
def orchestrate():
    # pkill -f से टार्गेट करने के लिए प्रोसेस को पहचाने जाने योग्य नाम दें
    setproctitle("batch-runner-example")
    # सिग्नल हैंडलर जोड़ें
    signal.signal(signal.SIGUSR1, on_usr1)
    signal.signal(signal.SIGTERM, on_term)
    tasks = [5, 4, 6, 3, 5, 4, 7, 2]
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as exe:
        submitted = []
        for item in tasks:
            if halt_queue.is_set():
                break
            fut = exe.submit(worker_job, item)
            submitted.append(fut)
        for fut in concurrent.futures.as_completed(submitted):
            _ = fut.result()
    # सभी सबमिट किए गए कार्य पूरे होने के बाद की वैकल्पिक पोस्ट-रन लॉजिक
    if terminate_run.is_set():
        # सुव्यवस्थित शटडाउन पहले ही किया जा चुका है
        pass
if __name__ == "__main__":
    orchestrate()
इसके बाद नियंत्रण बाहरी हो जाता है और स्क्रिप्ट्स से संचालित किया जा सकता है। एक बार प्रक्रिया का शीर्षक सेट करें और सिग्नल नाम से भेजें—stdin ब्लॉक किए बिना और टर्मिनल में की-लिस्नरों पर निर्भर हुए बिना।
नाम के आधार पर सिग्नल कैसे भेजें
PID तलाशे बिना भी सिग्नल भेजे जा सकते हैं। स्क्रिप्ट में दिया गया प्रक्रिया शीर्षक उपयोग करें। SIGUSR1 भेजने से नए कार्यों की शेड्यूलिंग रुक जाती है और मौजूदा subprocess पूरा हो लेते हैं। SIGTERM भेजने पर, डिफ़ॉल्ट रूप से, उसी तंत्र के जरिए ग्रेसफुल स्टॉप हो जाता है।
# आगे के कार्यों की शेड्यूलिंग रोकें (जो चल रहे हैं उन्हें खत्म होने दें)
pkill -USR1 -f batch-runner-example
# सौम्य समाप्ति का अनुरोध करें (डिफ़ॉल्ट सिग्नल SIGTERM है)
pkill -f batch-runner-example
चाहें तो ps से प्रक्रिया ढूंढ़कर kill के जरिए सीधे सिग्नल भेज सकते हैं।
Linux पर आप ps से process_id ढूंढ़ सकते हैं और फिर kill process_id चलाकर सिग्नल भेज सकते हैं (डिफ़ॉल्ट SIGTERM होता है)। उचित प्रतिक्रिया के लिए एप्लिकेशन कोड में सिग्नल हैंडलर होना चाहिए।
आम तौर पर Python प्रक्रिया को भेजे गए सिग्नल को संभालने के लिए signal मानक लाइब्रेरी मॉड्यूल में सिग्नल हैंडलर लागू किया जाता है।
समानांतर निष्पादन के वैकल्पिक रूप में, कुछ लोग स्क्रिप्ट्स को पैरेलल चलाने के लिए GNU Parallel की सलाह देते हैं। ऊपर की पद्धति का केन्द्र बिंदु हालांकि सिग्नलों के जरिए Python-पक्ष का नियंत्रण है।
यह क्यों मायने रखता है
जहां कीबोर्ड हुक अविश्वसनीय या अवांछित हों, वहां Unix सिग्नल आपको भरोसेमंद और बिना झंझट का नियंत्रण देते हैं। आप Ctrl+C से बचते हैं, जो subprocess को तुरंत मार देगा, और आप न तो blocking input का सहारा लेते हैं, न फाइल-पोलिंग का। नतीजा यह है कि बाहरी तौर पर काम को थोड़ी देर रोकना और उसे सुरक्षित रूप से ड्रेन होने देना संभव हो जाता है, बिना निष्पादन मॉडल में बड़े बदलाव किए।
मुख्य बातें
यदि ThreadPoolExecutor से subprocess शुरू करते हुए आपको शेड्यूलिंग रोकनी या सौम्य तरीके से समाप्ति करनी हो, तो WSL/Linux टर्मिनलों में हॉटकी लिस्नरों के बजाय सिग्नल चुनें। setproctitle से प्रक्रिया को अलग शीर्षक दें, SIGUSR1 और SIGTERM के हैंडलर जोड़ें, और एक साझा इवेंट पर टास्क-सबमिशन को निर्भर रखें। कतार में नए कार्य जोड़ना रोकने के लिए pkill -USR1 -f [exe_name] का इस्तेमाल करें, और व्यवस्थित निकास के लिए डिफ़ॉल्ट SIGTERM पर भरोसा रखें। इससे लंबे बैच रन के लिए आपका कंट्रोल प्लेन सरल, स्क्रिप्टेबल और मजबूत रहता है।