3.2 KiB
3.2 KiB
title, source_type, status, related_branches, related_projects, tags, created, status_label
| title | source_type | status | related_branches | related_projects | tags | created | status_label | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| error / testcontainers-two-context-shared-datasource-close-2026-06-11 | error-note | raw |
|
|
|
2026-06-11 | resolved |
error: testcontainers-two-context-shared-datasource-close-2026-06-11
Layer:
raw/errors/— 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw에 영구 보관한다.
Parent / 부모
- raw/branch-notes/feature-domain-event-outbox-contract —
OutboxPublisherLeaderElectionContractTest(2개 Spring context + 1000 rows SKIP LOCKED claim 계약 테스트) 인프라 작성 중 발생.
증상 / Symptom
- 에러 메시지 (원문 그대로):
java.sql.SQLException: HikariDataSource HikariDataSource (HikariPool-1) has been closed. - 발생 컨텍스트:
cd src && ./gradlew :app-bootstrap:test --tests '*Outbox*'— 하나의 PostgreSQL Testcontainers 컨테이너를 공유하는 두 개의AnnotationConfigApplicationContext중 첫 번째를close()하자 두 번째 context 의 쿼리가 전부 실패. - 재현 가능 여부:
always(공유 DataSource 를 bean 으로 등록한 두 context 중 하나라도 닫으면).
재현 절차 / Reproduction
- Testcontainers PG 컨테이너 1개에서
HikariDataSource1개를 만들고, 이를 두 개의AnnotationConfigApplicationContext에registerBean(DataSource.class, () -> sharedDs)로 등록. - 두 context 로 동시 작업 후 첫 번째 context 를
close(). - 기대: 외부에서 생성한 DataSource 는 context 가 소유하지 않으므로 살아 있어야 함.
- 실제: Spring 이 bean 의 추론된 destroy method(
close)를 호출해 공유 풀이 닫힘 → 두 번째 context 의 모든 쿼리 실패.
원인 / Root cause
- Spring 의
registerBean기본 동작은 inferred destroy method — bean 이close()/shutdown()을 가지면 context close 시 자동 호출한다. 외부 소유(externally-owned) 자원이라는 사실을 Spring 은 모른다.
해결 / Resolution
- DataSource bean definition 에
beanDefinition.setDestroyMethodName("")을 지정해 destroy 추론을 끈다 (소유권은 테스트 support 클래스가 유지, 마지막에 직접 close). - 같은 맥락에서
LocalContainerEntityManagerFactoryBean대신 직접EntityManagerFactory를 등록하고ContextClosedEventlistener 로 EMF 만 정리. - 적용 위치: ca-tmpl
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/outbox/OutboxContainerTestSupport.java(actually-implemented,locally-verified— 전체./gradlew check836/836 green).
동반 발견 (같은 테스트 인프라에서)
- 고정 Clock(t0) 으로 relay 를 돌리면서 row 는
Instant.now()로 insert →next_attempt_at <= :now술어가 전부 false 가 되어 published=0. 해결: row 와 relay 가 같은 t0 기반, relay clock 은t0.plusSeconds(1)버퍼. - 병렬 Gradle 실행 2개가 같은 모듈 테스트를 돌리면 JUnit XML report 쓰기 경합으로 위양성 실패 — 검증 명령은 직렬화할 것.