Render lock milik PDFiumPas adalah sebuah critical section per-dokumen — EnterRenderLock dan LeaveRenderLock, didukung sebuah field TRTLCriticalSection pada TPdf — dimaksudkan untuk membungkus setiap pemanggilan ke rasterizer PDFium sehingga sebuah halaman tidak bisa di-unload atau di-reload dari bawah sebuah render yang sedang berlangsung. Enam metode terbagi rata antara TPdf dan TPdfView memanggil API bitmap dan ekstraksi thumbnail milik PDFium secara langsung dan sepenuhnya melewati lock itu, sebuah celah yang ditutup PDFiumPas v2.26.0 dengan membungkus keenamnya dalam pasangan lock yang sama yang sudah digunakan setiap titik masuk rendering lainnya
Celah yang dibahas di sini bukan langkah pengerasan-ABI yang dibahas di tempat lain di blog ini, yang menelusuri sebuah ketidakcocokan calling-convention cdecl dan sebuah pemotongan lebar-pointer FPC Win64 dalam binding PDFium yang sama. Yang mengikuti lebih sempit dan lebih mekanis: sebuah checklist cakupan-lock untuk enam titik pemanggilan yang semuanya menjangkau ke jalur rendering PDFium, mengapa masing-masing mudah terlewat, dan mengapa race yang mengikuti dari lock yang hilang adalah salah satu defek yang lebih sulit direproduksi sesuai permintaan dalam codebase ini
Apa yang Sebenarnya Dilindungi Render Lock
PDFiumPas menyerialkan rendering karena halaman yang dimuat milik PDFium tidak aman dibaca dari satu thread sementara thread lain bebas melepaskannya. TPdf memiliki sebuah TRTLCriticalSection di FRenderLock, diinisialisasi dalam constructor dan dijaga oleh sebuah flag FRenderLockReady sehingga sebuah pemanggilan yang tiba setelah teardown menjadi sebuah no-op diam-diam alih-alih memasuki sebuah critical section yang sudah dihapus. EnterRenderLock dan LeaveRenderLock adalah satu-satunya cara masuk dan keluar yang disahkan dari section itu
procedure TPdf.EnterRenderLock;
begin
if FRenderLockReady then
EnterCriticalSection(FRenderLock);
end;
procedure TPdf.LeaveRenderLock;
begin
if FRenderLockReady then
LeaveCriticalSection(FRenderLock);
end;
TPdf.RenderPage, RenderTile, dan RenderPageProgressive sudah mengikuti disiplin itu sebelum audit khusus ini pernah dimulai, masing-masing mengambil lock sebelum memanggil ke PDFium dan melepaskannya dalam sebuah blok finally sehingga sebuah pre-render latar belakang dan sebuah UnloadPage di depan pada instance TPdf yang sama tidak bisa tumpang tindih. Celah yang ditemukan PDFiumPas v2.26.0 tidak ada di titik masuk yang jelas itu — ia muncul di enam metode yang terbaca seperti accessor alih-alih render, meski setiap satunya meminta PDFium merasterisasi piksel sebelum bisa mengembalikan apa pun
Pemanggilan Enam Mana yang Melewati Render Lock?
TPdf.GetObjectBitmap, TPdf.GetBitmap, dan TPdf.GetThumbnail membentuk separuh daftar, dan TPdfView.GetObjectBitmap, TPdfView.GetBitmap, dan TPdfView.GetThumbnail membentuk separuh lainnya — tiga operasi yang sama, diduplikasi di kedua kelas komponen yang mengekspos halaman yang mendasari sama. Keenamnya akhirnya memanggil baik FPDFImageObj_GetBitmap maupun FPDFPage_GetThumbnailAsBitmap, dan kedua titik masuk PDFium itu merasterisasi di tempat alih-alih menyerahkan kembali sebuah referensi ke sesuatu yang sudah dirender. Tidak ada apa pun dalam keenam nama metode itu yang mengatakan render, yang merupakan penjelasan masuk akal mengapa mereka tidak ditulis terhadap checklist yang sama seperti RenderPage dan RenderTile pada kali pertama
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;
Mengapa TPdfView Menjaga Pemanggilan Lock-nya dengan Pemeriksaan Nil
TPdfView tidak memiliki critical section-nya sendiri — setiap satu dari enam pemanggilan lock-nya meneruskan ke FPdf.EnterRenderLock dan FPdf.LeaveRenderLock, dibungkus dalam sebuah pemeriksaan bahwa referensi TPdf yang terkait tidak nil lebih dulu. Penjaga itu ada karena sebuah TPdfView bisa duduk pada sebuah form saat design time, atau sesaat di antara satu dokumen ditutup dan berikutnya dibuka, tanpa TPdf yang ditetapkan ke FPdf lagi. Melewati penjaga itu akan menukar satu crash dengan yang lain, karena sebuah pemanggilan locking terhadap sebuah referensi nil gagal tidak lebih anggun dari race yang ingin dicegah lock itu
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;
Mengapa RenderPage(HDC) Termasuk dalam Audit yang Sama?
TPdfView.RenderPage terhadap sebuah device context bukan salah satu dari enam itu — ia muncul satu rilis lebih awal, di PDFiumPas v2.25.0, dan layak masuk checklist ini karena ini adalah defek yang sama dengan tanda tangan berbeda. Overload itu memanggil FPDF_RenderPage langsung tanpa EnterRenderLock maupun pemanggilan SetArithmeticMask yang menjaga terhadap exception FPU pada compiler Delphi yang lebih lama, sementara overload TBitmap yang duduk beberapa baris di bawahnya dalam kelas yang sama sudah membawa keduanya. Dua langkah audit menangkap mode kegagalan yang sama terpisah satu rilis mengatakan lebih sedikit tentang satu metode mana pun dan lebih banyak tentang bentuk bug itu: ia bersembunyi di overload mana pun yang tidak dibaca ulang siapa pun begitu saudaranya terlihat benar
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;
Mengapa Race Ini Hampir Mustahil Direproduksi?
Celah render-lock milik PDFiumPas tidak gagal pada setiap jalan, atau bahkan pada kebanyakan jalan, karena ia membutuhkan dua hal spesifik untuk mendarat pada instance TPdf yang sama sekaligus: sebuah pemanggilan rasterisasi yang sudah berlangsung, dan sebuah UnloadPage atau ReloadPage konkuren yang tiba di dalam jendela yang sama itu. Pengujian single-threaded tidak pernah menjalankan jalur ini sama sekali, dan bahkan beban kerja yang benar-benar multi-threaded hanya memicunya ketika sebuah render latar belakang dan sebuah event lifecycle dokumen kebetulan tumpang tindih di dalam masa hidup satu halaman. Pemicu paling realistis adalah pre-rendering PDF latar belakang yang dibangun di atas future yang bisa dibatalkan, di mana sebuah worker thread merasterisasi halaman berikutnya sementara UI thread me-reload atau meng-unload halaman saat ini atas input pengguna
FPDFImageObj_GetBitmap dan FPDFPage_GetThumbnailAsBitmap menelusuri struktur page-object yang bebas dilepaskan UnloadPage di tengah traversal, sehingga sebuah race yang benar-benar terpicu juga tidak selalu menghasilkan sebuah access violation langsung. Sebuah struktur yang dibaca sesaat terlambat sama mudahnya menyerahkan kembali piksel sampah, atau merusak metadata heap yang baru crash beberapa alokasi tak-berkaitan belakangan, dalam sebuah fungsi yang tidak pernah menyentuh sebuah halaman PDF. Itulah alasan jujur mengapa kelas bug ini bisa bertahan dalam sebuah codebase melintasi beberapa siklus rilis: stack trace pada titik kegagalan jarang menunjuk ke mana pun dekat enam baris yang sebenarnya kehilangan sebuah lock
Apa yang Berubah bagi Pemanggil
GetBitmap, GetObjectBitmap, GetThumbnail, dan overload HDC milik RenderPage mempertahankan signature publiknya persis seperti sebelumnya, karena perbaikan ini adalah locking internal yang ditambahkan di sekitar pemanggilan yang ada alih-alih sebuah migrasi. Layak diingat bahwa render lock berlingkup per instance TPdf, bukan global bagi proses tersebut, sehingga dua thread yang merender dua dokumen yang dimuat terpisah tetap berjalan sepenuhnya paralel — lock itu hanya menyerialkan operasi terhadap satu dokumen yang kebetulan dibagi kedua thread tersebut. Jika locking Anda sudah benar dan render masih terasa lambat saat zoom atau scroll, itu adalah pertanyaan yang berbeda, dijawab di artikel taktik render cache dan performa zoom PDFium — kebenaran dan kecepatan adalah sumbu terpisah di sini, dan perbaikan ini hanya menyentuh yang pertama
Enam metode dan satu overload saudara adalah sebagian kecil dari permukaan PDFium yang diekspos PDFiumPas, tetapi mereka adalah bagian kecil yang hanya berperilaku salah di bawah beban yang kebetulan tidak pernah dijalankan siapa pun dalam sebuah debugger. Render lock itu sendiri, dan set lengkap titik masuk rendering yang sekarang dicakupnya, disertakan sebagai bagian dari PDFium Component untuk Delphi, C++Builder, dan Lazarus/FPC