Skip to content

Commit 0771fb5

Browse files
committed
test: make unique address creation thread-safe
1 parent 3bf769f commit 0771fb5

26 files changed

+166
-75
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@types/fs-extra": "^9.0.13",
3030
"@types/mocha": "^10.0.6",
3131
"@types/node": "^18.19.34",
32+
"@types/proper-lockfile": "^4.1.4",
3233
"@types/semver": "^7.5.8",
3334
"@types/shelljs": "^0.8.15",
3435
"@types/which": "^2.0.2",
@@ -49,6 +50,8 @@
4950
"npm-run-all2": "^6.2.0",
5051
"prebuildify": "^6.0.1",
5152
"prettier": "^2.8.8",
53+
"proper-lockfile": "^4.1.2",
54+
"random-words": "^1",
5255
"rocha": "^2.5.10",
5356
"semver": "^7.6.2",
5457
"ts-node": "~10.9.2",

pnpm-lock.yaml

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/unit/helpers.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as path from "path"
22
import * as semver from "semver"
33
import * as fs from "fs"
4+
import * as lockfile from "proper-lockfile"
45

56
import {spawn} from "child_process"
67

@@ -12,18 +13,63 @@ if (semver.satisfies(zmq.version, ">= 4.2")) {
1213
zmq.context.blocky = false
1314
}
1415

15-
/* Windows cannot bind on a ports just above 1014; start higher to be safe. */
16-
let seq = 5000
16+
/**
17+
* Get a unique id to be used as a port number or IPC path.
18+
* This function is thread-safe and will use a lock file to ensure that the id is unique.
19+
*/
20+
let idFallback = 5000
21+
async function getUniqueId() {
22+
const idPath = path.resolve(__dirname, "../../tmp/id")
23+
await fs.promises.mkdir(path.dirname(idPath), {recursive: true})
24+
25+
try {
26+
// Create the file if it doesn't exist
27+
if (!fs.existsSync(idPath)) {
28+
await fs.promises.writeFile(idPath, "5000", "utf8")
29+
30+
/* Windows cannot bind on a ports just above 1014; start higher to be safe. */
31+
return 5000
32+
}
33+
34+
await lockfile.lock(idPath, {retries: 10})
35+
36+
// Read the current number from the file
37+
const idString = await fs.promises.readFile(idPath, "utf8")
38+
let id = parseInt(idString, 10)
39+
40+
// Increment the number
41+
id++
42+
43+
// Ensure the number is within the valid port range
44+
if (id > 65535) {
45+
idFallback++
46+
id = idFallback
47+
}
48+
49+
// Write the new number back to the file
50+
await fs.promises.writeFile(idPath, id.toString(), "utf8")
51+
52+
return id
53+
} catch (err) {
54+
console.error(`Error getting unique id via id file: ${err}`)
55+
return idFallback++
56+
} finally {
57+
// Release the lock
58+
try {
59+
await lockfile.unlock(idPath)
60+
} catch {
61+
// ignore
62+
}
63+
}
64+
}
1765

1866
type Proto = "ipc" | "tcp" | "udp" | "inproc"
1967

20-
export function uniqAddress(proto: Proto) {
21-
const id = seq++
68+
export async function uniqAddress(proto: Proto) {
69+
const id = await getUniqueId()
2270
switch (proto) {
2371
case "ipc": {
2472
const sock = path.resolve(__dirname, `../../tmp/${proto}-${id}`)
25-
// create the directory
26-
fs.mkdirSync(path.dirname(sock), {recursive: true})
2773

2874
return `${proto}://${sock}`
2975
}

test/unit/proxy-router-dealer-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
2222
beforeEach(async function () {
2323
proxy = new zmq.Proxy(new zmq.Router(), new zmq.Dealer())
2424

25-
frontAddress = uniqAddress(proto)
26-
backAddress = uniqAddress(proto)
25+
frontAddress = await uniqAddress(proto)
26+
backAddress = await uniqAddress(proto)
2727

2828
req = new zmq.Request()
2929
rep = new zmq.Reply()

test/unit/proxy-run-test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
2525
})
2626

2727
it("should fail if front end is not bound or connected", async function () {
28-
await proxy.backEnd.bind(uniqAddress(proto))
28+
await proxy.backEnd.bind(await uniqAddress(proto))
2929

3030
try {
3131
await proxy.run()
@@ -39,8 +39,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
3939
})
4040

4141
it("should fail if front end is not open", async function () {
42-
await proxy.frontEnd.bind(uniqAddress(proto))
43-
await proxy.backEnd.bind(uniqAddress(proto))
42+
await proxy.frontEnd.bind(await uniqAddress(proto))
43+
await proxy.backEnd.bind(await uniqAddress(proto))
4444
proxy.frontEnd.close()
4545

4646
try {
@@ -55,7 +55,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
5555
})
5656

5757
it("should fail if back end is not bound or connected", async function () {
58-
await proxy.frontEnd.bind(uniqAddress(proto))
58+
await proxy.frontEnd.bind(await uniqAddress(proto))
5959

6060
try {
6161
await proxy.run()
@@ -71,8 +71,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
7171
})
7272

7373
it("should fail if back end is not open", async function () {
74-
await proxy.frontEnd.bind(uniqAddress(proto))
75-
await proxy.backEnd.bind(uniqAddress(proto))
74+
await proxy.frontEnd.bind(await uniqAddress(proto))
75+
await proxy.backEnd.bind(await uniqAddress(proto))
7676
proxy.backEnd.close()
7777

7878
try {

test/unit/proxy-terminate-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
2525
})
2626

2727
it("should throw if called after termination", async function () {
28-
await proxy.frontEnd.bind(uniqAddress(proto))
29-
await proxy.backEnd.bind(uniqAddress(proto))
28+
await proxy.frontEnd.bind(await uniqAddress(proto))
29+
await proxy.backEnd.bind(await uniqAddress(proto))
3030

3131
const sleep_ms = 50
3232

test/unit/socket-bind-unbind-test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
1919

2020
describe("bind", function () {
2121
it("should resolve", async function () {
22-
await sock.bind(uniqAddress(proto))
22+
await sock.bind(await uniqAddress(proto))
2323
assert.ok(true)
2424
})
2525

2626
it("should throw error if not bound to endpoint", async function () {
27-
const address = uniqAddress(proto)
27+
const address = await uniqAddress(proto)
2828
try {
2929
await sock.unbind(address)
3030
assert.ok(false)
@@ -72,8 +72,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
7272
it("should fail during other bind", async function () {
7373
let promise
7474
try {
75-
promise = sock.bind(uniqAddress(proto))
76-
await sock.bind(uniqAddress(proto))
75+
promise = sock.bind(await uniqAddress(proto))
76+
await sock.bind(await uniqAddress(proto))
7777
assert.ok(false)
7878
} catch (err) {
7979
if (!isFullError(err)) {
@@ -92,7 +92,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
9292

9393
describe("unbind", function () {
9494
it("should unbind", async function () {
95-
const address = uniqAddress(proto)
95+
const address = await uniqAddress(proto)
9696
await sock.bind(address)
9797
await sock.unbind(address)
9898
assert.ok(true)
@@ -130,7 +130,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
130130

131131
it("should fail during other unbind", async function () {
132132
let promise
133-
const address = uniqAddress(proto)
133+
const address = await uniqAddress(proto)
134134
await sock.bind(address)
135135
try {
136136
promise = sock.unbind(address)

test/unit/socket-close-test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
6060
})
6161

6262
it("should close after successful bind", async function () {
63-
const promise = sock.bind(uniqAddress(proto))
63+
const promise = sock.bind(await uniqAddress(proto))
6464
sock.close()
6565
assert.equal(sock.closed, false)
6666
await promise
6767
assert.equal(sock.closed, true)
6868
})
6969

7070
it("should close after unsuccessful bind", async function () {
71-
const address = uniqAddress(proto)
71+
const address = await uniqAddress(proto)
7272
await sock.bind(address)
7373
const promise = sock.bind(address)
7474
sock.close()
@@ -83,7 +83,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
8383
})
8484

8585
it("should close after successful unbind", async function () {
86-
const address = uniqAddress(proto)
86+
const address = await uniqAddress(proto)
8787
await sock.bind(address)
8888
const promise = sock.unbind(address)
8989
sock.close()
@@ -93,7 +93,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
9393
})
9494

9595
it("should close after unsuccessful unbind", async function () {
96-
const address = uniqAddress(proto)
96+
const address = await uniqAddress(proto)
9797
const promise = sock.unbind(address)
9898
sock.close()
9999
assert.equal(sock.closed, false)
@@ -130,7 +130,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
130130
context = undefined
131131

132132
global.gc!()
133-
socket.connect(uniqAddress(proto))
133+
socket.connect(await uniqAddress(proto))
134134
await socket.send(Buffer.from("foo"))
135135
socket.close()
136136
}

test/unit/socket-connect-disconnect-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
5252
if (semver.satisfies(zmq.version, ">= 4.1")) {
5353
it("should allow setting routing id on router", async function () {
5454
sock = new zmq.Router({mandatory: true, linger: 0})
55-
await sock.connect(uniqAddress(proto), {routingId: "remoteId"})
55+
await sock.connect(await uniqAddress(proto), {routingId: "remoteId"})
5656
await sock.send(["remoteId", "hi"])
5757
})
5858
}
5959
})
6060

6161
describe("disconnect", function () {
6262
it("should throw error if not connected to endpoint", async function () {
63-
const address = uniqAddress(proto)
63+
const address = await uniqAddress(proto)
6464
try {
6565
await sock.disconnect(address)
6666
assert.ok(false)

test/unit/socket-curve-send-receive-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) {
3939

4040
describe("when connected", function () {
4141
beforeEach(async function () {
42-
const address = uniqAddress(proto)
42+
const address = await uniqAddress(proto)
4343
await sockB.bind(address)
4444
await sockA.connect(address)
4545
})

0 commit comments

Comments
 (0)