Skip to content

Commit 3018395

Browse files
committed
Use toString() for online Set instead of interning Address
Address interning is a potential memory leak if a service creates many distinct addresses. Instead, use addr.toString() for the Set<string> operations on this.online in the directory.
1 parent c0b3c4e commit 3018395

File tree

3 files changed

+32
-52
lines changed

3 files changed

+32
-52
lines changed

acs-directory/lib/mqttcli.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ export default class MQTTCli {
452452

453453
async on_birth(address, payload) {
454454
this.log("device", `Registering BIRTH for ${address}`);
455-
this.online.add(address);
455+
this.online.add(address.toString());
456456

457457
let tree;
458458
if (payload.uuid === UUIDs.FactoryPlus) {
@@ -485,7 +485,7 @@ export default class MQTTCli {
485485
this.log("device", `Registering DEATH for ${address}`);
486486

487487
this.alerts.delete(address);
488-
this.online.delete(address);
488+
this.online.delete(address.toString());
489489
await this.model.death({address, time});
490490

491491
this.log("device", `Finished DEATH for ${address}`);
@@ -552,7 +552,7 @@ export default class MQTTCli {
552552
* rebirth any device we haven't seen a birth for, even if the
553553
* database says it's online. This is important because we might
554554
* have the wrong schema information. */
555-
if (this.online.has(addr) || pending[addr]) return false;
555+
if (this.online.has(addr.toString()) || pending[addr]) return false;
556556

557557
/* Mark that we're working on this device and wait 5-10s to see if
558558
* it rebirths on its own. */
@@ -561,7 +561,7 @@ export default class MQTTCli {
561561

562562
/* Clear our marker first so we retry next time */
563563
delete (pending[addr]);
564-
if (this.online.has(addr)) return false;
564+
if (this.online.has(addr.toString())) return false;
565565

566566
sent[addr] = Date.now();
567567
return true;

lib/js-service-client/lib/sparkplug/util.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ export function sparkplug_safe_string (e) {
5959

6060
/** Class representing a Sparkplug address. */
6161
export class Address {
62-
static #cache = new Map();
63-
6462
/** Construct an address.
6563
* Omitting the `device` or passing the empty string will create a
6664
* Node address.
@@ -71,19 +69,9 @@ export class Address {
7169
constructor (group, node, device) {
7270
if (device == null || device == "")
7371
device = undefined;
74-
75-
const key = device != null
76-
? `${group}/${node}/${device}`
77-
: `${group}/${node}`;
78-
79-
const existing = Address.#cache.get(key);
80-
if (existing) return existing;
81-
8272
this.group = group;
8373
this.node = node;
8474
this.device = device;
85-
86-
Address.#cache.set(key, this);
8775
}
8876

8977
/** Convert to Address.
Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,50 @@
11
import { describe, it, expect } from "vitest";
22
import { Address } from "../lib/sparkplug/util.js";
33

4-
describe("Address interning", () => {
5-
it("returns the same object for identical node addresses", () => {
6-
const a = new Address("G", "N");
7-
const b = new Address("G", "N");
8-
expect(a).toBe(b);
9-
});
10-
11-
it("returns the same object for identical device addresses", () => {
12-
const a = new Address("G", "N", "D");
13-
const b = new Address("G", "N", "D");
14-
expect(a).toBe(b);
15-
});
16-
17-
it("returns different objects for different addresses", () => {
18-
const a = new Address("G", "N", "D1");
19-
const b = new Address("G", "N", "D2");
20-
expect(a).not.toBe(b);
21-
});
22-
4+
describe("Address", () => {
235
it("treats empty string device as node address", () => {
246
const a = new Address("G", "N", "");
25-
const b = new Address("G", "N");
26-
expect(a).toBe(b);
7+
expect(a.device).toBeUndefined();
278
});
289

2910
it("treats null device as node address", () => {
3011
const a = new Address("G", "N", null);
31-
const b = new Address("G", "N");
32-
expect(a).toBe(b);
12+
expect(a.device).toBeUndefined();
3313
});
3414

35-
it("works correctly with Set", () => {
36-
const set = new Set();
15+
it("toString formats node address", () => {
16+
const a = new Address("G", "N");
17+
expect(a.toString()).toBe("G/N");
18+
});
19+
20+
it("toString formats device address", () => {
3721
const a = new Address("G", "N", "D");
38-
set.add(a);
22+
expect(a.toString()).toBe("G/N/D");
23+
});
3924

25+
it("equals compares by value", () => {
26+
const a = new Address("G", "N", "D");
4027
const b = new Address("G", "N", "D");
41-
expect(set.has(b)).toBe(true);
28+
expect(a.equals(b)).toBe(true);
29+
});
4230

43-
set.delete(b);
44-
expect(set.has(a)).toBe(false);
31+
it("equals returns false for different addresses", () => {
32+
const a = new Address("G", "N", "D1");
33+
const b = new Address("G", "N", "D2");
34+
expect(a.equals(b)).toBe(false);
4535
});
4636

47-
it("parse returns interned instances", () => {
37+
it("parse round-trips with toString", () => {
4838
const a = new Address("G", "N", "D");
49-
const b = Address.parse("G/N/D");
50-
expect(a).toBe(b);
39+
const b = Address.parse(a.toString());
40+
expect(b.equals(a)).toBe(true);
5141
});
5242

53-
it("parent_node returns interned instances", () => {
54-
const a = new Address("G", "N");
55-
const b = new Address("G", "N", "D").parent_node();
56-
expect(a).toBe(b);
43+
it("parent_node returns node address", () => {
44+
const a = new Address("G", "N", "D");
45+
const parent = a.parent_node();
46+
expect(parent.group).toBe("G");
47+
expect(parent.node).toBe("N");
48+
expect(parent.device).toBeUndefined();
5749
});
5850
});

0 commit comments

Comments
 (0)