# 청구 경로는 행을 잠근 뒤 데이터베이스 시각을 읽고 만료를 먼저 본다 127: IdempotencyRecordRow row = 128: rows.findForUpdate(request.scope().digest()).orElseThrow(this::indeterminateClaim); 129: Instant dbNow = rows.databaseNow(); 130: if (inserted == 1) { 131: return acquired(row); 132: } 133: 134: if (isExpiredCompleted(row, dbNow)) { 135: return resetClaim(request, row); 136: } 137: if (!row.requestHash().equals(request.requestFingerprint().hex())) { 138: return new IdempotencyClaimOutcome.FingerprintMismatch(); 139: } 140: if (row.state() == IdempotencyState.COMPLETED && row.replayUntil() != null) { 141: return new IdempotencyClaimOutcome.CompletedReplay( 142: new StoredResponse(row.responsePayload()), row.replayUntil()); 143: } 586: private static boolean isExpiredCompleted(IdempotencyRecordRow row, Instant dbNow) { 587- return row.state() == IdempotencyState.COMPLETED 588- && row.replayUntil() != null 589- && !dbNow.isBefore(row.replayUntil()); 590- } # claim 본문에서 databaseNow 를 부르는 줄: 1 # 조회 경로 전문 393: public IdempotencyInspection inspect(IdempotencyInspectionRequest request) { 394: Objects.requireNonNull(request, "request"); 395: Optional found = rows.find(request.scope().digest()); 396: if (found.isEmpty()) { 397: return IdempotencyInspection.outcome(IdempotencyInspectionOutcome.ABSENT); 398: } 399: IdempotencyRecordRow row = found.get(); 400: if (!row.requestHash().equals(request.requestFingerprint().hex())) { 401: return IdempotencyInspection.outcome(IdempotencyInspectionOutcome.FINGERPRINT_MISMATCH); 402: } 403: boolean sameAttempt = sameClaimAttempt(row, request.claimAttempt()); 404: if (row.state() == IdempotencyState.COMPLETED && row.responsePayload() != null) { 405: return new IdempotencyInspection( 406: IdempotencyInspectionOutcome.COMPLETED_REPLAY, 407: Optional.empty(), 408: Optional.empty(), 409: Optional.of(new StoredResponse(row.responsePayload())), 410: Optional.ofNullable(row.replayUntil())); 411: } 412: if (sameAttempt && row.state() == IdempotencyState.CLAIMED) { 413: return inspectionWithOwner(IdempotencyInspectionOutcome.CLAIMED_SAME_OPERATION, row); 414: } 415: if (sameAttempt && row.state() == IdempotencyState.EXECUTING) { 416: return inspectionWithOwner(IdempotencyInspectionOutcome.EXECUTING_SAME_OPERATION, row); 417: } 418: if (row.ownerToken().equals(request.claimAttempt().ownerToken()) && !sameAttempt) { 419: return IdempotencyInspection.outcome(IdempotencyInspectionOutcome.OPERATION_CONFLICT); 420: } 421: return switch (row.state()) { 422: case FAILED_RETRYABLE -> 423: IdempotencyInspection.outcome(IdempotencyInspectionOutcome.FAILED_RETRYABLE); 424: case ABANDONED -> IdempotencyInspection.outcome(IdempotencyInspectionOutcome.ABANDONED); 425: default -> IdempotencyInspection.outcome(IdempotencyInspectionOutcome.IN_PROGRESS_OTHER); 426: }; 427: } # inspect 본문에서 databaseNow 를 부르는 줄: 0 # inspect 본문이 replayUntil 로 하는 일 18: Optional.ofNullable(row.replayUntil())); # 만료 비교는 파일에 한 곳뿐이고, 그것을 부르는 곳 134: if (isExpiredCompleted(row, dbNow)) { 586: private static boolean isExpiredCompleted(IdempotencyRecordRow row, Instant dbNow) { # 조회가 쓰는 조회 SQL 에 시간 술어가 있는가 # 조회 결과를 받는 세 자리 146: case COMPLETED_REPLAY -> codec.deserialize(requireResponse(inspection).payload()); 147- case FINGERPRINT_MISMATCH -> 148- throw IdempotencyRequestMismatchException.forScopeDigest(diagnostic(request.scope())); 149- case IN_PROGRESS_OTHER -> -- 192: case COMPLETED_REPLAY -> codec.deserialize(requireResponse(inspection).payload()); 193- case UNAVAILABLE -> throw new IdempotencyUnavailableException(); 194- default -> throw recovery("execution start is indeterminate"); 195- }; -- 261: case COMPLETED_REPLAY -> { 262- StoredResponse stored = requireResponse(inspection); 263- if (stored.payload().equals(codec.serialize(result))) { 264- yield result; # 출하 통합 시험의 만료 시험이 무엇을 만료시키는가 130: void expiredClaimCanBeTakenOverButTheStaleOwnerCannotStart() { 164: void expiredExecutingRecordRequiresReconciliationAndIsNeverBlindlyTakenOver() { 138: Duration.ofMillis(25)))) 171: store.claim(request(original, Duration.ofMillis(25)))) 366: private static IdempotencyClaimRequest request( 367- IdempotencyClaimAttempt attempt, Duration processingLease) { 368- return new IdempotencyClaimRequest( 369- SCOPE, FINGERPRINT, attempt, processingLease, Duration.ofHours(24), "json.v1", 2); grep: adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/idempotency/IdempotencyRecordRows.java: No such file or directory