پرش به مطلب اصلی

بخش ۶: Build، آزمایش و Review

تغییر یک تونل صرفاً با Compileشدن تمام نمی‌شود؛ زمانی تمام است که با Preset سالم و شناخته‌شده Build شود، Testهای Integration مرتبط را پشت سر بگذارد و از Checklist بازبینی عبور کند. این بخش روش اعتبارسنجی و ارائه‌ی نتیجه را توضیح می‌دهد.

Build

Preset مربوط به linux در CMake را ترجیح دهید. یک بار Configure و سپس Build کنید:

cmake --preset linux
cmake --build --preset linux -j8

برای Build متمرکز فقط روی Target تونلی که تغییر کرده است، که هنگام تکرار سریع‌تر خواهد بود:

cmake --build --preset linux --target TlsClient -j8

نام Target تونل‌ها با نام دایرکتوری آن‌ها یکسان است؛ مانند TcpConnector، UdpConnector، ‏TcpListener، ‏TlsClient و MuxClient.

Build Tree پیکربندی‌شده در build/linux/ قرار دارد و فایل اجرایی برای هر Configuration جداگانه تولید می‌شود؛ برای مثال build/linux/Release/Waterwall و همچنین مسیرهای Debug/ و RelWithDebInfo/. در build/ درخت‌های Preset دیگری مانند linux-gcc-x64، ‏linux-clang-x64 و linux-asan نیز وجود دارد. در یک روند اعتبارسنجی آن‌ها را با هم ترکیب نکنید.

قواعد Build

  • دستور cmake --build build ... را روی یک درخت تصادفی build/ اجرا نکنید، مگر آنکه مطمئن شده باشید درخت سالمی است که با Preset ساخته شده و قصد استفاده از همان را دارید.
  • در یک روند اعتبارسنجی Build Treeهای build/، ‏build/linux و build/linux-gcc-x64 را با هم ترکیب نکنید؛ ممکن است در نهایت فایل اجرایی قدیمی را آزمایش کنید.
  • Build مربوط به Target با Preset را به Build از Root Tree ترجیح دهید.

آزمایش

Testها با نام‌هایی مانند waterwall.<case> در CTest ثبت شده‌اند و Harness موجود در tests/ آن‌ها را اجرا می‌کند. هر مورد Integration در tests/cases/<case>/ قرار دارد.

اجرای همه‌ی Testها:

ctest --preset linux --output-on-failure

اجرای یک مورد با نام ثبت‌شده؛ از Regex دارای Anchor استفاده کنید:

ctest --preset linux --output-on-failure -R '^waterwall\.tls_roundtrip$'

برای اجرای مستقیم یک مورد Integration هنگام Debug، فایل اجرایی، دایرکتوری Case و Timeout بر حسب ثانیه را بدهید:

tests/run_waterwall_case.sh \
build/linux/Release/Waterwall \
tests/cases/tls_roundtrip \
60

Caseهای Roundtrip زیادی برای الگوبرداری Test جدید وجود دارد، از جمله encryption_roundtrip، خانواده‌ی mux_*_roundtrip، ‏http1_* / http2_*، packets_stream_bridge_roundtrip و خانواده‌ی ping_*_roundtrip. نزدیک‌ترین مورد به تونل خود را انتخاب و ساختار آن را کپی کنید.

Testی بنویسید که با برعکس‌شدن جهت Fail شود

ارزشمندترین Test برای تغییر در جریان یا Packet آن است که با جابه‌جایی Handle مربوط به upstream/downstream بشکند. در تونل‌های جفت، مانند Client/Server یا Encode/Decode، همین یک Assert رایج‌ترین Regression این Codebase را پیدا می‌کند. قواعد جهت را در بخش ۲ و بخش ۴ ببینید.

بررسی‌های Syntax-only

دستور Compile را دستی سرهم نکنید. اگر به Compile تک‌فایل یا Syntax-only نیاز دارید، Flagهای دقیق همان فایل را از build/linux/compile_commands.json تولیدشده بردارید.

خطر تداخل structure.h

هر تونل Header محلی خود را با نام include/<Tunnel>/structure.h دارد. اگر در یک دستور Compile دستی، دایرکتوری include/ چند تونل را هم‌زمان اضافه کنید، ممکن است structure.h اشتباه بی‌سروصدا انتخاب شود. نتیجه می‌تواند Errorهای گیج‌کننده یا، بدتر از آن، Buildی باشد که Layout نادرست را Link کرده است. هر تونل را جداگانه اعتبارسنجی کنید یا ترتیب اصلی Includeها را از compile_commands.json حفظ کنید. این مهم‌ترین دلیل برای پرهیز از Compile دستی است.

خطاهای رایج Compile دستی عبارت‌اند از نبود Headerهای Dependency تولیدشده، Root اشتباه مربوط به Include در Build Tree و تداخل structure.h که بالاتر توضیح داده شد.

وقتی خود Compiler از کار می‌افتد

مشکلی شناخته‌شده در محیط Build وجود دارد که طی آن یک Build Tree دستی یا ساخته‌شده بدون Preset می‌تواند باعث Internal Compiler Error در GCC شود؛ برای مثال:

internal compiler error: Bus error

این اتفاق اغلب هنگام Compileشدن فایل‌های هسته مانند ww/libc/wlibc.c رخ می‌دهد؛ حتی پیش از آنکه Compiler به تونل تغییرکرده‌ی شما برسد.

در قدم اول، GCC ICE یا Bus Error را مشکل محیط Build بدانید، نه مدرکی برای نادرست‌بودن کد تونل. به‌خاطر آن Debugکردن Source تونل را شروع نکنید.

راهبرد جایگزین در صورت مسدودشدن Compile

این مراحل را به‌ترتیب انجام دهید:

  1. cmake --preset linux
  2. cmake --build --preset linux --target <ChangedTargets> -j8
  3. cmake --build --preset linux -j8
  4. اگر GCC همچنان Crash کرد، آن را به‌عنوان مشکل Compiler/محیط Build گزارش کنید.
  5. قوی‌ترین اعتبارسنجی باقی‌مانده را ادامه دهید:
    • Buildهای Target-based با Preset؛
    • بررسی Syntax-only با build/linux/compile_commands.json؛
    • Review متمرکز کد بر اساس قراردادهای این راهنما.

خلاصه‌ی اولویت‌های Build:

  • Preset مربوط به linux را ترجیح دهید؛
  • Build یک Target با Preset را بر Root مربوط به build/ ترجیح دهید؛
  • build/linux/compile_commands.json را به Flagهای حدسی ترجیح دهید؛
  • ابتدا GCC ICE یا Bus Error را مشکل محیط در نظر بگیرید.

Checklist بازبینی

پیش از Mergeکردن تغییر تونل، همه‌ی موارد مرتبط را تأیید کنید:

چرخه‌ی عمر و جهت

  • Init پیش از استفاده‌ی هر Callback، ‏Line State همین تونل را مقداردهی می‌کند.
  • Callbackهای upstream فقط با tunnelNextUpStream* Forward می‌شوند.
  • Callbackهای downstream فقط با tunnelPrevDownStream* Forward می‌شوند.
  • هیچ Flagی به نام initialized که Source Code نیازی به آن ندارد اضافه نشده است.

ایمنی Line

  • هر Callback از نوع Re-entrant یا بلافاصله برمی‌گردد، یا با withLineLocked() / Lock دستی و lineIsAlive() از Line محافظت می‌کند.
  • وقتی withLineLocked() مقدار false برمی‌گرداند، کد به line، ‏Line State یا LinestateDestroy() دست نمی‌زند.
  • فقط مالک Line تابع lineDestroy() را فراخوانی می‌کند.

Finish

  • Finish پیش از Propagateکردن Close واقعی، State محلی را نابود می‌کند.
  • Teardown از تونل میانی ابتدا upstream و سپس downstream را Finish می‌کند و بعد برمی‌گردد.
  • از Reflection مربوط به Pause/Resume به‌سمت Finishشده جلوگیری می‌شود.
  • اگر هنگام Finish ‏Byteهای پایانی پروتکل فرستاده می‌شوند، پیش از ارسال سمت فرستنده Finishشده علامت می‌خورد.

بافرها و Padding

  • هر Prepend در محدوده‌ی required_padding_left اعلام‌شده‌ی تونل قرار دارد.
  • فقط با ظرفیت کافی سمت چپ از sbufShiftLeft() استفاده می‌شود.
  • بافرها دقیقاً در مسیرهایی Recycle می‌شوند که هنوز مالکشان هستند و هیچ بافری Leak نمی‌شود.

معنای Packet، در صورت ارتباط

  • Packet Lineها در Runtime عادی زنده نگه داشته می‌شوند.
  • State مربوط به Packet Line، ‏State محلی و مشترک Worker در نظر گرفته می‌شود.
  • منبع Init مربوط به Packet Line، یعنی Node Manager یا onStart دستی، بررسی شده است.

اعتبارسنجی

  • Testها جهت موردنظر Callback را واقعاً پوشش می‌دهند.
  • اعتبارسنجی از Metadata مربوط به Build با Preset استفاده کرده است، نه Flagهای حدسی Compiler.

فهرست جامع خطاهای رایج

این راهنما برای پیشگیری از خطاهای زیر نوشته شده است:

  • دسترسی به Line State تونل پس از LinestateDestroy() یا خواندن فیلدهای Zeroشده.
  • افزودن Flagهای initialized برای جبران Control Flow ناامن.
  • دست‌زدن به Pointer نوع line_t* پس از Callback میان‌تونلی، بدون محافظت.
  • فراخوانی tunnelNext* یا tunnelPrev* در جهت اشتباه.
  • Reflection مربوط به Pause/Resume/Finish به‌سمتی که Finish شده است.
  • تولید Est، ‏Pause، ‏Resume یا Finish در زمان نادرست.
  • زنده نگه‌داشتن Line در طول Finish بدون lineLock().
  • فراموش‌کردن اینکه lineDestroy() انتظار دارد State تونل‌ها Zero شده باشد، مگر آنکه Reference Count همچنان مثبت باشد.
  • Leakشدن بافر وقتی Callback، ‏Line را از بین می‌برد.
  • استفاده‌ی نادرست از sbufShiftLeft() یا نادیده‌گرفتن required_padding_left.
  • رفتاری که فقط در یک چیدمان زنجیره کار می‌کند و قابلیت ترکیب عمومی را می‌شکند.
  • درنظرگرفتن Packet Line State به‌عنوان State مختص اتصال، یا نابودکردن Packet Line هنگام Runtime.
  • ساختن دستی دستور Compile در حالی که Metadata مربوط به Build با Preset وجود دارد.

خروجی مورد انتظار از Coding Agent

وقتی یک Coding Agent مبتنی بر AI روی WaterWall کار می‌کند، پاسخ آن باید ساختار زیر را داشته باشد. این قالب قراردادهای مهم را آشکار می‌کند؛ تغییری که نتواند درباره‌ی آن‌ها توضیح بدهد آماده نیست.

  1. درک مسئله — شرح کوتاه جریان تونل مرتبط؛ زنجیره را رسم کنید، جهت‌ها را مشخص کنید و نام مالک Line را بگویید.
  2. تغییر — Bug یا قابلیت درخواستی را دقیق توضیح دهید.
  3. حفظ درستی — توضیح دهید تغییر چگونه موارد زیر را حفظ می‌کند:
    • ترتیب چرخه‌ی عمر؛
    • ایمنی Line؛
    • مالکیت جهت؛
    • قواعد بافر و Padding؛
    • معنای Packet Line، در صورت ارتباط.
  4. پیاده‌سازی — تغییر واقعی با الگوبرداری از نزدیک‌ترین تونل پایدار.
  5. اعتبارسنجی — دستورهای اجراشده، نتیجه‌ی Build و Testها و بررسی‌های جایگزین.
  6. ریسک‌ها — Edge Caseها یا کارهای تکمیلی باقی‌مانده.

اگر چیزی روشن نیست، بر اساس Source Code و الگوهای موجود WaterWall محافظه‌کارانه استنباط کنید. مدل چرخه‌ی عمر تازه‌ای نسازید.


این پایان راهنمای توسعه‌ی WaterWall است. برای دیدن نقشه‌ی هر شش بخش به بخش ۱: نمای کلی و مدل ذهنی بازگردید.