Debugging a bug that does not play

 
3r3-31. On October 1? 201? our team released a new version of the React Native application. We are pleased and proud of it.
 
 
But what a horror: after a few hours, the number of crashes under Android suddenly increases.
 
 
Debugging a bug that does not play  

1?000 crashes under Android
3r33450.
 
 
Our crash monitoring tool Sentry going crazy.
 
Native Navigation where for each screen native fragments under Android are used.
 
3r33395. We have updated react-native-svg . There were a few exceptions related to SVG components, but this is hardly the case.
 
 
At the moment, we can not reproduce the error, so the best strategy:
 
 
3r3186.  
3r33395. Roll back one of the two libraries. Roll it out for 10% of users, which is trivially done in the Play Store. Check with several users if the crash persists. Thus, we will confirm or disprove the hypothesis.
 
3r3191.
 
 
But how to choose a library for rollback? Of course, you can throw a coin, but is this the best option?
 
 
3r33476.  

Getting to the point of


 
Let's analyze the previous trace more carefully. Perhaps this will help determine the library.
 
 
3r33333. /**
* Simple (non-synchronized) pool of objects.
*
* @param The pooled type.
* /
public static class SimplePool implements Pool {
private final Object[]mPool;
private int mPoolSize;
@Override
public boolean release (T instance) {
if (isInPool (instance)) {
throw new IllegalStateException ("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize]= instance;
mPoolSize ++;
return true; 3r3503.  
There was a failure. Error 3r3481. java.lang.ArrayIndexOutOfBoundsException: length = 10; index = -1 means r3r3481. mPool - array of size 1? but mPoolSize = -1 .
 
 
Okay, how did the mPoolSize = -1 ? In addition to the method. recycle above, the only place of change is mPoolSize is method acquire class 3r3481. SimplePool :
 
 
  3r33333. public T acquire () {
if (mPoolSize> 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex]= null;
mPoolSize--;
return instance;
}
return null;
} 3r33333.
 
Therefore, the only way to get a negative value is mPoolSize - This is to reduce it at mPoolSize = 0 . But how is this possible with the condition mPoolSize> 0 ?
 
 
Put a breakpoint in Android Studio and take a look at what happens when you start the application. I mean, the condition here is if This code should work fine!
 
 

Finally, a revelation!


 
3r33476. 3r3302. 3r3499.
 
See in 3r3481. DynamicFromMap static link to SimplePool .
 
 
  3r33333. private static final Pools.SimplePool    sPool = new Pools.SimplePool  <>(10);  3r33333.
 
After several dozen clicks of the Play button with carefully placed breakpoints, we see that the streams are [i] mqt_native_modules
refer to functions SimplePool.acquire and 3r3481. SimplePool.release using React Native to control the style properties of the React component (below property width component)
 
 
3r33333.
 
 
But they are also accessed by the mainstream main !
 
 
 
 
Above, we see that they are used to update the property fill in the main thread, as a rule, for the component react-native-svg ! And indeed, the library react-native-svg started using DynamicFromMap 3r33357. only from the seventh version of to improve the performance of native svg-animations.
 
 
And-and-and function can be accessed from two threads, but DynamicFromMap does not use 3r3481. SimplePool thread safe way. “Thread safe,” you say?
 
 

Thread safety, a bit of theory


 
In single-threaded jаvascript, developers usually do not need to deal with thread safety.
 
 
On the other hand, Java supports the concept of parallel or multi-threaded programs. Multiple threads can run within the same program and can potentially access the overall data structure, which sometimes leads to unexpected results.
 
 
To take a simple example is shown below the image that streams A and B in parallel:
 
 
  •  
    3r33395. read integer;  
    3r33395. increase its value;  
    3r33395. return it.  

 
3r33476. 3r3402. 3r3499.
 
Stream B can potentially access the data value before Stream A updates it. We expected that two separate steps will give the final value 19 3r3482. . Instead, we can get 18 . This situation, where the final state of the data depends on the relative order of the flow operations, is called the race state. The problem is that this state does not necessarily occur all the time. Perhaps in the above case, stream B has another job before increasing the value, which gives sufficient time for stream A to update the value. This explains the chance and inability to reproduce the failure.
 
 
The data structure is considered thread-safe if operations can be performed simultaneously by multiple threads without the risk of a race condition.
 
 
When one thread reads for a particular data item, another thread should not have the right to change or delete this item (this is called atomicity). In the previous example, if the update cycles were atomic, the race conditions could have been avoided. Flow B will wait for thread A to complete the operation, and then start itself.
 
 

In our case, this may happen:


 
3r33476. 3r33434. 3r3499.
 
Since DynamicFromMap contains a static link to SimplePool , multiple calls DynamicFromMap come from different streams, simultaneouslycalling the method acquire in 3r3481. SimplePool
.
 
 
In the illustration above, flow A calls the method, evaluating the condition as true , but he has not managed to reduce the value of mPoolSize (which is used in conjunction with thread B), while thread B also calls this method and also evaluates the condition as true . Subsequently, each call will reduce the value mPoolSize As a result, the “impossible” value is obtained.
 
 

Correction


 
Studying the correction options, we found pull request to react-native which has not yet joined the branch - and it provides thread safety in this case.
 
 
 
 
Then we rolled out the corrected version of React Native for users. Crash finally fixed, hurray!
 
 
3r33476. 3r33477. 3r3499.
 
So, thanks to the help of Jenik Duplessi (contributor to the core of React Native) and Michael Sand (maintainer 3r3481. React-native-svg ), Patch
included in the next minor version of React Native ???r3r3484. .
 
 
Fixing this bug required some effort, but it was a great opportunity to dig deeper into react-native and react-native-svg. A good debugger and some well-located breakpoints are important. I hope you learned something from this story too!
 
3r3499.
3r3499.
+ 0 -

Add comment