Skip to content

fix: 완료된 SSE emitter 전송 예외를 정리하도록 처리#556

Merged
dh2906 merged 3 commits intodevelopfrom
fix/sse-completed-emitter-send
Apr 16, 2026
Merged

fix: 완료된 SSE emitter 전송 예외를 정리하도록 처리#556
dh2906 merged 3 commits intodevelopfrom
fix/sse-completed-emitter-send

Conversation

@dh2906
Copy link
Copy Markdown
Contributor

@dh2906 dh2906 commented Apr 16, 2026

🔍 개요

  • 이미 완료되었거나 종료 경쟁 상태에 들어간 SSE emitter 때문에 알림 전송 정리 과정에서 예외가 다시 전파되지 않도록 예외 처리 경로를 보강했습니다.

🚀 주요 변경 내용

  • NotificationInboxSseService에서 IOException뿐 아니라 완료된 emitter 전송 시 발생하는 IllegalStateException도 함께 처리하도록 변경했습니다.
  • IOException 정리 과정에서 호출하는 completeWithError도 별도 try-catch로 감싸, emitter가 이미 종료된 경쟁 상황에서도 추가 예외가 전파되지 않도록 했습니다.
  • 테스트는 subscribe()의 completion callback 부수효과에 기대지 않고, 완료된 SseEmitter를 직접 주입해 예외 정리 경로를 결정적으로 재현하도록 바꿨습니다.

💬 참고 사항

  • 대상 테스트: ./gradlew test --tests \"gg.agit.konect.unit.domain.notification.service.NotificationInboxSseServiceTest\"
  • 리뷰 코멘트 반영으로 emitter 종료 race condition 보호와 테스트 안정성을 추가 보완했습니다.

✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

- 이미 complete된 emitter에 send를 시도하면 IllegalStateException이 발생할 수 있어
  알림 전송 자체보다 끊어진 연결 정리를 우선하도록 변경
- IOException만 completeWithError로 마무리하고 IllegalStateException은 emitter 제거로만 끝내
  이미 종료된 emitter를 다시 종료하면서 예외가 전파되는 상황을 막음
- 완료된 emitter가 맵에 남아 있어도 예외 없이 정리되는 단위 테스트를 추가해
  재연 경로를 고정하고 회귀를 방지
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

NotificationInboxSseService의 예외 처리 로직이 개선되어 IOExceptionIllegalStateException을 구분하여 처리하도록 변경되었습니다. IllegalStateException 발생 시에는 로깅 및 이미터 제거만 수행하고, IOException은 기존처럼 completeWithError를 호출합니다.

Changes

Cohort / File(s) Summary
SSE 예외 처리 개선
src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxSseService.java
emitter.send() 호출 시 IllegalStateException 예외를 추가로 캐치하여 처리합니다. IOExceptioncompleteWithError() 호출을 트리거하며, IllegalStateException은 로깅 및 이미터 제거만 수행하고 completeWithError는 호출하지 않습니다.
예외 처리 시나리오 테스트 업데이트
src/test/java/gg/agit/konect/unit/domain/notification/service/NotificationInboxSseServiceTest.java
완료된 이미터에 대한 IllegalStateException 처리 시나리오로 테스트를 변경했습니다. 완료된 이미터 상태를 설정한 후 send 메서드 실행이 예외를 발생시키지 않으며 내부 맵에서 이미터가 제거됨을 검증합니다.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

버그

Poem

🐰 완료된 이미터를 다루는 길,
예외의 종류를 구분하니,
IOException만 에러로 알리고,
IllegalState는 조용히 치우네요.
SSE의 안정성 한 뼘 더 가까워졌습니다!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 완료된 SSE emitter의 예외 처리 개선이라는 핵심 변경 사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 변경 사항과 명확하게 관련되어 있으며, SSE emitter 예외 처리 개선에 대한 구체적인 내용을 포함하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sse-completed-emitter-send

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dh2906 dh2906 self-assigned this Apr 16, 2026
@dh2906 dh2906 added the 버그 정상적으로 동작하지 않는 문제 상황 관련 이슈입니다. label Apr 16, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 16, 2026

🧪 JaCoCo Coverage Report (Changed Files)

Summary

  • Overall Coverage: 70.0% ✅
  • Covered Lines: 21 / 30
  • Changed Files: 1

Coverage by File

Class Coverage Lines Status
NotificationInboxSseService
gg.agit.konect.domain.notification.service
70.0% 21/30

📊 View Workflow Run

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxSseService.java`:
- Around line 53-55: The call to emitter.completeWithError(ioException) in
NotificationInboxSseService can itself throw IllegalStateException during race
conditions (emitter already completed); wrap the completeWithError(ioException)
invocation in its own try-catch that catches IllegalStateException and only logs
the occurrence (do not rethrow), so the send error handling does not propagate a
second exception; locate the call to emitter.completeWithError in the method
that handles IOException and add the separate try {
emitter.completeWithError(ioException); } catch (IllegalStateException e) { /*
log and swallow */ } around it.

In
`@src/test/java/gg/agit/konect/unit/domain/notification/service/NotificationInboxSseServiceTest.java`:
- Around line 116-123: The test is non-deterministic because it calls
notificationInboxSseService.subscribe(1).complete() then re-inserts the emitter
into emitters(), risking the completion callback to remove the entry before
send(1, ...) runs; change the setup to deterministically place a completed
emitter into the service map (e.g., create a new SseEmitter instance, call
complete() on it, and directly put that instance into emitters()) or use a test
double for SseEmitter that is already completed so that
notificationInboxSseService.send(1, response) always exercises the
IllegalStateException/cleanup branch; update NotificationInboxSseServiceTest to
stop relying on subscribe(...) side effects and instead inject the completed
emitter into emitters() before calling send().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f33fbe6-4df8-49bd-8381-1d02a9df592d

📥 Commits

Reviewing files that changed from the base of the PR and between f1c0dab and 1b7adac.

📒 Files selected for processing (2)
  • src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxSseService.java
  • src/test/java/gg/agit/konect/unit/domain/notification/service/NotificationInboxSseServiceTest.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: coverage
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxSseService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxSseService.java
  • src/test/java/gg/agit/konect/unit/domain/notification/service/NotificationInboxSseServiceTest.java

dh2906 added 2 commits April 16, 2026 21:31
- send 실패 후 completeWithError 호출도 이미 종료된 emitter와 경합할 수 있어
  정리 단계의 예외가 다시 상위로 전파되지 않도록 별도 보호 구문을 추가
- 알림 전송 실패 처리의 목적은 끊어진 emitter를 제거하는 것이므로
  종료 경쟁에서 생기는 IllegalStateException은 로그만 남기고 삼키도록 선택
- 기존 단위 테스트를 다시 실행해 알림 inbox SSE 경로가 회귀 없이 유지되는지 확인
- subscribe 경로의 completion callback 타이밍에 따라 테스트가 false positive가 될 수 있어
  완료된 emitter를 직접 주입하는 방식으로 재현 조건을 고정
- 이번 테스트의 목적은 IllegalStateException 정리 분기를 안정적으로 검증하는 것이므로
  구독 로직 부수효과보다 완료된 emitter 상태 자체를 최소 셋업으로 구성
- 수정 후 NotificationInboxSseService 단위 테스트를 다시 실행해 회귀 없이 통과함을 확인
@dh2906 dh2906 merged commit 1029a93 into develop Apr 16, 2026
5 checks passed
@dh2906 dh2906 deleted the fix/sse-completed-emitter-send branch April 16, 2026 12:38
dh2906 added a commit that referenced this pull request Apr 16, 2026
* fix: 완료된 SSE emitter 전송 예외를 정리하도록 처리

- 이미 complete된 emitter에 send를 시도하면 IllegalStateException이 발생할 수 있어
  알림 전송 자체보다 끊어진 연결 정리를 우선하도록 변경
- IOException만 completeWithError로 마무리하고 IllegalStateException은 emitter 제거로만 끝내
  이미 종료된 emitter를 다시 종료하면서 예외가 전파되는 상황을 막음
- 완료된 emitter가 맵에 남아 있어도 예외 없이 정리되는 단위 테스트를 추가해
  재연 경로를 고정하고 회귀를 방지

* fix: SSE emitter 종료 경합 예외를 추가로 흡수

- send 실패 후 completeWithError 호출도 이미 종료된 emitter와 경합할 수 있어
  정리 단계의 예외가 다시 상위로 전파되지 않도록 별도 보호 구문을 추가
- 알림 전송 실패 처리의 목적은 끊어진 emitter를 제거하는 것이므로
  종료 경쟁에서 생기는 IllegalStateException은 로그만 남기고 삼키도록 선택
- 기존 단위 테스트를 다시 실행해 알림 inbox SSE 경로가 회귀 없이 유지되는지 확인

* test: 완료된 SSE emitter 테스트를 결정적으로 고정

- subscribe 경로의 completion callback 타이밍에 따라 테스트가 false positive가 될 수 있어
  완료된 emitter를 직접 주입하는 방식으로 재현 조건을 고정
- 이번 테스트의 목적은 IllegalStateException 정리 분기를 안정적으로 검증하는 것이므로
  구독 로직 부수효과보다 완료된 emitter 상태 자체를 최소 셋업으로 구성
- 수정 후 NotificationInboxSseService 단위 테스트를 다시 실행해 회귀 없이 통과함을 확인
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

버그 정상적으로 동작하지 않는 문제 상황 관련 이슈입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant