Skip to content

Commit 36201e1

Browse files
authored
fix: macos state changes + sleep causing crash sometimes (#53)
* fix: macos state changes + sleep causing crash sometimes * chore: lock
1 parent a92b7d1 commit 36201e1

File tree

3 files changed

+73
-5
lines changed

3 files changed

+73
-5
lines changed

lib/common/include/ThreadSafeCallback.h

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ inline ThreadSafeCallback::~ThreadSafeCallback() {
7272

7373
inline void ThreadSafeCallback::call(ArgumentFunction argFunction) {
7474
auto argFn = new ArgumentFunction(argFunction);
75-
if (threadSafeFunction_.BlockingCall(argFn) != napi_ok) {
75+
// Use NonBlockingCall to avoid hanging if environment is destroyed
76+
// This will queue the callback but not block waiting for it
77+
if (threadSafeFunction_.NonBlockingCall(argFn) != napi_ok) {
7678
delete argFn;
7779
}
7880
}
@@ -83,13 +85,50 @@ inline void ThreadSafeCallback::callJsCallback(
8385
Napi::Reference<Napi::Value>* context,
8486
ArgumentFunction* argFn) {
8587

86-
if (argFn != nullptr) {
88+
if (argFn == nullptr) {
89+
return;
90+
}
91+
92+
// Check if environment and callback are valid before proceeding
93+
if (env == nullptr || jsCallback == nullptr || context == nullptr) {
94+
delete argFn;
95+
return;
96+
}
97+
98+
// Check if context reference is still valid
99+
if (context->IsEmpty()) {
100+
delete argFn;
101+
return;
102+
}
103+
104+
try {
87105
ArgumentVector args;
88106
(*argFn)(env, args);
89107
delete argFn;
90108

91-
if (env != nullptr && jsCallback != nullptr) {
92-
jsCallback.Call(context->Value(), args);
109+
// Get the receiver value and check if it's valid
110+
Napi::Value receiverValue = context->Value();
111+
// Check if receiver value is null or undefined (invalid)
112+
if (receiverValue.IsNull() || receiverValue.IsUndefined()) {
113+
return;
114+
}
115+
116+
// Attempt to call the callback with error handling
117+
// If the environment is being destroyed, this may fail
118+
// Note: N-API errors don't throw C++ exceptions, so this won't catch
119+
// napi_open_callback_scope failures, but it helps with other cases
120+
try {
121+
jsCallback.Call(receiverValue, args);
122+
} catch (const std::exception&) {
123+
// Silently ignore exceptions - environment might be destroyed
124+
} catch (...) {
125+
// Catch any other exceptions during callback execution
93126
}
127+
} catch (const std::exception&) {
128+
// If argument building fails, just clean up
129+
delete argFn;
130+
} catch (...) {
131+
// Catch any other exceptions and clean up
132+
delete argFn;
94133
}
95134
}

lib/mac/src/ble_manager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
@property NSMutableDictionary *peripherals;
1717
@property NSMutableDictionary *mtus;
1818
@property NSMutableSet *discovered;
19+
@property (strong) dispatch_source_t stateCheckTimer;
1920

2021

2122
- (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback;

lib/mac/src/ble_manager.mm

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ - (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&
2121
self.discovered = [NSMutableSet set];
2222
self.peripherals = [NSMutableDictionary dictionaryWithCapacity:10];
2323
self.mtus = [NSMutableDictionary dictionaryWithCapacity:10];
24+
25+
// Set up periodic state checking to handle sleep/wake cycles using GCD timer
26+
// Check every 2 seconds for state changes on the same dispatch queue
27+
self.stateCheckTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue);
28+
if (self.stateCheckTimer) {
29+
dispatch_source_set_timer(self.stateCheckTimer,
30+
dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC),
31+
2.0 * NSEC_PER_SEC,
32+
0.1 * NSEC_PER_SEC); // 100ms leeway
33+
__weak typeof(self) weakSelf = self;
34+
dispatch_source_set_event_handler(self.stateCheckTimer, ^{
35+
__strong typeof(weakSelf) strongSelf = weakSelf;
36+
if (strongSelf) {
37+
CBManagerState currentState = strongSelf.centralManager.state;
38+
if (currentState != strongSelf.lastState) {
39+
[strongSelf centralManagerDidUpdateState:strongSelf.centralManager];
40+
}
41+
}
42+
});
43+
dispatch_resume(self.stateCheckTimer);
44+
}
2445
}
2546
return self;
2647
}
@@ -519,5 +540,12 @@ -(CBDescriptor*)getDescriptor:(CBPeripheral*) peripheral ByHandle:(NSNumber*) ha
519540
return nil;
520541
}
521542

522-
@end
543+
- (void)dealloc {
544+
// Clean up GCD timer
545+
if (self.stateCheckTimer) {
546+
dispatch_source_cancel(self.stateCheckTimer);
547+
self.stateCheckTimer = nil;
548+
}
549+
}
523550

551+
@end

0 commit comments

Comments
 (0)