Skip to content

Commit

Permalink
Merge pull request #4 from TeamTroublePainter/feature/DRAW-375
Browse files Browse the repository at this point in the history
DRAW-375 synchronized -> Redis Lettuce 분산 락 리팩터링
  • Loading branch information
SunwoongH authored Oct 23, 2024
2 parents c757d3b + 5666112 commit 89f4bc2
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 17 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.xorker.draw.lock

import com.xorker.draw.exception.NotFoundLockKeyException
import com.xorker.draw.exception.UnSupportedException
import java.time.Duration
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component

@Component
internal class RedisLockAdapter(
private val redisTemplate: RedisTemplate<String, String>,
) : LockRepository {

override fun <R> lock(key: String, call: () -> R): R {
while (getLock(key).not()) {
try {
Thread.sleep(SLEEP_TIME)
} catch (e: InterruptedException) {
throw UnSupportedException
}
}
try {
return call.invoke()
} finally {
unlock(key)
}
}

private fun unlock(key: String) {
redisTemplate.delete(key + LOCK)
}

private fun getLock(key: String): Boolean {
return redisTemplate
.opsForValue()
.setIfAbsent(key + LOCK, LOCK, Duration.ofSeconds(LOCK_TIME)) ?: throw NotFoundLockKeyException
}

companion object {
private const val LOCK = "lock"
private const val LOCK_TIME = 1L
private const val SLEEP_TIME = 50L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fun XorkerException.getButtons(): List<ExceptionButtonType> {
AlreadyPlayingRoomException,
InvalidWebSocketStatusException,
is NotDefinedMessageCodeException,
NotFoundLockKeyException,
-> buttonOk
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.xorker.draw.exception.InvalidRequestOtherPlayingException
import com.xorker.draw.exception.InvalidRequestValueException
import com.xorker.draw.exception.MaxRoomException
import com.xorker.draw.exception.NotFoundRoomException
import com.xorker.draw.lock.LockRepository
import com.xorker.draw.room.Room
import com.xorker.draw.room.RoomId
import com.xorker.draw.room.RoomRepository
Expand All @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service
internal class MafiaGameRoomService(
private val mafiaGameRepository: MafiaGameRepository,
private val roomRepository: RoomRepository,
private val lockRepository: LockRepository,
private val mafiaGameInfoEventProducer: MafiaGameInfoEventProducer,
) : UserConnectionUseCase {

Expand All @@ -33,7 +35,7 @@ internal class MafiaGameRoomService(

val gameInfo = mafiaGameRepository.getGameInfo(roomId) ?: throw NotFoundRoomException

synchronized(gameInfo) {
lockRepository.lock(roomId.value) {
if (gameInfo.phase != MafiaPhase.Wait && gameInfo.room.players.any { it.userId == user.id }.not()) {
throw AlreadyPlayingRoomException
}
Expand Down Expand Up @@ -112,13 +114,15 @@ internal class MafiaGameRoomService(
gameInfo.room.remove(player)

if (gameInfo.room.players.isEmpty()) {
mafiaGameRepository.removePlayer(user.id)
mafiaGameRepository.removeGameInfo(gameInfo)
return
}

if (gameInfo.room.owner == player) {
gameInfo.room.owner = gameInfo.room.players.first()
}

mafiaGameRepository.removePlayer(player.userId)
mafiaGameRepository.saveGameInfo(gameInfo)
mafiaGameInfoEventProducer.disconnectUser(gameInfo)
Expand Down
10 changes: 8 additions & 2 deletions core/src/main/kotlin/com/xorker/draw/mafia/MafiaGameService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.xorker.draw.mafia
import com.xorker.draw.event.mafia.MafiaGameInfoEventProducer
import com.xorker.draw.exception.InvalidRequestOnlyMyTurnException
import com.xorker.draw.exception.InvalidRequestValueException
import com.xorker.draw.lock.LockRepository
import com.xorker.draw.mafia.dto.DrawRequest
import com.xorker.draw.mafia.phase.MafiaPhaseInferAnswerProcessor
import com.xorker.draw.mafia.phase.MafiaPhasePlayGameProcessor
Expand All @@ -23,6 +24,7 @@ internal class MafiaGameService(
private val mafiaPhaseInferAnswerProcessor: MafiaPhaseInferAnswerProcessor,
private val mafiaGameRepository: MafiaGameRepository,
private val timerRepository: TimerRepository,
private val lockRepository: LockRepository,
private val mafiaGameInfoEventProducer: MafiaGameInfoEventProducer,
) : MafiaGameUseCase {

Expand Down Expand Up @@ -73,6 +75,7 @@ internal class MafiaGameService(

vote(phase.players, user, targetUserId)

mafiaGameRepository.saveGameInfo(gameInfo)
mafiaGameInfoEventProducer.vote(gameInfo)
}

Expand All @@ -86,6 +89,7 @@ internal class MafiaGameService(

phase.answer = answer

mafiaGameRepository.saveGameInfo(gameInfo)
mafiaGameInfoEventProducer.answer(gameInfo, answer)
}

Expand All @@ -101,6 +105,8 @@ internal class MafiaGameService(

timerRepository.cancelTimer(room.id)

mafiaGameRepository.saveGameInfo(gameInfo)

mafiaPhaseInferAnswerProcessor.processInferAnswer(gameInfo) {
mafiaPhaseService.endGame(gameInfo.room.id)
}
Expand All @@ -122,9 +128,9 @@ internal class MafiaGameService(
voter: User,
targetUserId: UserId,
) {
synchronized(voter) {
val voterUserId = voter.id
val voterUserId = voter.id

lockRepository.lock(voterUserId.value.toString()) {
players.forEach { player ->
val userIds = player.value

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.xorker.draw.mafia.phase
import com.xorker.draw.event.mafia.MafiaGameInfoEventProducer
import com.xorker.draw.exception.InvalidRequestValueException
import com.xorker.draw.exception.NotFoundRoomException
import com.xorker.draw.lock.LockRepository
import com.xorker.draw.mafia.MafiaGameInfo
import com.xorker.draw.mafia.MafiaGameRepository
import com.xorker.draw.mafia.MafiaPhase
Expand All @@ -21,6 +22,7 @@ internal class MafiaPhaseService(
private val mafiaPhaseInferAnswerProcessor: MafiaPhaseInferAnswerProcessor,
private val mafiaPhaseEndGameProcessor: MafiaPhaseEndGameProcessor,
private val mafiaGameInfoEventProducer: MafiaGameInfoEventProducer,
private val lockRepository: LockRepository,
) : MafiaPhaseUseCase {

override fun startGame(user: User): MafiaPhase.Ready {
Expand All @@ -35,11 +37,11 @@ internal class MafiaPhaseService(

private fun startGame(gameInfo: MafiaGameInfo): MafiaPhase.Ready {
val roomId = gameInfo.room.id
val phase = synchronized(gameInfo) {

val phase = lockRepository.lock(roomId.value) {
assertIs<MafiaPhase.Wait>(gameInfo.phase)
mafiaPhaseStartGameProcessor.startMafiaGame(gameInfo) {
playGame(roomId)
}

mafiaPhaseStartGameProcessor.startMafiaGame(gameInfo) { playGame(roomId) }
}

mafiaGameInfoEventProducer.changePhase(gameInfo)
Expand All @@ -50,12 +52,11 @@ internal class MafiaPhaseService(
override fun playGame(roomId: RoomId): MafiaPhase.Playing {
val gameInfo = getGameInfo(roomId)

val phase = synchronized(gameInfo) {
val phase = lockRepository.lock(roomId.value) {
val readyPhase = gameInfo.phase
assertIs<MafiaPhase.Ready>(readyPhase)
mafiaPhasePlayGameProcessor.playMafiaGame(gameInfo) {
vote(roomId)
}

mafiaPhasePlayGameProcessor.playMafiaGame(gameInfo) { vote(roomId) }
}

mafiaGameInfoEventProducer.changePhase(gameInfo)
Expand All @@ -66,9 +67,10 @@ internal class MafiaPhaseService(
override fun vote(roomId: RoomId): MafiaPhase.Vote {
val gameInfo = getGameInfo(roomId)

val phase = synchronized(gameInfo) {
val phase = lockRepository.lock(roomId.value) {
val playingPhase = gameInfo.phase
assertIs<MafiaPhase.Playing>(playingPhase)

mafiaPhasePlayVoteProcessor.playVote(
gameInfo,
{
Expand All @@ -88,12 +90,11 @@ internal class MafiaPhaseService(
override fun interAnswer(roomId: RoomId): MafiaPhase.InferAnswer {
val gameInfo = getGameInfo(roomId)

val phase = synchronized(gameInfo) {
val phase = lockRepository.lock(roomId.value) {
val votePhase = gameInfo.phase
assertIs<MafiaPhase.Vote>(votePhase)
mafiaPhaseInferAnswerProcessor.playInferAnswer(gameInfo) {
endGame(roomId)
}

mafiaPhaseInferAnswerProcessor.playInferAnswer(gameInfo) { endGame(roomId) }
}

mafiaGameInfoEventProducer.changePhase(gameInfo)
Expand All @@ -104,9 +105,10 @@ internal class MafiaPhaseService(
override fun endGame(roomId: RoomId): MafiaPhase.End {
val gameInfo = getGameInfo(roomId)

val phase = synchronized(gameInfo) {
val phase = lockRepository.lock(roomId.value) {
val votePhase = gameInfo.phase
assert<MafiaPhase.Vote, MafiaPhase.InferAnswer>(votePhase)

mafiaPhaseEndGameProcessor.endGame(gameInfo)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ sealed class ServerException(code: String, message: String, cause: Throwable? =

data object NotFoundUserException : ServerException("s001", "유저가 존재하지 않음") { private fun readResolve(): Any = NotFoundUserException }
data object NotFoundWordException : ServerException("s002", "단어가 존재하지 않음") { private fun readResolve(): Any = NotFoundWordException }
data object NotFoundLockKeyException : ServerException("s003", "락 키가 존재하지 않음") { private fun readResolve(): Any = NotFoundLockKeyException }
//endregion

//region Critical
Expand Down
5 changes: 5 additions & 0 deletions domain/src/main/kotlin/com/xorker/draw/lock/LockRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.xorker.draw.lock

interface LockRepository {
fun <R> lock(key: String, call: () -> R): R
}

0 comments on commit 89f4bc2

Please sign in to comment.