InterprocessTests.m 14 KB

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