Skip to content

Commit

Permalink
Fix concurrency transactional
Browse files Browse the repository at this point in the history
  • Loading branch information
bifrurcated committed Jan 15, 2024
1 parent 9a611d2 commit 30639b1
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
<version>1.18.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.11</version>
</dependency>
</dependencies>

<build>
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/com/bifurcated/wallet/service/WalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
import com.bifurcated.wallet.errors.NotEnoughMoneyError;
import com.bifurcated.wallet.errors.WalletNotFoundError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.sql.SQLException;
import java.util.UUID;


Expand All @@ -20,11 +25,15 @@ public WalletService(WalletRepo walletRepo) {
this.walletRepo = walletRepo;
}

@Transactional
@Transactional(
isolation = Isolation.SERIALIZABLE,
propagation = Propagation.REQUIRES_NEW)
@Retryable(retryFor = SQLException.class, maxAttempts = 17, backoff = @Backoff(delay = 500))
public Wallet addAmount(UUID id, Float amount) {
var wallet = walletRepo.findById(id).orElseThrow(WalletNotFoundError::new);
wallet.setAmount(wallet.getAmount() + amount);
return walletRepo.save(wallet);
Wallet save = walletRepo.save(wallet);
return save;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Expand All @@ -26,6 +35,7 @@
@AutoConfigureMockMvc
@ActiveProfiles("test")
class WalletControllerTest {
private static Logger logger = LoggerFactory.getLogger(WalletControllerTest.class);
private static final String END_POINT_PATH = "/api/v1";
@Autowired
private MockMvc mockMvc;
Expand All @@ -34,7 +44,6 @@ class WalletControllerTest {
@Autowired
private WalletRepo repository;


@BeforeEach
public void setup() {
repository.deleteAll();
Expand Down Expand Up @@ -155,4 +164,39 @@ public void testWalletGetAmountNotFound() throws Exception {
.andDo(print())
.andExpect(status().isNotFound());
}

@Test
public void testWalletDepositDDOSAttack() throws Exception {
record WalletRequest(UUID valletId, String operationType, Float amount){}
record BalanceResponse(Float amount){}
UUID id = UUID.fromString("b3919077-79e6-4570-bfe0-980ef18f3731");
WalletRequest walletRequest = new WalletRequest(
id, "DEPOSIT", 1000F);

String requestBody = objectMapper.writeValueAsString(walletRequest);

ExecutorService executorService = Executors.newFixedThreadPool(10000);
List<Future<ResultActions>> futures = new ArrayList<>();
for (int i = 0; i < 300; i++) {
Callable<ResultActions> callable = () -> {
ResultActions perform = this.mockMvc.perform(post(END_POINT_PATH + "/wallet")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody));
return perform.andExpect(status().isOk());
};

Future<ResultActions> future = executorService.submit(callable);
futures.add(future);
}
for (Future<ResultActions> future : futures) {
future.get();
}

BalanceResponse balanceResponse = new BalanceResponse(302000F);
String response = objectMapper.writeValueAsString(balanceResponse);
this.mockMvc.perform(get(END_POINT_PATH+"/wallets/"+id))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json(response));
}
}

0 comments on commit 30639b1

Please sign in to comment.