PDFiumPas renderavimo užraktas yra vienam dokumentui skirta kritinė sekcija — EnterRenderLock ir LeaveRenderLock, remiama TRTLCriticalSection lauku ant TPdf — skirta apgaubti kiekvieną iškvietimą į PDFium rasterizatorių, kad puslapis negalėtų būti iškrautas arba iš naujo pakrautas iš po vykstančio renderavimo. Šeši metodai, pasidaliję lygiai tarp TPdf ir TPdfView, tiesiogiai iškvietė PDFium bitmap ir miniatiūrų ekstrahavimo API ir visiškai praleido tą užraktą, spragą, kurią PDFiumPas v2.26.0 uždarė suvyniodama visus šešis į tą patį užrakto porą, kurį kiekvienas kitas renderavimo įėjimo taškas jau naudojo
Spraga, aptarta čia, nėra ABI-sustiprinimo praėjimas, aptartas kitur šiame tinklaraštyje, kuris perėjo cdecl iškvietimo-konvencijos neatitikimą ir FPC Win64 rodyklės-ilgio trumpinimą tame pačiame PDFium susiejime. Kas seka yra siauresnis ir mechaninis: užrakto-padengties kontrolinis sąrašas šešiems iškvietimo taškams, kurie visi pasiekia PDFium renderavimo kelią, kodėl kiekvieną buvo lengva praleisti, ir kodėl lenktynės, kurios seka iš praleisto užrakto, yra viena iš sunkesnių defektų šiame kode reprodukuoti pagal pareikalavimą
Ką renderavimo užraktas iš tikrųjų saugo
PDFiumPas serializuoja renderavimą, nes PDFium pakrautas puslapis nėra saugu skaityti iš vienos gijos, kol kita gija laisva jį paleisti. TPdf turi TRTLCriticalSection FRenderLock, inicializuotą konstruktoriuje ir saugomą FRenderLockReady vėliavos, kad iškvietimas, atvykęs po ardymo, taptų tyliu no-op vietoj įėjimo į ištrintą kritinę sekciją. EnterRenderLock ir LeaveRenderLock yra vienintelis sankcionuotas kelias į ir iš tos sekcijos
procedure TPdf.EnterRenderLock;
begin
if FRenderLockReady then
EnterCriticalSection(FRenderLock);
end;
procedure TPdf.LeaveRenderLock;
begin
if FRenderLockReady then
LeaveCriticalSection(FRenderLock);
end;
TPdf.RenderPage, RenderTile ir RenderPageProgressive jau laikėsi tos disciplinos dar prieš šiam konkrečiam auditui prasidedant, kiekvienas imdamas užraktą prieš iškviesdamas PDFium ir paleisdamas jį finally bloke, kad foninis pre-render ir priekinis UnloadPage tame pačiame TPdf egzemplioriuje negalėtų persidengti. Spraga, kurią PDFiumPas v2.26.0 rado, nebuvo tuose akivaizdžiuose įėjimo taškuose — ji pasirodė šešiuose metoduose, kurie skaitomi kaip prieigos būdai, o ne renderiai, nors kiekvienas iš jų paprašo PDFium rasterizuoti pikselius, kol gali grąžinti ką nors
Kurie šeši iškvietimai praleido renderavimo užraktą?
TPdf.GetObjectBitmap, TPdf.GetBitmap ir TPdf.GetThumbnail sudarė pusę sąrašo, o TPdfView.GetObjectBitmap, TPdfView.GetBitmap ir TPdfView.GetThumbnail sudarė kitą pusę — tos pačios trys operacijos, dubliuotos per dvi komponentų klases, kurios atskleidžia tą patį pagrindinį puslapį. Visi šeši galiausiai iškviečia arba FPDFImageObj_GetBitmap, arba FPDFPage_GetThumbnailAsBitmap, ir abu tie PDFium įėjimo taškai rasterizuoja vietoje, užuot grąžinę nuorodą į ką nors jau renderuotą. Niekas jokiame iš šešių metodų pavadinimų nesako render, kas yra protingas paaiškinimas, kodėl jie nebuvo parašyti prieš tą patį kontrolinį sąrašą kaip RenderPage ir RenderTile pirmą kartą
function TPdf.GetObjectBitmap(Index: Integer): TBitmap;
var
Bitmap: FPDF_BITMAP;
begin
Result:= nil;
EnterRenderLock;
try
Bitmap:= FPDFImageObj_GetBitmap(GetObjectHandle(Index));
finally
LeaveRenderLock;
end;
if Bitmap<> nil then
try
Result:= ToBitmap(Bitmap);
finally
FPDFBitmap_Destroy(Bitmap);
end;
end;
Kodėl TPdfView saugo savo užrakto iškvietimą nil patikra
TPdfView neturi savo kritinės sekcijos — kiekvienas iš jo šešių užrakto iškvietimų persiunčia į FPdf.EnterRenderLock ir FPdf.LeaveRenderLock, suvyniotus į patikrą, kad susietas TPdf rodyklė nėra nil pirmiausia. To sargybūs egzistuoja todėl, kad TPdfView gali sėdėti formoje projektavimo metu, arba trumpai tarp vieno dokumento uždarymo ir kito atvėrimo, dar nepriskyrus TPdf FPdf. Sargybūs praleidimas mainytų vieną lūžį į kitą, nes užraktimo iškvietimas prieš nil rodyklę nepavyksta ne daugiau grakščiau nei lenktynės, kurias užraktas egzistuoja užkirsti kelią
function TPdfView.GetThumbnail: TBitmap;
var
PdfBitmap: FPDF_BITMAP;
begin
CheckActive;
Result:= nil;
if FPdf<> nil then
FPdf.EnterRenderLock;
try
PdfBitmap:= FPDFPage_GetThumbnailAsBitmap(Page);
finally
if FPdf<> nil then
FPdf.LeaveRenderLock;
end;
if PdfBitmap<> nil then
try
Result:= ToBitmap(PdfBitmap);
finally
FPDFBitmap_Destroy(PdfBitmap);
end;
end;
Kodėl RenderPage(HDC) priklauso tam pačiam auditui?
TPdfView.RenderPage prieš įrenginio kontekstą nėra vienas iš šešių — jis pasirodė reliazu anksčiau, PDFiumPas v2.25.0, ir pelno vietą šiame kontroliniame sąraše, nes tai yra tas pats defektas su kitu parašu. Ta perkrova iškvietė FPDF_RenderPage tiesiogiai be EnterRenderLock ir be SetArithmeticMask iškvietimo, kuris saugo nuo FPU išimčių ant senesnių Delphi kompiliatorių, o TBitmap perkrova, sėdinti keliomis eilutėmis žemiau toje pačioje klasėje, jau turėjo abu. Du audito praėjimai, pagaunantys tą patį nesėkmės režimą per reliazą, sako mažiau apie bet kurį vieną metodą ir daugiau apie klaidos formą: ji slepiasi toje perkrovoje, kurią niekas nebeskaito kartą, kai jos brolis atrodo teisingas
procedure TPdfView.RenderPage(DeviceContext: HDC; Left, Top, Width,
Height: Integer; Rotation: TRotation; Options: TRenderOptions);
var
ArithmeticMask: TArithmeticMask;
begin
CheckActive;
if FPdf<> nil then
FPdf.EnterRenderLock;
ArithmeticMask:= SetArithmeticMask;
try
FPDF_RenderPage(DeviceContext, FPage, Left, Top, Width, Height,
Ord(Rotation), EncodeRenderOptions(Options));
finally
RestoreArithmeticMask(ArithmeticMask);
if FPdf<> nil then
FPdf.LeaveRenderLock;
end;
end;
Kodėl šios lenktynės beveik neįmanoma reprodukuoti?
PDFiumPas renderavimo-užrakto spraga nepavyksta kiekviename paleidime, ar net daugumoje paleidimų, nes jai reikia dviejų konkrečių dalykų, nusileidžiančių ant to paties TPdf egzemplioriaus vienu metu: rasterizavimo iškvietimo jau vykstančio, ir lygiagrečio UnloadPage arba ReloadPage, atvykstančio per tą patį langą. Vienagijis testavimas iš viso neeksploatuoja kelio, ir netgi genuine daugiagijės apkrovos jį suklupo tik tada, kai foninis renderavimas ir dokumento gyvavimo ciklo įvykis atsitinka persidengti per vieno puslapio gyvavimą. Realistiškiausias trigeris yra foninis PDF pre-renderavimas, sukurtas ant atšaukiamų future, kur darbo gija rasterizuoja kitą puslapį, kol UI gija iš naujo pakrauna arba iškrauna dabartinį pagal vartotojo įvestį
FPDFImageObj_GetBitmap ir FPDFPage_GetThumbnailAsBitmap eina per puslapio-objekto struktūras, kurias UnloadPage laisvas paleisti viduryje-traversavimo, todėl lenktynės, kurios tikrai šaudo, ne visada gamina greitą prieigos pažeidimą. Struktūra, perskaityta akimirka per vėlai, gali lygiai taip pat lengvai grąžinti šiukšlių pikselius, arba sugadinti krūvos metaduomenis, kurie lūžta tik po kelių nesusijusių alokacijų vėliau, funkcijoje, kuri niekada nepalietė PDF puslapio. Tai yra sąžininga priežastis, kodėl šios klasės klaida gali išgyventi kode per kelias reliazų ciklus: dėklo sekimas nesėkmės taške retai rodo bet kur arti tų šešių eilučių, kurios iš tikrųjų trūko užrakto
Kas pasikeičia kviesčiams
GetBitmap, GetObjectBitmap, GetThumbnail ir HDC RenderPage perkrova išlaiko savo viešus parašus tiksliai tokius, kokie buvo, nes pataisa yra vidinis užraktimas, pridėtas aplink esamus iškvietimus, o ne migracija. Verta prisiminti, kad renderavimo užraktas yra apribotas vienam TPdf egzemplioriui, o ne globalus procesui, todėl dvi gijos, renderuojančios du atskirai pakrautus dokumentus, vis tiek veikia visiškai lygiagrečiai — užraktas serializuoja tik operacijas prieš tą vieną dokumentą, kurį abi gijos atsitiktinai dalijasi. Jei jūsų užraktimas jau tvirtas ir renderiai vis tiek jaučiasi lėti po zoom arba slinktimi, tai yra kitas klausimas, atsakytas PDFium render cachinimo ir zoom našumo taktikos straipsnyje — teisingumas ir greitis čia yra atskiros ašys, ir ši pataisa paliečia tik pirmąją
Šeši metodai ir vienas brolio perkrova yra maža PDFium paviršiaus, kurį PDFiumPas atskleidžia, dalis, bet tai buvo dalis, kuri elgėsi netinkamai tik po apkrova, kurią niekas atsitiktinai nepaleido derintuvėje. Pats renderavimo užraktas ir pilnas renderavimo įėjimo taškų rinkinys, kurį jis dabar padengia, atsiranda kaip PDFium Component Delphi, C++Builder ir Lazarus/FPC dalis