Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions lib/common/include/ThreadSafeCallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ inline ThreadSafeCallback::~ThreadSafeCallback() {

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

if (argFn != nullptr) {
if (argFn == nullptr) {
return;
}

// Check if environment and callback are valid before proceeding
if (env == nullptr || jsCallback == nullptr || context == nullptr) {
delete argFn;
return;
}

// Check if context reference is still valid
if (context->IsEmpty()) {
delete argFn;
return;
}

try {
ArgumentVector args;
(*argFn)(env, args);
delete argFn;

if (env != nullptr && jsCallback != nullptr) {
jsCallback.Call(context->Value(), args);
// Get the receiver value and check if it's valid
Napi::Value receiverValue = context->Value();
// Check if receiver value is null or undefined (invalid)
if (receiverValue.IsNull() || receiverValue.IsUndefined()) {
return;
}

// Attempt to call the callback with error handling
// If the environment is being destroyed, this may fail
// Note: N-API errors don't throw C++ exceptions, so this won't catch
// napi_open_callback_scope failures, but it helps with other cases
try {
jsCallback.Call(receiverValue, args);
} catch (const std::exception&) {
// Silently ignore exceptions - environment might be destroyed
} catch (...) {
// Catch any other exceptions during callback execution
}
} catch (const std::exception&) {
// If argument building fails, just clean up
delete argFn;
} catch (...) {
// Catch any other exceptions and clean up
delete argFn;
}
}
1 change: 1 addition & 0 deletions lib/mac/src/ble_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@property NSMutableDictionary *peripherals;
@property NSMutableDictionary *mtus;
@property NSMutableSet *discovered;
@property (strong) dispatch_source_t stateCheckTimer;


- (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback;
Expand Down
30 changes: 29 additions & 1 deletion lib/mac/src/ble_manager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ - (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&
self.discovered = [NSMutableSet set];
self.peripherals = [NSMutableDictionary dictionaryWithCapacity:10];
self.mtus = [NSMutableDictionary dictionaryWithCapacity:10];

// Set up periodic state checking to handle sleep/wake cycles using GCD timer
// Check every 2 seconds for state changes on the same dispatch queue
self.stateCheckTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue);
if (self.stateCheckTimer) {
dispatch_source_set_timer(self.stateCheckTimer,
dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC),
2.0 * NSEC_PER_SEC,
0.1 * NSEC_PER_SEC); // 100ms leeway
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(self.stateCheckTimer, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
CBManagerState currentState = strongSelf.centralManager.state;
if (currentState != strongSelf.lastState) {
[strongSelf centralManagerDidUpdateState:strongSelf.centralManager];
}
}
});
dispatch_resume(self.stateCheckTimer);
}
}
return self;
}
Expand Down Expand Up @@ -519,5 +540,12 @@ -(CBDescriptor*)getDescriptor:(CBPeripheral*) peripheral ByHandle:(NSNumber*) ha
return nil;
}

@end
- (void)dealloc {
// Clean up GCD timer
if (self.stateCheckTimer) {
dispatch_source_cancel(self.stateCheckTimer);
self.stateCheckTimer = nil;
}
}

@end
Loading