بخش ۶: 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. نزدیکترین مورد
به تونل خود را انتخاب و ساختار آن را کپی کنید.
بررسیهای 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
این مراحل را بهترتیب انجام دهید:
cmake --preset linuxcmake --build --preset linux --target <ChangedTargets> -j8cmake --build --preset linux -j8- اگر GCC همچنان Crash کرد، آن را بهعنوان مشکل Compiler/محیط Build گزارش کنید.
- قویترین اعتبارسنجی باقیمانده را ادامه دهید:
- 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شده جلوگیری میشود. - اگر هنگام
FinishByteهای پایانی پروتکل فرستاده میشوند، پیش از ارسال سمت فرستنده 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 کار میکند، پاسخ آن باید ساختار زیر را داشته باشد. این قالب قراردادهای مهم را آشکار میکند؛ تغییری که نتواند دربارهی آنها توضیح بدهد آماده نیست.
- درک مسئله — شرح کوتاه جریان تونل مرتبط؛ زنجیره را رسم کنید، جهتها را مشخص کنید و نام مالک Line را بگویید.
- تغییر — Bug یا قابلیت درخواستی را دقیق توضیح دهید.
- حفظ درستی — توضیح دهید تغییر چگونه موارد زیر را حفظ میکند:
- ترتیب چرخهی عمر؛
- ایمنی Line؛
- مالکیت جهت؛
- قواعد بافر و Padding؛
- معنای Packet Line، در صورت ارتباط.
- پیادهسازی — تغییر واقعی با الگوبرداری از نزدیکترین تونل پایدار.
- اعتبارسنجی — دستورهای اجراشده، نتیجهی Build و Testها و بررسیهای جایگزین.
- ریسکها — Edge Caseها یا کارهای تکمیلی باقیمانده.
اگر چیزی روشن نیست، بر اساس Source Code و الگوهای موجود WaterWall محافظهکارانه استنباط کنید. مدل چرخهی عمر تازهای نسازید.
این پایان راهنمای توسعهی WaterWall است. برای دیدن نقشهی هر شش بخش به بخش ۱: نمای کلی و مدل ذهنی بازگردید.