Skip to content

Commit

Permalink
Pessimistic Read lock scenarios (#1123)
Browse files Browse the repository at this point in the history
* ChennuruAkhil#8 Added test case for pessimistic read lock scenario

* ChennuruAkhil#8 Added update scenario during pessimistic read lock

* Fixed Sonar issues

* Fixed codacy checks
  • Loading branch information
ChennuruAkhil authored Feb 26, 2024
1 parent 3390b0f commit f09b6ad
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 8 deletions.
2 changes: 1 addition & 1 deletion jpa/boot-jpa-locks/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM eclipse-temurin:17.0.10_7-jre-focal as builder
FROM eclipse-temurin:17.0.10_7-jre-focal AS builder
WORKDIR application
ARG JAR_FILE=target/boot-jpa-locks-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} application.jar
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.example.locks.repositories;

import com.example.locks.entities.Actor;
import jakarta.persistence.LockModeType;

public interface CustomizedActorRepository {

long getLockTimeout();

void setLockTimeout(long timeoutDurationInMs);

Actor getActorAndObtainPessimisticWriteLockingOnItById(Long id);
Actor getActorAndObtainPessimisticLockingOnItById(Long id, LockModeType lockModeType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ public void setLockTimeout(long timeoutDurationInMs) {
}

@Override
public Actor getActorAndObtainPessimisticWriteLockingOnItById(Long id) {
public Actor getActorAndObtainPessimisticLockingOnItById(Long id, LockModeType lockModeType) {
log.info("Trying to obtain pessimistic lock ...");

Query query = em.createQuery("select actor from Actor actor where actor.id = :id");
query.setParameter("id", id);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.setLockMode(lockModeType);
query = setLockTimeoutIfRequired(query);
Actor actor = (Actor) query.getSingleResult();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.example.locks.model.response.ActorResponse;
import com.example.locks.model.response.PagedResult;
import com.example.locks.repositories.ActorRepository;
import jakarta.persistence.LockModeType;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
Expand All @@ -19,7 +20,6 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
Expand Down Expand Up @@ -89,14 +89,12 @@ public Actor updateActorWithLock(Long id, String name) {
log.info("Received exception for request {}", name);
log.error("Found pessimistic lock exception!", e);
sleepForAWhile();
// updateActorWithLock(id, name);
}
return null;
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Actor obtainPessimisticLockAndUpdate(Long id, String name) {
Actor actor = actorRepository.getActorAndObtainPessimisticWriteLockingOnItById(id);
Actor actor = actorRepository.getActorAndObtainPessimisticLockingOnItById(id, LockModeType.PESSIMISTIC_WRITE);
actor.setActorName(name);
return actorRepository.save(actor);
}
Expand All @@ -108,4 +106,9 @@ private void sleepForAWhile() {
Thread.currentThread().interrupt();
}
}

@Transactional
public Actor getActorWithPessimisticReadLock(Long id) {
return actorRepository.getActorAndObtainPessimisticLockingOnItById(id, LockModeType.PESSIMISTIC_READ);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.IntStream;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

Expand All @@ -25,6 +28,11 @@ class ActorServiceIntTest extends AbstractIntegrationTest {
@Autowired
private ActorRepository actorRepository;

@BeforeEach
void setUp() {
actorRepository.deleteAllInBatch();
}

@Test
void testPessimisticWriteLock() {

Expand Down Expand Up @@ -66,4 +74,53 @@ void testPessimisticWriteLock() {
assertThat(updatedActor.getVersion()).isEqualTo((short) 2);
});
}

@Test
void testPessimisticReadLock() throws ExecutionException, InterruptedException {
ActorResponse actorResponse = actorService.saveActor(new ActorRequest("Actor", null, "Indian"));

Optional<Actor> optionalActor = actorRepository.findById(actorResponse.actorId());
assertThat(optionalActor).isPresent();
Actor actor = optionalActor.get();
assertThat(actor.getActorName()).isEqualTo("Actor");
assertThat(actor.getVersion()).isEqualTo((short) 0);
// Obtaining a pessimistic read lock concurrently by two requests on the same record
List<CompletableFuture<Actor>> completableFutureList = IntStream.range(0, 2)
.boxed()
.map(actorName -> CompletableFuture.supplyAsync(() -> {
var readLockActor = new Actor();
try {
readLockActor = actorService.getActorWithPessimisticReadLock(actorResponse.actorId());
} catch (Exception e) {
log.error("exception occurred", e);
}
return readLockActor;
}))
.toList();

CompletableFuture.allOf(completableFutureList.toArray(CompletableFuture[]::new))
.join();
// As pessimistic read lock is a shared lock it will give read access to every request
assertThat(completableFutureList.get(0).get().getActorName()).isEqualTo("Actor");
assertThat(completableFutureList.get(1).get().getActorName()).isEqualTo("Actor");
}

@Test
void testUpdatePessimisticReadLock() {
ActorResponse actorResponse = actorService.saveActor(new ActorRequest("Actor", null, "Indian"));

Optional<Actor> optionalActor = actorRepository.findById(actorResponse.actorId());
assertThat(optionalActor).isPresent();
Actor actor = optionalActor.get();
assertThat(actor.getActorName()).isEqualTo("Actor");
assertThat(actor.getVersion()).isEqualTo((short) 0);
// Obtaining a pessimistic read lock and holding lock for 5 sec
CompletableFuture.runAsync(() -> actorService.getActorWithPessimisticReadLock(actor.getActorId()));
// As pessimistic read lock obtained on the record update can't be performed until the lock is released
await().atMost(Duration.ofSeconds(10)).pollDelay(Duration.ofSeconds(1)).untilAsserted(() -> {
ActorResponse updatedActor =
actorService.updateActor(actor.getActorId(), new ActorRequest("updateActor", null, "Indian"));
assertThat(updatedActor.actorName()).isEqualTo("updateActor");
});
}
}

0 comments on commit f09b6ad

Please sign in to comment.