Repository navigation
Conversation
, wonday#1041) PDFView.recycle() disposed the PdfFile synchronously on the main thread while RenderingHandler could still be rendering a page on the "PDF renderer" thread, freeing the native pdfium document under the renderer (SIGSEGV in libpdfium.so). PdfView.recycle() now detaches the PdfFile before super.recycle() and posts its dispose on the rendering handler, so it runs after any in-flight render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RenderingHandler.proceed() reads PDFView.pdfFile when it dequeues a render task. PdfFileDisposer cleared the field before super.recycle() had stopped the handler and removed the queued tasks, so a task dequeued in between hit a NullPointerException on the "PDF renderer" thread. Stop the handler and drop the queued tasks before clearing the field, as PDFView.recycle() itself does before its own dispose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #882
Fixes #976
Fixes #987
Fixes #989
Fixes #1024
Fixes #1041
Likely fixes #847
Summary
On Android, closing a
<Pdf>viewer (or changing itssource) while a page is still being rendered can crash the whole app with a nativeSIGSEGVinlibpdfium.so. This PR makes the native document close wait until any in-flight render has finished.Root cause
PDFView(com.github.zacharee:AndroidPdfViewer:4.0.1) renders pages on a dedicatedHandlerThread("PDF renderer",RenderingHandler).PDFView.recycle()runs on the main thread and does, in this order:renderingHandler.stop()+removeMessages(MSG_RENDER_TASK). This drops queued render tasks, but cannot interrupt the one currently running.pdfFile.dispose(), which synchronously closes the native pdfium document.Neither
PdfFile.dispose()norPdfFile.renderPageBitmap()take a lock (checked in the 4.0.1 bytecode: nomonitorenter). So if a render is in progress, the document is freed under the renderer → use-after-free in pdfium:FPDF_FFLDraw←PdfPage.nativeRenderPageBitmap+668←PdfFile.renderPageBitmap←RenderingHandler.proceedlibpdfium.so(FT_Done_Face) still occurring on `react-native-pdf@7.0.4 #1024:FT_Done_Face←PdfPage.nativeClosePage←PdfiumCore.renderPageBitmap←RenderingHandler.proceedrecycle()is reached from two paths, both affected:onDetachedFromWindow()→recycle()PDFView.Configurator.load()→recycle()AlreadyClosedBehavior.IGNORE(#989) only covers the case where pdfiumandroid sees the document is already closed before entering native code. It cannot protect a native call that is already running when the document is freed.This also explains why #989 was still reported on 7.0.4 after #999: IGNORE hides the Java-side IllegalStateException, but the document can still be closed while a render is in progress
Fix
PdfView.recycle()is overridden to:pdfFilefrom the view (set the field tonull), sosuper.recycle()skips its synchronousdispose(),super.recycle()as before (stop rendering, clear the cache, reset state…),pdfFile.dispose()on the view'sRenderingHandler.RenderingHandleris a single-threaded looper, so the posted dispose can only run after the render currently in progress (queued render tasks have already been removed byrecycle()). Close and render can no longer overlap.The helper
PdfFileDisposerlives in thecom.github.barteksc.pdfviewerpackage becausePDFView.pdfFile,PDFView.renderingHandlerand thePdfFileclass are package-private.Why it is safe
onDetachedFromWindow()callsrecycle()beforerenderingHandlerThread.quitSafely(), andquitSafely()still processes messages already queued. If the looper has already quit,post()returnsfalse: nothing can be rendering any more, so the document is disposed directly on the calling thread.pdfFile. The steps ofsuper.recycle()that run before its original dispose (animation stop, gesture disable, cache recycle, scroll handle) don't touchpdfFile, andrecycle()runs atomically on the main thread, so no gesture callback can interleave.DecodingAsyncTaskopens the new one. In pdfiumandroid 1.0.32, bothPdfiumCore.newDocument()andPdfDocument.close()synchronize on the same globalPdfiumCore.lock, with no nested lock. The new document gets a newRenderingHandler/PdfFile, so the two never mix.PdfFile.dispose()only callsPdfiumCore.closeDocument()and clears fields; nothing in it needs the main thread. As a side benefit, the main thread no longer blocks on the native close.android/build.gradle.Why not upgrade
io.legere:pdfiumandroid?pdfiumandroid ≥ 2.0.0 adds a configurable
LockManagerthat addresses this kind of race. But its bundledlibpdfium.soneeds API 26 at the native-link level, even though the module declaresminSdk 24. On API < 26 the library fails to load:This is the crash reported in #979 when this project moved to 1.0.34, and why it was reverted to 1.0.32 (c1879d0). I reproduced it again with 2.0.3 on an API 25 emulator and reported it upstream: johngray1965/PdfiumAndroidKt#55.
react-native-pdf supports
minSdkVersion 21, so upgrading would mean either raising that to 26 (breaking for apps that still support Android 5–7) or shipping a library that crashes at load on those devices. This fix stays on 1.0.32, needs no native rebuild and changes no public API. If upstream ships an API-21-compatible binary, the dependency can be upgraded later, and this workaround stays harmless.Testing
Stress test on a physical device: Samsung Galaxy Tab Active5 (SM-X306B), Android 16 / API 36, arm64, debug build of
FabricExample. A test screen mounts a localfile://PDF and tears the viewer down 0–150 ms afteronLoadComplete(20 % of iterations before load completes), in two modes:onDetachedFromWindow→recycle())RuntimeException: Get page pdf document nullon "PDF renderer"Configurator.load()→recycle())SIGSEGVinlibpdfium.so,nativeRenderPageBitmap+668(same as #1041) after ~89 loadsMemory: same native heap growth per mount/unmount with and without this PR (≈ 3.0 MB per load in both cases, measured outside the race window). The PR doesn't introduce a leak, and every load has a matching
PdfDocument.close. That growth already exists on master and is unrelated to this change. I'll report it separately.