Skip to content

fix: EgovReflectionSupport 멀티스레드 lazy-init 경합 제거 (getter 메서드맵 부분초기화 NPE)#240

Open
z3rotig4r wants to merge 1 commit into
eGovFramework:mainfrom
z3rotig4r:fix/batch-reflection-lazyinit-race
Open

fix: EgovReflectionSupport 멀티스레드 lazy-init 경합 제거 (getter 메서드맵 부분초기화 NPE)#240
z3rotig4r wants to merge 1 commit into
eGovFramework:mainfrom
z3rotig4r:fix/batch-reflection-lazyinit-race

Conversation

@z3rotig4r

Copy link
Copy Markdown
Contributor

개요

배치 reflection 헬퍼 EgovReflectionSupport(Batch/.../reflection/EgovReflectionSupport.java)의 generateGetterMethodMap 에 멀티스레드 lazy-init 경합이 있어 수정합니다.

문제

EgovFieldExtractor(FieldExtractorEgovJdbcBatchItemWriter(ItemWriter)는 빈으로 등록되어 멀티스레드 step(taskExecutor)에서 공유되며, 내부의 EgovReflectionSupport 인스턴스도 함께 공유됩니다.

public void generateGetterMethodMap(String[] names, T item) {
    if (methods == null) {
        methods = item.getClass().getMethods();   // (1) methods 먼저 비-null 대입
        methodMap = new HashMap<>();               // (2) 이 시점까지 methodMap 은 null
        ... methodMap 채움 ...                      // (3)
    }
}

스레드 A가 (1)만 수행한 상태에서 스레드 B가 methods != null 만 보고 블록을 건너뛰면, 아직 채워지지 않은(또는 null 인) methodMapinvokeGettterMethod 에서 사용하여 NullPointerException 이 발생합니다(고정길이 레코드가 아닌, 조회 결과 누락/예외). cold-start 시 재현되는 전형적인 부분초기화 발행(publication) 버그입니다.

EgovFixedLengthLineAggregator(별도 PR)와 동일한 "공유 인스턴스 + lazy-init" 패턴의 형제 사례입니다.

수정

  • double-checked locking 으로 초기화를 1회만 수행.
  • methods·methodMap지역변수로 완성한 뒤, methodMap 을 먼저 발행하고 methods 를 마지막에 대입 → 부분초기화 상태가 외부에 보이지 않음.
  • 두 필드를 volatile 로 선언해 가시성 보장.
  • 단일스레드 동작은 동일(동작 보존).

검증

EgovReflectionSupportConcurrencyTest 추가:

  • sharedInstanceColdStartIsThreadSafe — 32스레드 × 500라운드, 라운드마다 새 공유 인스턴스로 cold-start 동시호출.
  • singleThreadGetterExtractsValues — 단일스레드 추출 회귀.

수정 코드에서는 round 0 에서 NullPointerException(HashMap.get(...) is null)으로 실패하고, 수정 통과합니다.

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

EgovFieldExtractor·EgovJdbcBatchItemWriter 는 FieldExtractor/ItemWriter
빈으로 등록되어 멀티스레드 step 에서 공유되며, 그 안의
EgovReflectionSupport 인스턴스도 함께 공유된다.

generateGetterMethodMap 은 methods 를 먼저 대입한 뒤 methodMap 을
채웠다. 이로 인해 한 스레드가 methods 만 비-null 로 만든 상태에서
다른 스레드가 `methods != null` 조건만 보고 아직 채워지지 않은(또는
null 인) methodMap 을 사용하여 NPE 가 발생할 수 있었다(cold-start 경합).

double-checked locking 으로 초기화를 1회만 수행하고, methods·methodMap
을 지역변수로 완성한 뒤 methodMap 을 먼저 발행하고 methods 를 마지막에
대입하여 부분초기화 상태가 외부에 보이지 않게 한다. 두 필드를 volatile
로 선언해 가시성을 보장한다. 단일스레드 동작은 동일하다.

EgovReflectionSupportConcurrencyTest 를 추가한다(32스레드 cold-start
동시호출 + 단일스레드 회귀). 수정 전에는 round 0 에서 NullPointerException
으로 실패하고 수정 후 통과한다.
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