mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-20 00:11:21 +00:00
fix(publish): flush concurrent coordinate writes
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
d224c5a8ba
commit
182f7bacef
2 changed files with 14 additions and 5 deletions
|
|
@ -424,7 +424,11 @@ public class SkillPublishService {
|
|||
Skill newSkill = new Skill(namespace.getId(), skillSlug, publisherId, visibility);
|
||||
newSkill.setCreatedBy(publisherId);
|
||||
try {
|
||||
return skillRepository.save(newSkill);
|
||||
Skill savedSkill = skillRepository.save(newSkill);
|
||||
// save() may defer the unique-constraint check until transaction commit.
|
||||
// Flush here so this boundary can translate the coordinate race.
|
||||
skillRepository.flush();
|
||||
return savedSkill;
|
||||
} catch (DataIntegrityViolationException ex) {
|
||||
// A concurrent publish for the same (namespace, slug, owner) coordinate inserted
|
||||
// the skill first and won the unique-constraint race. Surface a deterministic
|
||||
|
|
@ -486,6 +490,8 @@ public class SkillPublishService {
|
|||
|
||||
try {
|
||||
version = skillVersionRepository.save(version);
|
||||
// Detect the version-coordinate race before object storage writes begin.
|
||||
skillVersionRepository.flush();
|
||||
} catch (DataIntegrityViolationException ex) {
|
||||
// A concurrent publish inserted the same (skillId, version) coordinate first and won the
|
||||
// unique-constraint race. Surface a deterministic business conflict rather than an HTTP 500.
|
||||
|
|
|
|||
|
|
@ -476,7 +476,7 @@ class SkillPublishServiceTest {
|
|||
verify(reviewTaskRepository).deleteBySkillVersionIdIn(List.of(8L));
|
||||
verify(skillFileRepository).deleteByVersionId(8L);
|
||||
verify(skillVersionRepository).delete(rejectedVersion);
|
||||
verify(skillVersionRepository).flush();
|
||||
verify(skillVersionRepository, times(2)).flush();
|
||||
verify(objectStorageService).deleteObjects(List.of("skills/1/8/SKILL.md", "packages/1/8/bundle.zip"));
|
||||
|
||||
ArgumentCaptor<ReviewTask> reviewTaskCaptor = ArgumentCaptor.forClass(ReviewTask.class);
|
||||
|
|
@ -1779,13 +1779,15 @@ class SkillPublishServiceTest {
|
|||
when(skillRepository.findByNamespaceIdAndSlugAndOwnerId(any(), eq("test-skill"), eq(publisherId)))
|
||||
.thenReturn(Optional.empty());
|
||||
// A concurrent publish inserts the same (namespace, slug, owner) coordinate first and wins the race.
|
||||
when(skillRepository.save(any())).thenThrow(new DataIntegrityViolationException("duplicate key"));
|
||||
when(skillRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
doThrow(new DataIntegrityViolationException("duplicate key")).when(skillRepository).flush();
|
||||
|
||||
DomainBadRequestException exception = assertThrows(DomainBadRequestException.class, () -> service.publishFromEntries(
|
||||
namespaceSlug, entries, publisherId, SkillVisibility.PUBLIC, Set.of()));
|
||||
|
||||
assertEquals("error.skill.publish.concurrentConflict", exception.messageCode());
|
||||
assertEquals("test-skill", String.valueOf(exception.messageArgs()[0]));
|
||||
verify(skillRepository).flush();
|
||||
verify(skillVersionRepository, never()).save(any(SkillVersion.class));
|
||||
}
|
||||
|
||||
|
|
@ -1816,13 +1818,14 @@ class SkillPublishServiceTest {
|
|||
.thenReturn(Optional.of(skill));
|
||||
when(skillVersionRepository.findBySkillIdAndVersion(any(), eq("1.0.0"))).thenReturn(Optional.empty());
|
||||
// A concurrent publish inserts the same (skillId, version) coordinate first and wins the race.
|
||||
when(skillVersionRepository.save(any(SkillVersion.class)))
|
||||
.thenThrow(new DataIntegrityViolationException("duplicate key"));
|
||||
when(skillVersionRepository.save(any(SkillVersion.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
doThrow(new DataIntegrityViolationException("duplicate key")).when(skillVersionRepository).flush();
|
||||
|
||||
DomainBadRequestException exception = assertThrows(DomainBadRequestException.class, () -> service.publishFromEntries(
|
||||
namespaceSlug, entries, publisherId, SkillVisibility.PUBLIC, Set.of()));
|
||||
|
||||
assertEquals("error.skill.publish.concurrentConflict", exception.messageCode());
|
||||
verify(skillVersionRepository).flush();
|
||||
// Nothing should be uploaded to object storage once the coordinate race is lost.
|
||||
verify(objectStorageService, never()).putObject(anyString(), any(), anyLong(), anyString());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue