Skip to content

fix(reliability): reconcile artifact lifecycle lock on current main - #439

Open
seonghobae wants to merge 7 commits into
mainfrom
fix/lifecycle-lock-current-main-20260813
Open

fix(reliability): reconcile artifact lifecycle lock on current main#439
seonghobae wants to merge 7 commits into
mainfrom
fix/lifecycle-lock-current-main-20260813

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Objective

Replace stale #350 with the same bounded process-local artifact lifecycle serialization primitive directly on current protected main, and remove the split-lock-domain defect discovered on the replacement head.

Exact current-base identity

  • protected base at reconstruction: 961f44a19c4ced87f8062b3ab9842a9a08d05235;
  • initial replacement head: d5069a3212007166fc40cf8ecfb4e59bfe5e0a42;
  • exact corrected head: d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed;
  • changed paths remain limited to ArtifactLifecycleLockRegistry.java and its focused test.

Test-first repair evidence

The initial replacement's exact-head CI failed one of 511 tests: constructionAndNullInputsFailClosed expected no public constructor but the class exposed a public constructor and Spring @Component. That allowed dependency injection and standalone callers to construct disjoint registries, defeating same-job mutual exclusion across those call paths.

The corrected implementation now has one private construction path, one process-wide fixed-memory registry, no Spring component registration, 256 lock stripes, fail-closed null handling, and unconditional lock release after action failure.

Exact-head gates

On d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed:

  • CI: success;
  • Security Scan: success;
  • SAST Semgrep: success;
  • fuzz: success;
  • mergeable: true;
  • unresolved review threads: zero.

Scope boundary

This protects one JVM only. It is not durable or distributed fencing and does not replace object-version preconditions, transactional outbox semantics, lease generations, or cross-instance deletion coordination.

Merge gate

Keep the exact head unchanged. Require a fresh current-head automated review, qualifying independent non-author formal approval, all live branch-protection rules, and zero valid unresolved findings. Auto-merge may act only after those conditions are satisfied; predecessor evidence does not transfer.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2d6ca1e-2a3e-4f9f-8d35-a03fcc7329e5

📥 Commits

Reviewing files that changed from the base of the PR and between 961f44a and d7e3e6e.

📒 Files selected for processing (2)
  • src/main/java/com/clearfolio/viewer/lifecycle/ArtifactLifecycleLockRegistry.java
  • src/test/java/com/clearfolio/viewer/lifecycle/ArtifactLifecycleLockRegistryTest.java

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as ready for review August 14, 2026 08:04
@seonghobae
seonghobae enabled auto-merge (squash) August 14, 2026 08:04

Copy link
Copy Markdown
Collaborator Author

Exact-current-head review request for d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed on protected main 961f44a19c4ced87f8062b3ab9842a9a08d05235. The predecessor exact head failed its one-registry construction contract; the corrected head removes Spring component registration and public construction so all callers share one fixed-memory lock domain. CI, Security Scan, SAST Semgrep, and fuzz are terminal-success; the PR is mergeable and has zero unresolved threads. Review read-only; do not mutate the branch or bypass independent approval.

@opencode-agent review
@cwl-noema-review review
@strix review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Repository identity =='
git rev-parse HEAD
git merge-base HEAD 961f44a19c4ced87f8062b3ab9842a9a08d05235
git diff --name-status 961f44a19c4ced87f8062b3ab9842a9a08d05235...HEAD

echo '== Changed Java files =='
fd -t f 'ArtifactLifecycleLockRegistry.*\.java$' .

echo '== AST outlines =='
while IFS= read -r file; do
  echo "--- $file"
  ast-grep outline "$file" --items all || true
done < <(fd -t f 'ArtifactLifecycleLockRegistry.*\.java$' .)

echo '== Exact diff =='
git diff --find-renames --find-copies --unified=80 \
  961f44a19c4ced87f8062b3ab9842a9a08d05235...HEAD -- \
  '*ArtifactLifecycleLockRegistry*.java'

echo '== Registry references =='
rg -n -C 4 --glob '*.java' \
  '\bArtifactLifecycleLockRegistry\b|\bwithLock\s*\(|\bexecute\s*\(' . || true

Length of output: 1649


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='961f44a19c4ced87f8062b3ab9842a9a08d05235'
head='d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed'
main_file='src/main/java/com/clearfolio/viewer/lifecycle/ArtifactLifecycleLockRegistry.java'
test_file='src/test/java/com/clearfolio/viewer/lifecycle/ArtifactLifecycleLockRegistryTest.java'

echo '== Production outline =='
ast-grep outline "$main_file" --items all

echo '== Test outline =='
ast-grep outline "$test_file" --items all

echo '== Exact production and test diff =='
git diff --unified=100 "$base...$head" -- "$main_file" "$test_file"

echo '== Production file =='
cat -n "$main_file"

echo '== Focused test file =='
cat -n "$test_file"

echo '== Non-test registry references =='
rg -n -C 6 --glob '*.java' --glob '!src/test/**' \
  '\bArtifactLifecycleLockRegistry\b' src

echo '== Registry method call sites =='
rg -n -C 4 --glob '*.java' \
  '\.(withLock|execute|runWithLock|lock)\s*\(' src || true

Length of output: 26314


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Lifecycle and artifact-related Java files =='
fd -t f -e java . src/main | rg -i '(artifact|lifecycle|conversion|document|delete|cleanup|storage)'

echo '== Candidate operation declarations and calls =='
rg -n -i -C 5 --glob '*.java' --glob '!src/test/**' \
  '\b(delete|remove|cleanup|convert|conversion|upload|write|store|save)\w*\s*\(' src/main

echo '== Artifact byte and object-storage references =='
rg -n -i -C 5 --glob '*.java' --glob '!src/test/**' \
  '(artifact|object.?storage|blob|s3|bucket|fileSystem|filesystem|deleteObject|putObject)' src/main

echo '== Existing synchronization and lifecycle lock mechanisms =='
rg -n -C 5 --glob '*.java' --glob '!src/test/**' \
  '(synchronized\b|ReentrantLock|ReadWriteLock|Semaphore|Lock\b|mutex|lifecycle)' src/main

Length of output: 201344


🏁 Script executed:

#!/bin/bash
set -euo pipefail

service='src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java'
worker='src/main/java/com/clearfolio/viewer/service/DefaultConversionWorker.java'
store='src/main/java/com/clearfolio/viewer/artifact/ArtifactStore.java'

echo '== Service outline =='
ast-grep outline "$service" --items all

echo '== Worker outline =='
ast-grep outline "$worker" --items all

echo '== Artifact store outline =='
ast-grep outline "$store" --items all

echo '== Service artifact deletion and storage contexts =='
rg -n -C 18 \
  '(deletePdf|artifactStore\.(putPdf|deletePdf)|\bdelete\s*\(|\bremove\s*\()' "$service"

echo '== Worker artifact write contexts =='
rg -n -C 18 \
  '(artifactStore\.(putPdf|deletePdf)|\bprocess\s*\(|\bconvert\w*\s*\()' "$worker"

echo '== All main-source withJobLock invocations =='
rg -n -C 4 --glob '*.java' \
  '\bwithJobLock\s*\(' src/main

Length of output: 13018


@seonghobae I found one blocking issue.

ArtifactLifecycleLockRegistry has no production caller. rg found no reference outside ArtifactLifecycleLockRegistry.java and no withJobLock(...) invocation in src/main.

DefaultDocumentConversionService.deleteJob(UUID) deletes artifact bytes at Line 240. DefaultDocumentConversionService.seedPdfPassthroughArtifact(...) writes bytes at Line 291. DefaultConversionWorker.performDefaultConversion(UUID) writes bytes at Line 281. None of these operations run inside the shared registry.

As a result, conversion and deletion can still overlap for the same job. The new lock registry is correct in isolation, but it has no runtime effect. Wrap each relevant artifact lifecycle mutation with ArtifactLifecycleLockRegistry.shared().withJobLock(jobId, ...). Add an integration test that holds conversion for one job and verifies that deletion for that job cannot enter until conversion releases the lock.

The exact head and the two-file scope match the requested base and head. The singleton construction, fixed 256-stripe storage, null handling, and finally unlock behavior are also correct.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Collaborator Author

@opencode-agent review
@coderabbitai review
@cwl-noema-review review
@strix review

Review exact head d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed only. Exact-head CI, Security Scan, SAST Semgrep, and fuzz are terminal-success; unresolved inline findings are zero. Verify the single process-wide striped lock domain, private construction boundary, fail-closed null handling, and unconditional release, while preserving the explicit non-distributed scope.

Review read-only; preserve independent reviewer identity. Do not mutate the branch, synthesize author approval, weaken protection, or merge.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='961f44a19c4ced87f8062b3ab9842a9a08d05235'
head='d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed'
main_file='src/main/java/com/clearfolio/viewer/lifecycle/ArtifactLifecycleLockRegistry.java'
test_file='src/test/java/com/clearfolio/viewer/lifecycle/ArtifactLifecycleLockRegistryTest.java'

echo '== Exact commit identity =='
git rev-parse HEAD
git cat-file -e "${head}^{commit}"
git merge-base "$head" "$base"
git diff --name-status "$base...$head"

echo '== Changed-source outlines =='
ast-grep outline "$main_file" --items all
ast-grep outline "$test_file" --items all

echo '== Exact diff =='
git diff --unified=100 "$base...$head" -- "$main_file" "$test_file"

echo '== Production implementation =='
git show "${head}:${main_file}" | cat -n

echo '== Focused tests =='
git show "${head}:${test_file}" | cat -n

echo '== Production registry references and lock invocations =='
git grep -n -C 5 -E '\bArtifactLifecycleLockRegistry\b|\bwithJobLock[[:space:]]*\(' "$head" -- 'src/main/**/*.java' || true

echo '== Lifecycle mutation candidates =='
git grep -n -C 8 -E '\b(deletePdf|putPdf|deleteJob|performDefaultConversion|seedPdfPassthroughArtifact)[[:space:]]*\(' \
  "$head" -- 'src/main/**/*.java' || true

Length of output: 68208


@seonghobae I found one blocking issue on exact head d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed.

ArtifactLifecycleLockRegistry has no production caller. No source under src/main references the class or invokes withJobLock(...).

Artifact lifecycle mutations remain outside the lock domain:

  • DefaultConversionWorker.performDefaultConversion(UUID) writes artifacts at Line 281.
  • DefaultDocumentConversionService.seedPdfPassthroughArtifact(ConversionJob, MultipartFile) writes artifacts at Line 291.
  • DefaultDocumentConversionService.deleteJob(UUID) deletes artifacts at Line 240.

Therefore, conversion and deletion can still overlap for one job. The new registry has no production effect.

The registry implementation itself meets the requested isolated checks:

  • The constructor is private.
  • shared() returns one static process-wide registry.
  • The registry uses 256 fixed lock stripes.
  • withJobLock(...) rejects null inputs before lock selection.
  • The finally block releases the lock after action failure.
  • The documentation preserves the one-JVM, non-distributed scope.

Wrap the relevant lifecycle operations with ArtifactLifecycleLockRegistry.shared().withJobLock(jobId, ...). Add an integration test that blocks conversion for one job and verifies that deletion cannot enter until conversion releases the lock.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Collaborator Author

Exact-current-head independent review request for d7e3e6e7b54bdc70a05f18ff09338b9ab54aaeed.

The two-file process-local lifecycle-lock slice is mergeable; CI, Security Scan, SAST Semgrep, and fuzz are terminal-success; unresolved inline findings are zero. The implementation exposes one private construction path and one process-wide fixed-memory registry, preventing disjoint lock domains while guaranteeing release after action failure. Protected main has advanced since construction, so any base update must regenerate all exact-head evidence.

@opencode-agent review
@coderabbitai review
@cwl-noema-review review
@strix review

Review this exact head read-only. Do not mutate the branch, synthesize author approval, weaken protection, or merge.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@seonghobae have exceeded the limit for the number of chat messages per hour. Please wait 3 minutes and 33 seconds before sending another message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant