realm-swift: Sequential calls to realm.write() sometimes throw an objc exception

In the context of a TestFlight beta-test, I get a significant number (a few each day) of crash reports similar to this one:

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0

Last Exception Backtrace:
0   CoreFoundation                	0x2596b916 __exceptionPreprocess + 122 (NSException.m:162)
1   libobjc.A.dylib               	0x25106e12 objc_exception_throw + 34 (objc-exception.mm:531)
2   Realm                         	0x520e68 -[RLMRealm beginWriteTransaction] + 180 (RLMRealm.mm:446)
3   RealmSwift                    	0x3eb150 _TFC10RealmSwift5Realm5writefzFzT_T_T_ + 44 (Realm.swift:153)
4   LapMonitor                    	0xe80fc _TTSf4g_n___TFC10LapMonitor16ActiveLapSessionP33_35B0DADE8A11F20A2053872E3981933117updateSessionDatafT20forDeviceSessionInfoVS_21LapMonitorSessionInfo_T_ + 972 (LapSession.swift:639)
5   LapMonitor                    	0xeafc8 _TTSf4d_g_n___TFC10LapMonitor16ActiveLapSession10lapMonitorfTCS_10LapMonitor20didUpdateSessionInfoVS_21LapMonitorSessionInfo_T_ + 360 (LapSession.swift:0)
6   LapMonitor                    	0xe13d8 _TTWC10LapMonitor16ActiveLapSessionS_24LapMonitorSessionHandlerS_FS1_10lapMonitorfTCS_10LapMonitor20didUpdateSessionInfoVS_21LapMonitorSessionInfo_T_ + 112 (LapSession.swift:0)
7   LapMonitor                    	0xd0c60 _TTSf4g_n___TFC10LapMonitor10LapMonitorP33_25576D73CF51C6E590B13A2BA772807319handleStatusMessagefVS0_P33_25576D73CF51C6E590B13A2BA772807323LapMonitorStatusMessageT_ + 564 (LapMonitor.swift:263)
8   LapMonitor                    	0xd25c0 _TTSf4d_g_d_n___TFC10LapMonitor10LapMonitor10peripheralfTCSo12CBPeripheral17didUpdateValueForCSo16CBCharacteristic5errorGSqPs5Error___T_ + 836 (LapMonitor.swift:0)
9   LapMonitor                    	0xcea44 _TToFC10LapMonitor10LapMonitor10peripheralfTCSo12CBPeripheral17didUpdateValueForCSo16CBCharacteristic5errorGSqPs5Error___T_ + 68 (LapMonitor.swift:0)
10  CoreBluetooth                 	0x2aa70cc0 -[CBPeripheral handleAttributeEvent:args:attributeSelector:delegateSelector:delegateFlag:] + 124 (CBPeripheral.m:644)
11  CoreBluetooth                 	0x2aa70d5c -[CBPeripheral handleCharacteristicEvent:characteristicSelector:delegateSelector:delegateFlag:] + 68 (CBPeripheral.m:666)
12  CoreBluetooth                 	0x2aa70e96 -[CBPeripheral handleCharacteristicValueUpdated:] + 70 (CBPeripheral.m:703)
13  CoreBluetooth                 	0x2aa769ee __29-[CBXpcConnection handleMsg:]_block_invoke + 58 (CBXpcConnection.m:227)
14  libdispatch.dylib             	0x254d981e _dispatch_call_block_and_release + 6 (init.c:757)
15  libdispatch.dylib             	0x254d980a _dispatch_client_callout + 18 (init.c:817)
16  libdispatch.dylib             	0x254e7ba4 _dispatch_main_queue_callback_4CF$VARIANT$mp + 1520 (inline_internal.h:1063)
17  CoreFoundation                	0x2592db68 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 4 (CFRunLoop.c:1612)
18  CoreFoundation                	0x2592c062 __CFRunLoopRun + 1570 (CFRunLoop.c:2718)
19  CoreFoundation                	0x2587b224 CFRunLoopRunSpecific + 516 (CFRunLoop.c:2814)
20  CoreFoundation                	0x2587b010 CFRunLoopRunInMode + 104 (CFRunLoop.c:2844)
21  GraphicsServices              	0x26e6bac4 GSEventRunModal + 156 (GSEvent.c:2245)
22  UIKit                         	0x29f4f184 UIApplicationMain + 140 (UIApplication.m:3772)
23  LapMonitor                    	0x6d208 main + 132 (LapMonitorData.swift:12)
24  libdyld.dylib                 	0x2552386e tlv_get_addr + 42 (threadLocalHelpers.s:311)

All Realm write transaction in the app use realm.write and are called from the main thread, so it seems very unlikely that a previous write transaction was left open.

I could not so far reproduce the issue in a debug environment, so any advice about how to add instrumentation to the code so that this issue can be further investigated via crash logs is welcome.

Note that this looks very similar to #3831, that was supposed to be fixed in v1.0.2.

Any idea of what in the app could cause this ?

This issue is problematic because if my understanding is correct the objc exception thrown by -[RLMRealm beginWriteTransaction] can not be catched in Swift , so the app will inevitably crash when this occurs.

Version of Realm and Tooling

Realm framework version: RealmSwift 2.4.x, RealmSwift 2.5.1

Realm Object Server version: n/a

Xcode version: Xcode 8.2, Xcode 8.3.1

iOS versions / devices:

  • iOS 9.3.5 (13G36) - iPhone4,1
  • iOS 10.2.1 (14D27) - iPhone7,2
  • iOS 10.3 (14E277) - iPhone7,2
  • iOS 10.3.1 (14E304) - iPhone6,2 iPhone7,2 iPhone8,4

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 22 (6 by maintainers)

Most upvoted comments

I had the same problem, but found a fairly simple solution that is working very well for me. Basically, check if isInWriteTransaction and, if so, first call realm.refresh(). I have the following write function in my DAO (data access object) base-class:

public func write(_ block: ((Realm) throws -> Swift.Void)) throws {
   let realm = self.realm()
   if realm.isInWriteTransaction {
      try! block(realm)
   } else {
      realm.doWriteTransaction {
         try! block(realm)
      }
   }
}

which relies on the following Realm extension (where the secret sauce is the refresh() call):

extension Realm {
   public func doWriteTransaction(file: String = #file, line: Int = #line, function: String = #function, _ block: (() throws -> Void)) {
      // Work around the crash that occurs when two write transactions are called in the same runloop.
      refresh()
      precondition(!isInWriteTransaction, "realm.write() was called when isInWriteTransaction == true")
      do {
         try self.write(block)
      } catch let error as NSError {
         let errorString = "Realm write error in \(function) (\(file) line \(line)): \(error)"
         ... handleErrorAnyWayYouPrefer(error, ...)
      }
   }
}

I’m running into the exact same problem, with RealmSwift 2.8.3. Actions triggered from UI cause sequential (non-overlapping) realm.write { … } blocks to be executed, and without fail the second realm.write { … } block will fail with exception.

Interestingly enough, if I call realm.refresh() before starting the second realm.write { … }, then the crash goes away.