Porting Ray till Microsoft Windows

By Johny vino

(Mehrdad Niknami) (28 september 2020)

Bakgrund

När Ray lanserades, skrevs den för Linux och macOS – UNIX-baserade operativsystem. Stöd för Windows, det mest populära stationära operativsystemet, saknades, men viktigt för projektets långsiktiga framgång. Medan Windows Subsystem for Linux (WSL) gav ett möjligt alternativ för vissa användare, begränsade det stödet till de senaste versionerna av Windows 10 och gjorde det mycket svårare för koden att interagera med det inbyggda Windows-operativsystemet, vilket innebar en dålig upplevelse för användarna. Med tanke på dessa hoppades vi verkligen att ge inbyggt Windows-support till användare om det är praktiskt.

Att porta Ray till Windows var dock långt ifrån en trivial uppgift. Liksom många andra projekt som försöker uppnå bärbarhet efter det faktum stötte vi på många utmaningar vars lösningar inte var uppenbara. I det här blogginlägget strävar vi efter att ta ett djupdyk i de tekniska detaljerna i processen som vi följde för att göra Ray kompatibel med Windows. Vi hoppas att det kan hjälpa andra projekt med en liknande vision att förstå några av de potentiella utmaningarna och hur de kan hanteras.

Översikt

Eftermonteringsstöd för Windows utgjorde en allt större utmaning när utvecklingen på Ray utvecklades ytterligare. Innan vi startade förväntade vi oss att vissa aspekter av koden skulle utgöra en betydande del av vårt portabilitetsarbete, inklusive följande:

  • Inter-process communication (IPC) and shared memory
  • Objekthandtag och filhandtag / beskrivningar (FD)
  • Spawning och hantering av processer, inklusive signalering
  • Filhantering, trådhantering och asynkron I / O
  • Användning av skal och systemkommando
  • Redis

Med tanke på omfattningen av problemen insåg vi snabbt att förhindra att ytterligare inkompatibiliteter införs bör prioriteras framför försök att lösa befintliga frågor. Vi försökte därför fortsätta i ungefär följande steg, även om detta är något av en förenkling eftersom specifika problem ibland behandlades i olika steg:

  1. Kompabilitet för beroenden från tredje part
  2. Kompabilitet för Ray (via tomma stubbar & TODO)
  3. Länkbarhet
  4. Kontinuerlig integration (CI) (för att blockera ytterligare oförenliga ändringar)
  5. Statisk kompatibilitet (mestadels C ++)
  6. Körbarhet för körning (minimal POC)
  7. Körning av körtid (mestadels Python)
  8. Kör förbättringar i tid (t.ex. Unicode-stöd)
Av Ashkan Forouzani

Utvecklingsprocess

På hög nivå kan metoderna här variera. För mindre projekt kan det vara mycket fördelaktigt att förenkla kodbasen samtidigt som koden överförs till en ny plattform. Det tillvägagångssätt vi tog, vilket visade sig vara till stor hjälp för en stor och dynamiskt förändrad kodbas, var att ta itu med frågor en i taget , håll ändringar så ortogonala mot varandra som möjligt och prioritera bevarande av semantik framför enkelhet .

Ibland krävde detta att man skrev potentiellt främmande kod för att hantera förhållanden som inte nödvändigtvis har inträffat i produktionen (säg, citera filsökväg som aldrig skulle ha mellanslag). Vid andra tillfällen krävde detta införandet av mer ”mekaniska” lösningar för allmänt ändamål som kan ha varit undvikbara (som att använda en std::shared_ptr för vissa fall där en annan design kan ha har tillåtits att en std::unique_ptr ska användas). Att bevara semantiken för befintlig kod var dock helt avgörande för att undvika att ständigt introducera nya buggar i kodbasen, vilket skulle ha påverkat resten av teamet. Och i efterhand var detta tillvägagångssätt ganska framgångsrikt: buggar på andra plattformar till följd av Windows-relaterade förändringar var ganska sällsynta och inträffade oftast på grund av förändringar i beroenden än på grund av semantiska förändringar i kodbasen.

Kompabilitet (tredjepartsberoende)

Det första hindret var att se till att tredje parts beroenden själva kunde byggas på Windows. Medan många av våra beroenden var allmänt använda bibliotek och de flesta inte hade större inkompatibiliteter, var det inte alltid enkelt. I synnerhet flödade oavsiktliga komplexiteter i flera aspekter av problemet.

  • Byggfilerna (särskilt Bazel-byggfiler) för vissa projekt var ibland otillräckliga i Windows och krävde korrigeringar . Ofta berodde dessa på problem som inträffade sällan på UNIX-plattformar, till exempel frågan om citerar filvägar med mellanslag, av lanserar rätt Python-tolk, eller frågan om att länkar delade bibliotek jämfört med statiska bibliotek. Tack och lov var ett av de mest användbara verktygen för att lösa detta problem Bazel själv: dess inbyggda förmåga att korrigera externa arbetsytor är, som vi snabbt lärde oss, extremt till hjälp. Det möjliggjorde en standardmetod för lappning av externa bibliotek utan att modifiera byggprocesserna på ett ad hoc-sätt och hålla kodbasen ren.
  • Byggverktygskedjorna uppvisade sina egna komplexiteter, eftersom kompilatorn eller länkvalet ofta påverkade biblioteken, API: er och definitioner som man kan lita på. Och tyvärr kan byta kompilatorer på Bazel vara ganska besvärligt . I synnerhet i Windows kommer den bästa upplevelsen ofta med att använda Microsoft Visual C ++ byggverktyg, men våra verktygskedjor på andra plattformar baserades på GCC eller Clang. Lyckligtvis kommer LLVM-verktygskedjan med Clang-Cl på Windows, vilket gör det möjligt att använda en blandning av Clang- och MSVC-funktioner, vilket gör många problem mycket lättare att lösa.
  • Biblioteksberoenden gav i allmänhet de största svårigheterna. Ibland var frågan lika vardaglig som en saknad rubrik eller motstridig definition som kunde hanteras mekaniskt, vilket även allmänt använda bibliotek (som Boost) inte var immuna mot. Lösningarna på dessa var ofta några lämpliga makrodefinitioner eller dummy-rubriker. I andra fall – till exempel för hirde och Pil -bibliotek – krävde bibliotek POSIX-funktionalitet som var inte tillgänglig i Windows (t.ex. signaler eller möjligheten att skicka filbeskrivare över UNIX-domänuttag). Dessa visade sig vara mycket svårare att lösa i vissa fall. Eftersom vi var mer bekymrade över kompilerbarheten i detta skede var det dock fruktbart att skjuta upp implementeringen av mer komplexa API: er till ett senare skede och att använda grundläggande stubbar eller inaktivera den kränkande koden för att låta byggandet fortsätta.

När vi var klara med att kompilera beroenden kunde vi fokusera på att kompilera kärnan i Ray själv.

Compilability (Ray)

Nästa hinder var att få Ray själv till kompilera, tillsammans med Plasma butik (del av Pil ). Detta var svårare eftersom koden ofta inte var utformad för Windows-modellen alls och förlitade sig mycket på POSIX API: er. I vissa fall handlade det bara om att hitta och använda lämpliga rubriker (som WinSock2.h istället för sys/time.h för struct timeval, vilket kan vara förvånande) eller skapa ersättare ( som för

unistd.h ). I andra fall inaktiverade vi den inkompatibla koden och lämnade TODO för att ta itu med i framtiden.

Även om detta konceptuellt liknade fallet med hantering av tredje parts beroenden, en unik högnivåproblem som uppstod här var att minimera ändringar i kodbasen , istället för att mest eleganta lösningen möjligt. I synnerhet, eftersom teamet kontinuerligt uppdaterade kodbasen under antagandet av ett POSIX API och eftersom kodbasen ännu inte kompilerades med Windows, lösningar som var ”drop-in” och som kunde användas transparent med minimala förändringar för att få svepande kompatibilitet över hela kodbasen var mycket mer användbar för att undvika syntaktiska eller semantiska sammanslagningskonflikter än lösningar som var kirurgiska till sin natur. På grund av detta faktum skapade vi istället för att ändra varje samtalswebbplats våra egna motsvarande Windows shims för POSIX API: er som simulerade önskat beteende, även om det var suboptimalt övergripande. Detta gjorde det möjligt för oss att säkerställa kompilerbarhet och stoppa spridningen av oförenliga förändringar mycket snabbare (och senare på ett enhetligt sätt ta itu med varje enskild fråga i hela kodbasen i batch) än vad som annars skulle vara möjligt.

Länkbarhet

När kompilerbarheten hade uppnåtts var nästa fråga rätt koppling av objektfiler för att erhålla körbara binärer.Även om teoretiskt enkla problem med länkbarhet ofta involverade många oavsiktliga komplexiteter, såsom följande:

  • Vissa systembibliotek var plattformsspecifika och otillgängliga eller olika på andra plattformar ( som libpthread)
  • Vissa bibliotek länkades dynamiskt när de förväntades länkas statiskt (eller vice versa)
  • Vissa symboldefinitioner saknades eller var motstridiga (till exempel connect från hyres motstridigt med connect för uttag)
  • Vissa beroenden fungerade samtidigt på POSIX-system men krävde uttrycklig hantering i Bazel på Windows

Lösningarna för dessa var ofta vardagliga (men kanske inte självklara) ändringar av byggfilerna, men i vissa fall var det nödvändigt att korrigera beroenden. Som tidigare var Bazels förmåga att korrigera praktiskt taget alla filer från en extern källa till stor hjälp när det gäller att lösa sådana problem.

Av Richy Great

Att få kodbasen att bygga framgångsrikt var en viktig milstolpe. När de plattformsspecifika ändringarna integrerats skulle hela teamet nu ta ansvar för att statisk bärbarhet i alla framtida förändringar och införandet av inkompatibla förändringar skulle minimeras över kodbasen. ( Dynamisk portabilitet var naturligtvis fortfarande inte möjligt vid denna tidpunkt, eftersom stubbarna ofta saknade nödvändig funktionalitet på Windows, och följaktligen inte kunde köra i det här skedet.)

Att få kodbasen att bygga framgångsrikt var en viktig milstolpe. När de plattformsspecifika ändringarna integrerats skulle hela teamet nu ta ansvar för att statisk bärbarhet i alla framtida förändringar och införandet av inkompatibla förändringar skulle minimeras över kodbasen. ( Dynamisk portabilitet var naturligtvis fortfarande inte möjligt vid denna tidpunkt, eftersom stubbarna ofta saknade nödvändig funktionalitet på Windows, och följaktligen inte kunde köra i det här skedet.)

Inte bara minskade detta arbetsbelastningen, men det gjorde det möjligt för Windows-bärbarhetsansträngningen att sakta ner och gå långsammare och noggrannare för att ta itu med svårare inkompatibiliteter på djupet, utan att behöva avleda uppmärksamheten på att bryta förändringar som ständigt införs i kodbasen. Detta möjliggjorde lösningar av högre kvalitet på lång sikt, inklusive viss refactoring av kodbasen som potentiellt påverkade semantiken på andra plattformar.

Statisk kompatibilitet (mestadels C / C ++)

Detta var möjligen det mest tidskrävande steget, där kompatibilitetsstubbar skulle tas bort eller utarbetas, inaktiverad kod kunde återaktiveras och enskilda problem kunde lösas. Den statiska karaktären av C ++ möjliggör för kompilerdiagnostik att styra de flesta sådana nödvändiga ändringar utan att behöva utföra någon kod alls.

De enskilda ändringarna här, inklusive några som nämnts tidigare, skulle vara för långa för att diskutera djupare i sin helhet, och vissa enskilda frågor skulle sannolikt vara värd sina egna långa blogginlägg. Processgytning och hantering är till exempel en förvånansvärt komplex strävan full av idiosynkrasier och fallgropar (se till exempel här ) för vilka det verkar inte finnas något bra. plattformsbibliotek, trots att detta är ett gammalt problem. En del av detta beror på dåliga initiala mönster i själva operativsystemets API: er. ( Kromkällkoden ger en glimt av några av de komplexiteter som ofta ignoreras någon annanstans. Kromkällkoden kan faktiskt ofta fungera som en bra illustration av hantering många plattformskompatibiliteter och subtiliteter.) Vår inledande bortse från detta faktum fick oss att försöka använda Boost.Process, men bristen på tydlig ägarskapssemantik för POSIXs pid_t (som används för båda processidentifiering och ägande) såväl som buggar i Boost.Process-biblioteket i sig resulterade i svårt att hitta buggar i kodbasen, vilket i slutändan resulterade i vårt beslut att återställa denna förändring och införa vår egen abstraktion. Dessutom var Boost.Process-biblioteket ganska tungt även för ett Boost-bibliotek, och detta saktade ner våra byggnader avsevärt. Istället skrev vi vårt eget omslag för processobjekt. Detta visade sig fungera ganska bra för våra ändamål. En av våra takeaways i det här fallet var att överväga att skräddarsy lösningar efter våra egna behov och inte anta att befintliga lösningar är det bästa valet .

Detta var naturligtvis bara en glimt av frågan om processhantering.Några andra aspekter av portabilitetsinsatsen är kanske värda att utarbeta också.

By Thomas Jensen

Delar av kodbasen (t.ex. kommunikation med plasmaservern i pilen) antas möjligheten att använda UNIX-domänuttag (AF_UNIX) i Windows. Medan de senaste versionerna av Windows 10 do stöder UNIX-domänuttag, är implementeringarna inte heller tillräckliga för att täcka all möjlig användning av UNIX-domän uttag och inte heller är UNIX-domänuttag särskilt eleganta: de kräver manuell rengöring och kan lämna onödiga filer i filsystemet i händelse av en icke-ren avslutning av en process, och de ger inte möjlighet att skicka kompletterande data (t.ex. filbeskrivare) till en annan process. Eftersom Ray använder Boost.Asio var den mest ändamålsenliga ersättningen för UNIX-domänuttag lokala TCP-uttag (som båda kunde abstraheras som allmänna uttag), så vi bytte till de senare, bara ersätta filvägar med strängade versioner av TCP / IP-adresser utan att behöva refaktorera kodbasen.

Detta var inte tillräckligt, eftersom användningen av TCP-uttag fortfarande inte gav möjlighet att replikera sockelbeskrivare till andra processer. En riktig lösning på detta problem hade faktiskt varit att helt undvika det. Eftersom det skulle kräva en längre siktning av relevanta delar av kodbasen (en uppgift som andra påbörjade) verkade emellertid en transparent strategi mer lämplig under tiden. Detta försvårades av det faktum att på UNIX-baserade system kräver duplicering av en filbeskrivare inte kunskap om målprocessens identitet, medan det i Windows krävs att man duplicerar ett handtag aktivt manipulerar målprocess. För att ta itu med dessa problem implementerade vi möjligheten att utbyta filbeskrivare genom att ersätta handskakningsproceduren när vi startade Plasma-butiken med en mer specialiserad -mekanism som skulle skapa en TCP-anslutning , leta upp ID: n för processen i andra änden (en kanske långsam men engångsförfarande), duplicera sockelhandtaget och informera målprocessen för det nya handtaget. Även om detta inte är en allmän lösning (och i själva verket kan vara benägen för rasförhållanden i allmänhet) och ganska ovanligt tillvägagångssätt, fungerade det bra för Ray: s ändamål, och kan vara ett tillvägagångssätt som andra projekt står inför samma problem skulle kunna dra nytta av.

Utöver detta var ett av de största problemen vi förväntade oss att möta vårt Redis serverberoende. Medan Microsoft Open Technologies (MSOpenTech) tidigare hade implementerat en Redis-port till Windows, hade projektet överges och stödde följaktligen inte de versioner av Redis som Ray krävde . Detta fick oss initialt att anta att vi fortfarande skulle behöva köra Redis-servern på Windows Subsystem for Linux (WSL), vilket sannolikt skulle ha visat sig vara obekvämt för användarna. Vi var alltså tacksamma över att upptäcka att en annan utvecklare hade fortsatt projektet för att producera senare Redis-binärer på Windows (se tporadowski / redis ). Detta förenklade vårt problem enormt och gjorde det möjligt för oss att ge inbyggt stöd för Ray för Windows.

Slutligen var möjligen de viktigaste hindren vi mötte (som MSOpenTech Redis gjorde, och som de flesta andra POSIX-program) frånvaron av triviala ersättare för vissa POSIX API: er på Windows. Några av dessa ( som

getppid()) var enkla, men lite tråkiga. Troligtvis var det svåraste problemet som uppstod under hela portningsprocessen dock det med filbeskrivare jämfört med filhandtag. Mycket av koden som vi litade på (till exempel Plasma-butiken i pilen) antog att POSIX-filbeskrivare användes (int s). Windows använder emellertid HANDLE s, som är pekstorlekar och analoga med size_t. I sig är detta dock inte ett betydande problem, eftersom Microsoft Visual C ++ runtime (CRT) ger ett POSIX-liknande lager. Skiktet är dock begränsat i funktionalitet, kräver översättningar på varje samtalswebbplats som inte stöder det, och i synnerhet kan det inte användas för saker som uttag eller delade minneshandtag. Dessutom ville vi inte anta att HANDLE s alltid skulle vara tillräckligt små för att passa in i ett 32-bitars heltal, även om detta ofta var fallet, eftersom det var oklart om omständigheter som vi inte var medvetna om kunde tyst bryta detta antagande.Detta förstärkte våra problem betydligt, eftersom den mest uppenbara lösningen skulle ha varit att upptäcka alla int som representerar filbeskrivare i ett bibliotek som Arrow, och att ersätta dem (och alla deras användningsområden ) med en alternativ datatyp, som var en felbenägen process och involverade betydande korrigeringar för extern kod, vilket skapade en betydande underhållsbörda.

Det var ganska svårt att bestämma vad man skulle göra i detta skede. MSOpenTech Redis lösning för samma problem gjorde det klart att detta var en ganska skrämmande uppgift, eftersom de hade löst problemet genom skapa en singleton, processomfattande filbeskrivningstabell på toppen av den befintliga CRT-implementeringen, som kräver att de hanterar trådsäkerhet och tvingar dem att fånga upp dem > all användning av POSIX API: er (även de som den redan kunde hantera) bara för att översätta filbeskrivare. Istället bestämde vi oss för att ta ett ovanligt tillvägagångssätt: vi förlängde POSIX-översättningsskiktet i CRT . Detta gjordes genom att identifiera inkompatibla handtag vid skapelsestiden och ”skjuta in” dessa handtag i buffertarna i ett överflödigt rör och istället returnera deskriptoren för det röret. Vi var då tvungna att modifiera användningsplatserna för dessa handtag, som var mycket viktiga att identifiera, eftersom de alla var socket och minneskartade filer API: er. I själva verket hjälpte detta till att undvika behovet av lapp, eftersom vi bara kunde omdirigera många funktioner via makron .

Medan strävan att utveckla detta unika tilläggsskikt (i win32fd.h) var betydelsefullt (och ganska ovanligt), det var sannolikt värt det, eftersom översättningsskiktet var i själva verket ganska liten i jämförelse och tillät oss att delegera de flesta orelaterade problemen (till exempel flertrådad låsning av filbeskrivningstabellen) till CRT API: er. Genom att utnyttja anonyma rör från samma globala filbeskrivningstabell (trots vår brist på direkt tillgång till den) kunde vi dessutom undvika att behöva fånga upp och översätta filbeskrivare för andra funktioner som redan kan hanteras direkt. Detta gjorde att mycket av koden kunde förbli i stort sett oförändrad med minimal prestandapåverkan tills vi senare fick chansen att refaktorera koden och ge bättre omslag på en högre nivå (t.ex. via Boost.Asio-omslag). Det är mycket möjligt att en förlängning av detta lager kan göra att andra projekt som Redis kan överföras till Windows mycket mer sömlöst och med mycket mindre drastiska förändringar eller potential för buggar.

Av Andrea Leopardi

Run-time Executability (Proof-of-Concept)

När vi trodde att Ray-kärnan fungerade korrekt var nästa milstolpe att se till att ett Python-test lyckades utöva våra kodvägar. Inledningsvis prioriterade vi inte att göra det. Detta visade sig dock vara ett misstag, eftersom senare ändringar av andra utvecklare i teamet faktiskt introducerade mer dynamiska inkompatibiliteter med Windows, och CI-systemet kunde inte upptäcka sådana brott. Vi prioriterade därför sedan att köra ett minimalt test på Windows-byggnaderna för att undvika ytterligare brott i byggnaden.

För För det mesta var våra ansträngningar framgångsrika och alla kvarvarande buggar i Ray-kärnan fanns i förutsägbara delar av kodbasen (även om adressering av dem ofta krävdes att man stegade igenom flertalet processkoder, vilket var långt ifrån trivialt). Det fanns dock åtminstone en något obehaglig överraskning längs vägen på C-sidan och en på Python-sidan, som båda (bland annat) uppmuntrade oss att läsa programdokumentation mer proaktivt i framtiden.

På C-sidan, vår första handskakning omslag för att byta uttagshandtag förlitade sig på att ersätta sendmsg och recvmsg med WSASendMsg och WSARecvMsg. Dessa Windows-API: er var de närmaste ekvivalenterna för POSIX-API: erna och verkade därför vara ett självklart val. Men vid körning skulle koden ständigt krascha, och källan till problemet var oklar. En del felsökning (inklusive med felsökningsversioner av build & runtimes) hjälpte till att avslöja att problemet var med stackvariablerna som skickades till WSASendMsg. Ytterligare felsökning och noggrann inspektion av minnesinnehållet antydde att problemet kan ha varit msg_flags -fältet i WSAMSG, eftersom detta var det enda som inte initierades fält.Detta tycktes dock vara irrelevant: msg_flags översattes bara från flags i struct msghdr, som inte användes vid inmatning och bara användes som en utmatningsparameter. Att läsa dokumentationen avslöjade emellertid problemet: i Windows fungerade fältet också som en parameter input och därmed lämnade det oinitialiserade resulterade i oförutsägbart beteende! Detta var ganska oväntat för oss och resulterade i att två viktiga takeaways gick framåt: för att läsa dokumentationen för varje funktion noggrant , och Dessutom att initialiseringsvariabler inte bara handlar om att säkerställa korrekthet med nuvarande API: er, utan också viktigt för att göra koden robust för framtida ändringar av de riktade API: erna .

På Pythonsidan stötte vi på ett annat problem. Våra inbyggda Python-moduler skulle initialt inte kunna laddas, trots avsaknaden av uppenbara problem. Efter flera dagars gissningar, steg igenom montering och CPython-källkoden och inspekterade variabler i CPython-kodbasen blev det uppenbart att problemet var bristen på ett .pyd -suffix på dynamiskt Python bibliotek på Windows. Som det visar sig vägrar Python av oklara skäl att ladda även .dll -filer på Windows som Python-moduler, trots att inbyggda delade bibliotek normalt kan laddas även med vilken fil som helst förlängning. Det visade sig faktiskt att detta faktum dokumenterades på Pythons webbplats . Tyvärr kunde närvaron av sådan dokumentation inte möjligen innebära att vi insåg att vi letade efter den.

I slutändan kunde Ray slutligen köra på Windows, och detta avslutade nästa milstolpe och gav ett bevis av koncept för strävan.

Av Hitesh Choudhary

Runtidskompatibilitet (mestadels Python)

Vid denna tidpunkt var kärnan i Ray en gång när vi arbetade kunde vi fokusera på att överföra kod på högre nivå. Vissa problem var ganska lätta att ta itu med – till exempel har vissa Python API: er som endast är UNIX (t.ex. os.uname()[1]) ofta lämpliga ersättare på Windows (t.ex. socket.gethostname()), och att hitta dem var en fråga om att veta att söka efter alla instanser av dem över kodbasen. Andra problem var svårare att spåra eller lösa. Ibland berodde de på användningen av POSIX-specifika kommandon (som ps), vilket krävde alternativa tillvägagångssätt (t.ex. användning av psutil för Python). Andra gånger berodde de på inkompatibilitet i tredjepartsbibliotek. Till exempel när ett uttag kopplas bort från Windows uppstår ett fel snarare än att det resulterar i tomma läsningar. Python-biblioteket för Redis verkade inte hantera detta. Sådana skillnader i beteende krävde uttrycklig apa-lapp för att undvika ibland förvirrande fel som skulle inträffa när Ray avslutades.

Medan vissa sådana problem är ganska tråkigt men ändå troligtvis förväntat (som att ersätta användningen av /tmp med plattformens tillfälliga katalog eller undvika antagandet att alla absoluta banor börjar med ett snedstreck), var vissa något oväntade (t.ex. motstridiga portreservationer ) eller (som ofta är fallet) på grund av felaktiga antaganden, och att förstå dem beror på att förstå Windows-arkitekturen och dess tillvägagångssätt för bakåtkompatibilitet.

En sådan historia kretsar kring användningen av snedstreck som katalogavgränsare i Windows. I allmänhet verkar dessa fungera bra och används ofta av utvecklare. Detta beror faktiskt på automatisk konvertering av snedstreck till bakåtvända snedstreck i Windows-delsystembiblioteken i användarläge, och viss automatisk bearbetning kan undertryckas genom uttryckligt prefixvägar med \\?\ prefix, vilket är användbart för att kringgå vissa kompatibilitetsfunktioner (t.ex. långa banor). Vi använde dock aldrig uttryckligen en sådan väg och antog att användarna kunde förväntas undvika ovanlig användning i våra experimentella utgåvor. Senare blev det dock uppenbart att när Bazel åberopade vissa Python-tester, skulle banor behandlas i detta format för att möjliggöra användning av långa banor, och detta inaktiverade den automatiska översättningen som vi implicit litade på. Detta ledde oss till viktiga takeaways: för det första att det i allmänhet är bättre att använda API: er på det mest lämpliga sättet för målsystemet, eftersom det ger minst möjligheter för oväntade problem att uppstå .För det andra, och viktigast av allt, är det helt enkelt en misstag att anta att en användares miljö är förutsägbar . Verkligheten är att modern programvara nästan alltid förlitar sig på att köra tredjepartskod vars exakta beteende vi inte är medvetna om. Även när en användare kan antas undvika problematiska situationer är programvara från tredje part helt omedveten om sådana dolda antaganden. Således är det troligt att de kommer att inträffa ändå, inte bara för användare utan också för mjukvaruutvecklarna själva, vilket resulterar i buggar som är svårare att spåra än att adressera när de skriver den första koden. Därför är det viktigt att undvika att lägga för mycket vikt på programvänlighet (motsatsen till ”användarvänlighet”) när man utformar ett robust system.

(Som en rolig sida: i själva verket på Windows, banor kan faktiskt innehålla citat och många andra specialtecken som normalt antas vara olagliga. Detta inträffar när man använder alternativa NTFS-dataströmmar. Dessa är dock sällsynta och komplexa så att även vanliga språkbibliotek ofta inte hanterar dem.)

När de mest betydande problemen löstes kunde många tester emellertid skicka Windows, vilket skapade den första experimentella Windows-implementeringen av Ray.

Av Ross Sneddon

Körtidsförbättringar (t.ex. Unicode-stöd)

Vid denna tidpunkt kan mycket av kärnan i Ray användas på Windows precis som på andra plattformar. Ändå kvarstår vissa problem, vilket kräver kontinuerliga ansträngningar för att ta itu med dem.

Unicode-support är en sådan fråga. På grund av historiska skäl har delsystemet Windows-användarläge två versioner av de flesta API: er: en ”ANSI” -version som stöder teckenuppsättningar med en byte och en ”Unicode” -version som stöder UCS-2 eller UTF-16 (beroende på uppgifter om API i fråga). Tyvärr är ingen av dessa UTF-8; även grundläggande stöd för Unicode kräver användning av breda teckensträngar (baserat på wchar_t) över hela kodbasen. (nb: Faktum är att Microsoft nyligen har försökt introducera UTF-8 som en kodsida, men det stöds inte tillräckligt för att lösa problemet problemfritt, åtminstone utan att förlita sig på potentiellt odokumenterade och spröda Windows-interner.)

Traditionellt hanterar Windows-program Unicode genom att använda makron som _T() eller TEXT(), som expanderar till smal eller bred -karaktärbokstäver beroende på om en Unicode-byggnad har specificerats och använd TCHAR som generiska karaktärstyper. På samma sätt har de flesta C API: er TCHAR -beroende versioner (som _tcslen() istället för strlen()) för att möjliggöra kompatibilitet med båda typerna av kod. Att migrera en UNIX-baserad kodbas till den här modellen är dock en ganska involverad strävan. Detta har ännu inte gjorts i Ray, och från och med detta skrivs stöder Ray inte korrekt Unicode i (till exempel) filvägar på Windows, och det bästa sättet att göra det kan fortfarande vara något av en öppen fråga. / p>

En annan sådan fråga är kommunikationsmekanismen mellan processer. Medan TCP-uttag kan fungera bra på Windows är de inte optimala, eftersom de introducerar ett lager av onödig komplexitet i logiken (som timeouts, keep-alives, Nagles algoritm), kan leda till oavsiktlig tillgänglighet från icke-lokala värdar och kan introducera lite prestandakostnader. I framtiden kan Named Pipes ge en bättre ersättning för UNIX-domänuttag på Windows; faktiskt, även på Linux kan antingen rör eller så kallade abstrakt UNIX-domänuttag också visa sig vara bättre alternativ, eftersom de inte kräver störningar och rengöring av uttagsfiler i filsystemet.

Slutligen är ett annat exempel på ett sådant problem BSD-uttagskompatibilitet, eller snarare, brist på det. Ett utmärkt svar på StackOverflow diskuterar några av problemen på djupet, men kort, medan vanliga sockel-API: er är derivat av det ursprungliga BSD-sockel-API: et, implementerar olika plattformar liknande sockel flaggor annorlunda. I synnerhet kan konflikter med befintliga IP-adresser eller TCP-portar ge olika beteenden över plattformar. Även om problemen är svåra att detaljera här, är slutresultatet att det kan göra det svårt att använda flera instanser av Ray på samma värd samtidigt. (I själva verket, eftersom detta beror på OS-kärnbeteende, påverkar det också WSL.) Detta är ännu en känd fråga vars lösning är ganska involverad och inte helt behandlad i det nuvarande systemet.

Slutsats

Processen med att portera en kodbas som Ray till Windows har varit en värdefull upplevelse som belyser för- och nackdelar med många aspekter av mjukvaruutveckling och deras inverkan på kodunderhåll.Den föregående beskrivningen belyser bara några av de hinder som påträffas under vägen. Många användbara slutsatser kan dras från processen, varav några kan vara värdefulla att dela här för andra projekt i hopp om att uppnå ett liknande mål. av vissa bibliotek (som hyrda) hade redan löst några problem som vi hade tagit upp. Lösningarna var inte alltid uppenbara, eftersom (till exempel) versionen av hire i de senaste Redis-versionerna faktiskt var en gammal kopia av hirde, vilket fick oss att tro att vissa problem ännu inte hade tagits upp. Senare ändringar tog inte heller alltid upp alla befintliga kompatibilitetsproblem. Ändå skulle det möjligen ha sparat lite ansträngning att söka djupare efter befintliga lösningar för vissa problem för att undvika att behöva lösa dem igen.

Av John Barkiple

Andra, mjukvaruförsörjningskedjan är ofta komplex . Bugs kan naturligt sammansättas vid varje lager, och det är en felaktighet att lita på att även allmänt använda öppen källkodsverktyg är ”stridstestade” och därför robusta, särskilt när de används i ilska . Dessutom har många långvariga eller vanliga problem med mjukvaruteknik inte tillfredsställande lösningar tillgängliga för användning, särskilt (men inte bara) när de kräver kompatibilitet mellan olika system. Bortsett från enbart problem, under processen att porta Ray till Windows, stötte vi på och rapporterade ofta buggar i många programvaror, inklusive men inte begränsat till en Git bug on Linux som påverkade Bazel-användningen, Redis (Linux) , glog , psutil (tolkningsfel som påverkar WSL) , grpc , många svårt att identifiera buggar i Bazel själv (t.ex. 1 , 2 , 3 , 4 ), Travis CI och GitHub Actions , bland andra. Detta uppmuntrade oss att också ägna större uppmärksamhet åt komplexiteten i våra beroenden.

För det tredje investera i verktyg och infrastruktur utdelar på lång sikt. Snabbare byggnader möjliggör snabbare utveckling och kraftfullare verktyg gör det lättare att lösa komplexa problem. I vårt fall hjälpte användningen av Bazel oss på många sätt, trots att den var långt ifrån perfekt och dess införande av en brant inlärningskurva. Att investera lite tid (eventuellt flera dagar) för att lära sig kapaciteter, styrkor och brister i ett nytt verktyg är sällan lätt, men troligen kommer det att vara till nytta för kodunderhåll. I vårt fall spenderade vi lite tid på att läsa Bazel-dokumentationen djupare mycket snabbare att hitta en mängd framtida problem och lösningar. Dessutom hjälpte det oss att integrera verktyg med Bazel som få andra uppenbarligen hade lyckats, till exempel Clangs verktyg inkludera vad du använder .

För det fjärde, och som tidigare nämnts, är det klokt att engagera sig i säkra kodningsmetoder såsom att initiera minne före användning när det finns ingen betydande avvägning. Till och med den mest noggranna ingenjören kan inte nödvändigtvis förutsäga framtida utveckling av det underliggande systemet som tyst kan ogiltigförklara antaganden.

Slutligen, som i allmänhet är fallet inom medicin, förebyggande är det bästa botemedlet . Att redogöra för möjlig framtida utveckling och kodning till standardiserade gränssnitt möjliggör mer utbyggbar koddesign än vad som lätt kan uppnås efter inkompatibilitet.

Även om porten till Ray till Windows ännu inte är komplett, har det varit ganska framgångsrikt hittills, och vi hoppas att delandet av vår erfarenhet och lösningar kan fungera som en bra guide för andra utvecklare som överväger att gå ut på en liknande resa.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *