# 클래스 javadoc 이 적은 성질
/**
* Atomic pointer from profile name to its current runtime generation (design §7.2).
*
*
A swap publishes the replacement first and drains the predecessor afterwards, so a rotation is
* never observable as a gap. The single scheduled executor exists only to enforce drain deadlines
* and is created lazily; it is shut down with the registry so no thread outlives it.
*/
# close() 전체
120: public void close() {
121: List all = new ArrayList<>();
122: runtimes.values().forEach(holder -> all.add(holder.get()));
123: // Retired-but-still-draining generations are closed too; they used to survive registry
124: // shutdown entirely.
125: all.addAll(retired);
126: // Every runtime is closed even when one refuses. forEach stopped at the first exception, so a
127: // single misbehaving pool left every remaining connection, thread and socket open — shutdown
128: // leaked more the worse the failure was.
129: RuntimeException firstFailure = null;
130: for (ClientRuntime runtime : all) {
131: try {
132: runtime.forceClose();
133: } catch (RuntimeException failure) {
134: if (firstFailure == null) {
135: firstFailure = failure;
136: } else {
137: firstFailure.addSuppressed(failure);
138: }
139: }
140: }
141: retired.clear();
142: runtimes.clear();
143: if (firstFailure != null) {
144: throw firstFailure;
145: }
146: ScheduledExecutorService scheduler = drainScheduler.getAndSet(null);
147: if (scheduler != null) {
148: // Await termination: a registry that returns while its drain thread is still alive would
149: // leak a thread per rotation cycle, which the resource-bound suite exists to catch.
150: scheduler.shutdownNow();
151: try {
152: if (!scheduler.awaitTermination(SHUTDOWN_AWAIT_MILLIS, TimeUnit.MILLISECONDS)) {
153: throw new IllegalStateException("http client drain scheduler did not terminate");
154: }
155: } catch (InterruptedException interrupted) {
156: Thread.currentThread().interrupt();
157: }
158: }
159: }
# 런타임 닫기는 상태를 먼저 바꾼다
90: /** Forced close at the drain deadline; in-flight calls lose their connections by design. */
91: public final void forceClose() {
92: close();
93: }
94:
95: @Override
96: public final void close() {
97: ClientRuntimeState previous = state.getAndSet(ClientRuntimeState.CLOSED);
98: if (previous != ClientRuntimeState.CLOSED) {
99: resourceCloser.run();
100: }
101: }
102:}
# 배수 스레드를 만드는 곳과 그 조건
99: previous.beginDrain(drainTimeout);
100: if (previous.state() != ClientRuntimeState.CLOSED && !drainTimeout.isZero()) {
101: ScheduledFuture> unusedDrainDeadline =
102: scheduler()
103: .schedule(
172: return existing;
173: }
174: ScheduledExecutorService created =
175: Executors.newSingleThreadScheduledExecutor(
176: runnable -> {
177: Thread thread = new Thread(runnable, "httpclient-runtime-drain");
178: thread.setDaemon(true);
179: return thread;
# 주석이 감시자로 지목한 묶음
18:/** Repeated rotation must not accumulate generations or threads (design §7.2, §28.8). */
19:class RuntimeRotationDrainTest {
20:
21: @Test
22: void repeatedRotationDrainsEveryPreviousGenerationAndLeavesNoThread() {
30: try (ClientRuntimeRegistry registry = new ClientRuntimeRegistry(Map.of(first.name(), first))) {
31: for (int generation = 2; generation <= 50; generation++) {
32: ClientRuntime replacement =
33: new ClientRuntime(
34: ClientProfiles.builder("rotating").build(),
35: new RuntimeGeneration(generation),
36: closed::incrementAndGet);
37: registry.swap(first.name(), replacement, Duration.ofSeconds(1));
38: }
44: PerformanceAssertions.structural("every retired generation was closed", closed.get() == 50);
45: assertThat(first.state()).isEqualTo(ClientRuntimeState.CLOSED);
46: Set threadNames =
47: Thread.getAllStackTraces().keySet().stream()
48: .map(Thread::getName)
49: .collect(Collectors.toSet());
50: assertThat(threadNames).noneMatch(name -> name.startsWith("httpclient-runtime-drain"));
51: }
# 같은 성질을 보는 단위 test
@Test
void closingTheRegistryReleasesEveryGenerationAndLeavesNoThread() {
ClientRuntime first = running(1);
ClientRuntime second = running(2);
ClientRuntimeRegistry registry = new ClientRuntimeRegistry(Map.of(first.name(), first));
ClientRuntimeLease lease = registry.acquire(first.name());
registry.swap(first.name(), second, Duration.ofSeconds(30));
registry.close();
lease.close();
assertThat(first.state()).isEqualTo(ClientRuntimeState.CLOSED);
assertThat(second.state()).isEqualTo(ClientRuntimeState.CLOSED);
Set threadNames =
Thread.getAllStackTraces().keySet().stream()
.map(Thread::getName)
.collect(Collectors.toSet());
assertThat(threadNames).noneMatch(name -> name.startsWith("httpclient-runtime-drain"));
}