Săriți la conținut

Blog ·

Un flux este un graf, iar el trebuie să fie finit

Transformarea unei liste de pași într-un editor de noduri pare o schimbare de interfață. Este o decizie despre bucle, ramuri neluate și un clic furat.

De Provibr

Un flux din panoul nostru face un singur lucru, foarte îngust: atunci când un server a terminat de aprovizionat, produce text. Un mesaj de bun venit, un fragment de configurare, o notă pentru tichet. Nu repornește niciun hipervizor și nu vorbește cu nicio interfață de programare. Tocmai această îngustime face ca funcția să poată fi dată în mâna unui client fără teamă.

Până la această rundă un flux era o listă: pasul unu, pasul doi, pasul trei, cu oprire la prima eroare dacă o bifă nu spunea altceva. Cererea a fost să devină o pânză, cu noduri și fire. Sună a altă afișare a acelorași date. Nu este, iar partea interesantă sunt întrebările pe care o pânză te obligă să le răspunzi și la care o listă răspundea deja singură.

Versiunea unu nu are bucle, iar asta nu este lene

Un fir care se întoarce înapoi este primul lucru pe care îl încearcă oricine. Îl refuzăm la salvare, iar mesajul numește nodurile care formează ciclul.

Motivul ține de natura subsistemului și nu de dificultatea codului. Un flux de randare nu are stare de așteptare. Nu există nod de întârziere, nu există așteptare după o aprobare, nu există un partener care raportează mai târziu; fiecare nod este sincron și gata în câteva milisecunde. O buclă fără stare de așteptare este așadar infinită prin construcție, iar singurul mod de a o îmblânzi ar fi un contor de iterații ascuns: o limită invizibilă pe care nimeni nu o vede și care, în ziua în care este atinsă, taie o execuție la jumătate fără ca cineva să poată explica de ce.

Refuzul ciclului la salvare este versiunea onestă a aceleiași limite. De îndată ce va exista o stare de așteptare reală, echilibrul se schimbă și o buclă devine o decizie de proiectare cu un motiv în spate. Astăzi ar fi o capcană.

Ramificarea este secvențială dintr-un motiv înrudit. Două fire care pleacă din același port sunt urmate unul după altul, în ordinea salvată, iar prima ramură este parcursă până la capăt înainte ca a doua să înceapă. Randarea lor în paralel ar însemna că două ramuri scriu în același timp în aceleași variabile, iar atunci rezultatul unui flux ar depinde de randarea care s-a terminat prima din întâmplare. Randarea costă milisecunde, deci nu este nimic de câștigat și este reproductibilitate de pierdut.

Șase feluri și porturi care trebuie să fie o funcție

Există șase feluri de noduri, intenționat puține: declanșator, script, ramificație, atribuire, notă și final. Fiecare fel nou înseamnă o intrare în paletă, un panou de proprietăți, nouă traduceri, o ramură în executor și un rând în tabelul tranzițiilor, așa că un test derivă acele cerințe chiar din listă. Un al șaptelea fel fără panou este un test roșu, nu un nod pe jumătate funcțional.

FelIeșiri
triggeruna, și există exact un declanșator pe graf
scriptok și error
switchuna pe regulă, plus otherwise
setuna
noteniciuna, și nu este vizitat niciodată
endniciuna, și rostește rezultatul

Rândul ramificației este motivul pentru care ieșirile sunt calculate de o funcție și nu citite dintr-un tabel. O ramificație cu trei reguli are patru ieșiri; un tabel fix ar lăsa pe cineva să adauge o a patra regulă fără să apară o ieșire, iar acea ramură ar dispărea în tăcere. Tot ce își schimbă forma în funcție de configurație nu se descrie cu un tabel fix.

Validarea se desfășoară într-o ordine fixă și fiecare refuz își numește nodul: identificatori duplicați, zero sau doi declanșatori, o cale de variabilă în afara catalogului, un tipar nesigur, un port necunoscut, un fir duplicat, un ciclu, un nod inaccesibil și, în final, mai multe noduri executabile decât limita de douăzeci și cinci. Ordinea contează, fiindcă primul refuz este cel care se citește, iar „aici există un ciclu” ajută mai mult decât „acest port nu există” atunci când amândouă sunt adevărate.

Scrierea în state. și niciodată în flow.

Un nod set scrie variabile. Spațiul de nume evident este cel care există deja, și este cel greșit. flow.name, flow.runId și flow.step poartă fapte despre execuție, deci un nod cu drept de scriere acolo ar lăsa un client să suprascrie flow.name, iar urma de audit ar minți într-o formă care arată ca o dată.

De aceea set scrie în state., un spațiu de nume nou, gol la început și care există doar cât durează o execuție. Un șablon care citește {{ state.role }} fără ca vreun nod să îl fi definit eșuează la fel ca orice altă variabilă lipsă, ceea ce este exact ce se dorește: un mod strict este un mesaj de eroare mai bun decât un rând gol într-un mesaj de bun venit.

Câștigă prima regulă, iar ramura pe care nimeni nu a luat-o

O ramificație citește o cale de variabilă și își evaluează regulile de sus în jos. Prima regulă care se potrivește decide portul; dacă niciuna nu se potrivește, execuția iese prin otherwise. Opt operatori, nu cei treizeci ai unei unelte generaliste, și toți opt se explică într-un rând.

Ceea ce a contat mai mult decât evaluarea este ceea ce rămâne scris. Fiecare nod vizitat produce un rând cu identificatorul, felul și rezultatul lui, iar la o ramificație acel rezultat este ce port a fost luat și ce valoare a fost citită. Asta nu putea intra în câmpul de eroare, care poartă motivul pentru care ceva a eșuat sau a fost sărit, nici în instantaneul de context, care este intrarea unei randări și poartă o promisiune.

Iar fiecare nod executabil care nu a fost vizitat produce și el un rând, cu propriul motiv. „Un pas anterior a eșuat, deci acesta nu a rulat” și „execuția a luat altă ramură, deci acest nod nu a fost atins” sunt două propoziții diferite, iar deosebirea lor este exact ceea ce faci când citești o execuție care te-a surprins.

Un tipar pe care nu îl poți întrerupe

Un operator al ramificației evaluează o expresie regulată, iar el este singura piesă cu adevărat periculoasă a funcției. JavaScript nu oferă nicio cale de a întrerupe un RegExp.test(). Nu există termen și nu există întrerupere, iar motorul revine înapoi, așa că un tipar precum (a+)+$ peste patruzeci de caractere blochează minute în șir un manipulator de cereri. Aceasta este o blocare a serviciului cu un singur câmp de intrare.

Partea de agent a rezolvat aceeași problemă într-o rundă anterioară limitând motorul, cu o implementare fără revenire și cu o limită de memorie. Acea cale nu există aici, așa că se limitează în schimb limbajul la o submulțime în care dezastrul nu poate fi exprimat: fără referințe înapoi, fără lookaround și cu înălțime de stea unu, adică o repetiție nu poate sta într-o repetiție. (a+)+ devine astfel imposibil, iar a+b+ rămâne permis. Cu o limită de lungime pe tipar și pe intrare, cel mai rău caz este pătratic pe câteva mii de caractere, ceva ce un nucleu modern rezolvă fără să bage de seamă.

Vederea în listă nu este cea mai slabă

Fiecare acțiune a pânzei are un buton într-o vedere în listă construită din același model pur: adăugare, selectare, mutare, ștergere, conectare și deconectare. Ordinea acolo este topologică, adică ordinea în care executorul vizitează nodurile și deci singura pe care o poate urma un cititor fără desen; la o ramificare, ramurile stau una sub alta, cu firele lor scrise în cuvinte.

Nu este o anexă de accesibilitate înșurubată pe un desen. Este motivul pentru care sub pânză nu se află nicio bibliotecă de grafuri. O bibliotecă poate face nodurile focalizabile și le poate muta cu săgețile, dar niciuna nu trasează un fir de la tastatură, deci acea a doua cale trebuia să existe oricum. Odată ce există, bibliotecii îi rămâne doar munca de maus, iar la douăzeci și cinci de noduri aceea este partea mică.

Un rând de cod care mânca fiecare clic

Pânza se deplasa bine, trăgea bine și nu selecta nimic. Un clic pe ieșirea ok a unui nod nu făcea absolut nimic, iar în DOM nu era nimic greșit: manipulatorul era atașat, elementul era acolo, coordonatele erau corecte.

setPointerCapture() pe un strămoș redirecționează fiecare eveniment de indicator următor către elementul care captează, inclusiv pointerup. Navigatorul declanșează apoi click pe strămoșul comun al apăsării și al ridicării, iar cu captarea activă acel strămoș este chiar containerul. Manipulatorul de clic al nodului nu este atins niciodată, iar nicăieri nu apare vreo eroare.

Reparația este structurală și nu un comutator: deplasarea atârnă acum de un strat de fundal așezat în spatele nodurilor, astfel încât o apăsare pe un nod nu mai poate porni captarea.

Există o a doua lecție în cât de aproape a fost ca asta să treacă neobservată. Verificarea din navigator pentru „a fost adăugat un fir” număra elemente svg path, iar fiecare pictogramă a interfeței este una dintre acestea, deci numărul creștea fie că apărea un fir, fie că nu. Acum numără căi din interiorul stratului firelor, față de o cantitate exactă.

Ce am nota

  1. Refuzați ciclul în loc să plafonați iterațiile. O limită ascunsă este o capcană; un refuz la salvare care numește nodurile vinovate este un mesaj.
  2. Faceți din porturi o funcție a nodului. Ceea ce își schimbă forma după configurație nu se descrie cu un tabel fix.
  3. Dați variabilelor noi un spațiu de nume nou. Lăsând intrarea utilizatorului să scrie peste câmpurile care descriu execuția, auditul devine o povestire.
  4. Consemnați ramura neluată, cu motivul ei. „Neatins” și „neexecutat după o eroare” răspund la întrebări diferite.
  5. Dacă motorul nu se întrerupe, limitați limbajul. Înălțimea de stea unu plus o limită de lungime transformă o explozie exponențială în aritmetică.
  6. Construiți întâi calea de la tastatură dacă este o cerință. Odată ce există, se descoperă adesea că biblioteca făcea doar jumătatea ușoară.
  7. Captarea indicatorului fură clicurile descendenților. Dacă o pânză se trage bine și nu selectează nimic, acela este primul loc de privit.

Pentru claritate despre ce este vorba: fluxurile trăiesc în ecranul scripturilor, care astăzi lipsește intenționat din meniul lateral, deci sunt accesibile și nepromovate. Trei lucruri nu sunt dovedite. Conversia din graf înapoi în listă a fost verificată în cod și niciodată condusă într-un navigator. Ramura de instalare prin rețea a unui flux în graf nu a fost parcursă de la un capăt la altul. Iar o execuție de probă este sincronă, deci nu există niciun eveniment în direct de emis.

Din munca de platformă a Provibr. De ce un pas care nu a făcut nimic în tăcere este mai rău decât unul care a eșuat se află în migrarea care nu a rulat niciodată, iar felul în care o singură vedere de progres citește tabelele altora fără să scrie vreodată în ele se află în a proiecta, niciodată a înlocui.

Începeți cu o gazdă și un agent

Creați un cont, instalați agentul cu o singură comandă și conectați primul dumneavoastră host Proxmox. Perioada de probă durează 14 zile și nu cere date de plată.