Skip to content

Adopt Sendable across PDF, LCP and Container APIs#803

Merged
mickael-menu merged 7 commits into
readium:swift6from
stevenzeck:pdf-concurrency
Jun 17, 2026
Merged

Adopt Sendable across PDF, LCP and Container APIs#803
mickael-menu merged 7 commits into
readium:swift6from
stevenzeck:pdf-concurrency

Conversation

@stevenzeck

@stevenzeck stevenzeck commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
  1. Silences warnings for system-owned types (PDFKit.PDFDocument and CGPDFDocument) using @retroactive @unchecked Sendable.
  2. Constrains HREF generic parameters across PDF service APIs to URLConvertible & Sendable.
  3. Refactors the License class to conform to Sendable by wrapping parts in a Mutex and utilizing weak closures.
  4. Uses Mutex synchronization for mutable state in stream processing (like EPUBDeobfuscator.readPosition).
  5. Isolates LCPDefaultRenewDelegate to @MainActor and uses @preconcurrency to bridge non-Sendable system protocols (SFSafariViewControllerDelegate).

Public API Breaking Changes

  1. Container, HTTPClient, PDFDocument, PDFDocumentFactory, PDFDocumentService, LCPClient, LCPLicenseRepository, LCPRenewDelegate, and UserRights all now conform to Sendable.
  2. LCPClientContext typealias changed from Any to Any & Sendable.
  3. HREF generic parameters in PDFDocumentFactory.open(resource:at:password:) and PDFDocumentService open methods now require URLConvertible & Sendable.
  4. ResourceTransformer in TransformingContainer now requires @Sendable.
  5. LCPDefaultRenewDelegate is now isolated to @MainActor.
  6. ProxyContainer initializer closure parameters now require @Sendable.

Relates to #758.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens Swift Concurrency guarantees across the toolkit by adopting Sendable widely, updating PDF-related APIs to require Sendable URL inputs, and refactoring mutable/shared state to use Mutex (or actor/main-actor isolation) to satisfy strict concurrency checking.

Changes:

  • Adds Sendable conformance to multiple core protocols/types (e.g. Container, HTTPClient, PDFDocument, UserRights, LCP protocols) and adjusts implementations/mocks accordingly.
  • Constrains PDF service/factory APIs to HREF: URLConvertible & Sendable and marks ResourceTransformer closures as @Sendable.
  • Refactors shared mutable state for concurrency safety (eg. License.documents, EPUB deobfuscation stream state, test mocks) and isolates LCPDefaultRenewDelegate to @MainActor.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Tests/StreamerTests/Parser/EPUB/Services/EPUBPositionsServiceTests.swift Updates a test mock container to satisfy new Sendable requirements.
Tests/SharedTests/Toolkit/HTTP/HTTPResourceTests.swift Makes mock HTTP client Sendable and guards mutable state with Mutex.
Tests/SharedTests/Publication/Services/Content/Iterators/PDFResourceContentIteratorTests.swift Makes PDF document mocks Sendable and synchronizes tracking with Mutex.
Tests/SharedTests/Publication/Services/Content Protection/ContentProtectionServiceTests.swift Makes test UserRights implementation Sendable and synchronizes counters with Mutex.
Tests/SharedTests/ProxyContainer.swift Adjusts stored closure to be @Sendable and updates existential spelling for Resource.
Sources/Streamer/Parser/EPUB/Resource Transformers/EPUBDeobfuscator.swift Synchronizes streaming read position using Mutex for concurrency safety.
Sources/Shared/Toolkit/ZIP/ZIPFoundation/ZIPFoundationArchiveFactory.swift Declares archive factory as Sendable.
Sources/Shared/Toolkit/PDF/PDFKit.swift Adds retroactive unchecked Sendable conformance to PDFKit.PDFDocument and tightens generic constraints.
Sources/Shared/Toolkit/PDF/PDFDocumentService.swift Makes PDF document service Sendable and constrains HREF generics to Sendable.
Sources/Shared/Toolkit/PDF/PDFDocument.swift Makes PDFDocument/PDFDocumentFactory Sendable and constrains HREF generics to Sendable.
Sources/Shared/Toolkit/PDF/CGPDF.swift Tightens HREF constraints and refactors callback state storage to use Mutex.
Sources/Shared/Toolkit/HTTP/HTTPContainer.swift Declares HTTPContainer as Sendable.
Sources/Shared/Toolkit/HTTP/HTTPClient.swift Declares HTTPClient as Sendable.
Sources/Shared/Toolkit/Data/Container/TransformingContainer.swift Makes transformer closures @Sendable and declares container Sendable.
Sources/Shared/Toolkit/Data/Container/SingleResourceContainer.swift Declares container Sendable.
Sources/Shared/Toolkit/Data/Container/Container.swift Makes Container Sendable and updates CompositeContainer to store any Container.
Sources/Shared/Publication/Services/Content Protection/UserRights.swift Makes UserRights Sendable.
Sources/Navigator/PDF/PDFNavigatorViewController.swift Tightens PDF navigator document-opening generic constraints to Sendable HREFs.
Sources/LCP/Services/DeviceService.swift Declares service Sendable and updates dependencies to any existentials.
Sources/LCP/License/License.swift Refactors License to be Sendable by protecting mutable state with Mutex and using weak observer capture.
Sources/LCP/LCPRenewDelegate.swift Makes renew delegate Sendable, isolates default delegate to @MainActor, bridges non-Sendable system delegate with @preconcurrency.
Sources/LCP/LCPLicenseRepository.swift Makes license repository protocol Sendable.
Sources/LCP/LCPClient.swift Makes LCP client protocol Sendable and updates LCPClientContext to Any & Sendable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mickael-menu mickael-menu changed the title Pdf concurrency Adopt Sendable across PDF, LCP and Container APIs Jun 17, 2026

@mickael-menu mickael-menu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @stevenzeck!

I made a few changes:

  • Fixed crash with EPUBDeobfuscator. Streamed Data chunks can be slices with non-zero start indices; positional mutation on them crashed.
  • Replaced the global @retroactive @unchecked Sendable conformance on PDFKit.PDFDocument (which leaked an unsound claim to every instance app-wide) with a private PDFKitPDFDocument wrapper that confines the assertion.
  • Removed redundant Sendable conformance from types that already inherit it via their protocol (Container, HTTPClient, etc.). I prefer to keep with idiomatic usage (when we conform to Hashable, we don't also add Equatable, for example) and SwiftFormat's default behavior (redundantSendable rule which was fixed recently to take into account public).

cc @grighakobian

@mickael-menu mickael-menu merged commit d60ea33 into readium:swift6 Jun 17, 2026
5 checks passed
@mickael-menu mickael-menu deleted the pdf-concurrency branch June 17, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants