Closed dismory closed 10 years ago
I find that even main thread Realm instance is not notified and refreshed when changes happened in other thread..
Check this test case:
#import <XCTest/XCTest.h>
@class RealmTestObjecet;
RLM_ARRAY_TYPE(RealmTestObjecet);
@interface RealmTestObjecet : RLMObject
@property NSString *text;
@end
@implementation RealmTestObjecet
@end
@interface JTRealmTests : XCTestCase {
RLMRealm *_mainThreadRealm;
RLMNotificationToken *_token;
}
@end
@implementation JTRealmTests
- (void)setUp
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
[RLMRealm useInMemoryDefaultRealm];
_mainThreadRealm = [RLMRealm defaultRealm];
_mainThreadRealm.autorefresh = YES;
RealmTestObjecet *object = [RealmTestObjecet new];
object.text = @"foo";
[_mainThreadRealm transactionWithBlock:^{
[_mainThreadRealm addObject:object];
}];
}
- (void)tearDown
{
[_mainThreadRealm removeNotification:_token];
_token = nil;
_mainThreadRealm = nil;
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testRealm
{
__block BOOL notifiedFlag = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
_token = [_mainThreadRealm addNotificationBlock:^(NSString *notification, RLMRealm *realm) {
// this is not called
notifiedFlag = YES;
}];
dispatch_async(queue, ^{
RLMRealm *realm = [RLMRealm defaultRealm];
RealmTestObjecet *object = [[RealmTestObjecet allObjectsInRealm:realm] firstObject];
[realm transactionWithBlock:^{
object.text = @"bar";
}];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
RealmTestObjecet *object = [[RealmTestObjecet allObjectsInRealm:_mainThreadRealm] firstObject];
// not refreshed
XCTAssertEqualObjects(object.text, @"bar", @"");
// not notified
XCTAssertTrue(notifiedFlag, @"");
}
@end
Autorefresh occurs between iterations of the event loop (so that you always get a consistent view of data while working and to remove the need for synchronization on every read). I don't think this is explicitly documented anywhere, so that needs to be fixed.
@tgoyne I know that but why it does not work as expected here?
The problem is that you are using semaphores. When you wait for the semaphore, the main thread is blocked, and the notification never has a chance to run. Your thread needs to be able to process events for the notification to fire. We have a test which tests this exact scenario here: https://github.com/realm/realm-cocoa/blob/master/Realm/Tests/RealmTests.m#L193
Also please note that there are currently issues with in-memory realms which we are looking into. Might want to persist your realm for the time being until we can resole the issue.
@alazier Look at my first test case, semaphore is not used there but still not working..
In your first testcase you need to run a runloop in your background queue for notifications to fire.
@dismory Is there any way you think we could document this better?
@bmunkholm IMHO, it is necessary to explicitly document RunLoop
and Timer
implementation details used in Realm as what @tgoyne what said.
@alazier I rewrite the test case(which use semaphores) by running a runloop manually and it works as expected. :smile:
#import <XCTest/XCTest.h>
@class RealmTestObjecet;
RLM_ARRAY_TYPE(RealmTestObjecet);
@interface RealmTestObjecet : RLMObject
@property NSString *text;
@end
@implementation RealmTestObjecet
@end
@interface JTRealmTests : XCTestCase {
RLMRealm *_mainThreadRealm;
RLMNotificationToken *_token;
}
@end
@implementation JTRealmTests
- (void)setUp
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
// This will cause crash
// [RLMRealm useInMemoryDefaultRealm];
_mainThreadRealm = [RLMRealm defaultRealm];
RealmTestObjecet *object = [RealmTestObjecet new];
object.text = @"foo";
[_mainThreadRealm transactionWithBlock:^{
[_mainThreadRealm addObject:object];
}];
}
- (void)tearDown
{
[_mainThreadRealm removeNotification:_token];
_token = nil;
_mainThreadRealm = nil;
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testRealm
{
__block BOOL notifiedFlag = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
_token = [_mainThreadRealm addNotificationBlock:^(NSString *notification, RLMRealm *realm) {
// this is not called
notifiedFlag = YES;
}];
dispatch_async(queue, ^{
RLMRealm * realm = [RLMRealm defaultRealm];
RealmTestObjecet *object = [[RealmTestObjecet allObjectsInRealm:realm] firstObject];
[realm transactionWithBlock:^{
object.text = @"bar";
}];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// run main runloop manually
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
RealmTestObjecet *object = [[RealmTestObjecet allObjectsInRealm:_mainThreadRealm] firstObject];
// not refreshed
XCTAssertEqualObjects(object.text, @"bar", @"");
// not notified
XCTAssertTrue(notifiedFlag, @"");
}
@end
Also as you said, it will crash in Realm -sendNotifications
message when using in memory default Realm.
FYI:
Tests`-[RLMRealm refresh] at RLMRealm.mm:512:
0x1066d903e: pushq %rbp
0x1066d903f: movq %rsp, %rbp
0x1066d9042: pushq %r14
0x1066d9044: pushq %rbx
0x1066d9045: movq %rdi, %rbx
0x1066d9048: movq 0x2f6aa1(%rip), %rsi ; "inWriteTransaction"
0x1066d904f: callq *0x2c1c33(%rip) ; (void *)0x000000010086afc0: objc_msgSend
0x1066d9055: testb %al, %al
0x1066d9057: jne 0x1066d9091 ; -[RLMRealm refresh] + 83 at RLMRealm.mm:530
0x1066d9059: movq 0x2ff6d8(%rip), %r14 ; RLMRealm._sharedGroup
0x1066d9060: movq (%rbx,%r14), %rdi
0x1066d9064: callq 0x1067c2430 ; tightdb::SharedGroup::has_changed()
0x1066d9069: testb %al, %al
0x1066d906b: je 0x1066d9091 ; -[RLMRealm refresh] + 83 at RLMRealm.mm:530
0x1066d906d: movq (%rbx,%r14), %rdi
0x1066d9071: movq 0x2ff6c8(%rip), %rax ; RLMRealm._writeLogs
0x1066d9078: movq (%rbx,%rax), %rsi
0x1066d907c: callq 0x1067c2a40 ; tightdb::SharedGroup::advance_read(tightdb::SharedGroup::TransactLogRegistry&)
0x1066d9081: movq 0x2f6a70(%rip), %rsi ; "sendNotifications"
---------------------------------------------------------------------------------------------
0x1066d9088: movq %rbx, %rdi
0x1066d908b: callq *0x2c1bf7(%rip) ; (void *)0x000000010086afc0: objc_msgSend
0x1066d9091: popq %rbx
0x1066d9092: popq %r14
0x1066d9094: popq %rbp
0x1066d9095: ret
0x1066d9096: movq %rdx, %rcx
0x1066d9099: movq %rax, %rbx
0x1066d909c: cmpl $0x1, %ecx
0x1066d909f: jne 0x1066d90c2 ; -[RLMRealm refresh] + 132 at RLMRealm.mm:529
0x1066d90a1: movq %rbx, %rdi
0x1066d90a4: callq 0x106913dd0 ; symbol stub for: __cxa_begin_catch
0x1066d90a9: movq %rax, %rdi
0x1066d90ac: callq 0x1066d8b2b ; (anonymous namespace)::throw_objc_exception(std::exception&) at RLMRealm.mm:63
0x1066d90b1: popq %rbx
0x1066d90b2: popq %r14
0x1066d90b4: popq %rbp
0x1066d90b5: jmpq 0x106913ddc ; symbol stub for: __cxa_end_catch
0x1066d90ba: movq %rax, %rbx
0x1066d90bd: callq 0x106913ddc ; symbol stub for: __cxa_end_catch
0x1066d90c2: movq %rbx, %rdi
0x1066d90c5: callq 0x106915984 ; symbol stub for: _Unwind_Resume
0x1066d90ca: movq %rax, %rdi
0x1066d90cd: callq 0x1066ce9c0 ; __clang_call_terminate
@dismory closing this issue. Please reopen if you feel there's still something we can improve here.
Run this code in an app(so we get an main RunLoop set up by free) in iOS simulator:
Do I misunderstand something?