Portering af Ray til Microsoft Windows

Af Johny vino

(Mehrdad Niknami) (28. september 2020)

Baggrund

Da Ray blev lanceret første gang, blev det skrevet til Linux og macOS – UNIX-baserede operativsystemer. Support til Windows, det mest populære desktop OS, manglede, men vigtigt for projektets langsigtede succes. Mens Windows Subsystem for Linux (WSL) gav en mulig mulighed for nogle brugere, begrænsede det understøttelse til nyere versioner af Windows 10 og gjorde det langt vanskeligere for kode at interagere med det oprindelige Windows-operativsystem, hvilket udgjorde en dårlig oplevelse for brugerne. I betragtning af disse håbede vi virkelig at kunne tilbyde oprindelig Windows-support til brugere, hvis det overhovedet var praktisk.

Porting af Ray til Windows var imidlertid langt fra en triviel opgave. Som mange andre projekter, der efterfølgende forsøger at opnå bærbarhed, stødte vi på mange udfordringer, hvis løsninger ikke var indlysende. I dette blogindlæg tilstræber vi at gå dybt ned i de tekniske detaljer i den proces, vi fulgte for at gøre Ray kompatibel med Windows. Vi håber, at det kan hjælpe andre projekter med en lignende vision med at forstå nogle af de potentielle udfordringer, der er involveret, og hvordan de kan håndteres.

Oversigt

Eftermontering af support til Windows udgjorde en stadig større udfordring, da udviklingen på Ray skred videre. Før vi startede, forventede vi, at visse aspekter af koden ville udgøre en væsentlig del af vores bærbarhedsindsats, herunder følgende:

  • Inter-process communication (IPC) and shared memory
  • Objekthåndtag og filhåndtag / deskriptorer (FDer)
  • Gydning og styring af processer, herunder signalering
  • Filhåndtering, trådadministration og asynkron I / O
  • Brug af shell og systemkommandoer
  • Redis

I betragtning af omfanget af problemerne indså vi hurtigt, at forhindring af yderligere inkompatibilitet i at blive introduceret skulle prioriteres frem for at forsøge at løse eksisterende problemer. Vi forsøgte således at gå videre i omtrent følgende trin, selv om dette er noget af en forenkling, da specifikke problemer undertiden blev behandlet i forskellige faser:

  1. Kompabilitet for tredjepartsafhængigheder
  2. Kompabilitet for Ray (via tomme stubber & TODOer)
  3. Linkbarhed
  4. Kontinuerlig integration (CI) (for at blokere for yderligere uforenelige ændringer)
  5. Statisk kompatibilitet (for det meste C ++)
  6. Run-time eksekverbarhed (minimal POC)
  7. Run-time kompatibilitet (for det meste Python)
  8. Run forbedringer i tid (f.eks. Unicode-understøttelse)
Af Ashkan Forouzani

Udviklingsproces

På et højt niveau kan fremgangsmåderne her variere. For mindre projekter kan det være meget fordelagtigt at forenkle kodebasen på samme tid som koden overføres til en ny platform. Den tilgang, vi tog, hvilket viste sig meget nyttigt for en stor og dynamisk skiftende kodebase, var at tackle problemer en ad gangen , hold ændringer så ortogonale over for hinanden som muligt og prioritere bevarelse af semantik frem for enkelhed .

Til tider krævede dette at skrive potentielt fremmed kode for at håndtere forhold, der muligvis ikke nødvendigvis har fundet sted i produktionen (f.eks. citere en filsti, der aldrig ville have mellemrum). På andre tidspunkter krævede dette introduktion af mere “mekaniske” løsninger, der muligvis har været undgåelige (såsom at bruge en std::shared_ptr i visse tilfælde, hvor et andet design kan have har fået tilladelse til, at en std::unique_ptr bruges). At bevare semantikken i eksisterende kode var imidlertid helt afgørende for at undgå den konstante introduktion af nye bugs i kodebasen, hvilket ville have påvirket resten af ​​holdet. Og i bakspejlet var denne tilgang ret vellykket: bugs på andre platforme som følge af Windows-relaterede ændringer var ret sjældne, og opstod hyppigst på grund af ændringer i afhængigheder end på grund af semantiske ændringer i kodebasen.

Kompabilitet (tredjepartsafhængigheder)

Den første forhindring var at sikre, at tredjepartsafhængigheder selv kunne bygges på Windows. Mens mange af vores afhængigheder var almindeligt anvendte biblioteker, og de fleste ikke havde større inkompatibiliteter, var dette ikke altid ligetil. Især var utilsigtede kompleksiteter rigelige i flere aspekter af problemet.

  • Build-filerne (især Bazel-build-filer) for nogle projekter var til tider utilstrækkelige i Windows, hvilket krævede patches . Ofte skyldtes disse problemer sjældnere på UNIX-platforme, f.eks. citerer filstier med mellemrum, af lancering af den rette Python-tolk, eller spørgsmålet om korrekt linkning delte biblioteker versus statiske biblioteker. Heldigvis var et af de mest nyttige værktøjer til løsning af dette problem Bazel selv: dens indbyggede evne til at patchere eksterne arbejdsområder er, som vi hurtigt lærte, ekstremt nyttige. Det tillod en standardmetode til patch af eksterne biblioteker uden at ændre build-processerne på en ad-hoc-måde og holde kodebasen ren.
  • Build-værktøjskæderne udviste deres egne kompleksiteter, da kompilatoren eller linkervalget ofte påvirkede bibliotekerne, APIerne og definitionerne, som man kunne stole på. Og desværre kan skifte kompilatorer på Bazel være ganske besværlig . Især på Windows kommer den bedste oplevelse ofte med at bruge Microsoft Visual C ++ build-værktøjer, men vores værktøjskæder på andre platforme var baseret på GCC eller Clang. Heldigvis leveres LLVM-værktøjskæden med Clang-Cl på Windows, hvilket gør det muligt at bruge en blanding af Clang- og MSVC-funktioner, hvilket gør mange problemer meget lettere at løse.
  • Biblioteksafhængigheder præsenterede generelt de fleste vanskeligheder. Nogle gange var spørgsmålet lige så dagligdags som en manglende overskrift eller modstridende definition, der kunne håndteres mekanisk, som selv udbredte biblioteker (såsom Boost) ikke var immune over for. Løsningerne på disse var ofte et par passende makrodefinitioner eller dummy headers. I andre tilfælde – f.eks. Til hirte og Pil -biblioteker – krævede biblioteker POSIX-funktionalitet, der var utilgængelig på Windows (såsom signaler eller evnen til at videregive filbeskrivelser over UNIX-domænesockets). Disse viste sig at være meget sværere at løse i nogle tilfælde. Da vi var mere bekymrede over kompilabilitet på dette stadium, var det dog frugtbart at udsætte implementeringen af ​​mere komplekse APIer til et senere tidspunkt og at bruge basale stubber eller deaktivere den krænkende kode for at tillade bygningen at fortsætte.

Når vi var færdige med at kompilere afhængigheder, kunne vi fokusere på at kompilere kernen i Ray selv.

Compilability (Ray)

Den næste forhindring var at få Ray selv til kompilere sammen med Plasma butikken (del af Pil ). Dette var vanskeligere, da koden ofte slet ikke var designet til Windows-modellen og stod stærkt på POSIX APIer. I nogle tilfælde var det kun et spørgsmål om at finde og bruge de passende overskrifter (såsom WinSock2.h i stedet for sys/time.h til struct timeval, hvilket kan være overraskende) eller skabe erstatninger ( som for

unistd.h ). I andre tilfælde deaktiverede vi den inkompatible kode og efterlod TODOer til at adressere i fremtiden.

Selvom dette konceptuelt lignede tilfældet med håndtering af tredjepartsafhængigheder, var en unik bekymring på højt niveau, der opstod her, minimering af ændringer i kodebasen i stedet for at levere mest elegante løsning mulig. Da teamet løbende opdaterede kodebasen under antagelse af en POSIX API, og da kodebasen endnu ikke kompilerede med Windows, blev løsninger, der var “drop-in”, og som kunne anvendes gennemsigtigt med minimale ændringer for at opnå en omfattende kompatibilitet på tværs hele kodebasen var meget mere nyttigt til at undgå syntaktiske eller semantiske fusionskonflikter end løsninger, der var kirurgiske. På grund af dette faktum oprettede vi i stedet for at ændre alle opkaldssider vores egne ækvivalente Windows shims til POSIX APIer, der simulerede den ønskede adfærd, selvom det var suboptimalt samlet set. Dette gjorde det muligt for os at sikre kompilerbarhed og standse spredningen af ​​uforenelige ændringer meget hurtigere (og senere ensartet behandle hvert enkelt problem på tværs af hele codebase i batch), end det ellers ville være muligt.

Linkbarhed

Når først kompilerbarheden var opnået, var det næste problem den korrekte sammenkædning af objektfiler for at opnå eksekverbare binære filer.Mens det er teoretisk simpelt involverede forbindelsesproblemer ofte mange utilsigtede kompleksiteter, såsom følgende:

  • Visse systembiblioteker var platformsspecifikke og utilgængelige eller forskellige på andre platforme ( såsom libpthread)
  • Visse biblioteker blev linket dynamisk, når de forventedes at være linket statisk (eller omvendt)
  • Visse symboldefinitioner manglede eller var i modstrid (f.eks. connect fra hyres modstridende med connect for stikkontakter)
  • Visse afhængigheder arbejdede sammenfaldende med POSIX-systemer, men krævede eksplicit håndtering i Bazel på Windows

Løsningerne til disse var ofte verdslige (dog måske ikke indlysende) ændringer i build-filerne, men i nogle tilfælde var det nødvendigt at patch-afhængigheder. Som før var Bazels evne til at lappe praktisk talt alle filer fra en ekstern kilde yderst nyttig til at hjælpe med at løse sådanne problemer.

Af Richy Great

At få kodebasen til at bygge med succes var en stor milepæl. Når de platformsspecifikke ændringer var integreret, ville hele teamet nu påtage sig ansvaret for at sikre statisk bærbarhed i alle fremtidige ændringer, og indførelsen af ​​inkompatible ændringer ville blive minimeret på tværs af kodebasen. ( Dynamisk bærbarhed var naturligvis stadig ikke mulig på dette tidspunkt, da stubberne ofte manglede den nødvendige funktionalitet på Windows, og følgelig koden ikke kunne køre på dette tidspunkt.)

At få kodebasen til at bygge med succes var en stor milepæl. Når de platformsspecifikke ændringer var integreret, ville hele teamet nu påtage sig ansvaret for at sikre statisk bærbarhed i alle fremtidige ændringer, og indførelsen af ​​inkompatible ændringer ville blive minimeret på tværs af kodebasen. ( Dynamisk bærbarhed var naturligvis stadig ikke mulig på dette tidspunkt, da stubberne ofte manglede den nødvendige funktionalitet på Windows, og følgelig koden ikke kunne køre på dette tidspunkt.)

Ikke alene mindskede dette arbejdsbyrden, men det tillod Windows-bærbarhedsindsatsen at bremse og gå langsommere og omhyggeligt for at tackle mere vanskelige uforeneligheder i dybden uden at skulle aflede opmærksomheden på at bryde ændringer, der konstant blev introduceret i kodebasen. Dette muliggjorde løsninger af højere kvalitet i det lange løb, herunder en del refactoring af kodebasen, der potentielt påvirkede semantik på andre platforme.

Statisk kompatibilitet (for det meste C / C ++)

Dette var muligvis det mest tidskrævende stadium, hvor kompatibilitetsstubber ville blive fjernet eller uddybet, deaktiveret kode kunne genaktiveres, og individuelle problemer kunne løses. Den statiske karakter af C ++ tillod kompilerdiagnostik at styre de fleste sådanne nødvendige ændringer uden behov for at udføre nogen kode overhovedet.

De enkelte ændringer her, herunder nogle tidligere nævnte, ville være for lange til at diskutere i dybden i deres helhed, og visse individuelle problemer vil sandsynligvis være værdige til deres egne lange blogindlæg. Processgydning og -administration er for eksempel faktisk en overraskende kompleks bestræbelse fuld af idiosynkrasier og faldgruber (se her for eksempel) for hvilke der ikke synes at være noget godt, på tværs af platforme, på trods af at dette er et gammelt problem. En del af dette skyldes dårlige indledende design i selve operativsystemets APIer. ( Chrom-kildekoden giver et glimt af nogle af de kompleksiteter, der ofte ses bort fra andre steder. Faktisk kan Chrom-kildekoden ofte tjene som en god illustration af håndtering mange platformskompatibiliteter og finesser.) Vores oprindelige tilsidesættelse af denne kendsgerning førte os til at forsøge at bruge Boost.Process, men manglen på klar ejerskabssemantik til POSIXs pid_t (som bruges til begge procesidentifikation og ejerskab) såvel som bugs i Boost.Process-biblioteket i sig selv resulterede i vanskeligt at finde bugs i codebasen, hvilket i sidste ende resulterede i vores beslutning om at tilbageføre denne ændring og introducere vores egen abstraktion. Desuden var Boost.Process-biblioteket ret tungt selv for et Boost-bibliotek, og dette bremsede vores byggeri betydeligt. I stedet for endte vi med at skrive vores egen indpakning til procesobjekter. Dette viste sig at fungere ganske godt til vores formål. En af vores takeaways i dette tilfælde var at overveje at skræddersy løsninger til vores egne behov og ikke antage, at eksisterende løsninger er det bedste valg .

Dette var selvfølgelig bare et glimt af spørgsmålet om processtyring.Nogle andre aspekter af bærbarhedsindsatsen er måske også værd at uddybe.

By Thomas Jensen

Dele af kodebasen (såsom kommunikation med Plasmaserveren i pilen) antages muligheden for at bruge UNIX-domænesockets (AF_UNIX) på Windows. Mens nyere versioner af Windows 10 do understøtter UNIX-domænesockets, er implementeringerne heller ikke tilstrækkelige til at dække alle mulige anvendelser af UNIX-domæne stikkontakter og heller ikke UNIX-domænesæt er særligt elegante: de kræver manuel oprydning og kan efterlade unødvendige filer i filsystemet i tilfælde af en ikke-ren afslutning af en proces, og de giver ikke mulighed for at sende supplerende data (såsom filbeskrivere) til en anden proces. Da Ray bruger Boost.Asio, var den mest hensigtsmæssige erstatning for UNIX-domæne-sokler lokale TCP-sokler (som begge kunne abstraheres som generelle sokler), så vi skiftede til sidstnævnte, bare udskiftning af filstier med strengede versioner af TCP / IP-adresser uden at skulle omformulere kodebasen.

Dette var ikke tilstrækkeligt, da brugen af ​​TCP-stik stadig ikke gav mulighed for at replikere socket deskriptorer i andre processer. Faktisk ville en ordentlig løsning på dette problem have været at undgå det helt. Da det ville kræve en langsigtet refactoring af de relevante dele af kodebasen (en opgave, som andre påbegyndte), syntes en gennemsigtig tilgang imidlertid mere passende i mellemtiden. Dette blev vanskeliggjort af det faktum, at duplikering af en filbeskrivelse på UNIX-baserede systemer ikke kræver viden om identiteten af ​​målprocessen, mens duplikering af et håndtag på Windows kræver aktiv manipulation af målproces. For at løse disse problemer implementerede vi muligheden for at udveksle filbeskrivere ved at erstatte håndtrykproceduren i lanceringen af ​​Plasma-butikken med en mere specialiseret -mekanisme , der ville etablere en TCP-forbindelse , slå op på idet for processen i den anden ende (en måske langsom, men engangsprocedure), duplikér sokkelhåndtaget, og informer målprocessen for det nye håndtag. Selvom dette ikke er en generel løsning (og faktisk kan være tilbøjelig til race-forhold i det generelle tilfælde) og en ganske usædvanlig tilgang, fungerede det godt til Rays formål og kan være en tilgang andre projekter, der står over for det samme problem kunne drage fordel af.

Ud over dette var et af de største problemer, vi forventede at stå over for, vores Redis serverafhængighed. Mens Microsoft Open Technologies (MSOpenTech) tidligere havde implementeret en Redis-port til Windows, var projektet blevet opgivet og understøttede følgelig ikke de versioner af Redis, som Ray krævede . Dette fik os oprindeligt til at antage, at vi stadig skulle køre Redis-serveren på Windows Subsystem til Linux (WSL), hvilket sandsynligvis ville have vist sig ubelejligt for brugerne. Vi var derfor meget taknemmelige for at opdage, at en anden udvikler havde fortsat projektet til at producere senere Redis-binære filer på Windows (se tporadowski / redis ). Dette forenklede vores problem enormt og tillod os at tilbyde indbygget support af Ray til Windows.

Endelig var muligvis de mest betydningsfulde forhindringer, vi stod over for (som MSOpenTech Redis gjorde, og som de fleste andre POSIX-programmer) kun fraværet af trivielle erstatninger for nogle POSIX APIer på Windows. Nogle af disse ( såsom

getppid()) var ligetil, men noget kedelige. Muligvis var det sværeste problem, der blev stødt på under hele porteringsprocessen, imidlertid filbeskrivere versus filhåndtag. Meget af den kode, som vi stolede på (såsom Plasma-butikken i pilen) antog brugen af ​​POSIX-filbeskrivere (int s). Windows bruger dog indbygget HANDLE s, som er markørstørrelse og analoge med size_t. I sig selv er dette dog ikke et væsentligt problem, da Microsoft Visual C ++ runtime (CRT) giver et POSIX-lignende lag. Laget er dog begrænset i funktionalitet, kræver oversættelser på hvert opkaldsside, der ikke understøtter det, og kan især ikke bruges til ting som stikkontakter eller delt hukommelseshåndtag. Desuden ønskede vi ikke at antage, at HANDLE s altid ville være lille nok til at passe ind i et 32-bit heltal, selvom dette ofte var tilfældet, da det var uklart, om omstændigheder, som vi ikke var opmærksomme på, kunne bryde denne antagelse lydløst.Dette forværrede vores problemer markant, da den mest åbenlyse løsning ville have været at opdage alle int s, der repræsenterer filbeskrivere i et bibliotek som Arrow, og at erstatte dem (og alle deres anvendelser ) med en alternativ datatype, som var en fejlbehæftet proces og involverede betydelige rettelser til ekstern kode, hvilket skabte en betydelig vedligeholdelsesbyrde.

Det var ret vanskeligt at beslutte, hvad der skulle gøres på dette stadium. MSOpenTech Rediss løsning til det samme problem gjorde det klart, at dette var en ret skræmmende opgave, da de havde løst dette problem ved at at oprette en singleton, procesdækkende filbeskrivelsestabel på top af den eksisterende CRT-implementering, der kræver, at de behandler trådsikkerhed, samt at tvinge dem til at opfange alle anvendelser af POSIX APIer (også dem, den allerede var i stand til at håndtere) kun til at oversætte filbeskrivere. I stedet besluttede vi at tage en usædvanlig tilgang: vi udvidede POSIX-oversættelseslaget i CRT . Dette blev gjort ved at identificere inkompatible håndtag ved oprettelsestidspunktet og “skubbe” disse håndtag ind i bufferne på et overflødigt rør og returnere deskriptoren til det rør i stedet. Derefter måtte vi kun ændre brugen af ​​disse håndtag, som i det væsentlige var trivielle at identificere, da de alle var socket og hukommelseskortede fil APIer. Faktisk hjalp dette med at undgå behovet for patch, da vi kun var i stand til at omdirigere mange funktioner via makroer .

Mens bestræbelserne på at udvikle dette unikke udvidelseslag (i win32fd.h) var signifikant (og ret uortodoks), det var sandsynligvis det værd, da oversættelseslaget var faktisk ret lille i sammenligning og tillod os at delegere de fleste ikke-relaterede problemer (såsom multitrådet låsning af filbeskrivelsestabellen) til CRT APIerne. Desuden ved at udnytte anonyme rør fra den samme globale filbeskrivelsestabel (på trods af vores manglende direkte adgang til den), var vi i stand til at undgå at skulle opfange og oversætte filbeskrivere til andre funktioner, der allerede kunne håndteres direkte. Dette gjorde det muligt for en stor del af koden at forblive stort set uændret med en minimal præstationspåvirkning, indtil vi senere havde en chance for at omlægge koden og give bedre indpakninger på et højere niveau (såsom via Boost.Asio indpakninger). Det er meget muligt, at en udvidelse af dette lag kan gøre det muligt for andre projekter som Redis at blive overført til Windows meget mere problemfrit og med langt mindre drastiske ændringer eller potentiale for fejl.

Af Andrea Leopardi

Run-time Executability (Proof-of-Concept)

Når vi troede, at Ray-kernen fungerede korrekt, var den næste milepæl at sikre, at en Python-test med succes kunne udøve vores kodestier. Oprindeligt prioriterede vi ikke at gøre det. Dette viste sig imidlertid at være en fejltagelse, da senere ændringer fra andre udviklere på holdet faktisk introducerede mere dynamiske inkompatibiliteter med Windows, og CI-systemet kunne ikke opdage sådanne brud. Vi prioriterede derfor efterfølgende at køre en minimal test på Windows builds for at undgå yderligere brud på build.

Til for det meste var vores indsats vellykket, og eventuelle dvælende bugs i Ray-kernen var i forudsigelige dele af kodebasen (selvom adressering af dem ofte krævede at gå gennem multi-proces-kode, hvilket var langt fra trivielt). Der var dog mindst en noget ubehagelig overraskelse undervejs på C-siden og en på Python-siden, som begge (blandt andet) tilskyndede os til at læse softwaredokumentation mere proaktivt i fremtiden.

På C-siden, vores første håndtryk indpakning til udveksling af fatningshåndtag påberåbt sig naivt at erstatte sendmsg og recvmsg med WSASendMsg og WSARecvMsg. Disse Windows APIer var de nærmeste ækvivalenter af POSIX APIerne og syntes derfor at være et oplagt valg. Men efter udførelse ville koden konstant gå ned, og kilden til problemet var uklar. Noget fejlretning (inklusive med fejlretningsversioner af builds & runtimes) hjalp med at afsløre, at problemet var med stackvariablerne, der blev sendt til WSASendMsg. Yderligere fejlfinding og nøje inspektion af hukommelsesindholdet antydede, at problemet muligvis har været msg_flags -feltet i WSAMSG, da dette var den eneste ikke-initialiserede Mark.Dette syntes imidlertid at være irrelevant: msg_flags blev blot oversat fra flags i struct msghdr, som ikke blev brugt på input og kun blev brugt som outputparameter. Læsning af dokumentationen afslørede imidlertid problemet: På Windows fungerede feltet også som en parameter input og dermed blev det uinitialiseret resulteret i uforudsigelig opførsel! Dette var ganske uventet for os og resulterede i to vigtige takeaways, der gik fremad: for at læse dokumentationen for hver funktion omhyggeligt , og desuden handler det om, at initialisering af variabler ikke kun handler om at sikre korrekthed med aktuelle APIer, men også vigtigt for at gøre kode robust til fremtidige ændringer til de målrettede APIer .

På Pythonsiden stødte vi på et andet problem. Vores oprindelige Python-moduler kunne oprindeligt ikke indlæses på trods af manglen på åbenlyse problemer. Efter flere dages gætterier, trin gennem samling og CPython-kildekoden og inspektion af variabler i CPython-kodebasen, blev det tydeligt, at problemet var manglen på et .pyd suffiks på dynamisk Python biblioteker på Windows. Som det viser sig, af grunde, der er uklare for os, nægter Python at indlæse selv .dll filer på Windows som Python-moduler, på trods af at oprindelige delte biblioteker normalt kunne indlæses selv med enhver fil udvidelse. Faktisk viste det sig, at denne kendsgerning blev dokumenteret på Pythons hjemmeside . Desværre kunne tilstedeværelsen af ​​sådan dokumentation muligvis ikke antyde vores erkendelse af at lede efter den.

Ikke desto mindre var Ray i sidste ende i stand til at køre på Windows med succes, og dette afsluttede den næste milepæl og leverede et bevis af koncept til bestræbelsen.

Af Hitesh Choudhary

Kørselstidskompatibilitet (for det meste Python)

På dette tidspunkt var en gang kernen i Ray arbejder vi i stand til at fokusere på at overføre kode på højere niveau. Nogle problemer var ret lette at løse – for eksempel har nogle Python APIer, der kun er UNIX (f.eks. os.uname()[1]) ofte passende erstatninger på Windows (såsom socket.gethostname()), og at finde dem var et spørgsmål om at vide at søge efter alle forekomster af dem på tværs af kodebasen. Andre problemer var sværere at spore eller løse. Nogle gange skyldtes de brugen af ​​POSIX-specifikke kommandoer (såsom ps), som krævede alternative tilgange (såsom brugen af ​​psutil for Python). Andre gange skyldtes de inkompatibilitet i tredjepartsbiblioteker. For eksempel, når et stik afbrydes på Windows, hæves en fejl i stedet for at resultere i tomme aflæsninger. Python-biblioteket til Redis så ikke ud til at håndtere dette. Sådanne forskelle i adfærd krævede eksplicit abepatchning for at undgå lejlighedsvis forvirrende fejl, der ville opstå ved afslutning af Ray.

Mens nogle af disse problemer er temmelig kedelig, men alligevel sandsynligvis forventet (såsom at erstatte anvendelser af /tmp med platformens midlertidige bibliotek eller undgå antagelsen om, at alle absolutte stier begynder med en skråstreg), var nogle noget uventede (såsom modstridende portreservationer ) eller (som det ofte er tilfældet) på grund af forkerte antagelser, og forståelsen af ​​dem afhænger af forståelsen af ​​Windows-arkitekturen og dens tilgange til bagudkompatibilitet.

En sådan historie drejer sig om brugen af ​​skråstreger som katalogadskillere på Windows. Generelt ser disse ud til at fungere fint og bruges ofte af udviklere. Dette skyldes imidlertid automatisk konvertering af skråstreger til tilbageslag i Windows-delsystembibliotekerne i brugertilstand, og visse automatiske processer kan undertrykkes ved eksplicit at prefikse stier med et \\?\ præfiks, hvilket er nyttigt til at omgå visse kompatibilitetsfunktioner (såsom lange stier). Vi brugte imidlertid aldrig eksplicit en sådan sti og antog, at brugerne kunne forventes at undgå usædvanlig brug i vores eksperimentelle udgivelser. Senere blev det imidlertid klart, at når Bazel påberåbte sig visse Python-tests, ville stier blive behandlet i dette format for at tillade brug af lange stier, og dette deaktiverede den automatiske oversættelse, vi implicit stolede på. Dette førte os til vigtige takeaways: for det første at det generelt er bedre at bruge APIer på den mest egnede måde til målsystemet, da det giver mindst mulige muligheder for uventede problemer .For det andet, og vigtigst af alt, er det simpelthen en fejlslutning at antage, at en brugers miljø er forudsigeligt . Virkeligheden er, at moderne software næsten altid er afhængig af at køre tredjepartskode, hvis nøjagtige opførsel vi ikke er opmærksomme på. Selv når en bruger kan antages at undgå problematiske situationer, er tredjepartssoftware fuldstændig uvidende om sådanne skjulte antagelser. Således er det sandsynligt, at de vil forekomme alligevel, ikke kun for brugerne, men også for softwareudviklerne selv, hvilket resulterer i fejl, der er sværere at spore end at adressere, når de skriver den oprindelige kode. Derfor er det vigtigt at undgå at lægge for meget vægt på programvenlighed (det modsatte af “brugervenlighed”), når man designer et robust system.

(Som en sjov til side: faktisk på Windows, stier kan faktisk indeholde anførselstegn og mange andre specialtegn, der normalt antages at være ulovlige. Dette sker, når du bruger NTFS alternative datastrømme. Disse er dog sjældne og komplekse nok til, at selv standard sprogbiblioteker ofte ikke håndterer dem.)

Når de mest betydningsfulde problemer blev løst, kunne mange tests dog videregive Windows, hvilket skabte den første eksperimentelle Windows-implementering af Ray.

Af Ross Sneddon

Kørselstidsforbedringer (f.eks. Unicode-understøttelse)

På dette tidspunkt kan meget af kernen i Ray bruges på Windows ligesom på andre platforme. Ikke desto mindre forbliver nogle problemer, hvilket kræver løbende bestræbelser på at løse dem.

Unicode-support er et sådant problem. På grund af historiske årsager har Windows-bruger-delsystemet to versioner af de fleste APIer: en “ANSI” -version, der understøtter single-byte-tegnsæt, og en “Unicode” -version, der understøtter UCS-2 eller UTF-16 (afhængigt af oplysninger om den pågældende API). Desværre er ingen af ​​disse UTF-8; selv grundlæggende support til Unicode kræver brug af bredstrengede strenge (baseret på wchar_t) på tværs af hele kodebasen. (nb: Faktisk har Microsoft for nylig forsøgt at introducere UTF-8 som en kodeside, men det understøttes ikke nok til at løse dette problem problemfrit, i det mindste uden at stole på potentielt udokumenterede og skøre Windows-interner.)

Traditionelt håndterer Windows-programmer Unicode ved hjælp af makroer som _T() eller TEXT(), som udvides til at være smal eller bred -karakterbogstaver afhængigt af, om der er angivet en Unicode-build, og brug TCHAR som deres generiske karaktertyper. Tilsvarende har de fleste C APIer TCHAR -afhængige versioner (såsom _tcslen() i stedet for strlen()) for at muliggøre kompatibilitet med begge typer kode. Men at migrere en UNIX-baseret codebase til denne model er en ret involveret indsats. Dette er endnu ikke gjort i Ray, og derfor understøtter Ray ikke denne ordentlige Unicode i (for eksempel) filstier på Windows i skrivende stund, og den bedste tilgang til at gøre det kan stadig være noget af et åbent spørgsmål. / p>

Et andet sådant problem er kommunikationsmekanismen mellem processer. Mens TCP-stikkontakter kan fungere fint på Windows, er de suboptimale, da de introducerer et lag med unødvendig kompleksitet i logikken (såsom timeouts, keep-alives, Nagles algoritme), kan resultere i utilsigtet tilgængelighed fra ikke-lokale værter og kan introducere nogle præstationsomkostninger. I fremtiden kunne Named Pipes give en bedre erstatning for UNIX-domænesockets på Windows; faktisk selv på Linux kan enten rør eller såkaldte abstrakte UNIX-domænesæt også vise sig at være bedre alternativer, da de ikke kræver rod og oprydning af sokkelfiler på filsystemet.

Endelig er et andet eksempel på et sådant problem BSD-sockets kompatibilitet, eller rettere, mangel på det. Et fremragende svar på StackOverflow diskuterer nogle af problemerne i dybden, men kort, mens almindelige sokkel-APIer er derivater af den originale BSD-sokkel-API, implementerer forskellige platforme lignende sokkel flag forskelligt. Især kan konflikter med eksisterende IP-adresser eller TCP-porte give forskellige adfærd på tværs af platforme. Mens problemerne er vanskelige at specificere her, er slutresultatet, at dette kan gøre det vanskeligt at bruge flere forekomster af Ray på samme vært samtidigt. (Faktisk, da dette afhænger af OS-kerneopførsel, påvirker det også WSL.) Dette er endnu et kendt problem, hvis løsning er temmelig involveret og ikke behandles fuldstændigt i det nuværende system.

Konklusion

Processen med at overføre en codebase som Ray til Windows har været en værdifuld oplevelse, der fremhæver fordele og ulemper ved mange aspekter af softwareudvikling og deres indvirkning på vedligeholdelse af kode.Den foregående beskrivelse fremhæver kun nogle af forhindringerne undervejs. Der kan drages mange nyttige konklusioner fra processen, hvoraf nogle kan være værdifulde at dele her til andre projekter i håb om at nå et lignende mål.

Først i nogle tilfælde fandt vi faktisk senere på de efterfølgende versioner af nogle biblioteker (såsom hyrede) havde allerede løst nogle problemer, som vi havde tacklet. Løsningerne var ikke altid indlysende, da (for eksempel) versionen af ​​hire i de nyere Redis-versioner faktisk var en forældet kopi af hire, hvilket fik os til at tro, at nogle problemer endnu ikke var blevet behandlet. Senere revisioner behandlede heller ikke altid alle eksisterende kompatibilitetsproblemer fuldt ud. Ikke desto mindre ville det muligvis have sparet en smule indsats for at tjekke dybere for eksisterende løsninger på nogle problemer for at undgå at skulle løse dem igen.

Af John Barkiple

Andet, softwareleveringskæden er ofte kompleks . Bugs kan naturligt sammensættes ved hvert lag, og det er en fejlslutning at stole på, at selv almindeligt anvendte open source-værktøjer er “kamptestede” og derfor robuste, især når de bruges i vrede . Desuden har mange mangeårige eller almindelige softwaretekniske problemer ikke tilfredsstillende løsninger til brug, især (men ikke kun), når de kræver kompatibilitet på tværs af forskellige systemer. Faktisk bortset fra blotte quirks, i processen med at portere Ray til Windows, stødte vi regelmæssigt på og rapporterede ofte bugs i adskillige stykker software, inklusive men ikke begrænset til en Git bug on Linux , der påvirkede Bazel-brugen, Redis (Linux) , glog , psutil (parsing bug, der påvirker WSL) , grpc , mange vanskeligt at identificere fejl i selve Bazel (f.eks. 1 , 2 , 3 , 4 ), Travis CI og GitHub Actions , blandt andre. Dette tilskyndede os til også at være mere opmærksomme på kompleksiteten i vores afhængigheder.

For det tredje investering i værktøj og infrastruktur betaler udbytte i det lange løb. Hurtigere opbygninger giver mulighed for hurtigere udvikling, og mere kraftfuld værktøj gør det lettere at løse komplekse problemer. I vores tilfælde hjalp brugen af ​​Bazel os på mange måder, på trods af at det langt fra var perfekt og dets indførelse af en stejl indlæringskurve. Det er sjældent let at investere lidt tid (muligvis flere dage) for at lære mulighederne, styrkerne og manglerne ved et nyt værktøj, men det vil sandsynligvis være gavnligt ved vedligeholdelse af koder. I vores tilfælde brugte vi lidt tid på at læse Bazel-dokumentationen i dybden os i hurtigere at finde frem til en lang række fremtidige problemer og løsninger. Desuden hjalp det os også med at integrere værktøj med Bazel, som kun få andre tilsyneladende havde formået, såsom Clangs include-what-you-use værktøj.

Fjerde, og som tidligere nævnt, er det klogt at engagere sig i sikre kodningsmetoder såsom initialisering af hukommelse inden brug, når der er ingen væsentlig kompromis. Selv den mest omhyggelige ingeniør kan ikke nødvendigvis forudsige fremtidig udvikling af det underliggende system, der stille kan antage antagelser.

Endelig, som det generelt er tilfældet inden for medicin, forebyggelse er den bedste kur . Regnskab for mulig fremtidig udvikling og kodning til standardiserede grænseflader giver mulighed for et mere udvideligt kodedesign, end det let kan opnås, når uforeneligheder opstår.

Selvom porten til Ray til Windows endnu ikke er komplet, har det været ganske indtil videre vellykket, og vi håber, at deling af vores erfaringer og løsninger kan tjene som en nyttig guide til andre udviklere, der overvejer at gå på en lignende rejse.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *