NativeScript / ios-jsc

NativeScript for iOS using JavaScriptCore

Home Page:http://docs.nativescript.org/runtimes/ios

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 NSDataand having the additional gurad check on the data view's byteLength fixes the issue for me.

Issue can be closed.