Getting detached ArrayBuffer when using interop.bufferFromData()
lambourn opened this issue · comments
Environment
Provide version numbers for the following components (information can be retrieved by running tns info
in your project folder or by inspecting the package.json
of the project):
- CLI: 6.4.2
- Cross-platform modules: 6.4.2
- iOS Runtime: 6.4.1
Describe the bug
In our app we use CoreBluetooth to read binary data from BLE/GATT peripherals. The received data (NSData
) is passed as ArrayBuffer
to the NativeScript application tier using interop.bufferFromData(nsData)
. Typically, this just works fine.
In some situations, typically during ongoing and continous data transfers, we see crashes due to the ArrayBuffer
being detached from the data view (Uint8Array
) that is used to access the data. Error message "TypeError: Underlying ArrayBuffer has been detached from the view"
What strikes me here is that from the application perspective, the access of the data view happens synchronously after the ArrayBuffer was passed to the place where processing happens.
Pseudo code / sending side:
// data is received from CoreBluetooth characteristic
const data: NSData = characteristic.value;
const buffer: ArrayBuffer = interop.bufferFromData(data);
// forward using rxJS
dataReceived.next(buffer);
Pseudo code / processing side:
dataReceived.subscribe((buffer: ArrayBuffer) => {
// wrap buffer in data view
const view = new Uint8Array(buffer);
view[0] = 1; // this sometimes causes "TypeError: Underlying ArrayBuffer has been detached from the view "
// more stuff ...
}
So somehow the NSData
seems to be gone or the ArrayBuffer
has been detached.
Any ideas what would cause the ios-runtime to invalidate/free/re-allocate the NSData
?
Native Stack Trace
***** Fatal JavaScript exception - application has been terminated. *****
Native stack trace:
1 0x102f46928 NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
2 0x102f8102c NativeScript::FFICallback<NativeScript::ObjCMethodCallback>::ffiClosureCallback(ffi_cif*, void*, void**, void*)
3 0x103aee768 ffi_closure_SYSV_inner
4 0x103af01b4 .Ldo_closure
5 0x18c4e0e64 __NSFireTimer
6 0x18c074830 <redacted>
7 0x18c07454c <redacted>
8 0x18c073c28 <redacted>
9 0x18c06ec38 <redacted>
10 0x18c06e2a4 CFRunLoopRunSpecific
11 0x19616038c GSEventRunModal
12 0x1901798d4 UIApplicationMain
13 0x103af0044 ffi_call_SYSV
14 0x103aede8c ffi_call_int
15 0x103aed988 ffi_call
16 0x102f0946c NativeScript::FunctionWrapper::call(JSC::ExecState*)
17 0x103abac60 llint_entry
18 0x103ab8078 llint_entry
19 0x103ab8078 llint_entry
20 0x103ab8078 llint_entry
21 0x103ab8078 llint_entry
22 0x103ab8078 llint_entry
23 0x103ab8078 llint_entry
24 0x103ab8078 llint_entry
25 0x103ab7fd0 llint_entry
26 0x103ab8078 llint_entry
27 0x103a99738 vmEntryToJavaScript
28 0x103045a68 JSC::Interpreter::executeCall(JSC::ExecState*, JSC::JSObject*, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&)
29 0x1038eac48 JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&)
30 0x102f21c40 NativeScript::GlobalObject::moduleLoaderEvaluate(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue)
31 0x1037b8590 JSC::JSModuleLoader::evaluate(JSC::ExecState*, JSC::JSValue, JSC::JSValue, JSC::JSValue)
Hi @lambourn,
Here's the implementation of bufferFromData. Looking at it, I don't see any reasons for the underlying buffer to become invalidated. The implementation puts a strong reference to the instance, so it shouldn't be deallocated prematurely. On the other side, what looks suspicious is that the NSData
's buffer itself can get invalidated if a modification happens in the meantime. The documentation says that:
For a mutable data object, the returned pointer is valid until the data object is deallocated or the data is mutated.
I have no knowledge who else owns references to this NSData
object in your app and what's going on with it. Can you validate whether it's kept intact?
You can also try running your app with Xcode's AddressSanitizer enabled so that you catch any other memory issues which might indirectly cause this one.
Hi @mbektchiev,
thanks for the insights!
The NSData object is owned by the CBCharacteristic
we read data from. As per the documentation for CBCharacteristic.value
, the property is non-mutable but retained
.
Regarding the JS runtime error, the following could explain it: new data has been received by CoreBlutooth and the existing NSData
object in the value property was released by the Obj-C memory management and replaced by a new NSData
object (this is how I understand retained
). That could explain, why the previously stored strong reference to NSData
has been invalidated, leading to a detached backing ArrayBuffer
in the JS realm.
As mentioned, we only see this error happening very, very rarely - which maybe again hints to memory / ARC management - but if it happens, the app crashes.
One workaround for now is to check if the data view is still ok to use by checking byteLength
:
const view = new Uint8Array(buffer);
if (view.byteLength === 0) {
// view's ArrayBuffer is detached!
...
} else {
// safe to access the data
...
}
at least this does not crash the app. Another thing I will try is to create a copy of the new NSData
that is read from CBCharacteristic.value
and pass that on instead of the retained reference.
using above approach of copying the data held by the source NSData
and having the additional gurad check on the data view's byteLength
fixes the issue for me.
Issue can be closed.