Skip to content

[FIX] 스케줄러 기반 SSE ping으로 idle 연결 끊김 방지#6

Merged
soomin0209 merged 2 commits into
devfrom
fix/sse
Apr 27, 2026
Merged

[FIX] 스케줄러 기반 SSE ping으로 idle 연결 끊김 방지#6
soomin0209 merged 2 commits into
devfrom
fix/sse

Conversation

@soomin0209
Copy link
Copy Markdown
Collaborator

@soomin0209 soomin0209 commented Apr 27, 2026

📝 작업 내용

  • 알림 구독 후 45초쯤 뒤 SSE 연결이 끊기는 문제 수정
  • @Scheduled(fixedRate = 30000)으로 30초마다 ping 이벤트 전송
  • 중간 인프라(nginx, 프록시 등)의 idle timeout으로 인한 연결 종료 방지

✅ 체크리스트 (Checklist)

  • 브랜치 이름 규칙을 준수했나요? (예: feat/login)
  • 코딩 컨벤션을 준수했나요?
  • 기능에 대한 테스트 코드를 작성/수행했나요?
  • 불필요한 주석이나 로그(console.log)를 제거했나요?

💬 기타 사항

중간 발표 때 튜터님이 말씀해주신 이슈 해결한 내용입니다!

Summary by CodeRabbit

신규 기능

  • 실시간 알림 연결의 안정성을 위한 자동 연결 유지 기능이 추가되었습니다. 서버에서는 주기적으로 클라이언트의 연결 상태를 확인하고 문제 있는 연결을 자동으로 정리하여 알림 서비스의 신뢰성을 향상시킵니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@soomin0209 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 37 minutes and 45 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 87da0084-df07-4a76-97e5-77e9712d21db

📥 Commits

Reviewing files that changed from the base of the PR and between d249277 and 83b3bda.

📒 Files selected for processing (1)
  • src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java

Walkthrough

Spring Boot 애플리케이션이 @EnableScheduling 어노테이션을 통해 스케줄된 작업 처리를 활성화하고, SSE 연결을 유지하기 위해 30초 간격의 주기적 핑(ping) 메커니즘이 추가됩니다.

Changes

Cohort / File(s) Summary
Application Scheduling Configuration
src/main/java/com/example/auctionnotification/AuctionNotificationApplication.java
@EnableScheduling 어노테이션과 관련 임포트 추가로 스케줄된 작업 처리 활성화.
SSE Connection Lifecycle Management
src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java
30초 간격으로 실행되는 sendPing() 메서드 추가. 모든 등록된 SSE emitter에 ping 이벤트를 전송하고, IOException 발생 시 emitter를 제거하며 경고 로그 기록.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 SSE idle 연결 문제를 스케줄러 기반 ping으로 해결하는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명은 작업 내용, 체크리스트, 기타 사항을 포함하고 있으나, 관련 이슈 번호(Closes #)가 누락되어 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sse

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.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java (1)

45-71: ⚠️ Potential issue | 🟡 Minor

동일 emitter에 대한 concurrent send() 호출은 가능하지만 Spring의 동기화로 안전함.

send()sendPing()이 모두 @Async("notificationExecutorWithVT")로 비동기 실행되고 @Scheduled(fixedRate = 30000)이 이전 실행 완료를 기다리지 않으므로, 동일한 SseEmitter에 여러 virtual thread의 동시 접근이 가능합니다. 다만 Spring Framework의 ResponseBodyEmitter#send()는 내부적으로 synchronized 메서드로 구현되어(Spring 4.2 SPR-13224 이후) SSE 프레임 인터리브를 방지하므로 클라이언트 파싱 이슈는 없습니다.

주의할 점은 메시지 순서 보장이 없다는 것입니다. 동기화는 프레임 손상을 막지만 서로 다른 스레드의 이벤트들이 도착 순서와 다르게 클라이언트에 전달될 수 있으므로, 메시지 순서가 중요하다면 emitter 인스턴스에 대한 외부 동기화를 검토하세요.

부수적으로, name("ping") 대신 SSE 주석 라인(:\n\n) 형태 keep-alive는 클라이언트 리스너를 트리거하지 않아 keep-alive 용도로 더 적합합니다(선택 사항).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java`
around lines 45 - 71, SseEmitterService currently allows concurrent send(Long,
NotificationResponse) and sendPing() calls on the same SseEmitter (both
annotated `@Async`), which Spring's ResponseBodyEmitter#send synchronizes
internally but does NOT guarantee message ordering; if ordering matters, add
external per-emitter synchronization: create and manage a per-user lock (e.g., a
ConcurrentHashMap<Long, Object> locks or a lightweight per-emitter mutex) and
wrap all calls that use emitters.get(userId) / emitters.entrySet() sends in
synchronized blocks on that lock (ensure send() and sendPing() both acquire the
same lock for a given user), and remove/cleanup the lock when removing the
emitter in error handling to avoid leaks; keep the existing IOException handling
and emitter removal logic in send/sendPing.
🤖 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/com/example/auctionnotification/notification/service/SseEmitterService.java`:
- Around line 62-71: The sendPing method iterates emitters and only catches
IOException, which allows other runtime exceptions (e.g., IllegalStateException
thrown by SseEmitter.send when the emitter has already completed) to escape and
abort the whole ping loop; update sendPing to catch Exception (or at least
IllegalStateException in addition to IOException) around
entry.getValue().send(...) so a failure for one emitter doesn't stop pings for
others, and when removing the emitter call emitters.remove(entry.getKey(),
entry.getValue()); also include the caught exception object in the log.warn call
(pass e as a parameter) to preserve the stacktrace and aid debugging; reference:
sendPing, emitters, SseEmitter.send, IOException, IllegalStateException, and the
existing log.warn/log.error usage.

---

Outside diff comments:
In
`@src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java`:
- Around line 45-71: SseEmitterService currently allows concurrent send(Long,
NotificationResponse) and sendPing() calls on the same SseEmitter (both
annotated `@Async`), which Spring's ResponseBodyEmitter#send synchronizes
internally but does NOT guarantee message ordering; if ordering matters, add
external per-emitter synchronization: create and manage a per-user lock (e.g., a
ConcurrentHashMap<Long, Object> locks or a lightweight per-emitter mutex) and
wrap all calls that use emitters.get(userId) / emitters.entrySet() sends in
synchronized blocks on that lock (ensure send() and sendPing() both acquire the
same lock for a given user), and remove/cleanup the lock when removing the
emitter in error handling to avoid leaks; keep the existing IOException handling
and emitter removal logic in send/sendPing.
🪄 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: CHILL

Plan: Pro

Run ID: 8bbd5b2a-c317-4981-94ba-7bfc842fe804

📥 Commits

Reviewing files that changed from the base of the PR and between 9883899 and d249277.

📒 Files selected for processing (2)
  • src/main/java/com/example/auctionnotification/AuctionNotificationApplication.java
  • src/main/java/com/example/auctionnotification/notification/service/SseEmitterService.java

Copy link
Copy Markdown
Contributor

@imprity imprity left a comment

Choose a reason for hiding this comment

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

LGTM!

@soomin0209 soomin0209 merged commit dd8192c into dev Apr 27, 2026
2 checks passed
@soomin0209 soomin0209 deleted the fix/sse branch April 27, 2026 10:04
@soomin0209 soomin0209 added the BugFix 버그를 고쳤습니다 label May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BugFix 버그를 고쳤습니다

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants