Skip to content

fix: MessageSender가 락 대기 중 interrupt를 무시해 지연 전송이 발생하는 문제 수정 - #320

Merged
jei007 merged 1 commit into
eGovFramework:mainfrom
masiljangajji:fix/message-sender-interruptible-lock
Aug 13, 2026
Merged

jei007 merged 1 commit into
eGovFramework:mainfrom
masiljangajji:fix/message-sender-interruptible-lock

Conversation

@masiljangajji

Copy link
Copy Markdown
Contributor

수정 사유 Reason for modification

  • 버그수정 Bug fixes

수정된 소스 내용 Modified source

Integration/org.egovframe.rte.itl.integration/.../support/AbstractService.java

문제: AbstractService.sendSync()와 DefaultResponse.receive()는 sender.join(timeout)이
만료되면 sender.interrupt()를 호출하고 TIME_OUT을 반환합니다. 하지만 MessageSender.run()은
synchronized(service)로 락을 잡는데, 자바 언어 명세(JLS 17.1)상 synchronized 진입 대기
(모니터 획득 대기) 중에는 interrupt()가 아무 효과가 없습니다.

그 결과, 같은 연계(Integration) 서비스 인스턴스에 동시 요청 2건이 들어오면:

  1. 먼저 온 요청이 락을 잡고 doSend()(실제 네트워크 I/O)를 실행합니다.
  2. 나중 요청은 락을 기다리다 자신의 timeout이 지나 TIME_OUT을 반환받고 호출자는 그대로
    종료합니다.
  3. 하지만 2번 요청의 스레드 자체는 죽지 않고 계속 락을 기다리다가, 1번 요청이 끝나 락이
    풀리는 순간 아무도 기다리지 않는 상태에서 doSend()를 뒤늦게 실행합니다("유령 전송").

이 락은 2017-02-13 커밋 주석에 명시된 대로 CWE-367(TOCTOU) 대응으로 의도적으로 추가된
것이므로, 이번 수정은 락을 제거하거나 범위를 좁히지 않고 같은 상호배제 보장을 유지한
채로 대기 중 인터럽트만 가능하게 바꿉니다.

변경 내용:

  • AbstractService에 ReentrantLock invocationLock 필드 추가
  • MessageSender.run()에서 synchronized(service) 대신
    service.invocationLock.lockInterruptibly() 사용
  • 락 대기 중 인터럽트되면(InterruptedException) doSend()를 시도하지 않고 즉시 반환
  • 락 획득 후에는 try/finally로 항상 unlock()

sendSync()/sendAsync()/DefaultResponse.receive()의 반환값이나 시그니처는 바뀌지
않습니다 - TIME_OUT을 반환한 뒤에는 실제로도 발송 시도가 없어진다는 점만 달라집니다.

JUnit 테스트 JUnit tests

  • JUnit 테스트 JUnit tests
  • 수동 테스트 Manual testing

MessageSenderLockContentionTest를 추가했습니다: 같은 서비스 인스턴스에 대해 느린 첫 호출이
락을 쥔 상태에서 두 번째 호출이 짧은 timeout으로 락 대기 중 타임아웃되면, 첫 호출이 끝나 락이
풀린 뒤에도 두 번째 호출의 doSend가 실행되지 않음을 확인합니다.

  • 신규 테스트 1건 + 기존 AbstractServiceTest 6건: 통과
  • Integration/org.egovframe.rte.itl.integration, Integration/org.egovframe.rte.itl.webservice
    두 모듈 전체 테스트: 통과 (회귀 없음)
  • 리포 전체 24개 모듈 mvn test: 644건 전체 통과, errors 0 / failures 0 / skipped 0
  • 이번 변경이 영향을 줄 수 있는 범위(AbstractService를 상속하는 클래스, 이 두 모듈에
    의존하는 다른 모듈)를 grep으로 확인한 결과 EgovWebService와 테스트 더미 외에는 없고,
    다른 모듈에서 이 두 모듈을 의존하는 곳도 없습니다.

테스트 브라우저 Test Browser

해당 없음 - 백엔드 동시성 수정으로 브라우저 테스트 대상이 아닙니다.
Not applicable - this is a backend concurrency fix with no browser-facing surface.

테스트 스크린샷 또는 캡처 영상 Test screenshots or captured video

해당 없음 (UI 변경 없음). 대신 JUnit 재현 테스트 로그로 수정 전/후 동작을 확인했습니다:

  • 수정 전: 두 번째 호출이 300ms 만에 TIME_OUT을 반환하지만, 그 스레드는 계속 대기하다
    첫 호출 종료(3000ms) 직후 아무도 기다리지 않는 상태에서 doSend를 실행함(로그 타임스탬프로
    2,638ms 지연 확인).
  • 수정 후: 동일 시나리오에서 MessageSender run() interrupted while waiting for the lock; doSend not attempted 로그가 즉시 찍히고, 첫 호출 종료 후에도 두 번째 호출의 doSend는
    실행되지 않음.

AbstractService.sendSync()/DefaultResponse.receive()는 timeout이 지나면 MessageSender
스레드에 interrupt()를 걸고 TIME_OUT을 반환하지만, MessageSender.run()이 그 락을
synchronized(service)로 잡고 있어 모니터 진입 대기 중에는 interrupt가 전혀 먹히지
않았다(JLS 17.1). 그 결과 이미 TIME_OUT을 반환하고 떠난 호출의 스레드가 계속 락을
기다리다, 앞선 호출이 끝나 락이 풀리는 순간 아무도 기다리지 않는 상태에서 뒤늦게
doSend()를 실행해 실제 발송이 나가는 문제가 있었다.

synchronized(service) 대신 새로 추가한 AbstractService.invocationLock
(ReentrantLock)에 lockInterruptibly()를 사용해, 락을 기다리는 동안 interrupt가
오면 doSend를 시도하지 않고 즉시 종료하도록 변경했다. 같은 서비스 인스턴스에 대해
doSend가 한 번에 하나씩만 실행된다는 기존 상호배제 보장(2017.02.13 CWE-367 대응)은
그대로 유지된다.

sendSync가 반환하는 결과값 자체는 바뀌지 않는다(여전히 TIME_OUT 메시지를 반환) -
바뀌는 것은 그 이후 백그라운드에서 뒤늦게 실행되던 doSend 호출이 사라진다는 점이다.

MessageSenderLockContentionTest 추가: 두 번째 호출이 락 대기로 타임아웃된 뒤,
첫 번째 호출이 끝나 락이 풀려도 doSend가 실행되지 않음을 확인한다.
@masiljangajji masiljangajji changed the title fix: MessageSender가 락 대기 중 interrupt를 무시해 유령(phantom) 지연 전송이 발생하는 문제 수정 fix: MessageSender가 락 대기 중 interrupt를 무시해 지연 전송이 발생하는 문제 수정 Aug 10, 2026

@jei007 jei007 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

표준프레임워크에 대한 지속적인 참여에
대단히 감사드립니다.

@jei007
jei007 merged commit 543fc07 into eGovFramework:main Aug 13, 2026
@masiljangajji
masiljangajji deleted the fix/message-sender-interruptible-lock branch August 13, 2026 01:22
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.

2 participants