From 97409f3295e666c7e833d73ee8d764e9285f5b3d Mon Sep 17 00:00:00 2001 From: Michelle Linington Date: Thu, 27 Jan 2022 18:55:17 -0800 Subject: [PATCH] Improves performance of launch jobs Randall hit an issue where his device would take a few seconds to start up. His logs indicate that his device is spending time running LaunchJobs. The first change here is to make sure we explicitly set the launch job work at an ultra-high priority. This is okay, since we don't actually present UI until after we finish these jobs. Running this work at UserInteractive is appropriate since it quite literally prevents the user from interacting with the app. There's also no other time-sensitive UI work we need to be doing (like running animations) that we could be contending with. The second change is to add a bit more logging that allows us to monitor the amount of work these jobs are doing. This will allow us to see if these jobs are performing an excessive amount of work. Finally, I moved these LaunchJobs to Swift. Mostly because the ObjC implementations were block based. The additional code was going to indent things further and our linter aggressiely indents blocks to begin with. Moving this to Swift is much more readable. --- SignalMessaging/environment/LaunchJobs.swift | 17 ++++- .../Attachments/TSAttachmentPointer.h | 2 +- .../FailedAttachmentDownloadsJob.swift | 60 +++++++++++++++ .../src/Messages/FailedMessagesJob.swift | 54 +++++++++++++ .../src/Messages/IncompleteCallsJob.swift | 74 ++++++++++++++++++ .../Messages/Interactions/TSOutgoingMessage.h | 2 +- .../Messages/Interactions/TSOutgoingMessage.m | 2 +- .../OWSFailedAttachmentDownloadsJob.h | 13 ---- .../OWSFailedAttachmentDownloadsJob.m | 73 ------------------ .../src/Messages/OWSFailedMessagesJob.h | 13 ---- .../src/Messages/OWSFailedMessagesJob.m | 67 ---------------- .../src/Messages/OWSIncompleteCallsJob.h | 13 ---- .../src/Messages/OWSIncompleteCallsJob.m | 76 ------------------- .../SharingThreadPickerViewController.swift | 2 +- 14 files changed, 205 insertions(+), 263 deletions(-) create mode 100644 SignalServiceKit/src/Messages/FailedAttachmentDownloadsJob.swift create mode 100644 SignalServiceKit/src/Messages/FailedMessagesJob.swift create mode 100644 SignalServiceKit/src/Messages/IncompleteCallsJob.swift delete mode 100644 SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m delete mode 100644 SignalServiceKit/src/Messages/OWSFailedMessagesJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSFailedMessagesJob.m delete mode 100644 SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m diff --git a/SignalMessaging/environment/LaunchJobs.swift b/SignalMessaging/environment/LaunchJobs.swift index 3334d5467c..d1944aed32 100644 --- a/SignalMessaging/environment/LaunchJobs.swift +++ b/SignalMessaging/environment/LaunchJobs.swift @@ -3,6 +3,7 @@ // import Foundation +import SignalServiceKit @objc public class LaunchJobs: NSObject { @@ -54,16 +55,24 @@ public class LaunchJobs: NSObject { Logger.verbose("Starting.") - DispatchQueue.global().async { + // Getting this work done ASAP is super high priority, since we won't finish launching until + // the completion block is fired. + // + // Originally, I considered making this all synchronous on the main thread (there's no UI to + // interrupt anyway) but I figured that in the absolute *worst* case where this work takes ~15s + // we risk getting watchdogged by SpringBoard. + // + // So instead, we'll use a user interactive queue and hope that's good enough. + DispatchQueue.sharedUserInteractive.async { // Mark all "attempting out" messages as "unsent", i.e. any messages that were not successfully // sent before the app exited should be marked as failures. - OWSFailedMessagesJob().runSync() + FailedMessagesJob().runSync() // Mark all "incomplete" calls as missed, e.g. any incoming or outgoing calls that were not // connected, failed or hung up before the app existed should be marked as missed. - OWSIncompleteCallsJob().runSync() + IncompleteCallsJob().runSync() // Mark all "downloading" attachments as "failed", i.e. any incoming attachments that were not // successfully downloaded before the app exited should be marked as failures. - OWSFailedAttachmentDownloadsJob().runSync() + FailedAttachmentDownloadsJob().runSync() // Kick off a low priority trim of the MSL // This will reschedule itself on a background queue ~24h or so diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h index 34bff12a2d..9db00181db 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h @@ -18,7 +18,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerType) { TSAttachmentPointerTypeRestoring = 2, }; -typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) { +typedef NS_CLOSED_ENUM(NSUInteger, TSAttachmentPointerState) { TSAttachmentPointerStateEnqueued = 0, TSAttachmentPointerStateDownloading = 1, TSAttachmentPointerStateFailed = 2, diff --git a/SignalServiceKit/src/Messages/FailedAttachmentDownloadsJob.swift b/SignalServiceKit/src/Messages/FailedAttachmentDownloadsJob.swift new file mode 100644 index 0000000000..6e3abdce20 --- /dev/null +++ b/SignalServiceKit/src/Messages/FailedAttachmentDownloadsJob.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public class FailedAttachmentDownloadsJob: Dependencies { + /// Used for logging the total number of attachments modified + private var count: UInt = 0 + public init() {} + + public func runSync() { + databaseStorage.write { writeTx in + AttachmentFinder.unfailedAttachmentPointerIds(transaction: writeTx).forEach { attachmentId in + // Since we can't directly mutate the enumerated attachments, we store only their ids in hopes + // of saving a little memory and then enumerate the (larger) TSAttachment objects one at a time. + autoreleasepool { + updateAttachmentPointerIfNecessary(attachmentId, transaction: writeTx) + } + } + } + Logger.info("Finished job. Marked \(count) in-progress attachments as failed") + } + + public func updateAttachmentPointerIfNecessary(_ uniqueId: String, transaction writeTx: SDSAnyWriteTransaction) { + // Preconditions: Must be a valid attachment pointer that hasn't failed + guard let attachment = TSAttachmentPointer.anyFetchAttachmentPointer( + uniqueId: uniqueId, + transaction: writeTx + ) else { + owsFailDebug("Missing attachment with id: \(uniqueId)") + return + } + + switch attachment.state { + case .enqueued, .downloading: + attachment.updateAttachmentPointerState(.failed, transaction: writeTx) + count += 1 + + switch count { + case ...3: + Logger.info("marked attachment pointer as failed: \(attachment.uniqueId)") + case 4: + Logger.info("eliding logs for further attachment pointers. final count will be reported once complete.") + default: + break + } + case .pendingMessageRequest: + // Do nothing. We don't want to mark this attachment as failed. + // It will be updated when the message request is resolved. + break + case .pendingManualDownload: + // Do nothing. We don't want to mark this attachment as failed. + break + case .failed: + // This should not have been returned from `unfailedAttachmentPointerIds` + owsFailDebug("Attachment has unexpected state \(attachment.uniqueId)."); + } + } +} diff --git a/SignalServiceKit/src/Messages/FailedMessagesJob.swift b/SignalServiceKit/src/Messages/FailedMessagesJob.swift new file mode 100644 index 0000000000..6e17bb2326 --- /dev/null +++ b/SignalServiceKit/src/Messages/FailedMessagesJob.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public class FailedMessagesJob: Dependencies { + + /// Used for logging the total number of messages modified + private var count: UInt = 0 + public init() {} + + public func runSync() { + databaseStorage.write { writeTx in + InteractionFinder.attemptingOutInteractionIds(transaction: writeTx).forEach { failedInteractionId in + // Since we can't directly mutate the enumerated "attempting out" expired messages, we store + // only their ids in hopes of saving a little memory and then enumerate the (larger) + // TSMessage objects one at a time. + autoreleasepool { + updateFailedMessageIfNecessary(failedInteractionId, transaction: writeTx) + } + } + } + } + + public func updateFailedMessageIfNecessary(_ uniqueId: String, transaction writeTx: SDSAnyWriteTransaction) { + // Preconditions + guard let message = TSOutgoingMessage.anyFetchOutgoingMessage(uniqueId: uniqueId, transaction: writeTx) else { + owsFailDebug("Missing interaction with id: \(uniqueId)") + return + } + guard message.messageState == .sending else { + owsFailDebug("Refusing to mark as unsent message \(message.timestamp) with state: \(message.messageState)") + return + } + + // Update + message.updateWithAllSendingRecipientsMarkedAsFailed(with: writeTx) + count += 1 + + // Log if appropriate + switch count { + case ...3: + Logger.info("marking message as unsent: \(message.uniqueId) \(message.timestamp)") + case 4: + Logger.info("eliding logs for further unsent messages. final update count will be reported once complete.") + default: + break + } + + // Postcondition + owsAssertDebug(message.messageState == .failed) + } +} diff --git a/SignalServiceKit/src/Messages/IncompleteCallsJob.swift b/SignalServiceKit/src/Messages/IncompleteCallsJob.swift new file mode 100644 index 0000000000..bf4c7abd73 --- /dev/null +++ b/SignalServiceKit/src/Messages/IncompleteCallsJob.swift @@ -0,0 +1,74 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public class IncompleteCallsJob: Dependencies { + + private var count: UInt = 0 + private let cutoffTimestamp: UInt64 + + public init() { + if CurrentAppContext().appLaunchTime.ows_millisecondsSince1970 > 0 { + cutoffTimestamp = CurrentAppContext().appLaunchTime.ows_millisecondsSince1970 + } else { + owsFailDebug("Invalid app launch time") + cutoffTimestamp = Date.ows_millisecondTimestamp() + } + } + + public func runSync() { + databaseStorage.write { writeTx in + InteractionFinder.incompleteCallIds(transaction: writeTx).forEach { incompleteCallId in + // Since we can't directly mutate the enumerated "incomplete" calls, we store only their ids in hopes + // of saving a little memory and then enumerate the (larger) TSCall objects one at a time. + autoreleasepool { + updateIncompleteCallIfNecessary(incompleteCallId, transaction: writeTx) + } + } + } + Logger.info("Finished job. Updated \(count) incomplete calls") + } + + public func updateIncompleteCallIfNecessary(_ uniqueId: String, transaction writeTx: SDSAnyWriteTransaction) { + // Preconditions: Must be a valid call that started before the app launched. + guard let call = TSCall.anyFetchCall(uniqueId: uniqueId, transaction: writeTx) else { + owsFailDebug("Missing call with id: \(uniqueId)") + return + } + guard call.timestamp < cutoffTimestamp else { + Logger.info("Ignoring new call: \(call.uniqueId)") + return + } + + // Update + let targetCallType: RPRecentCallType + switch call.callType { + case .outgoingIncomplete: + targetCallType = .outgoingMissed + case .incomingIncomplete: + targetCallType = .incomingMissed + default: + owsFailDebug("Call has unexpected type: \(call.callType)") + return + } + + call.updateCallType(targetCallType, transaction: writeTx) + count += 1 + + // Log if appropriate + switch count { + case ...3: + Logger.info("marked call as missed: \(call.uniqueId) \(call.timestamp)") + case 4: + Logger.info("eliding logs for further incomplete calls. final update count will be reported once complete.") + default: + break + } + + // Postcondition: Should be some kind of missed call + let validFinalStates: [RPRecentCallType] = [.incomingMissed, .outgoingMissed] + owsAssertDebug(validFinalStates.contains(call.callType)) + } +} diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h index 5741d01d9c..a4597bdf13 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h @@ -268,7 +268,7 @@ NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:receivedAtTimestamp transaction:(SDSAnyWriteTransaction *)transaction; // On app launch, all "sending" recipients should be marked as "failed". -- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(SDSAnyWriteTransaction *)transaction; +- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTransaction:(SDSAnyWriteTransaction *)transaction; - (BOOL)hasFailedRecipients; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index cb1a100a77..814f2ce2e3 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -619,7 +619,7 @@ NSUInteger const TSOutgoingMessageSchemaVersion = 1; }]; } -- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(SDSAnyWriteTransaction *)transaction +- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTransaction:(SDSAnyWriteTransaction *)transaction { OWSAssertDebug(transaction); diff --git a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h b/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h deleted file mode 100644 index 60378dffd3..0000000000 --- a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2021 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSFailedAttachmentDownloadsJob : NSObject - -- (void)runSync; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m b/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m deleted file mode 100644 index d9076ce6ad..0000000000 --- a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) 2021 Open Whisper Systems. All rights reserved. -// - -#import "OWSFailedAttachmentDownloadsJob.h" -#import "TSAttachmentPointer.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSFailedAttachmentDownloadsJob - -- (void)enumerateAttemptingOutAttachmentsWithBlock:(void (^_Nonnull)(TSAttachmentPointer *attachment))block - transaction:(SDSAnyReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated attachments, we store only their ids in hopes - // of saving a little memory and then enumerate the (larger) TSAttachment objects one at a time. - NSArray *attachmentIds = [AttachmentFinder unfailedAttachmentPointerIdsWithTransaction:transaction]; - for (NSString *attachmentId in attachmentIds) { - TSAttachmentPointer *_Nullable attachment = - [TSAttachmentPointer anyFetchAttachmentPointerWithUniqueId:attachmentId transaction:transaction]; - if (attachment == nil) { - OWSFailDebug(@"Missing attachment."); - continue; - } - block(attachment); - } -} - -- (void)runSync -{ - __block uint count = 0; - DatabaseStorageWrite( - self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { - [self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) { - // sanity check - if (attachment.state == TSAttachmentPointerStateFailed) { - OWSFailDebug(@"Attachment has unexpected state."); - return; - } - - switch (attachment.state) { - case TSAttachmentPointerStateFailed: - OWSFailDebug(@"Attachment has unexpected state."); - break; - case TSAttachmentPointerStatePendingMessageRequest: - // Do nothing. We don't want to mark this attachment as failed. - // It will be updated when the message request is resolved. - break; - case TSAttachmentPointerStateEnqueued: - case TSAttachmentPointerStateDownloading: - [attachment updateWithAttachmentPointerState:TSAttachmentPointerStateFailed - transaction:transaction]; - count++; - return; - case TSAttachmentPointerStatePendingManualDownload: - // Do nothing. We don't want to mark this attachment as failed. - break; - } - } - transaction:transaction]; - }); - - if (count > 0) { - OWSLogDebug(@"Marked %u attachments as failed", count); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.h b/SignalServiceKit/src/Messages/OWSFailedMessagesJob.h deleted file mode 100644 index 07fb3d92e1..0000000000 --- a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2021 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSFailedMessagesJob : NSObject - -- (void)runSync; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.m b/SignalServiceKit/src/Messages/OWSFailedMessagesJob.m deleted file mode 100644 index 5cd403d40c..0000000000 --- a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.m +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) 2021 Open Whisper Systems. All rights reserved. -// - -#import "OWSFailedMessagesJob.h" -#import "TSMessage.h" -#import "TSOutgoingMessage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSFailedMessagesJob - -- (NSArray *)fetchAttemptingOutMessageIdsWithTransaction:(SDSAnyReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - return [InteractionFinder attemptingOutInteractionIdsWithTransaction:transaction]; -} - -- (void)enumerateAttemptingOutMessagesWithBlock:(void (^_Nonnull)(TSOutgoingMessage *message))block - transaction:(SDSAnyReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated "attempting out" expired messages, we store only their ids in hopes - // of saving a little memory and then enumerate the (larger) TSMessage objects one at a time. - for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIdsWithTransaction:transaction]) { - TSOutgoingMessage *_Nullable message = - [TSOutgoingMessage anyFetchOutgoingMessageWithUniqueId:expiredMessageId transaction:transaction]; - if (message == nil) { - OWSFailDebug(@"Missing interaction."); - continue; - } - block(message); - } -} - -- (void)runSync -{ - __block uint count = 0; - - DatabaseStorageWrite( - self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { - [self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) { - // sanity check - OWSAssertDebug(message.messageState == TSOutgoingMessageStateSending); - if (message.messageState != TSOutgoingMessageStateSending) { - OWSLogError(@"Refusing to mark as unsent message with state: %d", (int)message.messageState); - return; - } - - OWSLogDebug(@"marking message as unsent: %@", message.uniqueId); - [message updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:transaction]; - OWSAssertDebug(message.messageState == TSOutgoingMessageStateFailed); - - count++; - } - transaction:transaction]; - }); - - OWSLogDebug(@"Marked %u messages as unsent", count); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h deleted file mode 100644 index 829e3462cd..0000000000 --- a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2021 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSIncompleteCallsJob : NSObject - -- (void)runSync; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m deleted file mode 100644 index f75adbc1fd..0000000000 --- a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) 2021 Open Whisper Systems. All rights reserved. -// - -#import "OWSIncompleteCallsJob.h" -#import "AppContext.h" -#import "TSCall.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSIncompleteCallsJob - -- (NSArray *)fetchIncompleteCallIdsWithTransaction:(SDSAnyWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - return [InteractionFinder incompleteCallIdsWithTransaction:transaction]; -} - -- (void)enumerateIncompleteCallsWithBlock:(void (^)(TSCall *call))block - transaction:(SDSAnyWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated "incomplete" calls, we store only their ids in hopes - // of saving a little memory and then enumerate the (larger) TSCall objects one at a time. - for (NSString *callId in [self fetchIncompleteCallIdsWithTransaction:transaction]) { - TSCall *_Nullable call = [TSCall anyFetchCallWithUniqueId:callId transaction:transaction]; - if (call == nil) { - OWSFailDebug(@"Missing call."); - continue; - } - block(call); - } -} - -- (void)runSync -{ - __block uint count = 0; - - OWSAssertDebug(CurrentAppContext().appLaunchTime); - uint64_t cutoffTimestamp = [NSDate ows_millisecondsSince1970ForDate:CurrentAppContext().appLaunchTime]; - - DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { - [self - enumerateIncompleteCallsWithBlock:^(TSCall *call) { - if (call.timestamp > cutoffTimestamp) { - OWSLogInfo(@"ignoring new call: %@", call.uniqueId); - return; - } - - if (call.callType == RPRecentCallTypeOutgoingIncomplete) { - OWSLogDebug(@"marking call as missed: %@", call.uniqueId); - [call updateCallType:RPRecentCallTypeOutgoingMissed transaction:transaction]; - OWSAssertDebug(call.callType == RPRecentCallTypeOutgoingMissed); - } else if (call.callType == RPRecentCallTypeIncomingIncomplete) { - OWSLogDebug(@"marking call as missed: %@", call.uniqueId); - [call updateCallType:RPRecentCallTypeIncomingMissed transaction:transaction]; - OWSAssertDebug(call.callType == RPRecentCallTypeIncomingMissed); - } else { - OWSFailDebug(@"call has unexpected call type: %@", NSStringFromCallType(call.callType)); - return; - } - count++; - } - transaction:transaction]; - }); - - OWSLogInfo(@"Marked %u calls as missed", count); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalShareExtension/SharingThreadPickerViewController.swift b/SignalShareExtension/SharingThreadPickerViewController.swift index 4c0593f1b5..1c6cca874b 100644 --- a/SignalShareExtension/SharingThreadPickerViewController.swift +++ b/SignalShareExtension/SharingThreadPickerViewController.swift @@ -400,7 +400,7 @@ extension SharingThreadPickerViewController { self.databaseStorage.write { transaction in for message in self.outgoingMessages { // If we sent the message to anyone, mark it as failed - message.updateWithAllSendingRecipientsMarkedAsFailed(withTansaction: transaction) + message.updateWithAllSendingRecipientsMarkedAsFailed(with: transaction) } } self.shareViewDelegate?.shareViewWasCancelled()