Add more logging around state restoration crash

This commit is contained in:
Shadowfacts 2023-01-15 11:30:34 -05:00
parent d75c2558ca
commit ababa4b428
1 changed files with 49 additions and 10 deletions

View File

@ -9,6 +9,7 @@
import UIKit import UIKit
import Pachyderm import Pachyderm
import Combine import Combine
import Sentry
class TimelineViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController, RefreshableViewController { class TimelineViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController, RefreshableViewController {
let timeline: Timeline let timeline: Timeline
@ -335,7 +336,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} }
loadViewIfNeeded() loadViewIfNeeded()
var loaded = false var loaded = false
await controller.restoreInitial { await controller.restoreInitial { @MainActor in
let hasStatusesToRestore = await loadStatusesToRestore(position: position) let hasStatusesToRestore = await loadStatusesToRestore(position: position)
if hasStatusesToRestore { if hasStatusesToRestore {
applyItemsToRestore(position: position) applyItemsToRestore(position: position)
@ -351,26 +352,45 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
guard !unloaded.isEmpty else { guard !unloaded.isEmpty else {
return true return true
} }
let statuses = await withTaskGroup(of: Status?.self) { group -> [Status] in let results = await withTaskGroup(of: (String, Result<Status, Swift.Error>).self) { group -> [(String, Result<Status, Swift.Error>)] in
for id in unloaded { for id in unloaded {
group.addTask { group.addTask {
do { do {
let (status, _) = try await self.mastodonController.run(Client.getStatus(id: id)) let (status, _) = try await self.mastodonController.run(Client.getStatus(id: id))
return status return (id, .success(status))
} catch { } catch {
print(error) return (id, .failure(error))
return nil
} }
} }
} }
return await group.reduce(into: []) { partialResult, status in return await group.reduce(into: []) { partialResult, result in
if let status { partialResult.append(result)
partialResult.append(status) }
} }
var statuses = [Status]()
for (id, result) in results {
switch result {
case .success(let status):
statuses.append(status)
case .failure(let error):
let crumb = Breadcrumb(level: .error, category: "TimelineViewController")
crumb.message = "Error loading status"
crumb.data = [
"error": String(describing: error),
"id": id
]
SentrySDK.addBreadcrumb(crumb: crumb)
} }
} }
await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext) await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext)
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Original position statusIDs"
crumb.data = [
"statusIDs": position.statusIDs,
]
SentrySDK.addBreadcrumb(crumb: crumb)
// update the timeline position in case some statuses couldn't be loaded // update the timeline position in case some statuses couldn't be loaded
if let center = position.centerStatusID { if let center = position.centerStatusID {
let nearestLoadedStatusToCenter = position.statusIDs[position.statusIDs.firstIndex(of: center)!...].first(where: { id in let nearestLoadedStatusToCenter = position.statusIDs[position.statusIDs.firstIndex(of: center)!...].first(where: { id in
@ -383,9 +403,17 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
!unloaded.contains(id) || statuses.contains(where: { $0.id == id }) !unloaded.contains(id) || statuses.contains(where: { $0.id == id })
} }
let crumb2 = Breadcrumb(level: .info, category: "TimelineViewController")
crumb2.message = "Filtered position statusIDs"
crumb2.data = [
"statusIDs": position.statusIDs,
]
SentrySDK.addBreadcrumb(crumb: crumb2)
return !position.statusIDs.isEmpty return !position.statusIDs.isEmpty
} }
@MainActor
private func applyItemsToRestore(position: TimelinePosition) { private func applyItemsToRestore(position: TimelinePosition) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.statuses]) snapshot.appendSections([.statuses])
@ -393,6 +421,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
let centerStatusID = position.centerStatusID let centerStatusID = position.centerStatusID
let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) } let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) }
snapshot.appendItems(items, toSection: .statuses) snapshot.appendItems(items, toSection: .statuses)
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
crumb.message = "Restoring statuses"
crumb.data = [
"statusIDs": position.statusIDs
]
SentrySDK.addBreadcrumb(crumb: crumb)
dataSource.apply(snapshot, animatingDifferences: false) { dataSource.apply(snapshot, animatingDifferences: false) {
if let centerStatusID, if let centerStatusID,
let index = statusIDs.firstIndex(of: centerStatusID), let index = statusIDs.firstIndex(of: centerStatusID),
@ -426,7 +460,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) { private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
let status = { let status = {
let status = self.mastodonController.persistentContainer.status(for: statusID)! guard let status = self.mastodonController.persistentContainer.status(for: statusID) else {
let crumb = Breadcrumb(level: .fatal, category: "TimelineViewController")
crumb.message = "Looking up status \(statusID)"
SentrySDK.addBreadcrumb(crumb: crumb)
preconditionFailure("Missing status for filtering")
}
// if the status is a reblog of another one, filter based on that one // if the status is a reblog of another one, filter based on that one
if let reblogged = status.reblog { if let reblogged = status.reblog {
return (reblogged, true) return (reblogged, true)