تصور کنید یک کارمند را استخدام کردهاید که برای ساعتها در مقابل میز یک تأمینکننده ایستاده و منتظر پاسخی است که هرگز نمیرسد، اما چون هنوز در اتاق حضور دارد، شما فکر میکنید سخت در حال کار است. او نه مرده است و نه در حال انجام کاری؛ او فقط منتظر کلمهای است که هرگز گفته نخواهد شد. این دقیقاً همان اتفاقی است که برای یک توسعهدهنده افتاد تا زمانی که متوجه شد عامل هوش مصنوعیاش ۱۴ ساعت است که در یک درخواست API یخ زده است.
به نقل از گزارش منتشر شده در dev.to در تاریخ ۲۶ سپتامبر ۲۰۲۶، یک توسعهدهنده تحلیل پس از حادثه (Post-mortem) خود را به اشتراک گذاشت و توضیح داد که چگونه یک خط کد گمشده و یک فراخوانی ناپایدار از API شخص ثالث، یک سهشنبهی کاری بهرهور را به یک شکست خاموش تبدیل کرد. اکثر ما با عامل (Agent) — شبیه به دستیاری که میتواند بهطور مستقل ابزارها را اجرا کند — به عنوان نیرویی خودمختار برخورد میکنیم، اما فراموش میکنیم که اینها همچنان بر پایه کتابخانههای استاندارد شبکه اجرا میشوند. در این راستا، مدیریت دسترسیها نیز به اندازه پایداری کد حیاتی است، چرا که دسترسیهای گسترده API در عاملهای هوشمند میتواند به حفرههای امنیتی خطرناکی تبدیل شود.
در این مورد، عامل از کتابخانه Python requests برای دریافت قیمتهای تأمینکنندگان استفاده میکرد. از آنجا که این کتابخانه بهصورت پیشفرض هیچ زمان انتظاری (Timeout) ندارد، اگر پاسخی از سرور راه دور متوقف شود، میتواند یک جایگاه کاری (Worker Slot) را بهطور نامحدود مسدود کند. همانطور که در تحلیلهای قبلی ما دربارهی پایداری سیستمهای عاملمحور اشاره کردیم، وابستگی به سرویسهای خارجی بدون لایهی حفاظتی، بزرگترین نقطه ضعف معماریهای فعلی است. این چالش نشان میدهد که چرا اجرای پایدار و ساختارمند در برابر تکیه بر پرامپتهای پیشرفته، برای پایداری عاملهای AI اولویت دارد.
کالبدشکافی یک انجماد خاموش
طبق مستندات این حادثه، خطا در ساعت ۲:۴۷ صبح رخ داد؛ زمانی که API یک تأمینکننده، اتصال TCP را پذیرفت اما در میانه ارسال پاسخ متوقف شد. مسیر کد به طرز فریبندهای ساده بود: یک فراخوانی استاندارد requests.get() و سپس resp.raise_for_status(). در حالی که این کد از نظر اکثر ابزارهای بررسی کد (Linter) و بازبینیهای انسانی درست به نظر میرسد، اما یک باگ نهفته ایجاد میکند. در شرایطی مانند خرابی یک لودبالانسر، یک سوکت نیمهبسته یا یک استقرار (Deploy) ناموفق در سمت تأمینکننده، کل فرآیند میتواند منجمد شود.
تا ساعت ۴ بعدازظهر، داشبورد عامل وضعیت «در حال اجرا» (Running) را نشان میداد. توسعهدهنده سه بار به داشبورد نگاه کرد و با رضایتی کاذب فکر کرد که عامل در حال پردازش یک دسته داده بزرگ است. او حتی در لاگهای توسعه خود به شوخی نوشته بود که عامل «تمام روز سرش پایین بوده و سخت کار کرده است». حقیقت تلخ زمانی آشکار شد که او از طریق SSH به Raspberry Pi متصل شد و با اجرای py-spy dump روی فرآیند، متوجه شد که یک رشته (Thread) تنها از پیش از طلوع آفتاب در وضعیت sock_recv متوقف شده و منتظر بایتهایی است که هرگز نمیرسند.
چرا سیستمهای نظارتی شکست خوردند؟
این توسعهدهنده چندین لایه نظارتی داشت، اما هیچکدام انجماد ۱۴ ساعته را تشخیص ندادند. این اتفاق یک نقص بحرانی را برجسته کرد: سیستم میتوانست یک عامل «مرده» را با دقت تقریباً کامل تشخیص دهد، اما یک عامل «گیر کرده» را اصلاً. عاملهای مرده خودشان را اعلام میکنند، اما عاملهای گیر کرده با مشغول به نظر رسیدن، دروغ میگویند:
- بررسی فرآیند: از نظر systemd، عامل همچنان «زنده» بود چون روی ورودی/خروجی (I/O) مسدود شده بود، نه اینکه کرش کرده باشد.
- سیگنالهای ضربان قلب (Heartbeats): این سیگنالها بین هر شغل ارسال میشدند. چون شغل هرگز تمام نشد، نوبت ارسال سیگنال بعدی هرگز نرسید.
- هشدار زمان اجرا: آستانه هشدار روی ۲۴ ساعت تنظیم شده بود تا از هشدارهای کاذب برای کارهای دستهای (Batch Jobs) مشروع که قبلاً تا ۹ ساعت طول کشیده بودند، جلوگیری شود.
- نشانه های بصری: وضعیت «در حال اجرا» در داشبورد، به اشتباه به عنوان بهرهوری بالا تفسیر شد.
این نوع عدم شفافیت در وضعیت عملیاتی، دقیقاً همان مشکلی است که رویکرد جدید COGEXT با ایجاد یک لایه پاسخگویی برای انتقال وضعیت عاملهای خودکار سعی در حل آن دارد.
راهکار چندلایه برای پایداری
برای جلوگیری از تکرار این اتفاق، یک رویکرد «کمربند و تعلیق» (Belt and Suspenders) — به معنای استفاده از لایههای حفاظتی موازی و تکراری برای اطمینان کامل — پیاده شد. توسعهدهنده تشخیص داد که وضعیتهای «انجماد» میتوانند در هر لایهای از پشته (Stack) رخ دهند.
حفاظت در لایه شبکه:
- زمانبندی متمرکز: توسعهدهنده دیگر به اضافه کردن دستی
timeout=اعتماد نکرد و یک Session متمرکز ایجاد کرد. این سیستم دو عدد مجزا را اجباری میکند: یک زمان انتظار ۵ ثانیهای برای اتصال (Connect Timeout) برای سرورهایی که اتصال را نمیپذیرند، و یک زمان انتظار ۶۰ ثانیهای برای خواندن (Read Timeout) که حداکثر فاصله مجاز بین دریافت بایتها را تعیین میکند. - اجبار در CI: یک دستور
grepساده اما مؤثر به خط لوله CI اضافه شد تا هر فراخوانیrequests.get،post،put،deleteیاpatchکه فاقد پارامتر timeout باشد را شناسایی و علامتگذاری کند. این ابزار تا کنون سه مورد فراموشی واقعی را شناسایی کرده است.
حفاظت در لایه اجرا:
- ضربالاجل زمانی (Wall-Clock Deadlines): برای محافظت در برابر انجماد در فرآیندهای فرزند (Subprocesses) یا قفلهای پایگاه داده که در آنها timeoutهای HTTP بیاثر هستند، هر شغل اکنون یک ضربالاجل سختگیرانه با استفاده از
time.monotonic()دارد. اگر این ضربالاجل رد شود، سیستم خطایJobTimeoutErrorصادر میکند. - پشتیبان سیگنالی: اجراکننده شغل، فرآیند را در
signal.alarm()قرار میدهد تا به عنوان آخرین لایه حفاظتی برای کدهایی که ممکن است بررسیهای تعاملی را نادیده بگیرند، عمل کند.
حفاظت در لایه نظارت:
- ضربان قلب حین کار: مکانیسم ضربان قلب از «بین شغلها» به «حین کار» منتقل شد. حالا عامل در هر تکرار حلقه، یک فایل ضربان قلب را بهروز میکند (Touch میکند).
- تأیید Cron: یک شغل Cron مجزا هر ۱۰ دقیقه اجرا میشود تا زمان آخرین تغییر فایل را بررسی کند. اگر سن فایل بیش از ۱۸۰۰ ثانیه باشد، یک سیگنال شکست به Healthchecks.io ارسال میکند و توسعهدهنده را ظرف ۳۰ دقیقه با خبر میکند.
بازتعریف هشدارهای زمانی
به جای یک هشدار کلی ۲۴ ساعته، اکنون از برچسبگذاری نوع شغل (Job-type Tagging) استفاده میشود. هشدارها در ۳ برابرِ زمان p95 (میانگین ۹۵ درصد موارد) برای هر تسک خاص فعال میشوند. برای یک شغل همگامسازی قیمت با p95 چهار دقیقهای، هشدار اکنون در دقیقه ۱۲ فعال میشود، نه فردا. در مقابل، یک شغل «دسته بزرگ» (Long Batch) اکنون روی حدود ۱۰ ساعت تنظیم شده است.
این تغییر در رویکرد، مدل پایداری را از یک مدل «مبتنی بر امید» به مدل «تأیید فعال» تغییر داد. توسعهدهنده گزارش داده که در ۶ هفته اخیر، سه قطعی مجزا — شامل دو قطعی تأمینکننده، یک نوسان ISP و یک شکست DNS — در عرض چند دقیقه شناسایی و بازیابی شدند.
برای هر کسی که عاملهای خود را روی یک Raspberry Pi یا در یک کمد سرور دورافتاده اجرا میکند، درس روشن است: سیستمهای بدون نظارت یا با صدای بلند میمیرند یا در سکوت دروغ میگویند. اگر نظارت شما فقط کرشها را تشخیص میدهد، شما نسبت به خطرناکترین نوع شکست — یعنی انجماد خاموش — کور هستید.
گام بعدی شما
- تمام فراخوانیهای API در کدهای پایتون خود را بررسی کنید و مطمئن شوید پارامتر
timeoutرا به صورت صریح تعریف کردهاید. - مکانیسم ضربان قلب (Heartbeat) خود را از حالت «پایان تسک» به حالت «حین اجرا» تغییر دهید تا انجمادهای میانی شناسایی شوند.
- برای تسکهای حساس، یک ضربالاجل زمانی سخت (Hard Deadline) در سطح سیستمعامل تعریف کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی بهینهسازی مصرف انرژی در گرههای لبه مراجعه کنید.




گفتگو