# 야간 워크플로가 이 레인에 대해 적는 것 jpa-nightly.yml:122 - name: Verify pool saturation and REQUIRES_NEW connection behaviour jpa-nightly.yml:123 working-directory: src jpa-nightly.yml:124 # A behaviour contract, not a measurement. This step used to switch assertions off with an jpa-nightly.yml:125 # explicit property and call the result a certification, so the only threshold it ever jpa-nightly.yml:126 # asserted was that thresholds were not being asserted. What jpa-nightly.yml:127 # it checks now — that REQUIRES_NEW needs two connections per concurrent thread, that a jpa-nightly.yml:128 # saturated pool reports its pending count, that a caller waits rather than proceeding jpa-nightly.yml:129 # without a connection — is true on any runner, so there is nothing to switch off. jpa-nightly.yml:130 run: >- jpa-nightly.yml:131 ./gradlew jpa-nightly.yml:132 :adapter:outbound:persistence-jpa:jpaPlatformPoolContractTest jpa-nightly.yml:133 --no-daemon jpa-nightly.yml:134 --stacktrace # 그 레인이 도는 소스 세트와 그 안의 시험 파일 persistence-jpa/build.gradle:281 // What the lane genuinely verifies is a behaviour contract: a REQUIRES_NEW depth of one needs two persistence-jpa/build.gradle:282 // connections per concurrent thread, a saturated pool reports its pending count, and a caller waits persistence-jpa/build.gradle:283 // rather than proceeding without a connection. Those are true on any machine, so they need no flag persistence-jpa/build.gradle:284 // — and this name does not promise a number nobody measured. A real performance gate needs a persistence-jpa/build.gradle:285 // dedicated runner, warmup and sample counts, and recorded thresholds; when that exists it belongs persistence-jpa/build.gradle:286 // in a lane of its own rather than behind a boolean on this one. persistence-jpa/build.gradle:287 def jpaPlatformPoolContractTest = tasks.register('jpaPlatformPoolContractTest', Test) { persistence-jpa/build.gradle:288 group = 'verification' persistence-jpa/build.gradle:289 description = 'Verifies Hikari pool and REQUIRES_NEW connection behaviour (design §38).' persistence-jpa/build.gradle:290 testClassesDirs = sourceSets.jpaPlatformPerformanceTest.output.classesDirs persistence-jpa/build.gradle:291 classpath = sourceSets.jpaPlatformPerformanceTest.runtimeClasspath persistence-jpa/build.gradle:292 useJUnitPlatform() persistence-jpa/build.gradle:293 failOnNoDiscoveredTests = true persistence-jpa/build.gradle:294 outputs.upToDateWhen { false } persistence-jpa/build.gradle:295 jvmArgs('-Duser.timezone=UTC') persistence-jpa/build.gradle:296 } persistence-jpa/build.gradle:297 persistence-jpa/build.gradle:298 // The JPA release gate (design §41). Aggregates every lane whose absence would let one of the persistence-jpa/build.gradle:299 // documented gates in docs/jpa/support-matrix.md pass unverified. persistence-jpa/build.gradle:300 tasks.register('jpaPlatformReleaseGate') { persistence-jpa/build.gradle:301 group = 'verification' persistence-jpa/build.gradle:302 description = 'Runs every JPA platform lane required for a release (design §41).' persistence-jpa/build.gradle:303 dependsOn tasks.named('test') persistence-jpa/build.gradle:304 dependsOn jpaPlatformContractTest persistence-jpa/build.gradle:305 dependsOn jpaPlatformMigrationTest persistence-jpa/build.gradle:306 dependsOn jpaPlatformFailureTest persistence-jpa/build.gradle:307 dependsOn jpaPlatformQueryPlanTest persistence-jpa/build.gradle:308 dependsOn jpaPlatformSecurityTest persistence-jpa/build.gradle:309 dependsOn jpaPlatformPoolContractTest persistence-jpa/build.gradle:310 } HikariPoolSaturationContractTest.java PoolPressureContractTest.java RequiresNewPoolPressureContractTest.java # 세 시험 파일의 시험과 단언 전수 --- HikariPoolSaturationContractTest.java :3 import static org.assertj.core.api.Assertions.assertThat; :4 import static org.assertj.core.api.Assertions.assertThatThrownBy; :52 void saturatedPoolFailsWithinItsTimeout() throws SQLException { :61 assertThatThrownBy(pool::getConnection).isInstanceOf(SQLException.class); :64 assertThat(waited) :77 void releasingAConnectionLetsTheNextCallerThrough() throws SQLException { :84 assertThat(third.isValid(1)).isTrue(); :92 void measurementReportsPoolState() throws SQLException { :97 new PoolMeasurement( :100 bean.getThreadsAwaitingConnection(), :103 assertThat(measurement.active()).isEqualTo(1); :104 assertThat(measurement.total()).isGreaterThanOrEqualTo(1); :105 assertThat(held.isValid(1)).isTrue(); --- PoolPressureContractTest.java :3 import static org.assertj.core.api.Assertions.assertThat; :28 void reportsPendingAndAcquireLatencyTogether() { :29 var measurement = new PoolMeasurement(4, 2, 3, Duration.ofMillis(80)); :31 assertThat(measurement.pending()).isEqualTo(3); :32 assertThat(measurement.acquireLatency()).isEqualTo(Duration.ofMillis(80)); :33 assertThat(measurement.total()).isEqualTo(6); :34 assertThat(measurement.saturated()).isTrue(); :39 void requiresNewNeedsTwoConnectionsPerThread() { :45 assertThat(required) --- RequiresNewPoolPressureContractTest.java :3 import static org.assertj.core.api.Assertions.assertThat; :4 import static org.assertj.core.api.Assertions.assertThatThrownBy; :45 void poolOfOneCannotServeAnInnerTransaction() throws SQLException { :50 assertThatThrownBy(pool::getConnection) :61 void poolOfTwoServesTheSameNesting() throws SQLException { :68 assertThat(inner.isValid(1)).isTrue(); :69 assertThat(inner).isNotSameAs(outer); :80 void sizingRuleMatchesTheObservedRequirement() { :86 assertThat(required) # 대기 수를 단언하는 자리 measurement.pending() 를 단언하는 줄 : 1 개 그 줄이 있는 시험의 입력 : PoolPressureContractTest.java:26 @Test PoolPressureContractTest.java:27 @DisplayName("pending count and acquire latency are reported together") PoolPressureContractTest.java:28 void reportsPendingAndAcquireLatencyTogether() { PoolPressureContractTest.java:29 var measurement = new PoolMeasurement(4, 2, 3, Duration.ofMillis(80)); PoolPressureContractTest.java:30 PoolPressureContractTest.java:31 assertThat(measurement.pending()).isEqualTo(3); PoolPressureContractTest.java:32 assertThat(measurement.acquireLatency()).isEqualTo(Duration.ofMillis(80)); PoolPressureContractTest.java:33 assertThat(measurement.total()).isEqualTo(6); PoolPressureContractTest.java:34 assertThat(measurement.saturated()).isTrue(); PoolPressureContractTest.java:35 } PoolPressureContractTest.java:36 PoolPressureContractTest.java:37 @Test PoolPressureContractTest.java:38 @DisplayName("a REQUIRES_NEW depth of one needs two connections per concurrent thread") PoolPressureContractTest.java:39 void requiresNewNeedsTwoConnectionsPerThread() { PoolPressureContractTest.java:40 int concurrentThreads = 8; PoolPressureContractTest.java:41 int maxRequiresNewDepth = 1; PoolPressureContractTest.java:42 PoolPressureContractTest.java:43 int required = concurrentThreads * (1 + maxRequiresNewDepth) + 1; PoolPressureContractTest.java:44 PoolPressureContractTest.java:45 assertThat(required) PoolPressureContractTest.java:46 .as("this is a property of REQUIRES_NEW, not of the machine, so it needs no opt-in") PoolPressureContractTest.java:47 .isEqualTo(17); 포화를 다루는 세 시험 전문 : HikariPoolSaturationContractTest.java:30 class HikariPoolSaturationContractTest { HikariPoolSaturationContractTest.java:31 HikariPoolSaturationContractTest.java:32 private static final int POOL_SIZE = 2; HikariPoolSaturationContractTest.java:33 private static final Duration ACQUIRE_TIMEOUT = Duration.ofMillis(500); HikariPoolSaturationContractTest.java:34 HikariPoolSaturationContractTest.java:50 @Test HikariPoolSaturationContractTest.java:51 @DisplayName("a saturated pool fails within its acquisition timeout") HikariPoolSaturationContractTest.java:52 void saturatedPoolFailsWithinItsTimeout() throws SQLException { HikariPoolSaturationContractTest.java:53 try (HikariDataSource pool = pool()) { HikariPoolSaturationContractTest.java:54 List held = new ArrayList<>(); HikariPoolSaturationContractTest.java:55 try { HikariPoolSaturationContractTest.java:56 for (int index = 0; index < POOL_SIZE; index++) { HikariPoolSaturationContractTest.java:57 held.add(pool.getConnection()); HikariPoolSaturationContractTest.java:58 } HikariPoolSaturationContractTest.java:59 HikariPoolSaturationContractTest.java:60 Instant startedAt = Instant.now(); HikariPoolSaturationContractTest.java:61 assertThatThrownBy(pool::getConnection).isInstanceOf(SQLException.class); HikariPoolSaturationContractTest.java:62 Duration waited = Duration.between(startedAt, Instant.now()); HikariPoolSaturationContractTest.java:63 HikariPoolSaturationContractTest.java:64 assertThat(waited) HikariPoolSaturationContractTest.java:65 .as("an unbounded wait turns exhaustion into requests that never return") HikariPoolSaturationContractTest.java:66 .isLessThan(ACQUIRE_TIMEOUT.plusSeconds(2)); HikariPoolSaturationContractTest.java:67 } finally { HikariPoolSaturationContractTest.java:68 for (Connection connection : held) { HikariPoolSaturationContractTest.java:69 connection.close(); HikariPoolSaturationContractTest.java:70 } HikariPoolSaturationContractTest.java:71 } HikariPoolSaturationContractTest.java:72 } HikariPoolSaturationContractTest.java:73 } HikariPoolSaturationContractTest.java:74 HikariPoolSaturationContractTest.java:75 @Test HikariPoolSaturationContractTest.java:76 @DisplayName("releasing a connection lets the next caller through") HikariPoolSaturationContractTest.java:77 void releasingAConnectionLetsTheNextCallerThrough() throws SQLException { HikariPoolSaturationContractTest.java:78 try (HikariDataSource pool = pool()) { HikariPoolSaturationContractTest.java:79 Connection first = pool.getConnection(); HikariPoolSaturationContractTest.java:80 Connection second = pool.getConnection(); HikariPoolSaturationContractTest.java:81 first.close(); HikariPoolSaturationContractTest.java:82 HikariPoolSaturationContractTest.java:83 try (Connection third = pool.getConnection()) { HikariPoolSaturationContractTest.java:84 assertThat(third.isValid(1)).isTrue(); HikariPoolSaturationContractTest.java:85 } HikariPoolSaturationContractTest.java:86 second.close(); HikariPoolSaturationContractTest.java:87 } HikariPoolSaturationContractTest.java:88 } HikariPoolSaturationContractTest.java:89 HikariPoolSaturationContractTest.java:90 @Test HikariPoolSaturationContractTest.java:91 @DisplayName("the measurement reports what the pool is actually doing") HikariPoolSaturationContractTest.java:92 void measurementReportsPoolState() throws SQLException { HikariPoolSaturationContractTest.java:93 try (HikariDataSource pool = pool()) { HikariPoolSaturationContractTest.java:94 try (Connection held = pool.getConnection()) { HikariPoolSaturationContractTest.java:95 var bean = pool.getHikariPoolMXBean(); HikariPoolSaturationContractTest.java:96 var measurement = HikariPoolSaturationContractTest.java:97 new PoolMeasurement( HikariPoolSaturationContractTest.java:98 bean.getActiveConnections(), HikariPoolSaturationContractTest.java:99 bean.getIdleConnections(), HikariPoolSaturationContractTest.java:100 bean.getThreadsAwaitingConnection(), HikariPoolSaturationContractTest.java:101 Duration.ZERO); HikariPoolSaturationContractTest.java:102 HikariPoolSaturationContractTest.java:103 assertThat(measurement.active()).isEqualTo(1); HikariPoolSaturationContractTest.java:104 assertThat(measurement.total()).isGreaterThanOrEqualTo(1); HikariPoolSaturationContractTest.java:105 assertThat(held.isValid(1)).isTrue(); HikariPoolSaturationContractTest.java:106 } HikariPoolSaturationContractTest.java:107 } HikariPoolSaturationContractTest.java:108 } HikariPoolSaturationContractTest.java:109 HikariPoolSaturationContractTest.java:110 private static HikariDataSource pool() { HikariPoolSaturationContractTest.java:111 HikariConfig config = new HikariConfig(); HikariPoolSaturationContractTest.java:112 config.setJdbcUrl(container.getJdbcUrl()); HikariPoolSaturationContractTest.java:113 config.setUsername(container.getUsername()); HikariPoolSaturationContractTest.java:114 config.setPassword(container.getPassword()); HikariPoolSaturationContractTest.java:115 config.setMaximumPoolSize(POOL_SIZE); HikariPoolSaturationContractTest.java:116 config.setConnectionTimeout(ACQUIRE_TIMEOUT.toMillis()); HikariPoolSaturationContractTest.java:117 return new HikariDataSource(config); HikariPoolSaturationContractTest.java:118 } # 나머지 두 주장에는 실제 단언이 있다 RequiresNewPoolPressureContractTest.java:43 @Test RequiresNewPoolPressureContractTest.java:44 @DisplayName("a pool of one deadlocks the moment an inner transaction is needed") RequiresNewPoolPressureContractTest.java:45 void poolOfOneCannotServeAnInnerTransaction() throws SQLException { RequiresNewPoolPressureContractTest.java:46 try (HikariDataSource pool = pool(1)) { RequiresNewPoolPressureContractTest.java:47 try (Connection outer = pool.getConnection()) { RequiresNewPoolPressureContractTest.java:48 outer.setAutoCommit(false); RequiresNewPoolPressureContractTest.java:49 RequiresNewPoolPressureContractTest.java:50 assertThatThrownBy(pool::getConnection) RequiresNewPoolPressureContractTest.java:51 .as("the outer transaction still holds its connection while the inner one is opened") RequiresNewPoolPressureContractTest.java:52 .isInstanceOf(SQLException.class); RequiresNewPoolPressureContractTest.java:53 RequiresNewPoolPressureContractTest.java:54 outer.rollback(); RequiresNewPoolPressureContractTest.java:55 } RequiresNewPoolPressureContractTest.java:56 } RequiresNewPoolPressureContractTest.java:57 } RequiresNewPoolPressureContractTest.java:58 RequiresNewPoolPressureContractTest.java:59 @Test RequiresNewPoolPressureContractTest.java:60 @DisplayName("a pool of two serves the same nesting") RequiresNewPoolPressureContractTest.java:61 void poolOfTwoServesTheSameNesting() throws SQLException { RequiresNewPoolPressureContractTest.java:62 try (HikariDataSource pool = pool(2)) { RequiresNewPoolPressureContractTest.java:63 try (Connection outer = pool.getConnection()) { RequiresNewPoolPressureContractTest.java:64 outer.setAutoCommit(false); RequiresNewPoolPressureContractTest.java:65 RequiresNewPoolPressureContractTest.java:66 try (Connection inner = pool.getConnection()) { RequiresNewPoolPressureContractTest.java:67 inner.setAutoCommit(false); RequiresNewPoolPressureContractTest.java:68 assertThat(inner.isValid(1)).isTrue(); RequiresNewPoolPressureContractTest.java:69 assertThat(inner).isNotSameAs(outer); RequiresNewPoolPressureContractTest.java:70 inner.commit(); RequiresNewPoolPressureContractTest.java:71 } RequiresNewPoolPressureContractTest.java:72 RequiresNewPoolPressureContractTest.java:73 outer.rollback(); RequiresNewPoolPressureContractTest.java:74 } RequiresNewPoolPressureContractTest.java:75 } RequiresNewPoolPressureContractTest.java:76 } RequiresNewPoolPressureContractTest.java:77 RequiresNewPoolPressureContractTest.java:78 @Test RequiresNewPoolPressureContractTest.java:79 @DisplayName("the sizing rule matches the observed requirement") RequiresNewPoolPressureContractTest.java:80 void sizingRuleMatchesTheObservedRequirement() { RequiresNewPoolPressureContractTest.java:81 int concurrentThreads = 1; RequiresNewPoolPressureContractTest.java:82 int maxRequiresNewDepth = 1; RequiresNewPoolPressureContractTest.java:83 RequiresNewPoolPressureContractTest.java:84 int required = concurrentThreads * (1 + maxRequiresNewDepth) + 1; RequiresNewPoolPressureContractTest.java:85 RequiresNewPoolPressureContractTest.java:86 assertThat(required) RequiresNewPoolPressureContractTest.java:87 .as("one thread at depth one needs two connections; the rule adds headroom") RequiresNewPoolPressureContractTest.java:88 .isEqualTo(3); RequiresNewPoolPressureContractTest.java:89 } # PoolMeasurement 는 어디서 온 타입인가 세 시험이 그것을 어디서 import 하는가 : HikariPoolSaturationContractTest.java:8 import dev.caskeleton.adapter.outbound.persistence.testkit.pool.PoolMeasurement; PoolPressureContractTest.java:5 import dev.caskeleton.adapter.outbound.persistence.testkit.pool.PoolMeasurement; PoolMeasurement.java:6 /** PoolMeasurement.java:7 * A reading of connection pool pressure (design §38). PoolMeasurement.java:8 * PoolMeasurement.java:9 *

Pending count and acquire latency are recorded together because either alone is misleading. A PoolMeasurement.java:10 * pool can show zero pending at the instant it is sampled while callers routinely wait; a high PoolMeasurement.java:11 * acquire latency with no pending count gives no idea how many callers are affected. PoolMeasurement.java:12 */ PoolMeasurement.java:13 public record PoolMeasurement(int active, int idle, int pending, Duration acquireLatency) { PoolMeasurement.java:14 PoolMeasurement.java:15 public PoolMeasurement { PoolMeasurement.java:16 Objects.requireNonNull(acquireLatency, "acquireLatency"); PoolMeasurement.java:17 if (active < 0 || idle < 0 || pending < 0) { PoolMeasurement.java:18 throw new IllegalArgumentException("pool counters must not be negative"); PoolMeasurement.java:19 } PoolMeasurement.java:20 if (acquireLatency.isNegative()) { PoolMeasurement.java:21 throw new IllegalArgumentException("acquire latency must not be negative"); PoolMeasurement.java:22 } PoolMeasurement.java:23 } PoolMeasurement.java:24 PoolMeasurement.java:25 /** How many connections the pool currently holds. */ PoolMeasurement.java:26 public int total() { PoolMeasurement.java:27 return active + idle; PoolMeasurement.java:28 } PoolMeasurement.java:29 PoolMeasurement.java:30 /** Whether callers are waiting for a connection. */ PoolMeasurement.java:31 public boolean saturated() { PoolMeasurement.java:32 return pending > 0; PoolMeasurement.java:33 } PoolMeasurement.java:34 } 프로덕션에서 대기 수를 읽는 자리 : 0 개 [대조] 그 검색이 훑은 main 파일 : 4714 개