Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(android): protect against headlessJS task multiple-invocation #1176

Merged
merged 2 commits into from
Dec 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
*/

public class HeadlessTask {
private static String HEADLESS_TASK_NAME = "NotifeeHeadlessJS";
private static final String HEADLESS_TASK_NAME = "NotifeeHeadlessJS";
// Hard-coded time-limit for headless-tasks is 60000 @todo configurable?
private static final int TASK_TIMEOUT = 60000;
private static final AtomicInteger sLastTaskId = new AtomicInteger(0);
Expand All @@ -107,6 +107,7 @@ static synchronized int getNextTaskId() {

private final List<TaskConfig> mTaskQueue = new ArrayList<>();
private final AtomicBoolean mIsReactContextInitialized = new AtomicBoolean(false);
private final AtomicBoolean mWillDrainTaskQueue = new AtomicBoolean(false);
private final AtomicBoolean mIsInitializingReactContext = new AtomicBoolean(false);
private final AtomicBoolean mIsHeadlessJsTaskListenerRegistered = new AtomicBoolean(false);

Expand All @@ -124,7 +125,7 @@ public void stopAllTasks() {
*/
public void onFinishHeadlessTask(int taskId) {
if (!mIsReactContextInitialized.get()) {
Log.w("NotifeeHeadlessJS", taskId + " found no ReactContext");
Log.w(HEADLESS_TASK_NAME, taskId + " found no ReactContext");
return;
}
ReactContext reactContext = getReactContext(EventSubscriber.getContext());
Expand All @@ -145,12 +146,12 @@ public void onFinishHeadlessTask(int taskId) {
// Tell RN we're done using the mapped getReactTaskId().
headlessJsTaskContext.finishTask(taskConfig.getReactTaskId());
} else {
Log.w("NotifeeHeadlessJS", "Failed to find task: " + taskId);
Log.w(HEADLESS_TASK_NAME, "Failed to find task: " + taskId);
}
}
} else {
Log.w(
"NotifeeHeadlessJS",
HEADLESS_TASK_NAME,
"Failed to finishHeadlessTask: "
+ taskId
+ " -- HeadlessTask onFinishHeadlessTask failed to find a ReactContext. This is"
Expand Down Expand Up @@ -184,6 +185,10 @@ public void startTask(Context context, final TaskConfig taskConfig) throws Asser

private synchronized void invokeStartTask(
ReactContext reactContext, final TaskConfig taskConfig) {
if (taskConfig.mReactTaskId > 0) {
Log.w(HEADLESS_TASK_NAME, "Task already invoked <IGNORED>: " + this);
return;
}
final HeadlessJsTaskContext headlessJsTaskContext =
HeadlessJsTaskContext.getInstance(reactContext);
try {
Expand Down Expand Up @@ -219,13 +224,13 @@ public void onHeadlessJsTaskFinish(int taskId) {
}
if (taskConfig != null) {
// Clear it from the Queue.
Log.d("NotifeeHeadlessJS", "taskId: " + taskConfig.getTaskId());
Log.d(HEADLESS_TASK_NAME, "taskId: " + taskConfig.getTaskId());
mTaskQueue.remove(taskConfig);
if (taskConfig.getCallback() != null) {
taskConfig.getCallback().call();
}
} else {
Log.w("NotifeeHeadlessJS", "Failed to find taskId: " + taskId);
Log.w(HEADLESS_TASK_NAME, "Failed to find taskId: " + taskId);
}
}
}
Expand All @@ -236,9 +241,9 @@ public void onHeadlessJsTaskFinish(int taskId) {
// Provide the RN taskId to our private TaskConfig instance, mapping the RN taskId to our
// TaskConfig's internal taskId.
taskConfig.setReactTaskId(taskId);
Log.d("NotifeeHeadlessJS", "taskId: " + taskId);
Log.d(HEADLESS_TASK_NAME, "taskId: " + taskId);
} catch (IllegalStateException e) {
Log.e("NotifeeHeadlessJS", e.getMessage(), e);
Log.e(HEADLESS_TASK_NAME, e.getMessage(), e);
}
}

Expand All @@ -265,7 +270,7 @@ public static ReactContext getReactContext(Context context) {
Method getCurrentReactContext = reactHost.getClass().getMethod("getCurrentReactContext");
return (ReactContext) getCurrentReactContext.invoke(reactHost);
} catch (Exception e) {
Log.e("NotifeeHeadlessJS", "Reflection error getCurrentReactContext: " + e.getMessage(), e);
Log.e(HEADLESS_TASK_NAME, "Reflection error getCurrentReactContext: " + e.getMessage(), e);
}
}
final ReactInstanceManager reactInstanceManager =
Expand All @@ -284,7 +289,7 @@ private void createReactContextAndScheduleTask(Context context) {
return;
}
if (mIsInitializingReactContext.compareAndSet(false, true)) {
Log.d("NotifeeHeadlessJS", "initialize ReactContext");
Log.d(HEADLESS_TASK_NAME, "initialize ReactContext");
final Object reactHost = getReactHost(context);
if (isBridgelessArchitectureEnabled()) { // NEW arch
ReactInstanceEventListener callback =
Expand All @@ -301,7 +306,7 @@ public void onReactContextInitialized(@NonNull ReactContext reactContext) {
"removeReactInstanceEventListener", ReactInstanceEventListener.class);
removeReactInstanceEventListener.invoke(reactHost, this);
} catch (Exception e) {
Log.e("NotifeeHeadlessJS", "reflection error A: " + e, e);
Log.e(HEADLESS_TASK_NAME, "reflection error A: " + e, e);
}
}
};
Expand All @@ -314,7 +319,7 @@ public void onReactContextInitialized(@NonNull ReactContext reactContext) {
Method startReactHost = reactHost.getClass().getMethod("start");
startReactHost.invoke(reactHost);
} catch (Exception e) {
Log.e("NotifeeHeadlessJS", "reflection error ReactHost start: " + e.getMessage(), e);
Log.e(HEADLESS_TASK_NAME, "reflection error ReactHost start: " + e.getMessage(), e);
}
} else { // OLD arch
final ReactInstanceManager reactInstanceManager =
Expand All @@ -339,16 +344,18 @@ public void onReactContextInitialized(@NonNull ReactContext reactContext) {
* @param reactContext
*/
private void drainTaskQueue(final ReactContext reactContext) {
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
synchronized (mTaskQueue) {
for (TaskConfig taskConfig : mTaskQueue) {
invokeStartTask(reactContext, taskConfig);
if (mWillDrainTaskQueue.compareAndSet(false, true)) {
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
synchronized (mTaskQueue) {
for (TaskConfig taskConfig : mTaskQueue) {
invokeStartTask(reactContext, taskConfig);
}
}
}
},
500);
},
500);
}
}

/**
Expand Down
Loading