# 주제: 종료 시 드레인 루프가 프로덕션에서 아무도 증가시키지 않는 카운터를 폴링한다
# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
# severity: P2

# ---- 종료 순서 ----
# MessagingShutdownLifecycle.java:83-102
#   public void stop() {
#     if (!running.compareAndSet(true, false)) return;
#     admission.stopAcceptingNewWork();                 // (1) 새 발행 거절
#     Instant startedAt = clock.get();
#     drain.beginDrain(startedAt);                      // (2) 드레인 시작
#     Instant deadline = startedAt.plus(drainDeadline);
#     while (!drain.isDrained(clock.get()) && clock.get().isBefore(deadline)) {
#       Thread.sleep(POLL.toMillis());                  // (3) 드레인 완료까지 대기
#     }
#   }
# 클래스 javadoc:15-19
#   "a context shutdown went straight to closing beans while publishes were still in flight ...
#    which is the shape that turns a deploy into duplicated effects and unexplained ambiguous
#    publishes."

# ---- 두 개의 서로 다른 in-flight 카운터 ----
# (A) MessagingAdmissionController.inFlight()   <- InFlightLimiter. 발행이 증가시킨다.
#     MessagingCoreAutoConfiguration:439-447 이 이것을 DefaultMessagePublisher 에 주입한다.
#     MessagingEndpoint:55 가 이것을 actuator 로 보고한다.
# (B) GracefulShutdownCoordinator.inFlight()    <- tryBeginWork()/endWork() 가 증가·감소시킨다.
#     MessagingShutdownLifecycle 의 드레인 루프가 폴링하는 것은 이쪽이다.

# ---- (B)를 증가시키는 프로덕션 코드가 없다 ----
# command: git grep -n "tryBeginWork|endWork()|mayCreateRetryAttempt" -- src | grep "/src/main/"
src/messaging/messaging-kafka/src/main/java/.../KafkaConsumerRegistrar.java:233,244,289,325
src/messaging/messaging-rabbit/src/main/java/.../RabbitConsumerRegistrar.java:103,150,217
# => 프로덕션 소비자는 두 개의 ConsumerRegistrar 뿐이다.
#    그 둘은 src/main 에서 한 번도 생성되지 않는다(EVD-269 계열, 그리고 EVD-316 의 스타터 빈 전수).
#
# command: git grep -n "GracefulShutdownCoordinator" -- src/messaging/messaging-runtime-core
# exit: 1   (DefaultMessagePublisher 는 이 타입을 모른다)
# DefaultMessagePublisher 생성자 파라미터(6-arg, 스타터가 쓰는 것):
#   DestinationProfileRegistry, DestinationAccessPolicy, MessageCodecRegistry,
#   MessagingAdmissionController, MessagingRuntimeRegistry, MessagingTransport
#   -> GracefulShutdownCoordinator 없음.

# ---- 결과 ----
# 부팅된 애플리케이션에서 GracefulShutdownCoordinator.inFlight() 는 항상 0이다.
# GracefulShutdownCoordinator.isDrained(now):126-128
#   if (!draining.get()) return false;
#   if (inFlight.get() == 0) return true;        <-- 항상 여기서 true
# => beginDrain 직후 첫 검사에서 isDrained 가 true 이고, while 루프는 한 번도 sleep 하지 않는다.
#    stop() 은 즉시 반환한다.
#
# 즉 (1) 새 발행 거절은 실제로 동작하지만,
#    (3) "이미 진행 중인 발행을 기다린다" 는 부분은 동작하지 않는다.
#    기다려야 할 대상은 (A)의 카운터인데 루프는 (B)를 본다.
#    javadoc 이 고쳤다고 말하는 "publishes were still in flight" 상태로 빈 소멸이 시작된다.

# ---- 테스트가 이것을 드러내지 못하는 이유 ----
# MessagingShutdownLifecycleTest.java:52-73
#   void aLeakedHandlerDoesNotHoldTheProcessOpen() {
#     // Work that begins and never ends: the drain can only finish by reaching its deadline.
#     GracefulShutdownCoordinator leaking = new GracefulShutdownCoordinator(Duration.ofMillis(100));
#     leaking.tryBeginWork();                   <-- 테스트가 직접 카운터를 채운다
#     ...
#   }
# => 프로덕션에서 아무도 호출하지 않는 메서드를 테스트가 손으로 호출해 상황을 만든다.
#
# MessagingShutdownLifecycleTest.java:37-50
#   void stoppingRefusesNewPublishesFirst() {
#     lifecycle.stop();
#     assertThatThrownBy(() -> admission.admit("order-events", 16)) ... // (1) 검증
#     assertThat(drain.isAcceptingWork()).isFalse();                    // (2) 검증
#     assertThat(lifecycle.isRunning()).isFalse();
#   }
# => 공유 drain 의 inFlight 는 0이므로 즉시 드레인 완료된다.
#    이 테스트는 "기다렸다" 를 단언하지 않는다 — (1)과 (2)만 본다.

# ---- 참고: 8단계 계약과의 관계 ----
# MessagingLifecycle.ShutdownPhase (messaging-transport-spi) 는 8단계를 선언하고 구현체가 0이다.
# MessagingShutdownLifecycle 이 수행하는 것:
#   STOP_PUBLISH_ADMISSION  -> admission.stopAcceptingNewWork()   (실제 동작)
#   STOP_NEW_HANDLERS       -> drain.beginDrain() 이 isAcceptingWork()=false 로 만듦
#                              (그러나 그것을 읽는 프로덕션 코드가 없음)
#   DRAIN_HANDLERS          -> 폴링 루프 (그러나 카운터가 항상 0)
#   CLOSE_CONNECTIONS       -> Spring 빈 소멸. KafkaMessagingAutoConfiguration:128
#                              @Bean(destroyMethod = "close") 가 producer 를 닫는다.
#   PAUSE_CONSUMERS / FLUSH_SETTLEMENTS / AWAIT_PRODUCER_CONFIRMS / RELEASE_OUTBOX_LEASES
#                           -> 수행 주체 없음
# (단계별 상세 판정은 analysis/messaging/messaging-transport-spi.md 소유)
