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: 작업 정보가 만료된 사용자 정보가 대기방에서 제거되지 않는 문제를 수정한다. #142

Merged
merged 5 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion backend-config
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public long getRemainingCount(String email, long performanceId) {
}

public void moveUserToRunning(long performanceId) {
runningManager.removeExpiredMemberInfo(performanceId);
Set<String> removeMemberEmails = runningManager.removeExpiredMemberInfo(performanceId);
waitingManager.removeMemberInfo(removeMemberEmails, performanceId);
long availableToRunning = runningManager.getAvailableToRunning(performanceId);
Set<WaitingMember> waitingMembers =
waitingManager.pullOutMembers(performanceId, availableToRunning);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ public interface RunningManager {

void pullOutRunningMember(String email, long performanceId);

void removeExpiredMemberInfo(long performanceId);
Set<String> removeExpiredMemberInfo(long performanceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface WaitingManager {
Set<WaitingMember> pullOutMembers(long performanceId, long availableToRunning);

void removeMemberInfo(String email, long performanceId);

void removeMemberInfo(Set<String> emails, long performanceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public void updateWaitingInfo(long waitingCount, ZonedDateTime enteredAt) {
this.waitingCount = waitingCount;
this.enteredAt = enteredAt;
}

public void enter() {
this.enteredAt = ZonedDateTime.now();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public long getAvailableToRunning(long performanceId) {

@Override
public void enterRunningRoom(long performanceId, Set<WaitingMember> waitingMembers) {
waitingMembers.forEach(WaitingMember::enter);
runningCounter.increment(performanceId, waitingMembers.size());
runningRoom.enter(performanceId, waitingMembers);
}
Expand All @@ -40,7 +41,7 @@ public void pullOutRunningMember(String email, long performanceId) {
}

@Override
public void removeExpiredMemberInfo(long performanceId) {
runningRoom.removeExpiredMemberInfo(performanceId);
public Set<String> removeExpiredMemberInfo(long performanceId) {
return runningRoom.removeExpiredMemberInfo(performanceId);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.thirdparty.ticketing.global.waitingsystem.memory.running;

import java.time.ZonedDateTime;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import com.thirdparty.ticketing.domain.waitingsystem.running.RunningRoom;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingMember;
Expand Down Expand Up @@ -52,18 +54,19 @@ public void pullOutRunningMember(String email, long performanceId) {
});
}

public void removeExpiredMemberInfo(long performanceId) {
room.computeIfPresent(
performanceId,
(key, room) -> {
ZonedDateTime fiveMinutesAgo = ZonedDateTime.now().minusMinutes(EXPIRED_MINUTE);
room.values().stream()
.filter(member -> member.getEnteredAt().isBefore(fiveMinutesAgo))
.forEach(
member -> {
room.remove(member.getEmail());
});
return room;
});
public Set<String> removeExpiredMemberInfo(long performanceId) {
ConcurrentMap<String, WaitingMember> performanceRoom = room.get(performanceId);
if (performanceRoom == null) {
return Set.of();
}

ZonedDateTime fiveMinutesAgo = ZonedDateTime.now().minusMinutes(EXPIRED_MINUTE);
Set<String> removeMemberEmails =
performanceRoom.entrySet().stream()
.filter(entry -> entry.getValue().getEnteredAt().isBefore(fiveMinutesAgo))
.map(Entry::getKey)
.collect(Collectors.toSet());
removeMemberEmails.forEach(performanceRoom::remove);
return removeMemberEmails;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ public Set<WaitingMember> pullOutMembers(long performanceId, long availableToRun
public void removeMemberInfo(String email, long performanceId) {
waitingRoom.removeMemberInfo(email, performanceId);
}

@Override
public void removeMemberInfo(Set<String> emails, long performanceId) {
waitingRoom.removeMemberInfo(emails, performanceId);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.thirdparty.ticketing.global.waitingsystem.memory.waiting;

import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

Expand Down Expand Up @@ -37,4 +38,13 @@ public void removeMemberInfo(String email, long performanceId) {
return waitingRoom;
});
}

public void removeMemberInfo(Set<String> emails, long performanceId) {
room.computeIfPresent(
performanceId,
(key, room) -> {
emails.forEach(room::remove);
return room;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void pullOutRunningMember(String email, long performanceId) {
}

@Override
public void removeExpiredMemberInfo(long performanceId) {
runningRoom.removeExpiredMemberInfo(performanceId);
public Set<String> removeExpiredMemberInfo(long performanceId) {
return runningRoom.removeExpiredMemberInfo(performanceId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ public void pullOutRunningMember(String email, long performanceId) {
runningRoom.remove(getRunningRoomKey(performanceId), email);
}

public void removeExpiredMemberInfo(long performanceId) {
public Set<String> removeExpiredMemberInfo(long performanceId) {
long removeRange = ZonedDateTime.now().minusMinutes(EXPIRED_MINUTE).toEpochSecond();
runningRoom.removeRangeByScore(getRunningRoomKey(performanceId), 0, removeRange);
String runningRoomKey = getRunningRoomKey(performanceId);
Set<String> removedMemberEmails = runningRoom.rangeByScore(runningRoomKey, 0, removeRange);
runningRoom.removeRangeByScore(runningRoomKey, 0, removeRange);
return removedMemberEmails;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ public Set<WaitingMember> pullOutMembers(long performanceId, long availableToRun
public void removeMemberInfo(String email, long performanceId) {
waitingRoom.removeMemberInfo(email, performanceId);
}

@Override
public void removeMemberInfo(Set<String> emails, long performanceId) {
waitingRoom.removeMemberInfo(emails, performanceId);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.thirdparty.ticketing.global.waitingsystem.redis.waiting;

import java.util.Optional;
import java.util.Set;

import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
Expand Down Expand Up @@ -49,4 +50,11 @@ public Optional<WaitingMember> findWaitingMember(String email, long performanceI
public void removeMemberInfo(String email, long performanceId) {
waitingRoom.delete(getWaitingRoomKey(performanceId), email);
}

public void removeMemberInfo(Set<String> emails, long performanceId) {
if (emails.isEmpty()) {
return;
}
waitingRoom.delete(getWaitingRoomKey(performanceId), emails.toArray(String[]::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.waitingsystem.running.RunningManager;
import com.thirdparty.ticketing.domain.waitingsystem.waiting.WaitingManager;
Expand All @@ -35,12 +38,14 @@ class WaitingSystemTest extends TestContainerStarter {

@Autowired private RunningManager runningManager;

private ZSetOperations<String, String> rawRunningRoom;

private SpyEventPublisher eventPublisher;

@Autowired private StringRedisTemplate redisTemplate;

@Autowired private ObjectMapper objectMapper;

private ZSetOperations<String, String> rawRunningRoom;
private HashOperations<String, String, String> rawWaitingRoom;
private ValueOperations<String, String> rawRunningCounter;

@BeforeEach
Expand All @@ -49,6 +54,7 @@ void setUp() {
waitingSystem = new WaitingSystem(waitingManager, runningManager, eventPublisher);
rawRunningRoom = redisTemplate.opsForZSet();
rawRunningCounter = redisTemplate.opsForValue();
rawWaitingRoom = redisTemplate.opsForHash();
redisTemplate.getConnectionFactory().getConnection().serverCommands().flushAll();
}

Expand All @@ -60,6 +66,10 @@ private String getRunningCounterKey(long performanceId) {
return "running_counter:" + performanceId;
}

private String getWaitingRoomKey(long performanceId) {
return "waiting_room:" + performanceId;
}

@Nested
@DisplayName("사용자의 남은 순번 조회 시")
class GetRemainingCountTest {
Expand Down Expand Up @@ -104,27 +114,67 @@ void publishPollingEvent() {
@DisplayName("대기열 사용자 작업 가능 공간 이동 호출 시")
class MoveUserToRunningTest {

private long performanceId;
private String email;
private ZonedDateTime fiveMinuteAgo;
private long score;

@BeforeEach
void setUp() throws JsonProcessingException {
performanceId = 1;
email = "[email protected]";
fiveMinuteAgo = ZonedDateTime.now().minusMinutes(5);
score = fiveMinuteAgo.toEpochSecond();

WaitingMember waitingMember = new WaitingMember(email, performanceId, 1, fiveMinuteAgo);
rawWaitingRoom.put(
getWaitingRoomKey(performanceId),
email,
objectMapper.writeValueAsString(waitingMember));
rawRunningRoom.add(getRunningRoomKey(performanceId), email, score);
}

@Test
@DisplayName("작업 공간의 작업 시간이 만료된 사용자를 제거한다.")
void removeExpiredMemberInfo() {
void removeExpiredMemberInfoFromRunningRoom() {
// given
long performanceId = 1;
String email = "[email protected]";
long score = ZonedDateTime.now().minusMinutes(5).toEpochSecond();
rawRunningRoom.add(getRunningRoomKey(performanceId), email, score);
String anotherEmail = "[email protected]";
ZonedDateTime now = ZonedDateTime.now();
rawRunningRoom.add(getRunningRoomKey(performanceId), anotherEmail, now.toEpochSecond());

// when
waitingSystem.moveUserToRunning(performanceId);

// then
assertThat(runningManager.isReadyToHandle(email, performanceId)).isFalse();
Set<String> emails = rawRunningRoom.range(getRunningRoomKey(performanceId), 0, -1);
assertThat(emails).hasSize(1).first().isEqualTo(anotherEmail);
}

@Test
@DisplayName("대기방의 시간이 만료된 사용자를 제거한다.")
void removeExpiredMemberInfoFromWaitingRoom() throws JsonProcessingException {
// given
String anotherEmail = "[email protected]";
ZonedDateTime now = ZonedDateTime.now();
WaitingMember waitingMember = new WaitingMember(anotherEmail, performanceId, 2, now);
rawRunningRoom.add(getRunningRoomKey(performanceId), anotherEmail, now.toEpochSecond());
rawWaitingRoom.put(
getWaitingRoomKey(performanceId),
anotherEmail,
objectMapper.writeValueAsString(waitingMember));

// when
waitingSystem.moveUserToRunning(performanceId);

// then
Set<String> emails = rawRunningRoom.range(getRunningRoomKey(performanceId), 0, -1);
assertThat(emails).hasSize(1).first().isEqualTo(anotherEmail);
}

@Test
@DisplayName("작업 가능 공간의 수용 가능한 인원이 감소한다.")
void decrementAvailableCount() {
// given
long performanceId = 1;
int memberCount = 25;
for (int i = 0; i < memberCount; i++) {
waitingManager.enterWaitingRoom("email" + i + "@email.com", performanceId);
Expand All @@ -143,7 +193,6 @@ void decrementAvailableCount() {
@DisplayName("더 이상 인원을 수용할 수 없으면 작업 가능 공간에 사용자를 추가하지 않는다.")
void doNotMoveUserToRunning_WhenNoMoreAvailableSpace() {
// given
long performanceId = 1;
for (int i = 0; i < 100; i++) {
waitingSystem.enterWaitingRoom("email" + i + "@email.com", performanceId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.thirdparty.ticketing.global.waitingsystem.memory.waiting;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchException;

import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

Expand Down Expand Up @@ -182,4 +184,43 @@ void ignore_WhenNotExistsWaitingRoom() {
assertThat(room.get(performanceId)).isNull();
}
}

@Nested
@DisplayName("대기방 사용자 정보 목록 제거 호출 시")
class RemoveMemberInfos {

@Test
@DisplayName("사용자 정보가 제거된다.")
void removeMemberInfo() {
// given
long performanceId = 1;
String email = "[email protected]";
String email2 = "[email protected]";
waitingRoom.enter(email, performanceId);
waitingRoom.enter(email2, performanceId);

// when
waitingRoom.removeMemberInfo(Set.of(email, email2), performanceId);

// then
assertThat(waitingRoom.findWaitingMember(email, performanceId)).isEmpty();
assertThat(waitingRoom.findWaitingMember(email2, performanceId)).isEmpty();
}

@Test
@DisplayName("사용자 정보가 대기방에 존재하지 않으면 무시한다.")
void ignore_WhenNotExistsWaitingRoom() {
// given
long performanceId = 1;
String email = "[email protected]";

// when
Exception exception =
catchException(
() -> waitingRoom.removeMemberInfo(Set.of(email), performanceId));

// then
assertThat(exception).doesNotThrowAnyException();
}
}
}
Loading
Loading