Portering av Ray til Microsoft Windows

By Johny vino

(Mehrdad Niknami) (28. september 2020)

Bakgrunn

Da Ray ble lansert, ble den skrevet for Linux og macOS – UNIX-baserte operativsystemer. Støtte for Windows, det mest populære stasjonære operativsystemet, manglet, men viktig for den langsiktige suksessen til prosjektet. Mens Windows Subsystem for Linux (WSL) ga et mulig alternativ for noen brukere, begrenset det støtte til nyere versjoner av Windows 10 og gjorde det langt vanskeligere for kode å samhandle med det opprinnelige Windows-operativsystemet, noe som utgjorde en dårlig opplevelse for brukerne. Gitt disse håpet vi virkelig å gi innfødt Windows-støtte til brukere hvis det i det hele tatt var praktisk.

Å porte Ray til Windows var imidlertid langt fra en triviell oppgave. Som mange andre prosjekter som forsøker å oppnå bærbarhet etter det, møtte vi mange utfordringer hvis løsninger ikke var åpenbare. I dette blogginnlegget tar vi sikte på å ta et dypdykk i de tekniske detaljene i prosessen som vi fulgte for å gjøre Ray kompatibel med Windows. Vi håper at det kan hjelpe andre prosjekter med en lignende visjon å forstå noen av de potensielle utfordringene som er involvert, og hvordan de kan håndteres.

Oversikt

Ettermontering av støtte for Windows utgjorde en stadig større utfordring ettersom utviklingen på Ray utviklet seg videre. Før vi startet, forventet vi at visse aspekter av koden ville utgjøre en betydelig del av vår portabilitetsinnsats, inkludert følgende:

  • Inter-process communication (IPC) and shared memory
  • Objekthåndtak og filhåndtak / beskrivelser (FDs)
  • Gyting og administrering av prosesser, inkludert signalering
  • Filbehandling, trådadministrasjon og asynkron I / O
  • Bruk av skall og systemkommando
  • Redis

Gitt omfanget av problemene, innså vi raskt at det å prioritere å forhindre ytterligere inkompatibilitet skulle prioriteres fremfor å prøve å løse eksisterende problemer. Vi forsøkte dermed å gå frem i omtrent følgende trinn, selv om dette er noe av en forenkling, da spesifikke problemer noen ganger ble adressert i forskjellige trinn:

  1. Kompabilitet for tredjepartsavhengigheter
  2. Kompilering for Ray (via tomme stubber & TODOs)
  3. Koblingsbarhet
  4. Kontinuerlig integrasjon (CI) (for å blokkere ytterligere inkompatible endringer)
  5. Statisk kompatibilitet (for det meste C ++)
  6. Kjørbarhetstid (minimal POC)
  7. Kjøretidskompatibilitet (for det meste Python)
  8. Kjør forbedringer i tid (f.eks. Unicode-støtte)
Av Ashkan Forouzani

Utviklingsprosess

På et høyt nivå kan tilnærmingene her variere. For mindre prosjekter kan det være veldig gunstig å forenkle kodebasen samtidig som koden blir portert til en ny plattform. Tilnærmingen vi tok, som viste seg ganske nyttig for en stor og dynamisk skiftende kodebase, var å takle problemer en om gangen , hold endringene så ortogonale mot hverandre som mulig , og prioritere bevaring av semantikk fremfor enkelhet .

Dette krevde til tider å skrive potensielt uvedkommende kode for å håndtere forhold som ikke nødvendigvis har skjedd i produksjonen (si å sitere filbane som aldri ville ha mellomrom). Andre ganger krevde dette å innføre mer «mekaniske» løsninger som kan ha vært unngått (for eksempel å bruke en std::shared_ptr i visse tilfeller der en annen utforming kan ha fått bruke en std::unique_ptr). Å bevare semantikken til eksisterende kode var imidlertid helt avgjørende for å unngå konstant innføring av nye feil i kodebasen, noe som ville ha påvirket resten av teamet. Og i ettertid var denne tilnærmingen ganske vellykket: feil på andre plattformer som følge av Windows-relaterte endringer var ganske sjeldne, og skjedde oftest på grunn av endringer i avhengigheter enn på grunn av semantiske endringer i kodebasen.

Kompabilitet (tredjepartsavhengigheter)

Den første hindringen var å sikre at tredjepartsavhengigheter selv kunne bygges på Windows. Mens mange av avhengighetene våre var mye brukte biblioteker, og de fleste ikke hadde store inkompatibiliteter, var dette ikke alltid greit. Spesielt var det tilfeldige kompleksiteter i mange fasetter av problemet.

  • Byggefilene (spesielt Bazel-byggefiler) for noen prosjekter var til tider utilstrekkelige på Windows, og krevde oppdateringer . Ofte skyldtes dette problemer som oppstod sjeldnere på UNIX-plattformer, for eksempel utgaven av sitering av filstier med mellomrom, av lanserer riktig Python-tolk, eller problemet med riktig linking delte biblioteker kontra statiske biblioteker. Heldigvis var et av de mest nyttige verktøyene for å løse dette problemet Bazel selv: den innebygde evnen til å lappe eksterne arbeidsområder er, som vi raskt lærte, ekstremt nyttig. Det tillot en standard metode for å lappe eksterne biblioteker uten å endre byggeprosessene på en ad-hoc måte, og holde kodebasen ren.
  • Byggverktøykjedene viste sine egne kompleksiteter, da kompilatoren eller koblingsvalget ofte påvirket bibliotekene, API-ene og definisjonene som kan brukes. Og dessverre kan bytte av kompilatorer på Bazel være ganske tungvint . Spesielt på Windows kommer den beste opplevelsen ofte med å bruke Microsoft Visual C ++ build-verktøy, men våre verktøykjeder på andre plattformer var basert på GCC eller Clang. Heldigvis kommer LLVM-verktøykjeden med Clang-Cl på Windows, som gjør det mulig å bruke en blanding av Clang- og MSVC-funksjoner, noe som gjør mange problemer mye enklere å løse.
  • Bibliotekavhengighet ga generelt de fleste vanskelighetene. Noen ganger var problemet like verdslig som en manglende overskrift eller motstridende definisjon som kunne håndteres mekanisk, som til og med mye brukte biblioteker (som Boost) ikke var immune mot. Løsningene på disse var ofte noen passende makrodefinisjoner eller dummyoverskrifter. I andre tilfeller – som for hireis og Pil -bibliotek – krevde biblioteker POSIX-funksjonalitet som var utilgjengelig på Windows (for eksempel signaler eller muligheten til å sende filbeskrivere over UNIX-domenekontakter). Disse viste seg å være mye vanskeligere å løse i noen tilfeller. Ettersom vi var mer opptatt av kompilerbarhet på dette stadiet, var det imidlertid fruktbart å utsette implementeringen av mer komplekse API-er til et senere stadium og å bruke grunnleggende stubber eller deaktivere den krenkende koden for å la bygningen fortsette.

Når vi var ferdige med å kompilere avhengigheter, kunne vi fokusere på å kompilere kjernen til Ray selv.

Compilability (Ray)

Neste hindring var å få Ray til kompilere, sammen med Plasma butikken (del av Pil ). Dette var vanskeligere ettersom koden ofte ikke var designet for Windows-modellen i det hele tatt og stolte sterkt på POSIX API-er. I noen tilfeller var dette bare et spørsmål om å finne og bruke passende overskrifter (for eksempel WinSock2.h i stedet for sys/time.h for struct timeval, noe som kan være overraskende) eller lage erstatninger ( som for

unistd.h ). I andre tilfeller deaktiverte vi den inkompatible koden og la TODOer til å adressere i fremtiden.

Selv om dette konseptuelt var likt tilfellet med håndtering av avhengigheter fra tredjeparter, var en unik bekymring på høyt nivå som dukket opp her minimering av endringer i kodebasen , i stedet for å gi mest elegante løsningen mulig. Spesielt, ettersom teamet kontinuerlig oppdaterte kodebasen under antagelse om en POSIX API og da kodebasen ennå ikke kompilerte med Windows, ble løsninger som var «drop-in» og som kunne brukes transparent med minimale endringer for å oppnå omfattende kompatibilitet over hele kodebasen var mye mer nyttig for å unngå syntaktiske eller semantiske sammenslåingskonflikter enn løsninger som var kirurgiske. På grunn av dette faktum, i stedet for å endre hvert anropsside, opprettet vi våre egne ekvivalente Windows shims for POSIX APIer som simulerte ønsket oppførsel, selv om det var suboptimalt alt i alt. Dette tillot oss å sikre kompilerbarhet og stanse spredningen av inkompatible endringer mye raskere (og senere ensartet løse hvert enkelt problem på tvers av hele kodebasen i batch) enn det som ellers ville være mulig.

Koblingsbarhet

Når kompilerbarheten var oppnådd, var neste problem riktig kobling av objektfiler for å oppnå kjørbare binære filer.Mens det er teoretisk enkle, involverte koblingsproblemer ofte mange tilfeldige kompleksiteter, for eksempel følgende:

  • Enkelte systembiblioteker var plattformspesifikke og utilgjengelige eller forskjellige på andre plattformer ( som libpthread)
  • Enkelte biblioteker ble koblet dynamisk når de var forventet å være lenket statisk (eller omvendt)
  • Visse symboldefinisjoner manglet eller var motstridende (for eksempel connect fra leier motstridende med connect for stikkontakter)
  • Visse avhengigheter arbeidet tilfeldigvis med POSIX-systemer, men krevde eksplisitt håndtering i Bazel on Windows

Løsningene på disse var ofte verdslige (men kanskje ikke åpenbare) endringer i build-filene, men i noen tilfeller var det nødvendig å lappe avhengigheter. Som før var Bazels evne til å lappe praktisk talt alle filer fra en ekstern kilde ekstremt nyttig for å hjelpe til med å løse slike problemer.

By Richy Great

Å få kodebasen til å bygge vellykket var en stor milepæl. Når de plattformsspesifikke endringene var integrert, ville hele teamet nå påta seg ansvaret for å sikre statisk bærbarhet i alle fremtidige endringer, og innføring av inkompatible endringer ville bli minimert på tvers av kodebasen. ( Dynamisk portabilitet var selvfølgelig fremdeles ikke mulig på dette tidspunktet, da stubber ofte manglet den nødvendige funksjonaliteten på Windows, og følgelig ikke koden kunne kjøres på dette stadiet.)

Å få kodebasen til å lykkes med å bygge var en stor milepæl. Når de plattformsspesifikke endringene var integrert, ville hele teamet nå påta seg ansvaret for å sikre statisk bærbarhet i alle fremtidige endringer, og innføring av inkompatible endringer ville bli minimert på tvers av kodebasen. ( Dynamisk portabilitet var selvfølgelig fremdeles ikke mulig på dette tidspunktet, da stubber ofte manglet den nødvendige funksjonaliteten på Windows, og følgelig ikke koden kunne kjøres på dette stadiet.)

Ikke bare reduserte dette arbeidsmengden, men det tillot Windows-portabilitetsinnsatsen å bremse og fortsette saktere og forsiktig for å løse vanskeligere inkompatibiliteter i dybden, uten å måtte avlede oppmerksomheten mot å bryte endringer som stadig ble introdusert i kodebasen. Dette åpnet for løsninger av høyere kvalitet i det lange løp, inkludert noen refactoring av kodebasen som potensielt påvirket semantikken på andre plattformer.

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

Dette var muligens det mest tidkrevende stadiet, der kompatibilitetsstubber vil bli fjernet eller utdypet, deaktivert kode kan aktiveres på nytt, og individuelle problemer kan løses. Den statiske karakteren til C ++ tillot kompilerdiagnostikk for å lede de fleste slike nødvendige endringer uten behov for å utføre noen kode i det hele tatt.

De enkelte endringene her, inkludert noen som er nevnt tidligere, ville være for lange til å diskutere i dybden i sin helhet, og visse enkeltutgaver vil sannsynligvis være verdige deres egne lange blogginnlegg. Prosessgyting og -administrasjon, for eksempel, er faktisk en overraskende kompleks innsats full av idiosynkrasier og fallgruver (se for eksempel her ) som det ikke ser ut til å være bra for, plattformbibliotek, til tross for at dette er et gammelt problem. En del av dette skyldes dårlig innledende design i selve operativsystem-API-ene. ( Kromkildekoden gir et glimt av noen av kompleksitetene som ofte blir sett bort fra andre steder. Kromkildekoden kan faktisk tjene som en god illustrasjon på håndtering mange plattformkompatibiliteter og finesser.) Vår første ignorering av dette faktum førte til at vi prøvde å bruke Boost.Process, men mangelen på tydelig eierskapssemantikk for POSIXs pid_t (som brukes til begge prosessidentifikasjon og eierskap) så vel som bugs i Boost.Process-biblioteket i seg selv resulterte i vanskelige å finne bugs i kodebasen, og til slutt resulterte det i vår beslutning om å tilbakestille denne endringen og introdusere vår egen abstraksjon. Videre var Boost.Process-biblioteket ganske tungt selv for et Boost-bibliotek, og dette bremset byggene våre betydelig. I stedet endte vi opp med å skrive vår egen innpakning for prosessobjekter. Dette viste seg å fungere ganske bra for våre formål. En av takeawayene våre i dette tilfellet var å vurdere å skreddersy løsninger etter våre egne behov og ikke anta at eksisterende løsninger er det beste valget .

Dette var selvfølgelig bare et glimt av spørsmålet om prosessadministrasjon.Noen andre aspekter av portabilitetsinnsatsen er kanskje verdt å utdype det også.

By Thomas Jensen

Deler av kodebasen (for eksempel kommunikasjon med Plasma-serveren i pilen) antas muligheten til å bruke UNIX-domenekontakter (AF_UNIX) på Windows. Mens nylige versjoner av Windows 10 do støtter UNIX-domenekontakter, er implementeringene heller ikke tilstrekkelige for å dekke all mulig bruk av UNIX-domene stikkontakter, og heller ikke UNIX-domenestikkontakter er spesielt elegante: de krever manuell opprydding og kan legge igjen unødvendige filer i filsystemet i tilfelle en ikke-ren avslutning av en prosess, og de gir ikke muligheten til å sende tilleggsdata (for eksempel filbeskrivere) til en annen prosess. Ettersom Ray bruker Boost.Asio, var den mest hensiktsmessige erstatningen for UNIX-domene-stikkontakter lokale TCP-stikkontakter (begge kan trekkes ut som generelle stikkontakter), så vi byttet til sistnevnte, bare erstatte filstier med strengede versjoner av TCP / IP-adresser uten å måtte omforme kodebasen.

Dette var ikke tilstrekkelig, ettersom bruk av TCP-stikkontakter fremdeles ikke ga muligheten til å replikere stikkontaktbeskrivere i andre prosesser. En riktig løsning på dette problemet hadde faktisk vært å unngå det helt. Ettersom det ville kreve en langsiktig refactoring av de relevante delene av kodebasen (en oppgave som andre startet på), syntes en gjennomsiktig tilnærming mer hensiktsmessig i mellomtiden. Dette ble vanskeliggjort av det faktum at duplisering av en filbeskrivelse på UNIX-baserte systemer ikke krever kunnskap om identiteten til målprosessen, mens duplisering av et håndtak på Windows krever aktiv manipulering av målprosess. For å løse disse problemene implementerte vi muligheten til å utveksle filbeskrivelser ved å erstatte håndtrykkprosedyren ved å lansere Plasma-butikken med en mer spesialisert -mekanisme som ville etablere en TCP-forbindelse , slå opp ID-en for prosessen i den andre enden (en kanskje treg, men engangsprosedyre), dupliser sokkelhåndtaket, og informer målprosessen om det nye håndtaket. Selv om dette ikke er en generell løsning (og faktisk kan være utsatt for løpsforhold generelt) og ganske en uvanlig tilnærming, fungerte det bra for Rays formål, og kan være en tilnærming andre prosjekter som har samme problem kunne ha nytte av.

Utover dette var en av de største problemene vi forventet å møte, vår Redis serveravhengighet. Mens Microsoft Open Technologies (MSOpenTech) tidligere hadde implementert en Redis-port til Windows, hadde prosjektet blitt forlatt og støttet følgelig ikke versjonene av Redis som Ray krevde . Dette fikk oss i utgangspunktet til å anta at vi fortsatt trenger å kjøre Redis-serveren på Windows Subsystem for Linux (WSL), noe som sannsynligvis ville ha vist seg å være upraktisk for brukerne. Vi var ganske takknemlige da vi oppdaget at en annen utvikler hadde fortsatt prosjektet for å produsere senere Redis-binære filer på Windows (se tporadowski / redis ). Dette forenklet problemet vårt enormt og tillot oss å tilby innfødt støtte til Ray for Windows. fraværet av trivielle erstatninger for noen POSIX APIer på Windows. Noen av disse ( som

getppid()) var greie, men litt kjedelige. Muligens det vanskeligste problemet som oppstod under hele porteringsprosessen, var imidlertid filbeskrivere kontra filhåndtak. Mye av koden som vi stolte på (for eksempel Plasma-butikken i Arrow) antok bruk av POSIX-filbeskrivere (int s). Windows bruker imidlertid innfødt HANDLE s, som er pekerstørrelse og analoge med size_t. I seg selv er dette imidlertid ikke et betydelig problem, da Microsoft Visual C ++ runtime (CRT) gir et POSIX-lignende lag. Laget er imidlertid begrenset i funksjonalitet, krever oversettelser på hvert anropsside som ikke støtter det, og kan spesielt ikke brukes til ting som stikkontakter eller delt minneshåndtak. Videre ønsket vi ikke å anta at HANDLE s alltid ville være små nok til å passe inn i et 32-biters heltall, selv om dette ofte var tilfelle, da det var uklart om omstendigheter vi ikke var klar over, kunne bryte denne antagelsen lydløst.Dette forsterket problemene våre betydelig, da den mest åpenbare løsningen ville ha vært å oppdage alle int som representerer filbeskrivere i et bibliotek som Arrow, og å erstatte dem (og alle deres bruksområder ) med en alternativ datatype, som var en feilutsatt prosess og involverte betydelige oppdateringer til ekstern kode, noe som skapte en betydelig vedlikeholdsbyrde.

Det var ganske vanskelig å bestemme hva som skulle gjøres på dette stadiet. MSOpenTech Redis løsning for det samme problemet gjorde det klart at dette var en ganske skremmende oppgave, da de hadde løst dette problemet ved å lage en singleton, prosessomfattende filbeskrivelsestabell på toppen av den eksisterende CRT-implementeringen, som krever at de håndterer trådsikkerhet, samt å tvinge dem til å fange opp dem > all bruk av POSIX API-er (også de den allerede var i stand til å håndtere) bare for å oversette filbeskrivere. I stedet bestemte vi oss for å ta en uvanlig tilnærming: vi utvidet POSIX-oversettelseslaget i CRT . Dette ble gjort ved å identifisere inkompatible håndtak ved opprettelsestidspunktet og «skyve» håndtakene inn i bufferne til et overflødig rør, og returnere deskriptoren til det røret i stedet. Vi måtte da bare endre brukssidene til disse håndtakene, som avgjørende var trivielle å identifisere, da de alle var socket og minnekartede filer APIer. Dette hjalp faktisk med å unngå behovet for oppdatering, ettersom vi bare kunne omdirigere mange funksjoner via makroer .

Mens arbeidet med å utvikle dette unike utvidelseslaget (i win32fd.h) var betydelig (og ganske uortodoks), det var sannsynligvis verdt det, da oversettelseslaget var faktisk ganske liten i sammenligning, og tillot oss å delegere mest urelaterte problemer (for eksempel flertrådet låsing av filbeskrivelsestabellen) til CRT API-ene. Videre, ved å utnytte anonyme rør fra den samme globale filbeskrivelsestabellen (til tross for vår manglende direkte tilgang til den), var vi i stand til å unngå å måtte fange opp og oversette filbeskrivelser for andre funksjoner som allerede kan håndteres direkte. Dette tillot mye av koden å forbli i det vesentlige uendret med minimal ytelsespåvirkning, til vi senere hadde en sjanse til å omformere koden og gi bedre innpakninger på et høyere nivå (for eksempel via Boost.Asio-innpakninger). Det er ganske mulig at en utvidelse av dette laget kan tillate at andre prosjekter som Redis blir portet til Windows mye mer sømløst og med langt mindre drastiske endringer eller potensial for feil.

Av Andrea Leopardi

Run-time Executability (Proof-of-Concept)

Når vi trodde at Ray-kjernen fungerte som den skal, var neste milepæl å sikre at en Python-test kunne utøve kodestiene våre. I utgangspunktet prioriterte vi ikke å gjøre det. Dette viste seg imidlertid å være en feil, siden senere endringer fra andre utviklere på teamet faktisk introduserte mer dynamiske inkompatibiliteter med Windows, og CI-systemet klarte ikke å oppdage slike brudd. Vi prioriterte derfor senere å kjøre en minimal test på Windows-buildene, for å unngå ytterligere brudd på build.

For for det meste var innsatsen vår vellykket, og eventuelle dvelende feil i Ray-kjernen var i forutsigbare deler av kodebasen (selv om adressering av dem ofte krevde å gå gjennom multik prosesskode, noe som var langt fra trivielt). Imidlertid var det minst en litt ubehagelig overraskelse underveis på C-siden, og en på Python-siden, som begge (blant annet) oppmuntret oss til å lese programvaredokumentasjon mer proaktivt i fremtiden.

På C-siden, vårt første håndtrykk wrapper for utveksling av sokkelhåndtak avhengige av å naivt erstatte sendmsg og recvmsg med WSASendMsg og WSARecvMsg. Disse Windows-API-ene var de nærmeste ekvivalenter av POSIX-APIene, og så ut til å være et åpenbart valg. Imidlertid, etter utførelse, ville koden stadig krasje, og kilden til problemet var uklar. Noe feilsøking (inkludert med feilsøkingsversjoner av builds & runtimes) bidro til å avsløre at problemet var med stackvariablene som ble sendt til WSASendMsg. Ytterligere feilsøking og nøye inspeksjon av minneinnholdet antydet at problemet kan ha vært msg_flags -feltet til WSAMSG, da dette var den eneste uinitialiserte felt.Dette syntes imidlertid å være irrelevant: msg_flags ble bare oversatt fra flags i struct msghdr, som ikke ble brukt på input, og bare ble brukt som en output-parameter. Å lese dokumentasjonen avdekket imidlertid problemet: På Windows fungerte feltet også som en input -parameter, og dermed ble det uinitialisert resulterte i uforutsigbar oppførsel! Dette var ganske uventet for oss og resulterte i at to viktige takeaways gikk fremover: for å lese dokumentasjonen for hver funksjon nøye , og dessuten at initialisering av variabler ikke bare handler om å sikre korrekthet med gjeldende API-er, men også viktig for å gjøre koden robust for fremtidige endringer i de målrettede API-ene .

På Python-siden opplevde vi et annet problem. Våre innfødte Python-moduler kunne i utgangspunktet ikke lastes inn, til tross for mangelen på åpenbare problemer. Etter flere dagers gjetning, å gå gjennom montering og CPython-kildekoden og inspisere variabler i CPython-kodebasen, ble det tydelig at problemet var mangelen på et .pyd -suffiks på dynamisk Python biblioteker på Windows. Som det viser seg, nekter Python av uklare grunner å laste selv .dll filer på Windows som Python-moduler, til tross for at innfødte delte biblioteker normalt kan lastes selv med hvilken som helst fil Utvidelse. Faktisk viste det seg at dette faktum ble dokumentert på Pythons nettsted . Dessverre kunne tilstedeværelsen av slik dokumentasjon muligens ikke antyde at vi var klar over å se etter den.

Likevel klarte Ray til slutt å lykkes på Windows, og dette avsluttet neste milepæl og ga et bevis av konseptet for arbeidet.

Av Hitesh Choudhary

Kjøretidskompatibilitet (for det meste Python)

På dette punktet var kjernen i Ray en gang arbeider, var vi i stand til å fokusere på å portere høyere nivå kode. Noen problemer var ganske enkle å løse – for eksempel har noen Python API-er som bare er UNIX (f.eks. os.uname()[1]) ofte passende erstatninger på Windows (for eksempel socket.gethostname()), og å finne dem var et spørsmål om å vite å søke etter alle forekomster av dem på tvers av kodebasen. Andre problemer var vanskeligere å spore opp eller løse. Noen ganger skyldtes de bruken av POSIX-spesifikke kommandoer (for eksempel ps), som krevde alternative tilnærminger (for eksempel bruk av psutil for Python). Andre ganger skyldtes de inkompatibilitet i tredjepartsbiblioteker. For eksempel når en stikkontakt kobles fra på Windows, heves en feil i stedet for å resultere i tomme avlesninger. Python-biblioteket for Redis så ikke ut til å håndtere dette. Slike forskjeller i atferd krevde eksplisitt apeoppdatering for å unngå tidvis forvirrende feil som ville oppstå ved avslutning av Ray.

Mens noen slike problemer er ganske kjedelig, men likevel sannsynligvis forventet (for eksempel å erstatte bruk av /tmp med plattformens midlertidige katalog, eller unngå antagelsen om at alle absolutte baner begynner med en skråstrek), var noen noe uventede (for eksempel motstridende portreservasjoner ) eller (som ofte er tilfelle) på grunn av feil antagelser, og å forstå dem avhenger av å forstå Windows-arkitekturen og dens tilnærminger til bakoverkompatibilitet.

En slik historie dreier seg om bruk av skråstreker som katalogskillere på Windows. Generelt ser disse ut til å fungere bra, og brukes ofte av utviklere. Dette skyldes imidlertid automatisk konvertering av skråstreker til tilbakeslag i Windows-delsystembibliotekene i brukermodus, og visse automatiske behandlinger kan undertrykkes ved eksplisitt å prefikse baner med et \\?\ prefiks, som er nyttig for å omgå visse kompatibilitetsfunksjoner (for eksempel lange stier). Vi brukte imidlertid aldri eksplisitt en slik bane, og antok at brukerne kunne forventes å unngå uvanlig bruk i våre eksperimentelle utgivelser. Det ble imidlertid senere tydelig at når Bazel påkalte visse Python-tester, ville stier bli behandlet i dette formatet for å tillate bruk av lange stier, og dette deaktiverte den automatiske oversettelsen vi implisitt stolte på. Dette førte oss til viktige takeaways: for det første at det generelt er bedre å bruke APIer på den mest passende måten for målsystemet, da det gir minst mulig muligheter for uventede problemer. .For det andre, og viktigst, er det rett og slett en feilslutning å anta at en brukers miljø er forutsigbar . Virkeligheten er at moderne programvare nesten alltid er avhengig av å kjøre tredjepartskode hvis nøyaktige oppførsel vi ikke er klar over. Selv når en bruker kan antas å unngå problematiske situasjoner, er tredjeparts programvare helt uvitende om slike skjulte antagelser. Dermed er det sannsynlig at de vil forekomme uansett, ikke bare for brukere, men også for programvareutviklerne selv, noe som resulterer i feil som er vanskeligere å spore enn å adressere når de skriver den første koden. Derfor er det viktig å unngå å legge for mye vekt på programvennlighet (det motsatte av «brukervennlighet») når du designer et robust system.

(Som en morsom side: faktisk på Windows, stier kan faktisk inneholde anførselstegn og mange andre spesialtegn som normalt antas å være ulovlige. Dette skjer når du bruker NTFS alternative datastrømmer. Disse er imidlertid sjeldne og komplekse nok til at selv standard språkbiblioteker ofte ikke håndterer dem.)

Når de mest betydningsfulle problemene ble løst, klarte imidlertid mange tester å overføre Windows, og opprettet den første eksperimentelle Windows-implementeringen av Ray.

Av Ross Sneddon

Kjøretidsforbedringer (f.eks. Unicode-støtte)

På dette tidspunktet kan mye av kjernen i Ray brukes på Windows akkurat som på andre plattformer. Likevel gjenstår noen problemer, som krever kontinuerlig innsats for å løse dem.

Unicode-støtte er et slikt problem. På grunn av historiske årsaker har delsystemet Windows-brukermodus to versjoner av de fleste API-er: en “ANSI” -versjon som støtter tegnsett med enkeltbyte, og en “Unicode” -versjon som støtter UCS-2 eller UTF-16 (avhengig av informasjon om det aktuelle APIet). Dessverre er ingen av disse UTF-8; selv grunnleggende støtte for Unicode krever bruk av bredstrengede strenger (basert på wchar_t) over hele kodebasen. (nb: Faktisk har Microsoft nylig forsøkt å introdusere UTF-8 som en kodeside, men det støttes ikke godt nok til å løse dette problemet sømløst, i det minste uten å stole på potensielt udokumenterte og sprø Windows-interner.)

Tradisjonelt håndterer Windows-programmer Unicode gjennom bruk av makroer som _T() eller TEXT(), som utvides til smal eller bred -karakterbokstaver avhengig av om en Unicode-konstruksjon er spesifisert, og bruk TCHAR som deres generiske karaktertyper. På samme måte har de fleste C API-er TCHAR -avhengige versjoner (som _tcslen() i stedet for strlen()) for å tillate kompatibilitet med begge typer koder. Imidlertid er det ganske involvert å migrere en UNIX-basert kodebase til denne modellen. Dette har ennå ikke blitt gjort i Ray, og i og med at dette skrives, støtter Ray ikke riktig Unicode i (for eksempel) filstier på Windows, og den beste tilnærmingen for å gjøre det kan fortsatt være noe av et åpent spørsmål. / p>

Et annet slikt problem er kommunikasjonsmekanismen mellom prosesser. Selv om TCP-stikkontakter kan fungere bra på Windows, er de ikke optimale, ettersom de introduserer et lag med unødvendig kompleksitet i logikken (som tidsavbrudd, keep-alives, Nagles algoritme), kan føre til utilsiktet tilgjengelighet fra ikke-lokale verter, og kan introdusere noen ytelse overhead. I fremtiden kan Named Pipes gi bedre erstatning for UNIX-domenekontakter på Windows; faktisk selv på Linux kan enten rør eller såkalte abstrakte UNIX-domenekontakter vise seg å være bedre alternativer også, ettersom de ikke krever rot og opprydding av stikkontaktfiler på filsystemet.

Til slutt er et annet eksempel på et slikt problem kompatibilitet med BSD-stikkontakter, eller rettere sagt mangel på det. Et utmerket svar på StackOverflow diskuterer noen av problemene i dybden, men kort, mens vanlige sokkel-API-er er derivater av den opprinnelige BSD-sokkel-API, implementerer forskjellige plattformer lignende sokkel flagger annerledes. Spesielt kan konflikter med eksisterende IP-adresser eller TCP-porter produsere ulik oppførsel på tvers av plattformer. Selv om problemene er vanskelige å detaljere her, er sluttresultatet at dette kan gjøre det vanskelig å bruke flere forekomster av Ray på samme vert samtidig. (Faktisk, ettersom dette avhenger av OS-kjerneatferd, påvirker det også WSL.) Dette er enda et kjent problem hvis løsning er ganske involvert og ikke fullstendig adressert i det nåværende systemet.

Konklusjon

Prosessen med å portere en kodebase som den til Ray til Windows har vært en verdifull opplevelse som fremhever fordeler og ulemper med mange aspekter ved programvareutvikling og deres innvirkning på vedlikehold av koder.Den foregående beskrivelsen fremhever bare noen av hindringene som oppstår underveis. Mange nyttige konklusjoner kan trekkes fra prosessen, hvorav noen kan være verdifulle å dele her for andre prosjekter i håp om å oppnå et lignende mål.

Først, i noen tilfeller, fant vi faktisk senere på påfølgende versjoner. av noen biblioteker (som for eksempel hireis) hadde allerede løst noen problemer som vi hadde taklet. Løsningene var ikke alltid åpenbare, da (for eksempel) versjonen av hireis i de siste Redis-versjonene faktisk var en foreldet kopi av hireis, noe som førte oss til å tro at noen problemer ennå ikke hadde blitt løst. Senere revisjoner tok heller ikke alltid opp alle eksisterende kompatibilitetsproblemer. Likevel ville det muligens ha spart litt innsats for å sjekke dypere for eksisterende løsninger på noen problemer for å unngå å måtte løse dem igjen.

Av John Barkiple

Second, forsyningskjeden for programvare er ofte kompleks . Feil kan naturlig sammensettes i hvert lag, og det er en feil å stole på selv brukte åpen kildekodeverktøy for å være «kamptestet» og derfor robuste, spesielt når de brukes i sinne . Videre har mange langvarige eller vanlige programvaretekniske problemer ikke tilfredsstillende løsninger tilgjengelig for bruk, spesielt (men ikke bare) når de krever kompatibilitet på tvers av forskjellige systemer. Faktisk, bortsett fra bare quirks, i ferd med å portere Ray til Windows, opplevde vi regelmessig og rapporterte ofte om feil i en rekke programvarer, inkludert men ikke begrenset til en Git bug on Linux som påvirket Bazel-bruken, Redis (Linux) , glog , psutil (parsing bug that affect WSL) , grpc , mange vanskelig å identifisere feil i selve Bazel (f.eks. 1 , 2 , 3 , 4 ), Travis CI og GitHub Actions , blant andre. Dette oppmuntret oss til å ta større hensyn til kompleksiteten i våre avhengigheter også.

For det tredje investere i verktøy og infrastruktur betaler utbytte på sikt. Raskere bygg muliggjør raskere utvikling, og kraftigere verktøy gjør det lettere å løse komplekse problemer. I vårt tilfelle hjalp bruken av Bazel oss på mange måter, til tross for at det var langt fra perfekt og at det ble innført en bratt læringskurve. Det er sjelden enkelt å investere litt tid (muligens flere dager) for å lære om evner, styrker og mangler ved et nytt verktøy, men sannsynligvis vil det være gunstig for vedlikehold av koder. I vårt tilfelle brukte vi litt tid på å lese Bazel-dokumentasjonen i dybden, oss til å finne mye raskere en rekke fremtidige problemer og løsninger. Dessuten hjalp det oss også med å integrere verktøy med Bazel som få andre tilsynelatende hadde klart å klare, for eksempel Clangs include-what-you-use -verktøy.

Fjerde, og som tidligere nevnt, er det forsvarlig å engasjere seg i sikker kodingspraksis som å initialisere minne før bruk når det er ingen vesentlig avveining. Selv den mest forsiktige ingeniøren kan ikke nødvendigvis forutsi fremtidig evolusjon av det underliggende systemet som kan stille ugyldigheter fra antagelser.

Til slutt, som generelt er tilfelle i medisin, forebygging er den beste kuren . Å ta hensyn til mulig fremtidig utvikling, og koding til standardiserte grensesnitt, muliggjør mer utvidbar kodedesign enn det lett kan oppnås etter at inkompatibiliteter oppstår.

Selv om porten til Ray til Windows ennå ikke er fullført, har det vært ganske vellykket så langt, og vi håper at delingen av vår erfaring og løsninger kan tjene som en nyttig guide for andre utviklere som vurderer å legge ut på en lignende reise.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *