Skip to content

Commit 11b8813

Browse files
author
Eugen
committed
Close owned mDNS instance and remove shutdown hook on stop
JmdnsHomekitAdvertiser created its own JmDNS when given an InetAddress but never closed it, and registered a shutdown hook on every advertise() that was never removed. Each stopped HomekitRoot therefore left a running JmDNS instance behind, kept reachable by its shutdown hook. stop() now removes the shutdown hook, closes the JmDNS only if the advertiser created it, and resets the advertising state so later changes to discoverability, MAC or configuration index do not re-register the service.
1 parent 46e2a02 commit 11b8813

3 files changed

Lines changed: 57 additions & 9 deletions

File tree

‎CHANGES.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# HAP-Java 2.0.10
22
* Add optional Mute characteristic to Doorbell service
3+
* Fix leak of mDNS instances and shutdown hooks when stopping `HomekitRoot`
34

45
# HAP-Java 2.0.9
56
* Fix Set Duration and Remaining Duration characteristics to advertise format `uint32` instead of `int` [#202](https://github.com/hap-java/HAP-Java/issues/202)

‎src/main/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiser.java‎

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public class JmdnsHomekitAdvertiser {
1717
private static final String SERVICE_TYPE = "_hap._tcp.local.";
1818

1919
private final JmDNS jmdns;
20+
private final boolean ownsJmdns;
21+
private Thread shutdownHook;
2022
private boolean discoverable = true;
2123
private static final Logger logger = LoggerFactory.getLogger(JmdnsHomekitAdvertiser.class);
2224
private boolean isAdvertising = false;
@@ -30,11 +32,16 @@ public class JmdnsHomekitAdvertiser {
3032
private int category;
3133

3234
public JmdnsHomekitAdvertiser(JmDNS jmdns) {
33-
this.jmdns = jmdns;
35+
this(jmdns, false);
3436
}
3537

3638
public JmdnsHomekitAdvertiser(InetAddress localAddress) throws UnknownHostException, IOException {
37-
jmdns = JmDNS.create(localAddress);
39+
this(JmDNS.create(localAddress), true);
40+
}
41+
42+
JmdnsHomekitAdvertiser(JmDNS jmdns, boolean ownsJmdns) {
43+
this.jmdns = jmdns;
44+
this.ownsJmdns = ownsJmdns;
3845
}
3946

4047
public synchronized void advertise(
@@ -54,18 +61,33 @@ public synchronized void advertise(
5461

5562
registerService();
5663

57-
Runtime.getRuntime()
58-
.addShutdownHook(
59-
new Thread(
60-
() -> {
61-
logger.trace("Stopping advertising in response to shutdown.");
62-
jmdns.unregisterAllServices();
63-
}));
64+
shutdownHook =
65+
new Thread(
66+
() -> {
67+
logger.trace("Stopping advertising in response to shutdown.");
68+
jmdns.unregisterAllServices();
69+
});
70+
Runtime.getRuntime().addShutdownHook(shutdownHook);
6471
isAdvertising = true;
6572
}
6673

6774
public synchronized void stop() {
6875
unregisterService();
76+
isAdvertising = false;
77+
if (shutdownHook != null) {
78+
try {
79+
Runtime.getRuntime().removeShutdownHook(shutdownHook);
80+
} catch (IllegalStateException e) {
81+
// already shutting down
82+
}
83+
}
84+
if (ownsJmdns) {
85+
try {
86+
jmdns.close();
87+
} catch (IOException e) {
88+
logger.warn("Failed to close mDNS", e);
89+
}
90+
}
6991
}
7092

7193
public synchronized void setDiscoverable(boolean discoverable) throws IOException {

‎src/test/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiserTest.java‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
import static org.mockito.ArgumentMatchers.any;
56
import static org.mockito.Mockito.mock;
7+
import static org.mockito.Mockito.never;
68
import static org.mockito.Mockito.verify;
79

810
import java.io.IOException;
@@ -54,6 +56,29 @@ public void testSetConfigurationIndex() throws Exception {
5456
assertThat(getArgumentFromUnregister().getPropertyString("c#")).isEqualTo("1");
5557
}
5658

59+
@Test
60+
public void testStopClosesOwnedJmdns() throws Exception {
61+
subject = new JmdnsHomekitAdvertiser(jmdns, true);
62+
advertise();
63+
subject.stop();
64+
verify(jmdns).close();
65+
}
66+
67+
@Test
68+
public void testStopDoesNotCloseSuppliedJmdns() throws Exception {
69+
advertise();
70+
subject.stop();
71+
verify(jmdns, never()).close();
72+
}
73+
74+
@Test
75+
public void testSetDiscoverableAfterStopDoesNotRegister() throws Exception {
76+
advertise();
77+
subject.stop();
78+
subject.setDiscoverable(false);
79+
verify(jmdns).registerService(any(ServiceInfo.class));
80+
}
81+
5782
private ServiceInfo getArgumentFromUnregister() {
5883
ArgumentCaptor<ServiceInfo> serviceInfoCaptor = ArgumentCaptor.forClass(ServiceInfo.class);
5984
verify(jmdns).unregisterService(serviceInfoCaptor.capture());

0 commit comments

Comments
 (0)