InterprocessTests.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMMultiProcessTestCase.h"
  19. #import "RLMConstants.h"
  20. #if !TARGET_OS_MACCATALYST
  21. @interface InterprocessTest : RLMMultiProcessTestCase
  22. @end
  23. @implementation InterprocessTest
  24. - (void)setUp {
  25. [super setUp];
  26. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  27. config.objectClasses = @[IntObject.class, DoubleObject.class];
  28. [RLMRealmConfiguration setDefaultConfiguration:config];
  29. }
  30. - (void)testCreateInitialRealmInChild {
  31. if (self.isParent) {
  32. RLMRunChildAndWait();
  33. RLMRealm *realm = [RLMRealm defaultRealm];
  34. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  35. }
  36. else {
  37. RLMRealm *realm = [RLMRealm defaultRealm];
  38. [realm beginWriteTransaction];
  39. [IntObject createInRealm:realm withValue:@[@0]];
  40. [realm commitWriteTransaction];
  41. }
  42. }
  43. - (void)testCreateInitialRealmInParent {
  44. RLMRealm *realm = [RLMRealm defaultRealm];
  45. if (self.isParent) {
  46. [realm beginWriteTransaction];
  47. [IntObject createInRealm:realm withValue:@[@0]];
  48. [realm commitWriteTransaction];
  49. RLMRunChildAndWait();
  50. }
  51. else {
  52. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  53. }
  54. }
  55. - (void)testCompactOnLaunchSuccessful {
  56. if (self.isParent) {
  57. @autoreleasepool {
  58. RLMRealm *realm = RLMRealm.defaultRealm;
  59. [realm transactionWithBlock:^{
  60. for (int i = 0; i < 1000; ++i) {
  61. [IntObject createInRealm:realm withValue:@[@(i)]];
  62. }
  63. }];
  64. [realm transactionWithBlock:^{
  65. [realm deleteAllObjects];
  66. }];
  67. }
  68. RLMRunChildAndWait(); // runs the event loop
  69. } else {
  70. unsigned long long (^fileSize)(NSString *) = ^unsigned long long(NSString *path) {
  71. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  72. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  73. };
  74. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  75. config.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  76. return YES;
  77. };
  78. unsigned long long sizeBefore = fileSize(config.fileURL.path);
  79. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  80. unsigned long long sizeAfter = fileSize(config.fileURL.path);
  81. XCTAssertGreaterThan(sizeBefore, sizeAfter);
  82. XCTAssertTrue(realm.isEmpty);
  83. }
  84. }
  85. - (void)testCompactOnLaunchBeginWriteFailed {
  86. if (self.isParent) {
  87. RLMRealm *realm = [RLMRealm defaultRealm];
  88. [realm beginWriteTransaction];
  89. RLMRunChildAndWait(); // runs the event loop
  90. [realm cancelWriteTransaction];
  91. } else {
  92. unsigned long long (^fileSize)(NSString *) = ^unsigned long long(NSString *path) {
  93. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  94. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  95. };
  96. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  97. config.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  98. return YES;
  99. };
  100. unsigned long long sizeBefore = fileSize(config.fileURL.path);
  101. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  102. unsigned long long sizeAfter = fileSize(config.fileURL.path);
  103. XCTAssertEqual(sizeBefore, sizeAfter);
  104. XCTAssertTrue(realm.isEmpty);
  105. }
  106. }
  107. - (void)testCompactOnLaunchFailSilently {
  108. if (self.isParent) {
  109. RLMRealm *realm = [RLMRealm defaultRealm];
  110. RLMRunChildAndWait(); // runs the event loop
  111. (void)[realm configuration]; // ensure the Realm stays open while the child process runs
  112. } else {
  113. unsigned long long (^fileSize)(NSString *) = ^unsigned long long(NSString *path) {
  114. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  115. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  116. };
  117. __block BOOL blockCalled = NO;
  118. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  119. config.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  120. blockCalled = YES;
  121. return YES;
  122. };
  123. unsigned long long sizeBefore = fileSize(config.fileURL.path);
  124. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  125. unsigned long long sizeAfter = fileSize(config.fileURL.path);
  126. XCTAssertLessThanOrEqual(sizeBefore, sizeAfter);
  127. XCTAssertTrue(realm.isEmpty);
  128. XCTAssertTrue(blockCalled);
  129. }
  130. }
  131. - (void)testOpenInParentThenAddObjectInChild {
  132. RLMRealm *realm = [RLMRealm defaultRealm];
  133. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  134. if (self.isParent) {
  135. RLMRunChildAndWait(); // runs the event loop
  136. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  137. }
  138. else {
  139. [realm beginWriteTransaction];
  140. [IntObject createInRealm:realm withValue:@[@0]];
  141. [realm commitWriteTransaction];
  142. }
  143. }
  144. - (void)testOpenInParentThenAddObjectInChildWithoutAutorefresh {
  145. RLMRealm *realm = [RLMRealm defaultRealm];
  146. realm.autorefresh = NO;
  147. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  148. if (self.isParent) {
  149. RLMRunChildAndWait();
  150. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  151. [realm refresh];
  152. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  153. }
  154. else {
  155. [realm beginWriteTransaction];
  156. [IntObject createInRealm:realm withValue:@[@0]];
  157. [realm commitWriteTransaction];
  158. }
  159. }
  160. - (void)testOpenInParentThenAddObjectInChildWithNoChanceToAutorefresh {
  161. RLMRealm *realm = [RLMRealm defaultRealm];
  162. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  163. if (self.isParent) {
  164. // Wait on a different thread so that this thread doesn't get the chance
  165. // to autorefresh
  166. [self dispatchAsyncAndWait:^{
  167. RLMRunChildAndWait();
  168. }];
  169. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  170. [realm refresh];
  171. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  172. }
  173. else {
  174. [realm beginWriteTransaction];
  175. [IntObject createInRealm:realm withValue:@[@0]];
  176. [realm commitWriteTransaction];
  177. }
  178. }
  179. - (void)testChangeInChildTriggersNotificationInParent {
  180. RLMRealm *realm = [RLMRealm defaultRealm];
  181. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  182. if (self.isParent) {
  183. [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
  184. RLMRunChildAndWait();
  185. }];
  186. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  187. }
  188. else {
  189. [realm beginWriteTransaction];
  190. [IntObject createInRealm:realm withValue:@[@0]];
  191. [realm commitWriteTransaction];
  192. }
  193. }
  194. - (void)testBackgroundProcessDoesNotTriggerSpuriousNotifications {
  195. RLMRealm *realm = [RLMRealm defaultRealm];
  196. RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
  197. XCTFail(@"Notification should not have been triggered");
  198. }];
  199. if (self.isParent) {
  200. RLMRunChildAndWait();
  201. }
  202. else {
  203. // Just a meaningless thing that reads from the realm
  204. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  205. }
  206. [token invalidate];
  207. }
  208. // FIXME: Re-enable this test when it can be made to pass reliably.
  209. - (void)DISABLED_testShareInMemoryRealm {
  210. RLMRealm *realm = [self inMemoryRealmWithIdentifier:@"test"];
  211. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  212. if (self.isParent) {
  213. [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
  214. RLMRunChildAndWait();
  215. }];
  216. XCTAssertEqual(1U, [IntObject allObjectsInRealm:realm].count);
  217. }
  218. else {
  219. [realm beginWriteTransaction];
  220. [IntObject createInRealm:realm withValue:@[@0]];
  221. [realm commitWriteTransaction];
  222. }
  223. }
  224. - (void)testBidirectionalCommunication {
  225. const int stopValue = 100;
  226. RLMRealm *realm = [self inMemoryRealmWithIdentifier:@"test"];
  227. [realm beginWriteTransaction];
  228. IntObject *obj = [IntObject allObjectsInRealm:realm].firstObject;
  229. if (!obj) {
  230. obj = [IntObject createInRealm:realm withValue:@[@0]];
  231. [realm commitWriteTransaction];
  232. }
  233. else {
  234. [realm cancelWriteTransaction];
  235. }
  236. RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) {
  237. if (obj.intCol % 2 == self.isParent && obj.intCol < stopValue) {
  238. [realm transactionWithBlock:^{
  239. obj.intCol++;
  240. }];
  241. }
  242. }];
  243. if (self.isParent) {
  244. dispatch_queue_t queue = dispatch_queue_create("background", 0);
  245. dispatch_async(queue, ^{ RLMRunChildAndWait(); });
  246. while (obj.intCol < stopValue) {
  247. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  248. }
  249. dispatch_sync(queue, ^{});
  250. }
  251. else {
  252. [realm transactionWithBlock:^{
  253. obj.intCol++;
  254. }];
  255. while (obj.intCol < stopValue) {
  256. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  257. }
  258. }
  259. [token invalidate];
  260. }
  261. - (void)testManyWriters {
  262. const int stopValue = 100;
  263. const int workers = 10;
  264. const RLMRealm *realm = RLMRealm.defaultRealm;
  265. if (self.isParent) {
  266. [realm beginWriteTransaction];
  267. IntObject *obj = [IntObject createInDefaultRealmWithValue:@[@(-workers)]];
  268. [realm commitWriteTransaction];
  269. dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
  270. for (int i = 0; i < workers; ++i) {
  271. dispatch_async(queue, ^{ RLMRunChildAndWait(); });
  272. }
  273. dispatch_barrier_sync(queue, ^{});
  274. [realm refresh];
  275. XCTAssertEqual(stopValue, obj.intCol);
  276. XCTAssertEqual(stopValue, [DoubleObject allObjects].count);
  277. XCTAssertEqual(stopValue / 2 + 1, [[DoubleObject.allObjects minOfProperty:@"doubleCol"] intValue]);
  278. return;
  279. }
  280. // Run the run loop until someone else makes a commit
  281. dispatch_block_t waitForExternalChange = ^{
  282. RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) {
  283. CFRunLoopStop(CFRunLoopGetCurrent());
  284. }];
  285. CFRunLoopRun();
  286. [token invalidate];
  287. };
  288. IntObject *obj = [IntObject allObjects].firstObject;
  289. int nextRun = -1;
  290. // Wait for all of the workers to start up
  291. while (obj.intCol < 0) {
  292. if (nextRun == -1) {
  293. [realm beginWriteTransaction];
  294. ++obj.intCol;
  295. [realm commitWriteTransaction];
  296. nextRun = 0;
  297. }
  298. waitForExternalChange();
  299. }
  300. while (true) {
  301. // Wait for someone else to run if it's not our turn yet
  302. if (obj.intCol < nextRun && nextRun < 100) {
  303. waitForExternalChange();
  304. continue;
  305. }
  306. [realm beginWriteTransaction];
  307. if (obj.intCol == stopValue) {
  308. [realm commitWriteTransaction];
  309. break;
  310. }
  311. ++obj.intCol;
  312. // Do some stuff
  313. [DoubleObject createInDefaultRealmWithValue:@[@(obj.intCol)]];
  314. [DoubleObject createInDefaultRealmWithValue:@[@(obj.intCol)]];
  315. RLMResults *min = [DoubleObject objectsWhere:@"doubleCol = %@", [DoubleObject.allObjects minOfProperty:@"doubleCol"]];
  316. [realm deleteObject:min.firstObject];
  317. [realm commitWriteTransaction];
  318. // Wait for a random number of other workers to do some work to avoid
  319. // having a strict order that processes run in and to avoid having a
  320. // single process do everything
  321. nextRun = obj.intCol + arc4random() % 10;
  322. }
  323. }
  324. - (void)testRecoverAfterCrash {
  325. if (self.isParent) {
  326. [self runChildAndWait];
  327. RLMRealm *realm = RLMRealm.defaultRealm;
  328. [realm beginWriteTransaction];
  329. [IntObject createInRealm:realm withValue:@[@0]];
  330. [realm commitWriteTransaction];
  331. XCTAssertEqual(1U, [IntObject allObjects].count);
  332. }
  333. else {
  334. RLMRealm *realm = RLMRealm.defaultRealm;
  335. [realm beginWriteTransaction];
  336. _Exit(1);
  337. }
  338. }
  339. - (void)testRecoverAfterCrashWithFileAlreadyOpen {
  340. if (self.isParent) {
  341. RLMRealm *realm = RLMRealm.defaultRealm;
  342. [self runChildAndWait];
  343. [realm beginWriteTransaction];
  344. [IntObject createInRealm:realm withValue:@[@0]];
  345. [realm commitWriteTransaction];
  346. XCTAssertEqual(1U, [IntObject allObjects].count);
  347. }
  348. else {
  349. RLMRealm *realm = RLMRealm.defaultRealm;
  350. [realm beginWriteTransaction];
  351. _Exit(1);
  352. }
  353. }
  354. - (void)testCanOpenAndReadWhileOtherProcessHoldsWriteLock {
  355. RLMRealm *realm = RLMRealm.defaultRealm;
  356. if (self.isParent) {
  357. [realm beginWriteTransaction];
  358. RLMRunChildAndWait();
  359. [realm commitWriteTransaction];
  360. }
  361. else {
  362. XCTAssertEqual(0U, [IntObject allObjectsInRealm:realm].count);
  363. }
  364. }
  365. @end
  366. #endif