۱۴۰۵ مهر ۱۵, چهارشنبه
دلار آمریکا۲۶۲٬۲۰۵▼ ۲٫۵۲٪یورو۲۹۴٬۸۳۰▼ ۲٫۸۴٪درهم امارات۷۱٬۷۷۶▼ ۲٫۲۹٪سکه امامی۲۶۷٬۳۸۵٬۰۰۰▼ ۱٫۶۹٪طلای ۱۸ عیار (گرم)۲۶٬۲۶۷٬۶۰۰▼ ۱٫۷۹٪انس طلا۴٬۱۱۶ $▼ ۱٫۱۴٪تتر۲۶۲٬۶۷۸▼ ۲٫۴۱٪بیت‌کوین۸۳٬۷۰۹ $▼ ۲٫۳٪اتریوم۲٬۵۸۴ $▼ ۴٫۲۶٪سولانا۱۱۷٫۵۵ $▼ ۳٫۰۱٪اپل۳۳۳٫۶۳ $▲ ۰٫۲۲٪انویدیا۲۳۹٫۲۴ $▲ ۰٫۱۴٪مایکروسافت۵۲۹٫۳۰ $▲ ۰٫۷۸٪آلفابت (گوگل)۳۴۷٫۶۸ $▲ ۰٫۳۵٪تسلا۳۸۰٫۶۸ $▲ ۰٫۵۲٪شاخص نزدک۲۷٬۶۰۰ $▲ ۰٫۴۵٪
نرخ ارز

کرش اپلیکیشن‌های iOS با Firebase؛ ریشه در یک پاسخ سرور گوگل

کرش اپلیکیشن‌های iOS با Firebase از یک پاسخ نادرست سرویس sdk-exp گوگل ریشه گرفت؛ اپ‌های منتشرشده هنگام اجرا بسته شدند و گوگل مشکل را کاملاً سمت سرور رفع کرد.

کاور گرافیکی DevNA با لوگوی Firebase و عنوان «Firebase»: پنجره کد با متن کرش NSInvalidArgumentException و پیام «key cannot be nil» در تم قرمز هشدار
کاور گرافیکی DevNA با لوگوی Firebase و عنوان «Firebase»: پنجره کد با متن کرش NSInvalidArgumentException و پیام «key cannot be nil» در تم قرمز هشدار
فهرست مطالب
  1. دقیقاً چه چیزی در اپ‌های iOS کرش می‌کرد؟
  2. خط زمانی رسمی گوگل
  3. گوگل در Firebase چه چیزی را عوض کرد؟
  4. درسی که برای توسعه‌دهنده‌ها می‌ماند

کرش اپلیکیشن‌های iOS با Firebase بامداد سه‌شنبه ۷ مهر ۱۴۰۵ (۲۹ سپتامبر ۲۰۲۶) شروع شد: شمار زیادی از اپ‌های منتشرشده iOS که هفته‌ها دست‌نخورده بودند، هنگام اجرا بسته شدند. ریشه ماجرا نه در کد آن اپ‌ها بود و نه در یک نسخه تازه SDK، بلکه در پاسخی نادرست که سرور گوگل به کتابخانه Google Analytics for Firebase می‌داد. گوگل کمی بیش از دو ساعت بعد اصلاحیه را کاملاً سمت سرور منتشر کرد و هیچ اپی نیاز به انتشار نسخه تازه نداشت.

دقیقاً چه چیزی در اپ‌های iOS کرش می‌کرد؟

گزارش اصلی در مخزن رسمی firebase-ios-sdk ثبت شد: یک اپ تولیدی از ساعت ۰۰:۴۱ UTC روز ۲۹ سپتامبر روی چهار بیلدی که قبلاً منتشر شده بود کرش کرد، بدون آنکه تیم چیزی منتشر کرده باشد. امضای خطا در همه گزارش‌ها یکی بود:

Fatal Exception: NSInvalidArgumentException
*** -[__NSDictionaryM setObject:forKeyedSubscript:]: key cannot be nil

و استک همیشه به یک مسیر می‌رسید:

-[GULMutableDictionary dictionary]
-[APMEExperiment copyWithZone:]
-[APMESnapshot initWithSDKName:experiments:]
-[APMESnapshot initWithProtobuf:]
-[APMETaskManager experimentSnapshotsFromExperimentResponse:]
-[APMETaskManager handleFetchingExperimentsResponse:data:error:]
__35-[APMETaskManager fetchExperiments]_block_invoke

یعنی کرش دقیقاً هنگام پردازش پاسخ یک درخواست API رخ می‌داد: POST https://app-analytics-services.com/sdk-exp؛ همان سرویسی که پیکربندی آزمایش‌های (experiment) داخلی SDK را تحویل می‌دهد. اپ کمتر از یک ثانیه پس از اجرا بسته می‌شد.

چرا هیچ نسخه‌ای امن نبود

گزارش اولیه SDK نسخه 12.14.0 را نام می‌برد، اما توسعه‌دهنده‌های دیگر همان کرش را روی طیف گسترده‌ای از نسخه‌ها گزارش کردند؛ از 10.29.0 و 11.11.0 گرفته تا 12.2.0، 12.8.0 و 12.17.0. همین گستره نشان داد مشکل به یک نسخه خاص SDK ربط ندارد و منشأ آن سمت سرور است. اپ‌هایی هم آسیب دیدند که هیچ‌کدام از APIهای Analytics را مستقیم صدا نمی‌زدند و فقط FirebaseApp.configure() را اجرا کرده بودند.

نکته فنی اینجاست که GULMutableDictionary نوشتن در دیکشنری را با dispatch_async روی صف داخلی خودش انجام می‌دهد. بنابراین استثنای «کلید nil» در آن صف پرتاب می‌شود، نه در فریم فراخوان — و عملاً با @try/@catch قابل گرفتن نیست. یک کلید خالی در داده سرور، کل پروسه را مستقیم از کار می‌انداخت.

خط زمانی رسمی گوگل

زمان (US/PDT) به وقت تهران رویداد
۲۸ سپتامبر، ۱۷:۴۱ ۲۹ سپتامبر، ۰۴:۱۱ آغاز سرو شدن پاسخ نادرست از sdk-exp
۲۸ سپتامبر، ۱۹:۵۲ ۲۹ سپتامبر، ۰۶:۲۲ تکمیل انتشار اصلاحیه (بازگردانی پیکربندی)
۲۸ سپتامبر، ۲۳:۵۲ ۲۹ سپتامبر، ۱۰:۲۲ رفع کامل و پایان اثر کش‌های قدیمی

از شروع خرابی تا رفع کامل، شش ساعت و یازده دقیقه طول کشید. گوگل در جمع‌بندی رسمی‌اش نوشت که «هیچ به‌روزرسانی SDK از سمت شما لازم نیست» و توضیح داد به‌دلیل رفتار کش (cache)، ممکن است برخی نمونه‌های اپ تا چهار ساعت پس از تکمیل انتشار اصلاحیه هنوز کرش کنند. گزارش‌های کرش هم با تأخیر به داشبوردها می‌رسند، پس نمودارها دیرتر از واقعیت آرام شدند.

گوگل این خرابی را در تاریخچه رسمی داشبورد وضعیت Firebase ثبت نکرد؛ آن داشبورد برای ۲۸ و ۲۹ سپتامبر «بدون رخداد» است و فقط یک بنر بالای صفحه دارد که ماجرا را در چند جمله کوتاه توضیح می‌دهد و برای جزئیات به همان کامنت گیت‌هاب لینک می‌دهد. شرح کامل ماجرا فقط همان‌جاست.

گوگل در Firebase چه چیزی را عوض کرد؟

اصلاح فوری کاملاً سمت سرور بود: گوگل پیکربندی sdk-exp را به وضعیت قبلی بازگرداند. در کنار آن، تغییری در کتابخانه GoogleUtilities ادغام شد که از این پس به‌جای از کار انداختن اپ، کلید یا مقدار nil را در GULMutableDictionary نادیده می‌گیرد. یکی از مهندسان Firebase در تاپیک نوشت که به‌روزرسانی فوری لازم نیست، این نسخه مقاوم‌تر پس از بازبینی و تست منتشر می‌شود و فرایندهای به‌روزرسانی سمت سرور هم در حال بازنگری است.

درسی که برای توسعه‌دهنده‌ها می‌ماند

هر SDK شخص‌ثالثی که پیکربندی‌اش را از سرور می‌گیرد، یک وابستگی زمان‌اجرا است، نه یک کتابخانه ساکن. باینری‌ای که کامپایل و امضا کرده‌اید ثابت می‌ماند، اما رفتارش با داده تازه عوض می‌شود؛ یعنی بخشی از سطح خطای محصول شما هر روز از راه دور بازنویسی می‌شود (نمونه دیگری از یک مسیر شبکه‌ای که کسی پایشش نمی‌کرد: توقف آموزش OpenAI). سه نتیجه عملی:

  • پارس کردن باید کرش‌ناپذیر باشد. داده‌ای که از شبکه می‌آید ورودی نامعتبر است تا خلافش ثابت شود. هیچ مسیر پردازش پاسخی نباید بتواند کل پروسه را از کار بیندازد، به‌ویژه وقتی کار روی صف پس‌زمینه انجام می‌شود و استثنا اصلاً به دست فراخوان نمی‌رسد. تست با ورودی خراب دقیقاً برای همین لحظه‌هاست و جای اجرای خودکارش خط لوله CI/CD است.
  • انتشار پیکربندی هم انتشار نرم‌افزار است. پیکربندی‌ای که به دستگاه کاربران نهایی می‌رسد باید همان انتشار تدریجی (staged rollout)، همان canary و همان بازگردانی خودکارِ کد را داشته باشد؛ وگرنه یک رکورد خراب در چند دقیقه به کل ناوگان می‌رسد.
  • کلید قطع (kill switch) داشته باشید. یکی از توسعه‌دهنده‌ها در همان تاپیک گزارش کرد که خاموش کردن جمع‌آوری داده Analytics از راه دور با Analytics.setAnalyticsCollectionEnabled(false) کرش را متوقف کرده است. اما این گزینه فقط برای تیم‌هایی در دسترس بود که از پیش امکان تغییر پیکربندی از راه دور را ساخته بودند؛ بقیه جز انتظار کاری نداشتند.

دیدگاه‌ها

۰/۲٬۰۰۰

دیدگاه‌ها پس از بررسی تحریریه منتشر می‌شوند. توهین، تبلیغ و لینک‌های بی‌ربط حذف می‌شوند؛ نقد فنی و مستند همیشه خوش‌آمد است.

$ comments --count۰

هنوز کسی چیزی ننوشته. اولین دیدگاه را شما ثبت کنید.

مطالب مرتبط