Browse Source

guard cache evict, drop holder array, derive tenant from edge

pull/15733/head
Andrii Landiak 3 months ago
parent
commit
44ea99b1e1
  1. 65
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  2. 6
      application/src/test/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcServiceTest.java

65
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java

@ -405,7 +405,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
cancelPendingDisconnectNotification(edgeId); cancelPendingDisconnectNotification(edgeId);
// Connect notifies immediately; only the disconnect notification is debounced (see scheduleDisconnectNotification). // Connect notifies immediately; only the disconnect notification is debounced (see scheduleDisconnectNotification).
pushStateEventToRuleEngine(tenantId, edge, lastConnectTs, TbMsgType.CONNECT_EVENT); pushStateEventToRuleEngine(tenantId, edge, lastConnectTs, TbMsgType.CONNECT_EVENT);
notifyEdgeConnectivity(tenantId, edge, true); notifyEdgeConnectivity(edge, true);
cancelScheduleEdgeEventsCheck(edgeId); cancelScheduleEdgeEventsCheck(edgeId);
edgeEventsMigrationProcessed.putIfAbsent(edgeId, Boolean.FALSE); edgeEventsMigrationProcessed.putIfAbsent(edgeId, Boolean.FALSE);
scheduleEdgeEventsCheck(edgeGrpcSession); scheduleEdgeEventsCheck(edgeGrpcSession);
@ -562,7 +562,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
long lastDisconnectTs = System.currentTimeMillis(); long lastDisconnectTs = System.currentTimeMillis();
save(tenantId, edgeId, LAST_DISCONNECT_TIME, lastDisconnectTs); save(tenantId, edgeId, LAST_DISCONNECT_TIME, lastDisconnectTs);
pushStateEventToRuleEngine(toRemove.getEdge().getTenantId(), edge, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT); pushStateEventToRuleEngine(toRemove.getEdge().getTenantId(), edge, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT);
scheduleDisconnectNotification(tenantId, edge); scheduleDisconnectNotification(edge);
cancelScheduleEdgeEventsCheck(edgeId); cancelScheduleEdgeEventsCheck(edgeId);
} else { } else {
log.info("[{}] edge session [{}] is not current anymore. Attempting to destroy it by sessionId.", edgeId, sessionId); log.info("[{}] edge session [{}] is not current anymore. Attempting to destroy it by sessionId.", edgeId, sessionId);
@ -578,7 +578,12 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
log.debug("[{}] No session found by sessionId [{}] to destroy", edgeId, sessionId); log.debug("[{}] No session found by sessionId [{}] to destroy", edgeId, sessionId);
} }
} }
evictServiceIdCacheIfOwnedByThisNode(edgeId); // Don't evict while a live session for this edge still exists on this node (e.g. a stale session
// disconnecting after a newer one already replaced it) - that newer session legitimately owns the
// cache entry, and wiping it would let another node's pending task fire a false 'disconnected' notification.
if (!sessions.containsKey(edgeId)) {
evictServiceIdCacheIfOwnedByThisNode(edgeId);
}
} }
// Only evict if the cache still points to this node. If the edge already reconnected to a different // Only evict if the cache still points to this node. If the edge already reconnected to a different
@ -702,22 +707,22 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
} }
} }
private void notifyEdgeConnectivity(TenantId tenantId, Edge edge, boolean connected) { private void notifyEdgeConnectivity(Edge edge, boolean connected) {
try { try {
ctx.getRuleProcessor().process(EdgeConnectionTrigger.builder() ctx.getRuleProcessor().process(EdgeConnectionTrigger.builder()
.tenantId(tenantId) .tenantId(edge.getTenantId())
.customerId(edge.getCustomerId()) .customerId(edge.getCustomerId())
.edgeId(edge.getId()) .edgeId(edge.getId())
.edgeName(edge.getName()) .edgeName(edge.getName())
.connected(connected).build()); .connected(connected).build());
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Failed to process edge connectivity notification (connected={})", tenantId, edge.getId(), connected, e); log.warn("[{}][{}] Failed to process edge connectivity notification (connected={})", edge.getTenantId(), edge.getId(), connected, e);
} }
} }
private void scheduleDisconnectNotification(TenantId tenantId, Edge edge) { private void scheduleDisconnectNotification(Edge edge) {
if (disconnectNotificationDelayMs <= 0) { if (disconnectNotificationDelayMs <= 0) {
notifyEdgeConnectivity(tenantId, edge, false); notifyEdgeConnectivity(edge, false);
return; return;
} }
EdgeId edgeId = edge.getId(); EdgeId edgeId = edge.getId();
@ -725,28 +730,30 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
if (existing != null) { if (existing != null) {
cancelIfPending(existing.future()); cancelIfPending(existing.future());
} }
// Single-element holder so the scheduled task can reference its own pending entry, which doesn't exist // Create the pending entry first so the scheduled task can reference it (for the identity-keyed
// until schedule(...) returns. The task needs it for the identity-keyed remove in fireDelayedDisconnectNotification. // remove in fireDelayedDisconnectNotification), then back-fill its future. Doing this inside compute()
PendingDisconnect[] holder = {null}; // keeps it under the map bin lock, so the future is set before the entry becomes visible to other threads.
PendingDisconnect pending = new PendingDisconnect(edge);
ScheduledFuture<?> future = executorService.schedule( ScheduledFuture<?> future = executorService.schedule(
() -> fireDelayedDisconnectNotification(holder[0]), () -> fireDelayedDisconnectNotification(pending),
disconnectNotificationDelayMs, TimeUnit.MILLISECONDS); disconnectNotificationDelayMs, TimeUnit.MILLISECONDS);
holder[0] = new PendingDisconnect(tenantId, edge, future); pending.setFuture(future);
return holder[0]; return pending;
}); });
} }
private void fireDelayedDisconnectNotification(PendingDisconnect pending) { private void fireDelayedDisconnectNotification(PendingDisconnect pending) {
EdgeId edgeId = pending.edge().getId(); Edge edge = pending.edge();
EdgeId edgeId = edge.getId();
// Identity-keyed remove: don't clobber a newer entry if a second disconnect races with this task firing. // Identity-keyed remove: don't clobber a newer entry if a second disconnect races with this task firing.
pendingDisconnectNotifications.remove(edgeId, pending); pendingDisconnectNotifications.remove(edgeId, pending);
// Re-verify the edge is still disconnected. The cache is cluster-wide, so this also covers the case // Re-verify the edge is still disconnected. The cache is cluster-wide, so this also covers the case
// where the edge dropped on this node and reconnected to a different TB-Core node within the window. // where the edge dropped on this node and reconnected to a different TB-Core node within the window.
if (sessions.containsKey(edgeId) || edgeIdServiceIdCache.get(edgeId) != null) { if (sessions.containsKey(edgeId) || edgeIdServiceIdCache.get(edgeId) != null) {
log.debug("[{}][{}] Edge reconnected within the disconnect notification delay - skipping disconnect notification", pending.tenantId(), edgeId); log.debug("[{}][{}] Edge reconnected within the disconnect notification delay - skipping disconnect notification", edge.getTenantId(), edgeId);
return; return;
} }
notifyEdgeConnectivity(pending.tenantId(), pending.edge(), false); notifyEdgeConnectivity(edge, false);
} }
private void cancelPendingDisconnectNotification(EdgeId edgeId) { private void cancelPendingDisconnectNotification(EdgeId edgeId) {
@ -757,8 +764,28 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
} }
// Carries the context the delayed task needs, so a pending notification can still be fired on shutdown // Carries the context the delayed task needs, so a pending notification can still be fired on shutdown
// (see destroy()), not just cancelled. Package-private for unit testing of fireDelayedDisconnectNotification. // (see destroy()), not just cancelled. The future is back-filled right after scheduling (see
record PendingDisconnect(TenantId tenantId, Edge edge, ScheduledFuture<?> future) {} // scheduleDisconnectNotification). Package-private for unit testing of fireDelayedDisconnectNotification.
static final class PendingDisconnect {
private final Edge edge;
private ScheduledFuture<?> future;
PendingDisconnect(Edge edge) {
this.edge = edge;
}
Edge edge() {
return edge;
}
ScheduledFuture<?> future() {
return future;
}
void setFuture(ScheduledFuture<?> future) {
this.future = future;
}
}
private static void cancelIfPending(ScheduledFuture<?> future) { private static void cancelIfPending(ScheduledFuture<?> future) {
cancelIfPending(future, false); cancelIfPending(future, false);

6
application/src/test/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcServiceTest.java

@ -156,7 +156,7 @@ public class EdgeGrpcServiceTest {
// notification rather than drop it, otherwise a restart within the delay window swallows the alert. // notification rather than drop it, otherwise a restart within the delay window swallows the alert.
when(edgeIdServiceIdCache.get(edgeId)).thenReturn(null); when(edgeIdServiceIdCache.get(edgeId)).thenReturn(null);
when(ctx.getRuleProcessor()).thenReturn(ruleProcessor); when(ctx.getRuleProcessor()).thenReturn(ruleProcessor);
pendingDisconnects().put(edgeId, new EdgeGrpcService.PendingDisconnect(tenantId, edge, null)); pendingDisconnects().put(edgeId, new EdgeGrpcService.PendingDisconnect(edge));
destroy(); destroy();
@ -167,7 +167,7 @@ public class EdgeGrpcServiceTest {
public void givenPendingDisconnectButReconnectedElsewhere_whenDestroy_thenNotificationSuppressed() { public void givenPendingDisconnectButReconnectedElsewhere_whenDestroy_thenNotificationSuppressed() {
// The flush still honors the re-verify guard: an edge that reconnected to another node must not alert. // The flush still honors the re-verify guard: an edge that reconnected to another node must not alert.
when(edgeIdServiceIdCache.get(edgeId)).thenReturn(SimpleTbCacheValueWrapper.wrap(OTHER_NODE)); when(edgeIdServiceIdCache.get(edgeId)).thenReturn(SimpleTbCacheValueWrapper.wrap(OTHER_NODE));
pendingDisconnects().put(edgeId, new EdgeGrpcService.PendingDisconnect(tenantId, edge, null)); pendingDisconnects().put(edgeId, new EdgeGrpcService.PendingDisconnect(edge));
destroy(); destroy();
@ -183,7 +183,7 @@ public class EdgeGrpcServiceTest {
} }
private void fireDelayedDisconnectNotification() { private void fireDelayedDisconnectNotification() {
EdgeGrpcService.PendingDisconnect pending = new EdgeGrpcService.PendingDisconnect(tenantId, edge, null); EdgeGrpcService.PendingDisconnect pending = new EdgeGrpcService.PendingDisconnect(edge);
ReflectionTestUtils.invokeMethod(edgeGrpcService, "fireDelayedDisconnectNotification", pending); ReflectionTestUtils.invokeMethod(edgeGrpcService, "fireDelayedDisconnectNotification", pending);
} }

Loading…
Cancel
Save