Tekninen artikkeli

HotXLS:n kaatumissietoiset tallennukset: vaiheistetut väliaikaistiedostot Delphissä

Tallennus, joka kuolee kesken kaiken, olipa syynä pakotettu uudelleenkäynnistys, tapettu prosessi tai kesken kirjoituksen täyttyvä levy, on perinteisesti tarkoittanut yhtä asiaa muodolle, joka on rakennettu paikallaan tapahtuvien kirjoitusten ympärille: mitkä tahansa tavut ehtivät levylle ennen keskeytystä, ne saat takaisin, eikä katkennut työkirja avaudu enää. HotXLS sulkee tämän vikatilan kaatumissietoisella tallennuspolulla, jota käytetään jokaisen kirjoittamansa XLSX-, ODS- ja klassisen XLS-tiedoston kohdalla. Jokainen SaveAs-kutsu kirjoittaa täydellisen uuden tiedoston kohteen viereen luotuun väliaikaistiedostoon, ja sitoo sen sitten yhdellä atomisella MoveFileExW-uudelleennimeämisellä Windows API:sta, joten keskeytynyt tallennus voi vain epäonnistua tuottamaan uuden tiedoston — se ei koskaan vahingoita sitä, joka sinulla jo oli. Sama vaiheista-ja-vaihda-kuri toimii yhtenäisesti HotXLS:n molempien tallennusmoottoreiden yli, klassisen XLS:n takana olevan BIFF8-kirjoittimen ja XLSX:n ja ODS:n takana olevan OOXML-kirjoittimen, ja se on malli, joka kannattaa lainata mihin tahansa tiedostoon, jonka oma Delphi-koodisi ylikirjoittaa suoraan, olipa kyse laskentataulukoista tai ei

Mitä tapahtuu, jos työkirjan tallennus keskeytyy kesken?

Suora vastaus on, että se riippuu täysin siitä, miten kirjoitin koskettaa kohdetiedostoa, ja yleinen toteutus, kohdetiedoston avaaminen ja uuden sisällön suoratoistaminen suoraan siihen, toimii niin kauan kuin mikään ei koskaan mene pieleen. Heti kun jotain menee, kaatuminen, pakotettu prosessin tappaminen, kesken kirjoituksen katkeava verkkojako, levylle jäävä tiedosto on siinä välitilassa, johon kirjoitin ehti: ZIP-keskushakemisto, jota ei koskaan liitetty XLSX:lle tai ODS:lle, tai BIFF-virta, josta puuttuu tietueita, joita lukija odottaa klassiselle XLS:lle. Excel ei korjaa tätä sulavasti, eikä mikään muu kuluttaja, joka odottaa täydellistä tiedostoa, joten käytännön tulos on työkirja, joka avautui hyvin eilen ja kieltäytyy avautumasta tänään

Miten HotXLS vaiheistaa jokaisen tallennuksen yhden atomisen vaihdon taakse

HotXLS ei koskaan avaa kohdetiedostoa kirjoitusta varten suoraan, missään kolmesta muodosta, jota se tallentaa. Sekvenssi on sama muoto joka kerta: rakenna täydellinen tuloste jossain, mikä ei ole tiedosto, joka käyttäjällä jo on levyllä, ja siirrä se paikalleen vasta, kun tuo rakennus on täysin onnistunut. Konkreettisesti SaveAs luo tyhjän väliaikaistiedoston samaan kansioon kuin kohdepolku, kirjoittaa koko uuden työkirjan tuohon väliaikaistiedostoon, ja vasta sen jälkeen, kun tuo kirjoitus palautuu ilman virhettä, se sitoo väliaikaistiedoston kohteen päälle yhdellä uudelleennimeämisellä. Mikään tästä ei vaadi ominaisuutta otettavaksi käyttöön; se on yksinkertaisesti se, mitä SaveAs tekee tavalliselle tiedostopolulle, joka kutsulla

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Report');
    Sheet.Cells[1, 1].Value := 'Nothing special to enable here';
    // If this call is interrupted, monthly-report.xlsx on disk stays
    // either the old version, complete, or the new version, complete
    if Book.SaveAs('monthly-report.xlsx', xlsxOpenXMLWorkbook) <> 1 then
      raise Exception.Create('Save failed, see Book.LastDiagnostic');
  finally
    Book.Free;
  end;
end;

Sama kuri koskee klassista XLS-kirjoitinta, ei vain OOXML-kirjoitinta, ja nämä kaksi väliaikaistiedostoa jopa jakavat nimeämiskäytännön: molemmat kutsuvat Windowsin GetTempFileNameW-API:a hxl-etuliitteellä, joten ennen siivousta keskeytynyt tallennus voi jättää jälkeensä eksyneen tiedoston, jonka nimi on jotain kuten hxl4C2A.tmp, työkirjasi vieressä. Tuo tiedosto ei ole vioittuma, se on todiste siitä, että mekanismi toimi juuri suunnitellusti: keskeneräinen kirjoitus pysähtyi siihen, ja todellista työkirjaasi ei koskaan avattu kirjoitusta varten ylipäätään. Tällaisen näkeminen kaatumisen jälkeen on turvallista poistaa, eikä siinä ole mitään tutkittavaa

Miksi väliaikaistiedosto vaiheistetaan työkirjan viereen eikä %TEMP%-kansioon?

Lyhyt vastaus on, että MoveFileExW:n uudelleennimeäminen on atominen vain, kun lähde ja kohde sijaitsevat samalla asemalla, ja varmin tapa taata tämä pyytämättä kutsujaa määrittämään mitään on johtaa väliaikaistiedoston sijainti itse kohdepolusta. HotXLS laskee kohteen oman kansion ja antaa tuon hakemiston suoraan GetTempFileNameW:lle, joten väliaikaistiedosto luodaan aina samalle asemalle, samalle levylle, kuin tiedosto, jonka se on korvaamassa, automaattisesti, jokaisella tallennuksella. Jos kirjasto olisi sen sijaan vaiheistanut kirjoitukset järjestelmän temp-kansioon, kohdepolku eri asemalla tai kytketyllä verkkoasemalla olisi muuttanut viimeisen vaiheen asemien väliseksi toiminnoksi, jonka Windows API joko suoraan kieltäytyy tekemästä tai, jos kutsuja nimenomaisesti valitsee lisälipulla, jota HotXLS ei tässä aseta, hiljaa heikkenee ei-atomiseksi kopioksi, jota seuraa poisto, avaten uudelleen juuri sen keskeytysikkunan, jonka sulkemiseksi koko tämä mekanismi on olemassa

Sitomisvaihe: MoveFileExW, write-through ja mitä tapahtuu epäonnistuessa

Jokaisen tallennuksen viimeinen vaihe on täsmälleen yksi Windows API -kutsu, MoveFileExW, kantaen kaksi lippua, jotka kumpikin tekevät erillisen työn. MOVEFILE_REPLACE_EXISTING on se, mikä sallii uudelleennimeämisen osua jo olemassa olevaan tiedostoon; ilman sitä uudelleennimeäminen, joka kohdistuu olemassa olevaan polkuun, yksinkertaisesti epäonnistuu, mikä tekisi tyhjäksi koko sen tallennuksen tarkoituksen, jonka on tarkoitus korvata työkirja, joka sinulla jo on. MOVEFILE_WRITE_THROUGH kattaa kestävyyden: se kertoo funktiolle, ettei se saa palautua, ennen kuin siirto on todella suoritettu levylle, sen sijaan että palautuisi heti, kun uudelleennimeäminen on vain jonotettu, sulkien kapeamman mutta todellisen kilpa-ajotilanteen, jossa kaatuminen välittömästi SaveAs-kutsun palautumisen jälkeen voisi silti nappaa vaihdon kesken

Jos väliaikaistiedostoa ei voida luoda, tai lopullinen uudelleennimeäminen epäonnistuu mistä tahansa syystä (käyttöoikeusongelma, lukittu kohde, asemien yhteensopimattomuus), HotXLS poistaa väliaikaistiedoston itse sen sijaan, että jättäisi roskaa jälkeensä, ja kohdetiedosto jää täsmälleen sellaiseksi kuin se oli ennen kutsua

Result := Book.SaveAs(TargetPath, xlsxOpenXMLWorkbook);
if Result <> 1 then
begin
  // TargetPath on disk is unchanged; safe to retry, alert, or
  // fall back to a different path without touching prior output
  LogWriter.Write(Format('SaveAs failed (%d): %s',
    [Book.LastDiagnostic.Code, Book.LastDiagnostic.Message]));
  Exit(False);
end;

SaveAs itse pitää HotXLS:n yhteisen paluuarvokäytännön, ykkösen onnistuessa, negatiivisen luvun epäonnistuessa, mutta paljas kokonaisluku ei kerro, miksi tallennus epäonnistui, ja jokaisen negatiivisen tuloksen kohteleminen samalla tavalla heittää pois tietoa, jota uudelleenyrityskäytäntö voisi todella käyttää. LastDiagnostic-ominaisuus, ja sen takana oleva täydempi Diagnostics-kokoelma, kantaa viestin, jonka HotXLS tuotti sisäisesti, erottaen väliaikaistiedoston, jota ei voitu luoda, uudelleennimeämisestä, jonka Windows kieltäytyi tekemästä. Eräajo, joka kirjaa Code- ja Message-arvot jokaisesta epäonnistuneesta SaveAs-kutsusta, kerää juuri sen todistusaineiston, jota haluat sen yhden kerran, kun asiakas raportoi tallennuksen, joka hiljaa ei tehnyt mitään

Klassinen XLS maksaa muistilla, XLSX ja ODS maksavat levyllä

Kaksi tallennusmoottoria saavuttavat saman kaatumissietoisen lopputuloksen eri reittejä, ja ero on merkityksellinen, jos jo virität kumpaakin suurta eräajoa varten. Klassinen XLS-kirjoitin rakentaa koko OLE-yhdistelmäasiakirjan ensin muistiin, käyttäen jäsenneltyä tallennusta muistikahvan tukemana, ja kopioi vasta sen jälkeen tuon valmiin puskurin ulos sisarusväliaikaistiedostoon yhdellä kirjoituksella; HotXLS:n omassa lähdekoodissa oleva perustelu on suora: koko tiedoston rakentaminen ensin muistiin on se, mikä estää epäonnistunutta tai peruutettua tallennusta koskaan katkaisemasta kohdetta. XLSX- ja ODS-kirjoitin sen sijaan suoratoistaa ZIP-merkintänsä väliaikaistiedostoon niiden tuottamisen yhteydessä, saman tiedostotason vaiheistuksen eri muistiprofiililla. Jos jo nojaat StreamingWrite-toimintoon pitääksesi suuret XLSX-viennit kontin muistirajan sisällä, tiedä, ettei vastaavaa vipua klassisen XLS-viennin osalta ole olemassa samassa muodossa: kaatumissietoinen takuu on ehdoton joka tapauksessa, mutta hyvin suuri vanhan mallin .xls-vienti pitää täydellisen tulosteensa RAM-muistissa joka tapauksessa, kompromissi, joka käsitellään syvällisemmin artikkelissamme suoratoistokirjoituksista palvelimen eräajoja varten

Saman mallin soveltaminen HotXLS:n ulkopuolella, ja missä takuu päättyy

Mallin lainaaminen on lähinnä kysymys samojen kahden Windows API -kutsun kytkemisestä, joihin HotXLS luottaa sisäisesti. GetTempFileNameW antaa sinulle yksilöllisesti nimetyn, tyhjän tiedoston valitsemassasi kansiossa, ja MoveFileExW sitoo valmiin kirjoituksesi todellisen kohteen päälle yhdessä vaiheessa; minimaalinen versio samasta rutiinista, jonka HotXLS ajaa ennen jokaista SaveAs-kutsua, näyttää tältä

function SaveFileAtomically(const Path: WideString; const Contents: TBytes): Boolean;
var
  Dir, TempName: WideString;
  Buffer: array[0..MAX_PATH] of WideChar;
  FS: TFileStream;
begin
  Result := False;
  Dir := ExtractFilePath(ExpandFileName(Path));
  FillChar(Buffer, SizeOf(Buffer), 0);
  if GetTempFileNameW(PWideChar(Dir), 'app', 0, @Buffer[0]) = 0 then
    Exit;
  TempName := PWideChar(@Buffer[0]);
  try
    FS := TFileStream.Create(TempName, fmCreate or fmShareExclusive);
    try
      FS.WriteBuffer(Contents[0], Length(Contents));
    finally
      FS.Free;
    end;
    Result := MoveFileExW(PWideChar(TempName), PWideChar(ExpandFileName(Path)),
      MOVEFILE_REPLACE_EXISTING or MOVEFILE_WRITE_THROUGH);
  finally
    if not Result then
      DeleteFileW(PWideChar(TempName));
  end;
end;

Takuulla on todellisia reunoja, jotka kannattaa tuntea ennen kuin luotat siihen sokeasti. Täyden kopion vaiheistaminen ennen alkuperäisen korvaamista tarkoittaa, että tallennus tarvitsee hetkellisesti levytilaa sekä vanhalle että uudelle tiedostolle, karkeasti kaksinkertaisesti työkirjan koon verran kirjoituksen ajaksi, mikä on hyvä raportille ja kannattaa tarkistaa moniigatavuisessa viennissä, joka ajetaan lähes täyttä asemaa vasten. Väliaikaistiedoston on myös päädyttävä samaan kansioon kuin kohde, joten tilillä, jonka alla HotXLS toimii, on oltava tiedostonluontioikeus nimenomaan tuohon kansioon, ei vain oikeus ylikirjoittaa se yksi tiedosto, jonka se jo tuntee; käyttöönotto, joka lukitsee kohdekansion vain paikallaan tapahtuviin muokkauksiin tiettyihin olemassa oleviin tiedostonimiin, kansiotason kirjoitusoikeuden sijaan, näkee SaveAs-kutsun epäonnistuvan väliaikaistiedostovaiheessa, vaikka vastaava suora kirjoitus olisi onnistunut

Kaksi lisärajaa kannattaa merkitä selvästi. Kohde verkkojaossa tai OneDriven tai vastaavan asiakasohjelman synkronoimassa kansiossa voi käyttäytyä eri tavalla kuin paikallinen NTFS, vaikka Windows edelleen raportoi sen yhtenä asemana, koska sen edessä oleva tiedostojärjestelmäajuri ei ehkä toteuta uudelleennimeämistä samalla tavalla; jos käyttöönottokohteesi tallentaa verkkopolun yli, kannattaa testata pakotettu keskeytys nimenomaan siellä sen sijaan, että oletettaisiin paikallisen levyn käytöksen siirtyvän sellaisenaan. Ja koko mekanismi on rajattu nimettyyn tiedostoon tallentamiseen. Kutsu SaveAs sen sijaan TStream-oliota vasten, ja HotXLS kirjoittaa suoraan mihin tahansa virtaan, jonka sille annoit, ilman kohdetiedostoa vaiheistettavaksi tai suojattavaksi, koska tuon virran (muistipuskuri, verkkolataus, tietokannan blob) kestävyys on kokonaan koodisi vastuulla siitä eteenpäin

Vahvistuskierros saa luottaa täsmälleen tähän takuuseen jälkeenpäin, mukaan lukien sellainen, joka on rakennettu työkirjan tarkastus- ja muunnostyöpenkkiin: uudelleen avattu tiedosto, joka palautuu vajaana tai puuttuvana, on todellinen muunnosongelma jäljitettäväksi, ei koskaan tallennus, joka keskeytyi kesken ja jätti jotain epäselvää levylle. Kaatumissietoiset vaiheistetut kirjoitukset on rakennettu SaveAs-toimintoon jokaista HotXLS-komponentin tuottamaa XLSX-, ODS- ja klassista XLS-työkirjaa varten Delphille ja C++Builderille, ilman että sen käyttöönotto vaatii mitään määritystä