fix(personal): keep reservation validation side effect free
This commit is contained in:
+8
-8
@@ -38,6 +38,14 @@ public class PersonalSpaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures and locks the current owner's space, then validates that one future item of the given size can fit.
|
||||||
|
* This check intentionally does not mutate usage counters. The future ingestion application service must call
|
||||||
|
* it inside its outer transaction and perform the item insert plus counter mutation in that same transaction so
|
||||||
|
* the row lock remains effective. This method must not use an independent transaction.
|
||||||
|
*
|
||||||
|
* @return the locked personal space id
|
||||||
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public long reserve(PersonalOwner owner, long bytes) {
|
public long reserve(PersonalOwner owner, long bytes) {
|
||||||
if (bytes < 0 || properties.getMaxItems() <= 0 || properties.getMaxSpaceMb() <= 0) {
|
if (bytes < 0 || properties.getMaxItems() <= 0 || properties.getMaxSpaceMb() <= 0) {
|
||||||
@@ -54,14 +62,6 @@ public class PersonalSpaceService {
|
|||||||
throw new ServiceException(QUOTA_EXCEEDED);
|
throw new ServiceException(QUOTA_EXCEEDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = jdbcTemplate.update("""
|
|
||||||
update aihr_personal_space
|
|
||||||
set used_bytes = used_bytes + ?, item_count = item_count + 1
|
|
||||||
where id = ? and tenant_id = ? and owner_user_id = ?
|
|
||||||
""", bytes, spaceId, owner.tenantId(), owner.userId());
|
|
||||||
if (updated != 1) {
|
|
||||||
throw new ServiceException(QUOTA_EXCEEDED);
|
|
||||||
}
|
|
||||||
return spaceId;
|
return spaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-11
@@ -76,12 +76,10 @@ class PersonalSpaceServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reserveUpsertsOwnerSpaceBeforeLockAndAtomicallyConsumesCapacity() {
|
void reserveUpsertsOwnerSpaceBeforeLockAndReturnsWithoutMutatingCounters() {
|
||||||
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L)))
|
when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L)))
|
||||||
.thenReturn(space(7L, 500L * 1024 * 1024, 0L, 0));
|
.thenReturn(space(7L, 500L * 1024 * 1024, 0L, 0));
|
||||||
when(jdbc.update(contains("set used_bytes = used_bytes + ?"),
|
|
||||||
eq(1024L), eq(7L), eq("000000"), eq(101L))).thenReturn(1);
|
|
||||||
PersonalSpaceService service = new PersonalSpaceService(jdbc, properties());
|
PersonalSpaceService service = new PersonalSpaceService(jdbc, properties());
|
||||||
|
|
||||||
assertEquals(7L, service.reserve(new PersonalOwner("000000", 101L, "ext-101"), 1024L));
|
assertEquals(7L, service.reserve(new PersonalOwner("000000", 101L, "ext-101"), 1024L));
|
||||||
@@ -90,25 +88,22 @@ class PersonalSpaceServiceTest {
|
|||||||
order.verify(jdbc).update(contains("on duplicate key update"),
|
order.verify(jdbc).update(contains("on duplicate key update"),
|
||||||
eq("000000"), eq(101L), eq("ext-101"), eq(500L * 1024 * 1024));
|
eq("000000"), eq(101L), eq("ext-101"), eq(500L * 1024 * 1024));
|
||||||
order.verify(jdbc).queryForMap(contains("for update"), eq("000000"), eq(101L));
|
order.verify(jdbc).queryForMap(contains("for update"), eq("000000"), eq(101L));
|
||||||
order.verify(jdbc).update(
|
|
||||||
contains("where id = ? and tenant_id = ? and owner_user_id = ?"),
|
|
||||||
eq(1024L), eq(7L), eq("000000"), eq(101L));
|
|
||||||
verifyNoMoreInteractions(jdbc);
|
verifyNoMoreInteractions(jdbc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reserveZeroBytesStillConsumesOneItemSlot() {
|
void reserveZeroBytesIsValidWithoutMutatingCounters() {
|
||||||
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L)))
|
when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L)))
|
||||||
.thenReturn(space(7L, 1024L, 500L, 3));
|
.thenReturn(space(7L, 1024L, 500L, 3));
|
||||||
when(jdbc.update(contains("set used_bytes = used_bytes + ?"),
|
|
||||||
eq(0L), eq(7L), eq("000000"), eq(101L))).thenReturn(1);
|
|
||||||
PersonalSpaceService service = new PersonalSpaceService(jdbc, properties());
|
PersonalSpaceService service = new PersonalSpaceService(jdbc, properties());
|
||||||
|
|
||||||
assertEquals(7L, service.reserve(new PersonalOwner("000000", 101L, null), 0L));
|
assertEquals(7L, service.reserve(new PersonalOwner("000000", 101L, null), 0L));
|
||||||
|
|
||||||
verify(jdbc).update(contains("item_count = item_count + 1"),
|
verify(jdbc).update(contains("on duplicate key update"),
|
||||||
eq(0L), eq(7L), eq("000000"), eq(101L));
|
eq("000000"), eq(101L), eq(null), eq(500L * 1024 * 1024));
|
||||||
|
verify(jdbc).queryForMap(contains("for update"), eq("000000"), eq(101L));
|
||||||
|
verifyNoMoreInteractions(jdbc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user