# 루트 예외가 세우는 두 규칙 MongoPersistenceException.java:6 /** MongoPersistenceException.java:7 * Root of the provider-stable MongoDB error hierarchy (design §15). MongoPersistenceException.java:8 * MongoPersistenceException.java:9 *
Two rules shape this class. First, the message is derived from {@link MongoFailureContext}, MongoPersistenceException.java:10 * which can only hold bounded, data-free values — so no caller can accidentally put a document or a MongoPersistenceException.java:11 * query parameter into an exception message. Second, no constructor accepts a {@link Throwable} MongoPersistenceException.java:12 * cause: attaching the driver exception would re-expose everything the failure context deliberately MongoPersistenceException.java:13 * dropped, through {@code getCause()} and through every stack trace printer. The driver's MongoPersistenceException.java:14 * information survives as the error labels and server code already carried by the context. MongoPersistenceException.java:15 */ MongoPersistenceException.java:16 public abstract class MongoPersistenceException extends RuntimeException { MongoPersistenceException.java:17 MongoPersistenceException.java:18 @Serial private static final long serialVersionUID = 1L; MongoPersistenceException.java:19 MongoPersistenceException.java:20 private final transient MongoFailureContext failureContext; MongoPersistenceException.java:21 MongoPersistenceException.java:22 protected MongoPersistenceException(String summary, MongoFailureContext failureContext) { MongoPersistenceException.java:23 super( MongoPersistenceException.java:24 summary + " [" + Objects.requireNonNull(failureContext, "failureContext").describe() + "]"); MongoPersistenceException.java:25 this.failureContext = failureContext; MongoPersistenceException.java:26 } MongoPersistenceException.java:27 MongoPersistenceException.java:28 /** The bounded metadata for this failure. Never contains document or query data. */ MongoPersistenceException.java:29 public MongoFailureContext failureContext() { MongoPersistenceException.java:30 return failureContext; MongoPersistenceException.java:31 } # 그 규칙이 적용되는 범위 MongoPersistenceException 을 상속하는 main 타입 : 22 개 MongoBulkPartialFailureException.java MongoChangeHistoryLostException.java MongoConnectionException.java MongoCursorException.java MongoDataSchemaUnsupportedException.java MongoDocumentNotFoundException.java MongoDocumentTooLargeException.java MongoDuplicateKeyException.java MongoEncryptionException.java MongoOperationRejectedException.java MongoOptimisticConflictException.java MongoReadConcernException.java MongoResumeException.java MongoSchemaValidationException.java MongoServerSelectionException.java MongoShardRoutingException.java MongoTimeoutException.java MongoTransactionCommitUnknownException.java MongoTransactionTransientException.java MongoUnclassifiedFailureException.java MongoWriteConcernException.java MongoWriteConflictException.java # 이 계층에서 원인을 받거나 설정하는 자리 전수 Throwable 을 인자로 받는 생성자 줄 : 1 개 initCause 를 부르는 줄 : 1 개 MongoTimeoutException.java:25 public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) { MongoTimeoutException.java:27 initCause(cause); [대조] 그 검색이 훑은 파일 : 351 개 [대조] 옆 계층 persistence-jpa 에서 같은 검색 : 19 줄 # 그 하나의 타입 MongoTimeoutException.java:5 /** MongoTimeoutException.java:6 * The operation exceeded its deadline (design §15). MongoTimeoutException.java:7 * MongoTimeoutException.java:8 *
A timeout says when the client stopped waiting, not what the server did. A write that timed MongoTimeoutException.java:9 * out after being sent is {@code WRITE_RESULT_UNKNOWN} and must be reconciled; only a timeout that MongoTimeoutException.java:10 * fired before the command left is safe to replay. MongoTimeoutException.java:11 */ MongoTimeoutException.java:12 public final class MongoTimeoutException extends MongoPersistenceException { MongoTimeoutException.java:13 MongoTimeoutException.java:14 @Serial private static final long serialVersionUID = 1L; MongoTimeoutException.java:15 MongoTimeoutException.java:16 public MongoTimeoutException(MongoFailureContext failureContext) { MongoTimeoutException.java:17 super("the MongoDB operation exceeded its deadline", failureContext); MongoTimeoutException.java:18 } MongoTimeoutException.java:19 MongoTimeoutException.java:20 /** MongoTimeoutException.java:21 * Creates the exception from a client-side timeout. MongoTimeoutException.java:22 * MongoTimeoutException.java:23 * @param cause the underlying timeout, kept so a reactive stack trace still names its source MongoTimeoutException.java:24 */ MongoTimeoutException.java:25 public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) { MongoTimeoutException.java:26 super("the MongoDB operation exceeded its deadline", failureContext); MongoTimeoutException.java:27 initCause(cause); MongoTimeoutException.java:28 } MongoTimeoutException.java:29 } # 2 인자 생성자를 부르는 자리 MongoTimeoutException.java:16 public MongoTimeoutException(MongoFailureContext failureContext) { MongoTimeoutException.java:25 public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) { DefaultMongoFailureTranslator.java:104 case TIMEOUT -> new MongoTimeoutException(failureContext); DefaultReactiveMongoExecutor.java:152 new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException( DefaultReactiveMongoExecutor.java:138 MongoOperationObservation observation, DefaultReactiveMongoExecutor.java:139 Throwable failure) { DefaultReactiveMongoExecutor.java:140 if (failure instanceof MongoPersistenceException alreadyTranslated) { DefaultReactiveMongoExecutor.java:141 if (observation != null) { DefaultReactiveMongoExecutor.java:142 observation.failure(alreadyTranslated.failureContext()); DefaultReactiveMongoExecutor.java:143 } DefaultReactiveMongoExecutor.java:144 return alreadyTranslated; DefaultReactiveMongoExecutor.java:145 } DefaultReactiveMongoExecutor.java:146 if (failure instanceof java.util.concurrent.TimeoutException reactorTimeout) { DefaultReactiveMongoExecutor.java:147 // Reactor's own timeout, not the driver's. It used to escape as a raw DefaultReactiveMongoExecutor.java:148 // java.util.concurrent.TimeoutException — a type no caller of this platform is told about, DefaultReactiveMongoExecutor.java:149 // and one that carries no operation, no outcome and no observation. The failure was also DefaultReactiveMongoExecutor.java:150 // never recorded, because this method returned before reaching the observer. DefaultReactiveMongoExecutor.java:151 MongoPersistenceException timedOut = DefaultReactiveMongoExecutor.java:152 new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException( DefaultReactiveMongoExecutor.java:153 dev.caskeleton.adapter.outbound.mongo.api.error.MongoFailureContext.timedOut( DefaultReactiveMongoExecutor.java:154 context.operationName(), operationType, context.timeout()), DefaultReactiveMongoExecutor.java:155 reactorTimeout); DefaultReactiveMongoExecutor.java:156 if (observation != null) { DefaultReactiveMongoExecutor.java:157 observation.failure(timedOut.failureContext()); DefaultReactiveMongoExecutor.java:158 } DefaultReactiveMongoExecutor.java:159 return timedOut; DefaultReactiveMongoExecutor.java:160 } DefaultReactiveMongoExecutor.java:161 MongoException driverFailure = asDriverFailure(failure); DefaultReactiveMongoExecutor.java:162 if (driverFailure == null) { DefaultReactiveMongoExecutor.java:163 return failure; DefaultReactiveMongoExecutor.java:164 } DefaultReactiveMongoExecutor.java:165 MongoPersistenceException translated = DefaultReactiveMongoExecutor.java:166 translator.translate( DefaultReactiveMongoExecutor.java:167 context, operationType, Duration.ZERO, MongoDriverFailureView.from(driverFailure), 1); DefaultReactiveMongoExecutor.java:168 if (observation != null) { DefaultReactiveMongoExecutor.java:169 observation.failure(translated.failureContext().withTraceId(observation.traceId())); DefaultReactiveMongoExecutor.java:170 } DefaultReactiveMongoExecutor.java:171 return translated; DefaultReactiveMongoExecutor.java:172 } # 생성자를 지나지 않고 드라이버 예외가 붙는 자리 이 계층에서 addSuppressed 를 부르는 줄 : 3 개 SpringMongoTransactionSessionFactory.java:88 startFailed.addSuppressed(closeFailed); SpringMongoTransactionSessionFactory.java:194 inFlight.addSuppressed(failure); SpringMongoTransactionSessionFactory.java:200 cleanupFailure.addSuppressed(failure); SpringMongoTransactionSessionFactory.java:159 public void abort() { SpringMongoTransactionSessionFactory.java:160 if (!committed && session.hasActiveTransaction()) { SpringMongoTransactionSessionFactory.java:161 session.abortTransaction(); SpringMongoTransactionSessionFactory.java:162 } SpringMongoTransactionSessionFactory.java:163 } SpringMongoTransactionSessionFactory.java:164 SpringMongoTransactionSessionFactory.java:165 @Override SpringMongoTransactionSessionFactory.java:166 public void close() { SpringMongoTransactionSessionFactory.java:167 try { SpringMongoTransactionSessionFactory.java:168 abort(); SpringMongoTransactionSessionFactory.java:169 } catch (RuntimeException abortFailed) { SpringMongoTransactionSessionFactory.java:170 recordCleanupFailure(abortFailed); SpringMongoTransactionSessionFactory.java:171 } finally { SpringMongoTransactionSessionFactory.java:172 try { SpringMongoTransactionSessionFactory.java:173 session.close(); SpringMongoTransactionSessionFactory.java:174 } catch (RuntimeException closeFailed) { SpringMongoTransactionSessionFactory.java:175 recordCleanupFailure(closeFailed); SpringMongoTransactionSessionFactory.java:176 } SpringMongoTransactionSessionFactory.java:185 /** SpringMongoTransactionSessionFactory.java:186 * Keeps a cleanup failure without letting it become the outcome. SpringMongoTransactionSessionFactory.java:187 * SpringMongoTransactionSessionFactory.java:188 *
Attached to the failure already in flight when there is one, so the stack the caller sees SpringMongoTransactionSessionFactory.java:189 * still names what actually went wrong, with the failed abort alongside it rather than in place SpringMongoTransactionSessionFactory.java:190 * of it. SpringMongoTransactionSessionFactory.java:191 */ SpringMongoTransactionSessionFactory.java:192 private void recordCleanupFailure(RuntimeException failure) { SpringMongoTransactionSessionFactory.java:193 if (inFlight != null) { SpringMongoTransactionSessionFactory.java:194 inFlight.addSuppressed(failure); SpringMongoTransactionSessionFactory.java:195 return; SpringMongoTransactionSessionFactory.java:196 } SpringMongoTransactionSessionFactory.java:197 if (cleanupFailure == null) { SpringMongoTransactionSessionFactory.java:198 cleanupFailure = failure; SpringMongoTransactionSessionFactory.java:199 } else { SpringMongoTransactionSessionFactory.java:212 private RuntimeException translate(Throwable failure, MongoFailurePhase phase) { SpringMongoTransactionSessionFactory.java:213 RuntimeException translated = classify(failure, phase); SpringMongoTransactionSessionFactory.java:214 inFlight = translated; SpringMongoTransactionSessionFactory.java:215 return translated; SpringMongoTransactionSessionFactory.java:216 } SpringMongoTransactionSessionFactory.java:217 SpringMongoTransactionSessionFactory.java:218 private RuntimeException translateCommit(Throwable failure) { SpringMongoTransactionSessionFactory.java:219 RuntimeException translated = classify(failure, MongoFailurePhase.TRANSACTION_COMMIT); SpringMongoTransactionSessionFactory.java:220 inFlight = translated; SpringMongoTransactionSessionFactory.java:221 return translated; SpringMongoTransactionSessionFactory.java:222 } SpringMongoTransactionSessionFactory.java:223 SpringMongoTransactionSessionFactory.java:224 private RuntimeException classify(Throwable failure, MongoFailurePhase phase) { SpringMongoTransactionSessionFactory.java:225 MongoDriverFailureView view = MongoFailureExtractor.viewOf(failure).orElse(null); SpringMongoTransactionSessionFactory.java:226 if (view == null) { SpringMongoTransactionSessionFactory.java:227 // Not a Mongo failure, or a cause chain that cannot be walked. Terminal is the fail-closed SpringMongoTransactionSessionFactory.java:228 // verdict when the alternative is replaying work. SpringMongoTransactionSessionFactory.java:229 return failure instanceof RuntimeException runtime SpringMongoTransactionSessionFactory.java:230 ? runtime SpringMongoTransactionSessionFactory.java:231 : new IllegalStateException("unclassifiable Mongo failure", failure); SpringMongoTransactionSessionFactory.java:232 } SpringMongoTransactionSessionFactory.java:233 // The phase is passed through: the same socket failure is a body replay before the commit SpringMongoTransactionSessionFactory.java:234 // and a commit-unknown during it, and the classifier could not tell them apart. SpringMongoTransactionSessionFactory.java:235 MongoRetryScope scope = SpringMongoTransactionSessionFactory.java:236 classifier SpringMongoTransactionSessionFactory.java:237 .classify( SpringMongoTransactionSessionFactory.java:238 phase == MongoFailurePhase.TRANSACTION_COMMIT SpringMongoTransactionSessionFactory.java:239 ? MongoOperationType.TRANSACTION_COMMIT SpringMongoTransactionSessionFactory.java:240 : MongoOperationType.UPDATE, SpringMongoTransactionSessionFactory.java:241 phase, SpringMongoTransactionSessionFactory.java:242 view) SpringMongoTransactionSessionFactory.java:243 .retryScope(); SpringMongoTransactionSessionFactory.java:244 String serverCode = view.hasServerCode() ? Integer.toString(view.serverCode()) : ""; SpringMongoTransactionSessionFactory.java:245 // The context is derived from the scope, not built first and then labelled. Building a SpringMongoTransactionSessionFactory.java:246 // commit-unknown context and wrapping it in whichever exception the classifier chose is how SpringMongoTransactionSessionFactory.java:247 // a "replay the body" exception came to carry "unknown commit, not retryable, ambiguous". SpringMongoTransactionSessionFactory.java:248 if (scope == MongoRetryScope.COMMIT_ONLY) { SpringMongoTransactionSessionFactory.java:249 return new MongoTransactionCommitUnknownException( SpringMongoTransactionSessionFactory.java:250 MongoFailureContext.commitUnknown(SESSION_OPERATION, serverCode, Duration.ZERO), SpringMongoTransactionSessionFactory.java:251 "read the transaction record, version or idempotency key"); SpringMongoTransactionSessionFactory.java:252 } SpringMongoTransactionSessionFactory.java:253 if (scope == MongoRetryScope.WHOLE_TRANSACTION) { SpringMongoTransactionSessionFactory.java:254 return new MongoTransactionTransientException( SpringMongoTransactionSessionFactory.java:255 MongoFailureContext.transientTransaction(SESSION_OPERATION, serverCode, Duration.ZERO)); SpringMongoTransactionSessionFactory.java:256 } # 그 규칙을 검사하는 시험 이 계층의 시험에서 getCause 를 단언하는 줄 : 1 개 MongoFailureContextTest.java:65 assertThat(exception.getCause()).isNull(); [대조] test 소스 세트에서는 : 9 줄 [대조] 시험 소스 세트를 모두 훑으면 : 9 줄 [대조] 이 계층의 하위 타입 가운데 final 로 닫힌 것 : 22 개 MongoFailureContextTest.java:56 MongoFailureContextTest.java:57 @Test MongoFailureContextTest.java:58 void exceptionsDoNotExposeADriverCause() { MongoFailureContextTest.java:59 MongoPersistenceException exception = MongoFailureContextTest.java:60 new MongoTransactionCommitUnknownException( MongoFailureContextTest.java:61 MongoFailureContext.commitUnknown( MongoFailureContextTest.java:62 new MongoOperationName("order.reserve"), "251", Duration.ZERO), MongoFailureContextTest.java:63 "read the transaction record"); MongoFailureContextTest.java:64 MongoFailureContextTest.java:65 assertThat(exception.getCause()).isNull(); MongoFailureContextTest.java:66 assertThat(exception.category()).isEqualTo(MongoFailureCategory.TRANSACTION_COMMIT_UNKNOWN); MongoFailureContextTest.java:67 } MongoTimeoutException 이 나오는 시험 줄 : 0 개 [대조] MongoTransactionCommitUnknownException 은 : 4 줄