react-native-quick-sqlite icon indicating copy to clipboard operation
react-native-quick-sqlite copied to clipboard

App crashes after writing multiple blobs

Open elliotsayes opened this issue 3 years ago • 14 comments

To reproduce:

  1. npx react-native init BlobRepro --template react-native-template-typescript && cd BlobRepro
  2. yarn add react-native-quick-sqlite && npx pod-install
  3. Add a function to write maby blobs, e.g.:
  const dbTest = () => {
    const db = open({name: 'test_data.sqlite'});
    const res = db.execute('CREATE TABLE TEST (data blob)')
    // console.log({res})

    const arr = [...Array(100).keys()];
    console.log({a: arr})
    arr.forEach(i => {
      const res2 = db.execute('INSERT INTO TEST (data) VALUES (?)', [new ArrayBuffer(8)])
      console.log({res2})
    });

    db.close()
  }
  1. yarn start --reset-cache , yarn ios
  2. Run the test code, e.g. via <Button title='dbTest' onPress={dbTest} />
  3. Observe app crashes: Screen Shot 2022-10-11 at 7 37 02 PM
  4. Observe only some of the data is present in the sqlite database: Screen Shot 2022-10-11 at 7 39 04 PM

elliotsayes avatar Oct 11 '22 06:10 elliotsayes

Hmm I did not do the blob integration myself, it was added on https://github.com/ospfranco/react-native-quick-sqlite/pull/12.

@andymatuschak any idea what could be going on?

ospfranco avatar Oct 11 '22 13:10 ospfranco

Screenshot 001119

You seem to have run into some Hermes memory allocation issue/race condition. Don't think there is anything wrong the the code in the library. The stack trace doesn't show any of the library code but just internal Hermes functions.

The implementation does mention that ArrayBuffers are shared around the memory... so maybe something funky is going on there.

ospfranco avatar Oct 11 '22 14:10 ospfranco

Hey, implementation of blobs in React Native is platform-specific. Basically, each blob has its own UUID and is stored in a hashmap in Java/Objective-C. From what I remember, there may be a slight delay between creating blob in JSI vs. adding an entry in the hashmap on the native side. Have you tried flushing the native queue before accessing blobs?

tomekzaw avatar Oct 11 '22 14:10 tomekzaw

How would you do that?

ospfranco avatar Oct 11 '22 16:10 ospfranco

One way would be to call some blocking synchronous method of native module. This could prove my hypothesis but this is rather unacceptable as it stops JS runtime until the native queue is flushed. Perhaps blob implementation in React Native should be rewritten using JSI so the calls are synchronous. This sounds like an ideal use case for TurboModules from the New Architecture.

tomekzaw avatar Oct 11 '22 17:10 tomekzaw

I'm not sure I'm following, but current implementation is already using synchronous JSI calls.

ospfranco avatar Oct 11 '22 18:10 ospfranco

Oh sorry, I misread that, you meant the RN blob implementation... I guess you mean the ArrayBuffer, which would fall again under hermes. Does this happen under JSC?

ospfranco avatar Oct 11 '22 18:10 ospfranco

@ospfranco I am also experiencing crashes under JSC

BlobRepro(15342,0x16dd7f000) malloc: *** error for object 0x11e7f00e0: pointer being freed was not allocated

Error originates from facebook::jsc::JSCRuntime::createFunctionFromHostFunction

elliotsayes avatar Oct 11 '22 19:10 elliotsayes

Could this be relevant for fixing the issue in hermes? https://github.com/facebook/hermes/commit/e6d887ae96bef5c71032f11ed1a9fb9fecec7b46

elliotsayes avatar Oct 11 '22 19:10 elliotsayes

No, I don't think so. That issue would (I believe) let us remove this extra copy I noted here on reads, but this issue pertains to writes. Furthermore, that API's absence shouldn't affect correctness: we're making a full copy, so anything we do with that memory subsequently should be independent of whatever Hermes is up to.

My first impression here is that JSI is violating its API contract here: our JSI function is being invoked with an array buffer argument, and that buffer is (apparently) being freed concurrently before our function returns control. It's possible that I've misunderstood the contract, but if that's the case I don't see where, nor do I see how it might be remedied.

I'm afraid I'm not going to have time to look at this in detail anytime soon.

andymatuschak avatar Oct 11 '22 20:10 andymatuschak

You might try changing SQLITE_STATIC to SQLITE_TRANSIENT here. That will cause an extra copy of the incoming ArrayBuffer (which shouldn't be necessary) but will make the potential race window smaller. (I don't currently have an environment set up where it would be easy for me to test that)

andymatuschak avatar Oct 11 '22 20:10 andymatuschak

No, I don't think so. That issue would (I believe) let us remove this extra copy I noted here on reads, but this issue pertains to writes.

Note that the issue does also appear on reads (in fact reads appear to crash it more reliably)

elliotsayes avatar Oct 11 '22 20:10 elliotsayes

Ah, got it, thanks for clarifying. Still, the API you mention would only affect reads; if it's occurring on writes without reads, that API wouldn't change anything here AFAICT.

andymatuschak avatar Oct 12 '22 19:10 andymatuschak

Just as a thought, maybe this or this are missing move semantics. You can try to add a std::move in there, seems unlikely but memory handling is tricky

ospfranco avatar Oct 13 '22 14:10 ospfranco

The problem was not reserving memory when copying the blob data. Awesome find my @amika-sq.

https://github.com/margelo/react-native-quick-sqlite/pull/11

I already copied the fix to op-sqlite. All new development will be on that package.

ospfranco avatar Nov 09 '23 14:11 ospfranco