Skip to content
Open
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
27 changes: 25 additions & 2 deletions src/main/java/com/ssu/unithon/call/application/CallService.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Service
public class CallService {
Expand Down Expand Up @@ -174,7 +176,7 @@ public void terminate(UUID callId, String participantKey) {
Call call = lockedCall(callId);
if (call.getStatus() == CallStatus.ENDED) return;
call.end(clock.instant());
closeLiveKitRoom(call);
afterCommit(() -> closeLiveKitRoom(call));
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -320,7 +322,7 @@ public void end(UUID callId, String creatorKey) {
}

call.end(clock.instant());
closeLiveKitRoom(call);
afterCommit(() -> closeLiveKitRoom(call));
}

@Transactional(readOnly = true)
Expand All @@ -345,6 +347,27 @@ private void cleanupStalePhoneCalls(String phoneNumber) {
}
}

/**
* Runs {@code action} only after this transaction commits. closeLiveKitRoom kicks the other
* participant's live LiveKit connection immediately (a real-time push, not a poll), which was
* racing the ENDED/REJECTED status write it's paired with: that write doesn't durably commit
* until the surrounding @Transactional method returns, so the kicked participant's client
* could poll /status right after being disconnected and still read the old ACTIVE status.
* Deferring to afterCommit makes the status change visible before the disconnect fires.
*/
private void afterCommit(Runnable action) {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
action.run();
return;
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
action.run();
}
});
}

private void closeLiveKitRoom(Call call) {
for (ParticipantSlot slot : slots.findByCallId(call.getId())) {
gateway.remove(call.getLivekitRoomName(), slot.getIdentity());
Expand Down
Loading