Skip to content

Commit fda255b

Browse files
authored
Merge pull request #72 from Shopify/bugfix/71-fix-ios-race-conditions
Fix ios race conditions
2 parents 9143daa + ab06559 commit fda255b

13 files changed

+261
-235
lines changed

package/android/cpp/jni/include/JniSkiaDrawView.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ namespace RNSkia
4444

4545
void updateTouchPoints(jni::JArrayDouble touches);
4646

47-
void setIsRemovedExternal() { setIsRemoved(); }
47+
void setIsRemovedExternal() { invalidate(); }
4848

4949
~JniSkiaDrawView() {
5050
}

package/cpp/rnskia/RNSkDrawView.cpp

+65-98
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
#include "RNSkDrawView.h"
66

77
#include <chrono>
8-
#include <condition_variable>
9-
#include <mutex>
8+
#include <functional>
109

1110
#include "RNSkLog.h"
1211

@@ -30,38 +29,26 @@ RNSkDrawView::RNSkDrawView(std::shared_ptr<RNSkPlatformContext> context)
3029
_platformContext(context),
3130
_infoObject(std::make_shared<RNSkInfoObject>()),
3231
_timingInfo(std::make_shared<RNSkTimingInfo>()),
33-
_isRemoved(false) {}
32+
_isDrawing(new std::timed_mutex())
33+
{}
3434

3535
RNSkDrawView::~RNSkDrawView() {
36-
{
37-
_isRemoved = true;
38-
// This is a very simple fix to an issue where the view posts a redraw
39-
// function to the javascript thread, and the object is destroyed and then
40-
// the redraw function is called and ends up executing on a destroyed draw
41-
// view. Since _isDrawing is an atomic bool we know that as long as it is
42-
// true we are drawing and should wait.
43-
// It is limited to only wait for 500 milliseconds - if it is stuck we
44-
// might have gotten an exception that caused the flag never to be reset.
45-
milliseconds start = std::chrono::duration_cast<milliseconds>(
46-
system_clock::now().time_since_epoch());
47-
48-
while (_isDrawing == true) {
49-
milliseconds now = std::chrono::duration_cast<milliseconds>(
50-
system_clock::now().time_since_epoch());
51-
if (now.count() - start.count() > 500) {
52-
RNSkLogger::logToConsole("Timed out waiting for RNSkDrawView delete...");
53-
break;
54-
}
55-
}
36+
invalidate();
37+
38+
// Wait for the drawing lock (if set)
39+
if(!_isDrawing->try_lock_for(system_clock::now().time_since_epoch() + milliseconds(500))) {
40+
RNSkLogger::logToConsole("Failed to delete since drawing is still locked for native view with id %i", _nativeId);
5641
}
42+
43+
delete _isDrawing;
5744
}
5845

59-
void RNSkDrawView::setIsRemoved() {
60-
_isRemoved = true;
46+
void RNSkDrawView::invalidate() {
6147
endDrawingLoop();
48+
_isValid = false;
6249
}
6350

64-
void RNSkDrawView::setDrawCallback(size_t nativeId, std::shared_ptr<jsi::Function> callback) {
51+
void RNSkDrawView::setDrawCallback(std::shared_ptr<jsi::Function> callback) {
6552

6653
if (callback == nullptr) {
6754
_drawCallback = nullptr;
@@ -70,9 +57,6 @@ void RNSkDrawView::setDrawCallback(size_t nativeId, std::shared_ptr<jsi::Functio
7057
return;
7158
}
7259

73-
// Update native id
74-
_nativeId = nativeId;
75-
7660
// Reset timing info
7761
_timingInfo->reset();
7862

@@ -82,7 +66,7 @@ void RNSkDrawView::setDrawCallback(size_t nativeId, std::shared_ptr<jsi::Functio
8266
int height, double timestamp,
8367
std::shared_ptr<RNSkPlatformContext> context) {
8468
auto runtime = context->getJsRuntime();
85-
69+
8670
// Update info parameter
8771
_infoObject->beginDrawCallback(width, height, timestamp);
8872

@@ -134,7 +118,7 @@ void RNSkDrawView::drawInSurface(sk_sp<SkSurface> surface, int width,
134118
std::shared_ptr<RNSkPlatformContext> context) {
135119

136120
try {
137-
if(getIsRemoved()) {
121+
if(!isValid()) {
138122
return;
139123
}
140124

@@ -177,49 +161,50 @@ void RNSkDrawView::updateTouchState(const std::vector<RNSkTouchPoint> &points) {
177161
}
178162
}
179163

180-
void RNSkDrawView::requestRedraw() {
181-
if (!isReadyToDraw()) {
182-
_redrawRequestCounter++;
183-
return;
184-
}
185-
186-
_isDrawing = true;
187-
188-
auto performDraw = [this]() {
189-
if(getIsRemoved()) {
190-
RNSkLogger::logToConsole("Warning: Trying to redraw after delete!");
191-
_isDrawing = false;
192-
return;
193-
}
194-
195-
if (_drawingMode == RNSkDrawingMode::Continuous) {
196-
_isDrawing = false;
197-
beginDrawingLoop();
198-
return;
199-
}
200-
201-
milliseconds ms = std::chrono::duration_cast<milliseconds>(
164+
void RNSkDrawView::performDraw() {
165+
if(isValid()) {
166+
// Calculate milliseconds since start
167+
milliseconds ms = duration_cast<milliseconds>(
202168
system_clock::now().time_since_epoch());
203-
169+
170+
// Call draw frame method in sub class
204171
drawFrame(ms.count() / 1000.0);
205-
206-
_isDrawing = false;
207-
208-
if(_redrawRequestCounter > 0) {
172+
173+
// Unlock the drawing lock
174+
_isDrawing->unlock();
175+
176+
// Should we request a new redraw?
177+
if(_drawingMode != RNSkDrawingMode::Continuous && _redrawRequestCounter > 0) {
209178
_redrawRequestCounter = 0;
210179
requestRedraw();
211180
}
212-
};
213-
214-
_platformContext->runOnJavascriptThread(performDraw);
181+
} else {
182+
_isDrawing->unlock();
183+
}
215184
}
216185

217-
bool RNSkDrawView::isReadyToDraw() {
218-
if (_isDrawing) {
219-
return false;
186+
void RNSkDrawView::requestRedraw() {
187+
if (!isReadyToDraw()) {
188+
return;
189+
}
190+
191+
// If we are in continuous mode, we can just start the drawing loop
192+
if (_drawingMode == RNSkDrawingMode::Continuous) {
193+
beginDrawingLoop();
194+
return;
220195
}
196+
197+
// Check if we are already in a draw
198+
if(!_isDrawing->try_lock()) {
199+
_redrawRequestCounter++;
200+
return;
201+
}
202+
203+
_platformContext->runOnJavascriptThread(std::bind(&RNSkDrawView::performDraw, this));
204+
}
221205

222-
if(getIsRemoved()) {
206+
bool RNSkDrawView::isReadyToDraw() {
207+
if(!isValid()) {
223208
return false;
224209
}
225210

@@ -237,58 +222,40 @@ bool RNSkDrawView::isReadyToDraw() {
237222
}
238223

239224
void RNSkDrawView::beginDrawingLoop() {
240-
if(getIsRemoved()) {
225+
if(!isValid()) {
241226
return;
242227
}
243228

244-
if (_drawingLoopId != -1) {
229+
if (_drawingLoopId != 0 || _nativeId == 0) {
245230
return;
246231
}
247232

248233
// Set to zero to avoid calling beginDrawLoop before we return
249-
_drawingLoopId = 0;
250234
_drawingLoopId =
251235
_platformContext->beginDrawLoop(_nativeId, [this]() {
252-
auto performDraw = [&]() {
253-
if(getIsRemoved()) {
254-
return;
255-
}
256-
257-
milliseconds ms = std::chrono::duration_cast<milliseconds>(
258-
system_clock::now().time_since_epoch());
259-
260-
// Only redraw if view is still alive
261-
drawFrame(ms.count() / 1000.0);
262-
263-
_isDrawing = false;
264-
};
265-
266-
if (!isReadyToDraw()) {
267-
return;
236+
if(_isDrawing->try_lock()) {
237+
_platformContext->runOnJavascriptThread(std::bind(&RNSkDrawView::performDraw, this));
268238
}
269-
270-
_isDrawing = true;
271-
_platformContext->runOnJavascriptThread(performDraw);
272239
});
273240
}
274241

275242
void RNSkDrawView::endDrawingLoop() {
276-
_platformContext->endDrawLoop(_nativeId);
277-
_drawingLoopId = -1;
243+
if(_drawingLoopId != 0) {
244+
_drawingLoopId = 0;
245+
_platformContext->endDrawLoop(_nativeId);
246+
}
278247
}
279248

280249
void RNSkDrawView::setDrawingMode(RNSkDrawingMode mode) {
281-
if(getIsRemoved()) {
250+
if(!isValid() || mode == _drawingMode || _nativeId == 0) {
282251
return;
283252
}
284-
if(mode != _drawingMode) {
285-
_drawingMode = mode;
286-
if(mode == RNSkDrawingMode::Default) {
287-
endDrawingLoop();
288-
} else {
289-
beginDrawingLoop();
290-
requestRedraw();
291-
}
253+
_drawingMode = mode;
254+
if(mode == RNSkDrawingMode::Default) {
255+
endDrawingLoop();
256+
} else {
257+
beginDrawingLoop();
258+
requestRedraw();
292259
}
293260
}
294261

package/cpp/rnskia/RNSkDrawView.h

+25-10
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,26 @@ class RNSkDrawView {
4141
* thread and js runtime.
4242
*/
4343
void requestRedraw();
44+
45+
/**
46+
Calls the drawing callback on the javascript thread
47+
*/
48+
void performDraw();
4449

4550
/**
4651
* Installs the draw callback for the view
4752
*/
48-
void setDrawCallback(size_t nativeId, std::shared_ptr<jsi::Function> callback);
53+
void setDrawCallback(std::shared_ptr<jsi::Function> callback);
54+
55+
/**
56+
Sets the native id of the view
57+
*/
58+
void setNativeId(size_t nativeId) { _nativeId = nativeId; }
59+
60+
/**
61+
Returns the native id
62+
*/
63+
size_t getNativeId() { return _nativeId; }
4964

5065
/**
5166
* Call this method with a valid Skia surface to let the draw drawCallback do
@@ -75,17 +90,17 @@ class RNSkDrawView {
7590
/**
7691
* Setup and draw the frame
7792
*/
78-
virtual void drawFrame(double time) = 0;
93+
virtual void drawFrame(double time) {};
7994

8095
/**
81-
* Mark view as removed from the RN view stack
96+
* Mark view as invalidated
8297
*/
83-
void setIsRemoved();
98+
void invalidate();
8499

85100
/**
86101
* @return True if the view was marked as deleted
87102
*/
88-
bool getIsRemoved() { return _isRemoved; }
103+
bool isValid() { return _isValid; }
89104

90105
/**
91106
Updates the last duration value
@@ -130,9 +145,9 @@ class RNSkDrawView {
130145
std::shared_ptr<JsiSkCanvas> _jsiCanvas;
131146

132147
/**
133-
* is drawing flag
148+
* drawing mutex
134149
*/
135-
std::atomic<bool> _isDrawing{false};
150+
std::timed_mutex* _isDrawing;
136151

137152
/**
138153
* Pointer to the platform context
@@ -152,7 +167,7 @@ class RNSkDrawView {
152167
/**
153168
* True if the drawing loop has been requested
154169
*/
155-
size_t _drawingLoopId = -1;
170+
size_t _drawingLoopId = 0;
156171

157172
/**
158173
* Info object parameter
@@ -168,9 +183,9 @@ class RNSkDrawView {
168183
*/
169184
std::atomic<int> _redrawRequestCounter;
170185
/**
171-
Flag indicating that the view is no longer available
186+
Flag indicating that the view is valid / invalid
172187
*/
173-
std::atomic<bool> _isRemoved;
188+
std::atomic<bool> _isValid { true };
174189

175190
/**
176191
* Native id

0 commit comments

Comments
 (0)