Skip to content

Commit 9862ba4

Browse files
committed
CSSTUDIO-3410 Add check for and annunciation of disconnection from the Alarm Server to TalkClient.
1 parent f667776 commit 9862ba4

File tree

1 file changed

+80
-0
lines changed
  • app/alarm/model/src/main/java/org/phoebus/applications/alarm/talk

1 file changed

+80
-0
lines changed

app/alarm/model/src/main/java/org/phoebus/applications/alarm/talk/TalkClient.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77
*******************************************************************************/
88
package org.phoebus.applications.alarm.talk;
99

10+
import static java.lang.Thread.sleep;
1011
import static org.phoebus.applications.alarm.AlarmSystem.logger;
1112

13+
import java.time.Duration;
14+
import java.time.Instant;
1215
import java.util.Collections;
1316
import java.util.List;
1417
import java.util.Objects;
18+
import java.util.Optional;
1519
import java.util.concurrent.CopyOnWriteArrayList;
1620
import java.util.concurrent.atomic.AtomicBoolean;
21+
import java.util.concurrent.atomic.AtomicReference;
1722
import java.util.logging.Level;
1823

1924
import org.apache.kafka.clients.consumer.Consumer;
@@ -41,7 +46,10 @@ public class TalkClient
4146
private final CopyOnWriteArrayList<TalkClientListener> listeners = new CopyOnWriteArrayList<>();
4247
private final AtomicBoolean running = new AtomicBoolean(true);
4348
private final Consumer<String, String> consumer;
49+
private final Consumer<String, String> heartbeatConsumer;
4450
private final Thread thread;
51+
private final Thread updateHeartbeatTimestampThread;
52+
private final Thread annunciateDisconnectionThread;
4553

4654
/** @param server - Kafka Server host:port
4755
* @param config_name - Name of kafka config topic that the talk topic accompanies.
@@ -56,6 +64,16 @@ public TalkClient(final String server, final String config_name)
5664

5765
thread = new Thread(this::run, "TalkClient");
5866
thread.setDaemon(true);
67+
68+
{
69+
heartbeatConsumer = KafkaHelper.connectConsumer(server, List.of(config_name), Collections.emptyList(), AlarmSystem.kafka_properties);
70+
updateHeartbeatTimestampThread = new Thread(() -> updateHeartbeatTimestampLoop(), "UpdateHeartbeatTimestampThread");
71+
updateHeartbeatTimestampThread.setDaemon(false);
72+
updateHeartbeatTimestampThread.start();
73+
annunciateDisconnectionThread = new Thread(() -> annunciateDisconnectionLoop(), "AnnunciateDisconnectionThread");
74+
annunciateDisconnectionThread.setDaemon(false);
75+
annunciateDisconnectionThread.start();
76+
}
5977
}
6078

6179
/** @param listener - Listener to add */
@@ -144,11 +162,57 @@ private void checkUpdates()
144162
}
145163
}
146164

165+
private Optional<Instant> nextDisconnectedAnnunciation = Optional.empty(); // When alarm server is disconnected: point in time for next annunciation of disconnection.
166+
private final Duration disconnectionAnnunciationPeriod = Duration.ofMillis(AlarmSystem.nag_period_ms);
167+
private final Duration idleTimeoutDuration = Duration.ofMillis(AlarmSystem.idle_timeout_ms).multipliedBy(3);
168+
private AtomicReference<Instant> lastReceivedUpdateFromAlarmServer = new AtomicReference<>(Instant.now());
169+
170+
private void updateHeartbeatTimestampLoop() {
171+
while (running.get()) {
172+
final ConsumerRecords<String, String> records = heartbeatConsumer.poll(100);
173+
if (!records.isEmpty()) {
174+
lastReceivedUpdateFromAlarmServer.set(Instant.now());
175+
}
176+
try {
177+
sleep(1000);
178+
} catch (InterruptedException e) {
179+
logger.log(Level.WARNING, "updateHeartbeatTimestampLoop() was interrupted when sleeping.");
180+
}
181+
}
182+
}
183+
184+
/** Background thread loop that detects and annunciates disconnections. */
185+
private void annunciateDisconnectionLoop() {
186+
while (running.get()) {
187+
Instant now = Instant.now();
188+
if (Duration.between(lastReceivedUpdateFromAlarmServer.get(), now).compareTo(idleTimeoutDuration) > 0) {
189+
if (nextDisconnectedAnnunciation.isEmpty() || nextDisconnectedAnnunciation.get().isBefore(now)) {
190+
try {
191+
for (final TalkClientListener listener : listeners) {
192+
listener.messageReceived(SeverityLevel.UNDEFINED, true, "Alarm Server Disconnected");
193+
}
194+
} catch (final Exception ex) {
195+
logger.log(Level.WARNING, "Talk error for " + SeverityLevel.UNDEFINED + ", " + "Alarm Server Disconnected", ex);
196+
}
197+
nextDisconnectedAnnunciation = Optional.of(now.plus(disconnectionAnnunciationPeriod));
198+
}
199+
} else {
200+
nextDisconnectedAnnunciation = Optional.empty(); // Connection to the Alarm Server exists.
201+
}
202+
try {
203+
sleep(1000);
204+
} catch (InterruptedException e) {
205+
logger.log(Level.WARNING, "annunciateDisconnectionLoop() was interrupted when sleeping.");
206+
}
207+
}
208+
}
209+
147210
/** Stop client */
148211
public void shutdown()
149212
{
150213
running.set(false);
151214
consumer.wakeup();
215+
heartbeatConsumer.wakeup();
152216
try
153217
{
154218
thread.join(2000);
@@ -157,6 +221,22 @@ public void shutdown()
157221
{
158222
logger.log(Level.WARNING, "Talk client thread doesn't shut down", ex);
159223
}
224+
try
225+
{
226+
annunciateDisconnectionThread.join(2000);
227+
}
228+
catch (final InterruptedException ex)
229+
{
230+
logger.log(Level.WARNING, "Annunciate Disconnection from Alarm Server thread doesn't shut down", ex);
231+
}
232+
try
233+
{
234+
updateHeartbeatTimestampThread.join(2000);
235+
}
236+
catch (final InterruptedException ex)
237+
{
238+
logger.log(Level.WARNING, "Update Alarm Server Heartbeat thread doesn't shut down", ex);
239+
}
160240
logger.info(thread.getName() + " shut down");
161241
}
162242
}

0 commit comments

Comments
 (0)