Skip to content

Commit 3e2aefe

Browse files
committed
attempt to fix data race in call initiation
1 parent b2de9f6 commit 3e2aefe

3 files changed

Lines changed: 55 additions & 27 deletions

File tree

app/src/main/java/co/tinode/tindroid/Cache.java

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ public class Cache {
4444
// Currently active topic.
4545
private String mTopicSelected = null;
4646

47-
// Current video call.
48-
private CallInProgress mCallInProgress = null;
47+
// Current video call. Volatile for visibility across threads.
48+
private volatile CallInProgress mCallInProgress = null;
4949

5050
private boolean mFCMTokenRequested = false;
5151

@@ -187,47 +187,57 @@ static void invalidate() {
187187
}
188188

189189
public static CallInProgress getCallInProgress() {
190-
return sInstance.mCallInProgress;
190+
synchronized (sInstance) {
191+
return sInstance.mCallInProgress;
192+
}
191193
}
192194

193195
public static void prepareNewCall(@NonNull String topic, int seq, @Nullable CallConnection conn) {
194-
if (sInstance.mCallInProgress == null) {
195-
sInstance.mCallInProgress = new CallInProgress(topic, seq, conn);
196-
} else if (!sInstance.mCallInProgress.equals(topic, seq)) {
197-
// Include stacktrace to identify the caller which attempted to start a conflicting call.
198-
throw new IllegalStateException("prepareNewCall called while another call is in progress" +
199-
"\n\tNew: " + topic + ":" + seq);
196+
synchronized (sInstance) {
197+
if (sInstance.mCallInProgress == null) {
198+
sInstance.mCallInProgress = new CallInProgress(topic, seq, conn);
199+
} else if (!sInstance.mCallInProgress.equals(topic, seq)) {
200+
// Include stacktrace to identify the caller which attempted to start a conflicting call.
201+
throw new IllegalStateException("prepareNewCall called while another call is in progress" +
202+
"\n\tNew: " + topic + ":" + seq);
203+
}
200204
}
201205
}
202206

203207
public static void setCallActive(String topic, int seqId) {
204-
if (sInstance.mCallInProgress != null) {
205-
try {
206-
sInstance.mCallInProgress.setCallActive(topic, seqId);
207-
} catch (IllegalArgumentException iae) {
208-
// Preserve fatal behavior but add context about the cache state.
209-
throw new IllegalArgumentException("setCallActive failed. Existing=" +
210-
sInstance.mCallInProgress +
211-
" New=" + topic + ":" + seqId + "; err=" + iae.getMessage());
208+
synchronized (sInstance) {
209+
if (sInstance.mCallInProgress != null) {
210+
try {
211+
sInstance.mCallInProgress.setCallActive(topic, seqId);
212+
} catch (IllegalArgumentException iae) {
213+
// Preserve fatal behavior but add context about the cache state.
214+
throw new IllegalArgumentException("setCallActive failed. Existing=" +
215+
sInstance.mCallInProgress +
216+
" New=" + topic + ":" + seqId + "; err=" + iae.getMessage());
217+
}
218+
} else {
219+
throw new IllegalStateException("setCallActive without prepareNewCall, New="
220+
+ topic + ":" + seqId);
212221
}
213-
} else {
214-
throw new IllegalStateException("setCallActive without prepareNewCall, New="
215-
+ topic + ":" + seqId);
216222
}
217223
}
218224

219225
public static void setCallConnected() {
220-
if (sInstance.mCallInProgress != null) {
221-
sInstance.mCallInProgress.setCallConnected();
222-
} else {
223-
Log.e(TAG, "Attempt to mark call connected with no configured call");
226+
synchronized (sInstance) {
227+
if (sInstance.mCallInProgress != null) {
228+
sInstance.mCallInProgress.setCallConnected();
229+
} else {
230+
Log.e(TAG, "Attempt to mark call connected with no configured call");
231+
}
224232
}
225233
}
226234

227235
public static void endCallInProgress() {
228-
if (sInstance.mCallInProgress != null) {
229-
sInstance.mCallInProgress.endCall();
230-
sInstance.mCallInProgress = null;
236+
synchronized (sInstance) {
237+
if (sInstance.mCallInProgress != null) {
238+
sInstance.mCallInProgress.endCall();
239+
sInstance.mCallInProgress = null;
240+
}
231241
}
232242
}
233243

app/src/main/java/co/tinode/tindroid/CallManager.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,22 @@ public static void acceptIncomingCall(Context context, String caller, int seq, b
233233

234234
public static void showOutgoingCallUi(Context context, String topicName,
235235
boolean audioOnly, CallConnection conn) {
236+
CallInProgress existingCall = Cache.getCallInProgress();
237+
if (existingCall != null) {
238+
if (existingCall.isConnected() || existingCall.isConnectionUseful()) {
239+
// A real active call is in progress (ringing or connected). Don't clobber it.
240+
Log.w(TAG, "Cannot start outgoing call: another call is active: " + existingCall);
241+
if (conn != null) {
242+
conn.setDisconnected(new android.telecom.DisconnectCause(
243+
android.telecom.DisconnectCause.BUSY));
244+
conn.destroy();
245+
}
246+
return;
247+
}
248+
// Stale/zombie call left over from a previous session. Clean it up.
249+
Log.w(TAG, "Ending stale call before starting new outgoing call: " + existingCall);
250+
Cache.endCallInProgress();
251+
}
236252
Cache.prepareNewCall(topicName, 0, conn);
237253

238254
Intent intent = new Intent(context, CallActivity.class);

app/src/main/java/co/tinode/tindroid/services/CallConnectionService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public Connection onCreateIncomingConnection(@Nullable PhoneAccountHandle connec
7373

7474
conn.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
7575

76+
// This throws but shouldn't. We are not catching here to be able to find
77+
// the root cause of the problem (data race).
7678
Cache.prepareNewCall(callerUri.getSchemeSpecificPart(), seq, conn);
7779

7880
conn.setConnectionCapabilities(Connection.CAPABILITY_MUTE);

0 commit comments

Comments
 (0)