17.1 Projektkövetelmény, protokoll és ownership térkép

A záróprojektben egyetlen, pontosan körülhatárolt feladatot valósítunk meg: név=érték alakú, LF-fel lezárt rekordokból álló bájtfolyamot ellenőrzünk, majd rövid, gépileg feldolgozható összesítést készítünk róla. Ugyanazt a dg_* parserkönyvtárat használja a fájlból vagy standard bemenetről olvasó diag parancs és a TCP-kapcsolatokat kiszolgáló adapter. A 39. fejezet C++20 portja is ezt a szerződést viszi tovább, változtatás nélkül.

A cél nem egy éles monitoringrendszer, hanem egy ellenőrizhető rendszerprogramozási szerződés: mely bájtok érvényesek, mennyi erőforrást használhatunk, ki birtokolja az állapotot, és mi történik részleges bemenet vagy leállítás esetén.

A bounds-ellenőrzött parser állapotátmeneteire, az eseményvezérelt I/O modelljére és az opaque könyvtári interfész elválasztására építünk. A könyvtári magot és a CLI-t a C könyvtári mag és CLI megvalósítási labor, a hálózati adaptert a Nonblocking TCP adapter és leállítás valósítja meg. Ez az alfejezet a kanonikus projektszerződés: a későbbi fejezetek kódja és tesztjei ehhez igazodnak, és ha eltérnek tőle, az hiba.

17.1.1 A szolgáltatás és a parancssori felület szerződése

A rekordfolyam nulla vagy több, közvetlenül egymás után írt rekordból áll. Nincs köztük elválasztó, és nincs kötelező zárórekord. Az üres folyam érvényes. A bemenet végét a fájlvég jelzi.

A CLI használata:

diag [FILE|-]

Argumentum nélkül vagy - argumentummal a program a standard bemenetet olvassa, egyébként a megadott fájlt. Kettőnél több parancssori szó használati hiba.

A CLI nem keres új rekordhatárt sérült bemenet után. Az első hibánál megáll, mert az ismeretlen bájtok átugrása könnyen érvényesnek láttatna egy sérült folyamot. Részleges sikerjelentés nincs: hibás bemenetnél a standard kimenet üres marad.

Siker esetén a standard kimenetre pontosan egy sor kerül:

OK records=2 sum=19 bytes=11
Mező Szerződés
records A teljes egészében ellenőrzött, érvényes rekordok száma
sum Az érvényes rekordok értékeinek összege
bytes A parser által elfogyasztott bemeneti oktettek száma

A mezősorrend, az egyetlen ASCII szóköz elválasztó és a záró LF a szerződés része. A sor nem tartalmaz bemenetből átvett szöveget vagy útvonalat, ezért hossza korlátos, és nincs szükség escape-elésre.

Hiba esetén a diagnosztika a standard hibakimenetre kerül, így a gépi kimenetet nem szennyezi. Parserhibánál a sor a hibakódot és a hiba bájtoffsetjét tartalmazza:

ERR code=truncated offset=10

Más hibához nem tartozik értelmes bájtpozíció, ezért ott csak a kód szerepel, például ERR code=open.

Diagnosztikai kód Forrás Kilépési kód
syntax, truncated, limit Parser: hibás, csonka vagy túl hosszú bemenet 4
state Parser: API-használati hiba, például finish utáni feed 5
usage Túl sok argumentum 2
open, read, close A bemenet megnyitása, olvasása vagy saját fájl lezárása 3
alloc, setup Parserallokáció vagy a SIGPIPE kezelésének beállítása 5
write A jelentés kiírása vagy flush-a 6

Sikeres futás kilépési kódja 0. A kilépési kódok a program rendes vezérlési útjaira vonatkoznak, nem a folyamat külső, kényszerített megszüntetésére. A hibakimenet írása best effort: hibás stderr esetén üzenetet nem tudunk garantálni.

17.1.2 A rekord pontos formátuma

17.1.2.1 Nyelvtan és rekordtípusok

Projektszerződés: a bemenet 8 bites oktettekből áll. A laborban CHAR_BIT == 8, ezért egy oktett egyben egy C bájt. Ezt a könyvtár fordításkor ellenőrzi. A karakterek alább mindig a megadott ASCII-kódú oktettet jelentik.

Egy rekord nyelvtana ABNF-hez hasonló jelöléssel:

rekord = név "=" érték LF
név    = 1*16( %x61-7A )      ; a–z
érték  = 1*5( %x30-39 )       ; 0–9, decimális értéke <= 65535
LF     = %x0A
Elem Szerződés
Név 1–16 ASCII kisbetű, kizárólag a–z
Elválasztó Egyetlen = (0x3d)
Érték 1–5 decimális számjegy, értéke legfeljebb 65535
Vezető nullák Megengedettek, de beleszámítanak az öt számjegybe
Rekordlezárás Kötelező LF; CRLF nem elfogadott
Ismétlődő név Megengedett; minden rekord külön számít
Teljes bemenet Legfeljebb 1 048 576 oktett

Minden más oktett a nyelvtan által nem engedett helyen szintaktikai hiba: nagybetű, szóköz, \r, 0x00, üres név vagy érték, 17. névbetű, 6. számjegy, illetve olyan számjegy, amellyel az érték 65535 fölé nőne. A parser bájtokat vizsgál, nem C-sztringeket: a beágyazott nullbájt nem rövidíti meg a bemenetet, hanem hiba.

A parser nem tárolja el a neveket: a projekt a rekordfolyam szerkezeti érvényességét és összesített számait vizsgálja, nem metrikanevekből épít adatbázist.

17.1.2.2 Kidolgozott rekordpélda

A a=7 és a cpu=12 rekord együtt, hexadecimálisan:

61 3d 37 0a
63 70 75 3d 31 32 0a

Az első rekord 4, a második 7 oktett, összesen:

B=4+7=11. B = 4 + 7 = 11.

Az eredmény OK records=2 sum=19 bytes=11, mert 7+12=197+12=19. A bemenet tetszőleges darabolásban érkezhet: egy olvasás visszaadhat két oktettet, egy későbbi a maradékot. A parser eredménye nem függhet ettől a darabolástól.

Ha a második rekord záró LF-je hiányzik, a fájlvégkor:

17.1.2.3 A TCP-adapter kérés–válasz szerződése

A TCP-adapter ugyanerre a nyelvtanra épül, de kérés–válasz protokollként működik. Egy kérés pontosan egy rekord, az első LF-ig bezárólag. A válasz az az egyetlen sor, amelyet a CLI erre az egy rekordból álló bemenetre a standard kimenetre (siker) vagy a standard hibakimenetre (parserhiba) írna. Az offset ezért a kérés első oktettjéhez viszonyított.

Kérés Válasz
a=7\n OK records=1 sum=7 bytes=4\n
cpu=12\n OK records=1 sum=12 bytes=7\n
A=1\n ERR code=syntax offset=0\n
Parserallokációs hiba ERR code=alloc\n

Mivel az LF minden kérést egyértelműen lezár, egy szintaktikailag hibás kérés után a kapcsolat a következő kéréssel folytatódhat. Keretszintű hiba viszont kapcsolatbontást okoz válasz nélkül: ilyen a kapcsolat bemeneti tárolóját (4096 oktett) LF nélkül megtöltő adat, illetve a félbemaradt kérés a kliens küldési irányának lezárásakor.

17.1.3 Erőforráskorlátok és ellenőrzési sorrend

A limitek a protokoll megvalósításának részei, nem pusztán üzemeltetési ajánlások. Mindegyiket egyetlen helyen, névvel ellátott konstans rögzíti; a számításokat ezekből vezetjük le.

Erőforrás Projektlimit Konstans, forrás
Egy munkamenet teljes bemenete 1 048 576 oktett DG_MAX_INPUT, include/diag.h
Egy rekord, LF-fel legfeljebb 23 oktett a nyelvtanból: 16+1+5+116+1+5+1
Parser saját tárolója egyetlen, rögzített méretű objektum struct dg_parser
CLI olvasópuffere 4096 C bájt src/main.c
Aktív TCP-kapcsolatok 64 a szerver kapcsolattáblája
Kapcsolatonkénti bemeneti tároló 4096 C bájt DIAG_IN_CAP, include/diag_tcp.h
Kapcsolatonkénti kimeneti tároló 1024 C bájt DIAG_OUT_CAP, include/diag_tcp.h

A parser inkrementálisan dolgozik, ezért egyik puffernek sem kell egy teljes munkamenetet vagy rekordot befogadnia. A TCP bemeneti tároló a leghosszabb érvényes kérésnél (23 oktett) jóval nagyobb, így pipelined kérések is elférnek benne. A válasz legfeljebb 32 oktett (OK records=1 sum=65535 bytes=23\n), tehát a kimeneti tároló is elegendő.

A TCP-kiszolgáló (diag_server [PORT]) csak a 127.0.0.1 címen figyel; 0 vagy hiányzó port esetén a kernel választ portot. Indulás után a standard kimenetre egyetlen LISTEN 127.0.0.1:PORT sort ír. Kilépési kódja rendes leállítás után 0, hibás argumentumnál 2, indítási vagy eseményciklus-hibánál 5. TLS, hitelesítés és hálózati publikálás nem része ennek a projektnek.

17.1.3.1 Méretellenőrzés túlcsordulás nélkül

Legyen MM a munkamenet méretlimitje, BB az eddig elfogyasztott oktettek száma. A parser minden új oktett előtt ellenőrzi:

B<M. B < M.

Ha B=MB = M, az új oktettet nem fogyasztja el, és limit hibát ad BB offsettel. Nem vezetünk be B+nB + n alakú összeget, amely túlcsordulhatna: a számláló oktettenként, egyesével nő, és az invariáns szerint sosem lépi át MM-et.

Az érték túlcsordulását ugyanígy a művelet előtt zárjuk ki. Ha vv az eddigi érték és dd az új számjegy, akkor a v′=10v+dv' = 10v+d érték akkor fér el, ha

v≤⌊65535−d10⌋. v \le \left\lfloor\frac{65535-d}{10}\right\rfloor.

A részletes levezetést és az összeg felső korlátját a megvalósítási labor adja.

Az ellenőrzési sorrend determinisztikus:

  1. Új oktett előtt a teljes bemeneti limit.
  2. Az oktett a rekord aktuális fázisában: névbetű, =, számjegy vagy LF.
  3. Számjegynél a számjegyszám és az értékhatár.
  4. Fájlvégnél (finish) a félbemaradt rekord.

A határ elérése önmagában nem hiba. Pontosan MM oktettnyi érvényes bemenet után a fájlvég sikeres; egy további oktett már limit hibát jelent, elfogyasztás nélkül.

A hibát kiváltó oktettet a parser nem fogyasztja el, ezért szintaktikai hibánál az offset az első elutasított oktett helye, csonkolásnál a bemenet vége.

17.1.3.2 Idő- és memóriakeret

A TCP-adapter monoton órán mért határidői:

Határidő Érték Kezdőesemény
Tétlen, üres kapcsolat 30 s Az utolsó teljesen feldolgozott kérés vagy az elfogadás
Befejezetlen kérés 5 s A parseradapter első DIAG_MORE eredménye
Függő válasz 5 s A válasz sorba állítása
Teljes szerverleállítás 3 s A leállítás kezdete

Egy-egy új oktett vagy részleges küldés nem indítja újra a határidőt. Ezek alkalmazásszintű határidők, nem valós idejű ütemezési garanciák: a lejáratot az eseményciklus észleli.

A kapcsolatok puszta adattömbjeinek felső korlátja:

64(4096+1024)=327680C bájt=320KiB. 64\,(4096 + 1024) = 327\,680\ \text{C bájt} = 320\ \text{KiB}.

Ez nem RSS-garancia. Nem tartalmazza a struktúratagokat, az allocator metaadatait, a libc belső állapotát, a végrehajtási vermet vagy a kernel socketpuffereit. Azokat külön kell mérni. Az aktívkapcsolat-limit a már elfogadott kapcsolatokra vonatkozik, nem a kernel várakozó kapcsolati sorára.

17.1.4 Modulhatárok és hibaszerződések

17.1.4.1 A könyvtári mag nem végez I/O-t

A projekt fájlszintű felosztása:

Modul Feladat Hibakimenet
include/diag.h, src/diag.c Opaque parserállapot: dg_create, dg_feed, dg_finish, dg_get_report, dg_get_error, dg_code_name, dg_destroy Ragadós dg_error; létrehozási hiba: NULL
src/main.c CLI: argumentumok, bemeneti FILE, report formázása Kilépési kód és diagnosztika
include/diag_tcp.h, src/tcp_io.c Kapcsolatonkénti nonblocking olvasás, írás, backpressure enum diag_step
src/tcp_reply.c Core-adapter a TCP-hez: egy kérés feldolgozása és a válasz formázása enum diag_core_result
src/diag_server.c Szerver-vezérlő: listener, epoll, határidők, leállítás Kapcsolat lezárása; kilépési kód

A kapcsolóelemek pontos jelentése:

17.1.4.2 A specifikációs példák mint futtatható teszt

Az alábbi teszt a fenti nyelvtan példáit rögzíti, és a megvalósítási labor könyvtárához linkelve fut. A táblázat a specifikációból származik, nem az implementáció kimenetéből; így a megvalósítást a szerződéshez mérjük, nem önmagához.

/* tests/spec_examples.c -- a 17.1 specifikációs példái a 17.2 magon */
#include "diag.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct example {
    const char *input;      /* a bemenet bájtjai */
    size_t size;            /* beágyazott 0x00 miatt külön hossz */
    dg_code code;           /* DG_OK vagy a várt hibakód */
    size_t offset;          /* hiba esetén */
    size_t records;         /* siker esetén */
    unsigned long long sum;
    size_t bytes;
};

#define EX(s) s, sizeof s - 1

static const struct example examples[] = {
    { EX(""),                     DG_OK,         0, 0,  0,  0 },
    { EX("a=7\ncpu=12\n"),        DG_OK,         0, 2, 19, 11 },
    { EX("a=65535\n"),            DG_OK,         0, 1, 65535, 8 },
    { EX("abcdefghijklmnop=1\n"), DG_OK,         0, 1,  1, 19 },
    { EX("a=7\ncpu=12"),          DG_TRUNCATED, 10, 0,  0,  0 },
    { EX("cpu="),                 DG_TRUNCATED,  4, 0,  0,  0 },
    { EX("a=65536\n"),            DG_SYNTAX,     6, 0,  0,  0 },
    { EX("a=000001\n"),           DG_SYNTAX,     7, 0,  0,  0 },
    { EX("abcdefghijklmnopq=1\n"),DG_SYNTAX,    16, 0,  0,  0 },
    { EX("a=1\r\n"),              DG_SYNTAX,     3, 0,  0,  0 },
    { EX("a=\n"),                 DG_SYNTAX,     2, 0,  0,  0 },
    { EX("=1\n"),                 DG_SYNTAX,     0, 0,  0,  0 },
    { EX("A=1\n"),                DG_SYNTAX,     0, 0,  0,  0 },
    { EX("a=1\0\n"),              DG_SYNTAX,     3, 0,  0,  0 },
};

int main(void)
{
    int failures = 0;

    for (size_t i = 0; i < sizeof examples / sizeof examples[0]; ++i) {
        const struct example *ex = &examples[i];
        dg_parser *parser = dg_create(NULL);
        if (parser == NULL)
            return EXIT_FAILURE;

        dg_view view = { (const unsigned char *)ex->input, ex->size };
        bool ok = dg_feed(parser, view) && dg_finish(parser);
        dg_error e = dg_get_error(parser);
        dg_report r = dg_get_report(parser);

        bool pass = ok ? ex->code == DG_OK && r.records == ex->records &&
                         r.sum == ex->sum && r.bytes == ex->bytes
                       : e.code == ex->code && e.offset == ex->offset;
        if (!pass) {
            (void)fprintf(stderr, "example %zu failed\n", i);
            ++failures;
        }
        dg_destroy(parser);
    }
    return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

A "a=000001\n" példa a hatodik számjegynél (offset 7) hibás, akkor is, ha az érték kicsi: a számjegyszám-korlát a nyelvtan része, nem az értékhatár következménye.

Fordítás és futtatás a 17.2 buildje után:

gcc -std=c17 -O2 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror \
    -Iinclude tests/spec_examples.c build/gcc/libdiag.a \
    -o build/gcc/spec_examples
build/gcc/spec_examples

Buildjegyzőkönyv (lab-gcc profil). A teszt, a 17.2 könyvtára és CLI-je Linux x86-64 környezetben, GCC 13.3.0, glibc 2.39 és GNU binutils 2.42 mellett hibaüzenet és figyelmeztetés nélkül fordult, és mind a 14 példa sikeres volt. Clanggal ebben a jegyzőkönyvben nem fordítottunk.

17.1.5 Tulajdonlási térkép és leállítás

17.1.5.1 Owner, tároló és kölcsönzés

Erőforrás vagy nézet Tulajdonos Élettartam és érvénytelenítés
CLI által megnyitott bemeneti FILE CLI fopen sikerétől a cleanup-ágban végzett egyszeri fclose-ig
stdin, stdout, stderr Futási környezet; a CLI csak kölcsönzi A CLI nem zárja le
Listener fd Szerver-vezérlő Sikeres létrehozástól leállításig
Elfogadott socket fd Kapcsolatrekesz Átadás után kizárólag a rekesz teardownja zárja le
CLI parserobjektuma CLI dg_create sikerétől dg_destroy-ig
TCP-kérés parserobjektuma diag_core_reply hívás A hívás végéig; visszatérés előtt megszűnik
Kapcsolat bemeneti és kimeneti tömbje Kapcsolatrekesz A rekesz élettartama; a bemenet tömörítéskor változik
Parsernek átadott bájtszelet Nem tulajdonló nézet Csak a dg_feed hívás alatt használható
Következő kiírandó válaszrész Nem tulajdonló nézet A kimeneti tömb újrafelhasználása érvényteleníti

A kapcsolat életciklusa szöveges ownership diagramként:

szerver-vezérlő (owner)
  -> kapcsolatrekesz (owner)
       -> socket fd
       -> bemeneti tömb -> diag_core_reply alatti borrowed szelet
            -> rövid életű parser (a hívás alatt)
       -> kimeneti tömb -> részleges írás alatti borrowed szelet

A parser élettartama a TCP-adapterben rövidebb a kapcsolaténál: a válasz a hívás végéig saját másolatként a kimeneti tömbbe kerül. A kimeneti tömb tartalma viszont egy részleges írás után változatlanul megmarad, amíg az utolsó bájt el nem ment.

Az elfogadás utáni inicializálási hiba külön ownership eset. Ha a kapcsolatrekesz még nem vette át a socketet, az elfogadó ág zárja le. Sikeres átadás után már nem zárhatja le másodszor.

17.1.5.2 EOF, hiba és shutdown

A CLI egy munkamenetet dolgoz fel: a fájlvégnél hívja a dg_finish műveletet, majd kiírja a jelentést.

A TCP-kapcsolaton a recv nulla visszatérési értéke jelzi a kliens küldési irányának lezárását. Ha ekkor nincs tárolt, félbemaradt kérés, a kapcsolat rendben lezárul; ha van, az keretszintű hiba, és a kapcsolat válasz nélkül bomlik. A recv ideiglenes „nincs adat” eredménye nem fájlvég.

A kapcsolat állapotai és az eseményérdeklődés a TCP-adapter állapottábláját követik. Reset vagy írási hiba esetén a válasz nem kézbesíthető; ilyenkor teardown következik. A helyi sikeres send() sem bizonyítja, hogy a távoli alkalmazás feldolgozta a választ.

Kezelt leállításkor:

  1. A listener figyelése megszűnik, a listener bezárul; új kapcsolatot nem fogadunk.
  2. Rögzítjük a közös, 3 másodperces leállítási határidőt.
  3. Minden kapcsolaton eldobjuk a még fel nem dolgozott bemenetet; új kérést nem dolgozunk fel.
  4. A már sorba állított válaszokat a határidőig kiírjuk, majd féllezárás következik.
  5. A határidő után a maradó kapcsolatokat lezárjuk, és felszabadítjuk az állapotukat.

A signalkezelés mechanikáját nem itt vezetjük le. A követelmény az, hogy a felszabadítás és a reportkészítés rendes vezérlési környezetben történjen.

17.1.6 Elfogadási tesztek és tipikus hibák

Az elfogadás a minőségi kapuk elvére épül: minden követelményhez megfigyelhető ellenőrzés tartozik.

Eset Elvárt eredmény
Üres bemenet OK records=0 sum=0 bytes=0, kilépési kód 0
A bemutatott 11 oktettes folyam OK records=2 sum=19 bytes=11
Ugyanez minden lehetséges kettévágással Azonos végeredmény
Fájlvég félbemaradt rekordban truncated, offset a bemenet vége, kilépési kód 4
Nyelvtanon kívüli oktett syntax, az első elutasított oktett offsetje
65535, illetve 65536 érték Elfogadás, illetve syntax az utolsó számjegynél
262 144 darab a=0\n rekord; majd plusz egy oktett Siker 1 048 576 bájttal, illetve limit 1 048 576 offsettel
diag FILE extra ERR code=usage, kilépési kód 2, üres stdout
Nem létező fájl ERR code=open, kilépési kód 3
TCP: két érvényes kérés egy szegmensben, majd egy hibás Három válaszsor, sorrendben
TCP: részleges válaszírás, ismételt várakozás Nincs adatvesztés, duplikálás vagy pufferfelülírás
TCP: 64 aktív kapcsolat után új elfogadás Az új kapcsolat nem kap rekeszt, nincs állapotbővítés
Létrehozási hiba és shutdown Minden saját fd és tároló pontosan egyszer szabadul

A pontos méretlimit fixture-je a legrövidebb rekordból épül: 262144⋅4=1048576262\,144 \cdot 4 = 1\,048\,576 oktett.

Gyakori hiba a bytes számláló növelése a teljes read eredményével akkor is, ha a parser hibát talált a darab közepén. A javítás: a jelentett bájtszám mindig a parser által elfogyasztott mennyiség, nem az olvasott.

Ugyancsak hibás a finish meghívása átmeneti olvasási szünetben, a kimeneti tömb újrafelhasználása részleges írás közben, illetve az fd másolatainak külön tulajdonosként kezelése. Az fd egész értékének másolása nem hoz létre új erőforrást.

17.1.7 Feladatok

17.1.7.1 Magyarázó feladat: hol van a hibapozíció?

A bemutatott kétrekordos folyamból a második rekord értékének utolsó számjegye és a záró LF is hiányzik: a=7\ncpu=1. Add meg a CLI kimenetét és kilépési kódját. Miért nem szabad a második rekordot megszámolni, és miért nincs részleges OK jelentés?

17.1.7.2 Kódolási feladat: specifikációs példák bővítése

Bővítsd a tests/spec_examples.c táblázatát a következő esetekkel: üres sor (\n), dupla =, 16 betűs név öt számjeggyel, cpu LF nélkül, valamint két rekord, amelyek közül a második hibás. Minden várt értéket a nyelvtanból vezess le, ne a program futtatásából.

17.1.7.3 Diagnosztikai feladat: eltűnő válasz és fd-szivárgás

Egy TCP-adapter a választ lokális tömbben formázza, eltárolja annak pointerét a kapcsolatban, majd visszatér. Másik hibája, hogy sikertelen rekeszinicializáláskor visszatér az elfogadási ágból, de nem zárja le a socketet. Készíts ownership-alapú javítási tervet.

17.1.8 Megoldások

17.1.8.1 A hiányos rekord eredménye

A bemenet a=7\ncpu=1, 9 oktett. A parser mind a 9 oktettet elfogyasztja, mert mindegyik a nyelvtan szerinti helyen áll. A fájlvégnél a második rekord a számfázisban van, ezért a finish csonkolást jelez:

ERR code=truncated offset=9

A kilépési kód 4, a standard kimenet üres. A második rekord csak a záró LF után számítható be, mert addig nem tudható, hogy érvényes-e. Részleges OK jelentés azért nincs, mert a szerződés a teljes munkamenetről szól: egy csonka folyam összegét a fogyasztó tévesen teljesnek tekinthetné.

Ellenőrzési feltétel: minden bemeneti darabolás ugyanezt az eredményt adja.

17.1.8.2 A bővített példák várt értékei

Bemenet Várt eredmény
"\n" syntax, offset 0: üres név
"a==1\n" syntax, offset 2: a második = nem számjegy
"abcdefghijklmnop=65535\n" siker: 1 rekord, összeg 65535, 23 bájt
"cpu" truncated, offset 3
"a=1\nb=x\n" syntax, offset 6

Az utolsó esetben az első rekord 4 oktettje elfogyott, a b és a = is; az x a 6. offseten áll. A tesztek a tényleges visszatérési értéket ellenőrizzék, ne kizárólag kikapcsolható assert állításokra épüljenek.

17.1.8.3 A tulajdonlás javítása

A válasz tárolója legyen a kapcsolatrekesz kimeneti tömbje; így a részleges írás után is él. A lokális tömbre mutató pointer a függvényből visszatéréskor dangling lesz.

A socketnek az elfogadás pillanatától legyen azonosítható tulajdonosa. Inicializálási hiba esetén az elfogadó ág zárja le; sikeres átadás után a rekesz teardownja felel érte.

Ellenőrzési feltétel: késleltetett írással és létrehozási hiba-injektálással se maradjon érvénytelen pointer vagy nyitva felejtett fd.

17.1.9 Összefoglaló

A projekt lényegi eredménye a közös, ellenőrizhető szerződés: egyértelmű rekordnyelvtan, rögzített CLI-kimenet és kilépési kódok, korlátozott erőforrás-használat, I/O-tól független parser és explicit ownership. A CLI és a TCP-adapter ugyanazt a dg_* magot használja, és ugyanazt a sorformátumot írja; csak az adatmozgatás és a lezárás mechanikája különbözik. A következő megvalósítási lépések és a 39. fejezet C++ portja ehhez a szerződéshez mérendő, nem pusztán néhány sikeres futáshoz.