Skip to content

Commit 22b0971

Browse files
Trottrvagg
authored andcommitted
test: eliminate multicast test FreeBSD flakiness
test-dgram-multicast-multi-process was flaky on FreeBSD and Raspeberry Pi. This refactoring fixes the issue by eliminating a race condition. Fixes: #2474 PR-URL: #4042 Reviewed-By: Ben Noordhuis <[email protected]>
1 parent c93e267 commit 22b0971

File tree

1 file changed

+150
-151
lines changed

1 file changed

+150
-151
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,122 @@
11
'use strict';
2-
var common = require('../common'),
3-
assert = require('assert'),
4-
dgram = require('dgram'),
5-
util = require('util'),
6-
Buffer = require('buffer').Buffer,
7-
fork = require('child_process').fork,
8-
LOCAL_BROADCAST_HOST = '224.0.0.114',
9-
TIMEOUT = common.platformTimeout(5000),
10-
messages = [
11-
new Buffer('First message to send'),
12-
new Buffer('Second message to send'),
13-
new Buffer('Third message to send'),
14-
new Buffer('Fourth message to send')
15-
];
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const dgram = require('dgram');
5+
const fork = require('child_process').fork;
6+
const LOCAL_BROADCAST_HOST = '224.0.0.114';
7+
const TIMEOUT = common.platformTimeout(5000);
8+
const messages = [
9+
new Buffer('First message to send'),
10+
new Buffer('Second message to send'),
11+
new Buffer('Third message to send'),
12+
new Buffer('Fourth message to send')
13+
];
14+
const workers = {};
15+
const listeners = 3;
16+
17+
18+
// Skip test in FreeBSD jails.
19+
if (common.inFreeBSDJail) {
20+
console.log('1..0 # Skipped: In a FreeBSD jail');
21+
return;
22+
}
23+
24+
function launchChildProcess(index) {
25+
const worker = fork(__filename, ['child']);
26+
workers[worker.pid] = worker;
27+
28+
worker.messagesReceived = [];
29+
30+
// Handle the death of workers.
31+
worker.on('exit', function(code, signal) {
32+
// Don't consider this the true death if the worker has finished
33+
// successfully or if the exit code is 0.
34+
if (worker.isDone || code === 0) {
35+
return;
36+
}
37+
38+
dead += 1;
39+
console.error('[PARENT] Worker %d died. %d dead of %d',
40+
worker.pid,
41+
dead,
42+
listeners);
43+
44+
if (dead === listeners) {
45+
console.error('[PARENT] All workers have died.');
46+
console.error('[PARENT] Fail');
47+
process.exit(1);
48+
}
49+
});
50+
51+
worker.on('message', function(msg) {
52+
if (msg.listening) {
53+
listening += 1;
54+
55+
if (listening === listeners) {
56+
// All child process are listening, so start sending.
57+
sendSocket.sendNext();
58+
}
59+
return;
60+
}
61+
if (msg.message) {
62+
worker.messagesReceived.push(msg.message);
63+
64+
if (worker.messagesReceived.length === messages.length) {
65+
done += 1;
66+
worker.isDone = true;
67+
console.error('[PARENT] %d received %d messages total.',
68+
worker.pid,
69+
worker.messagesReceived.length);
70+
}
71+
72+
if (done === listeners) {
73+
console.error('[PARENT] All workers have received the ' +
74+
'required number of messages. Will now compare.');
75+
76+
Object.keys(workers).forEach(function(pid) {
77+
const worker = workers[pid];
78+
79+
var count = 0;
80+
81+
worker.messagesReceived.forEach(function(buf) {
82+
for (var i = 0; i < messages.length; ++i) {
83+
if (buf.toString() === messages[i].toString()) {
84+
count++;
85+
break;
86+
}
87+
}
88+
});
89+
90+
console.error('[PARENT] %d received %d matching messages.',
91+
worker.pid, count);
92+
93+
assert.strictEqual(count, messages.length,
94+
'A worker received an invalid multicast message');
95+
});
96+
97+
clearTimeout(timer);
98+
console.error('[PARENT] Success');
99+
killChildren(workers);
100+
}
101+
}
102+
});
103+
}
104+
105+
function killChildren(children) {
106+
Object.keys(children).forEach(function(key) {
107+
const child = children[key];
108+
child.kill();
109+
});
110+
}
16111

17112
if (process.argv[2] !== 'child') {
18-
var workers = {},
19-
listeners = 3,
20-
listening = 0,
21-
dead = 0,
22-
i = 0,
23-
done = 0,
24-
timer = null;
25-
26-
//exit the test if it doesn't succeed within TIMEOUT
27-
timer = setTimeout(function() {
113+
var listening = 0;
114+
var dead = 0;
115+
var i = 0;
116+
var done = 0;
117+
118+
// Exit the test if it doesn't succeed within TIMEOUT.
119+
var timer = setTimeout(function() {
28120
console.error('[PARENT] Responses were not received within %d ms.',
29121
TIMEOUT);
30122
console.error('[PARENT] Fail');
@@ -34,101 +126,18 @@ if (process.argv[2] !== 'child') {
34126
process.exit(1);
35127
}, TIMEOUT);
36128

37-
//launch child processes
129+
// Launch child processes.
38130
for (var x = 0; x < listeners; x++) {
39-
(function() {
40-
var worker = fork(process.argv[1], ['child']);
41-
workers[worker.pid] = worker;
42-
43-
worker.messagesReceived = [];
44-
45-
//handle the death of workers
46-
worker.on('exit', function(code, signal) {
47-
// don't consider this the true death if the
48-
// worker has finished successfully
49-
50-
// or if the exit code is 0
51-
if (worker.isDone || code === 0) {
52-
return;
53-
}
54-
55-
dead += 1;
56-
console.error('[PARENT] Worker %d died. %d dead of %d',
57-
worker.pid,
58-
dead,
59-
listeners);
60-
61-
if (dead === listeners) {
62-
console.error('[PARENT] All workers have died.');
63-
console.error('[PARENT] Fail');
64-
65-
killChildren(workers);
66-
67-
process.exit(1);
68-
}
69-
});
70-
71-
worker.on('message', function(msg) {
72-
if (msg.listening) {
73-
listening += 1;
74-
75-
if (listening === listeners) {
76-
//all child process are listening, so start sending
77-
sendSocket.sendNext();
78-
}
79-
}
80-
else if (msg.message) {
81-
worker.messagesReceived.push(msg.message);
82-
83-
if (worker.messagesReceived.length === messages.length) {
84-
done += 1;
85-
worker.isDone = true;
86-
console.error('[PARENT] %d received %d messages total.',
87-
worker.pid,
88-
worker.messagesReceived.length);
89-
}
90-
91-
if (done === listeners) {
92-
console.error('[PARENT] All workers have received the ' +
93-
'required number of messages. Will now compare.');
94-
95-
Object.keys(workers).forEach(function(pid) {
96-
var worker = workers[pid];
97-
98-
var count = 0;
99-
100-
worker.messagesReceived.forEach(function(buf) {
101-
for (var i = 0; i < messages.length; ++i) {
102-
if (buf.toString() === messages[i].toString()) {
103-
count++;
104-
break;
105-
}
106-
}
107-
});
108-
109-
console.error('[PARENT] %d received %d matching messages.',
110-
worker.pid, count);
111-
112-
assert.equal(count, messages.length,
113-
'A worker received an invalid multicast message');
114-
});
115-
116-
clearTimeout(timer);
117-
console.error('[PARENT] Success');
118-
killChildren(workers);
119-
}
120-
}
121-
});
122-
})(x);
131+
launchChildProcess(x);
123132
}
124133

125134
var sendSocket = dgram.createSocket('udp4');
126-
// FIXME a libuv limitation makes it necessary to bind()
127-
// before calling any of the set*() functions - the bind()
128-
// call is what creates the actual socket...
135+
// FIXME: a libuv limitation makes it necessary to bind()
136+
// before calling any of the set*() functions. The bind()
137+
// call is what creates the actual socket.
129138
sendSocket.bind();
130139

131-
// The socket is actually created async now
140+
// The socket is actually created async now.
132141
sendSocket.on('listening', function() {
133142
sendSocket.setTTL(1);
134143
sendSocket.setBroadcast(true);
@@ -141,7 +150,7 @@ if (process.argv[2] !== 'child') {
141150
});
142151

143152
sendSocket.sendNext = function() {
144-
var buf = messages[i++];
153+
const buf = messages[i++];
145154

146155
if (!buf) {
147156
try { sendSocket.close(); } catch (e) {}
@@ -151,61 +160,51 @@ if (process.argv[2] !== 'child') {
151160
sendSocket.send(buf, 0, buf.length,
152161
common.PORT, LOCAL_BROADCAST_HOST, function(err) {
153162
if (err) throw err;
154-
console.error('[PARENT] sent %s to %s:%s',
155-
util.inspect(buf.toString()),
163+
console.error('[PARENT] sent "%s" to %s:%s',
164+
buf.toString(),
156165
LOCAL_BROADCAST_HOST, common.PORT);
157166
process.nextTick(sendSocket.sendNext);
158167
});
159168
};
160-
161-
function killChildren(children) {
162-
Object.keys(children).forEach(function(key) {
163-
var child = children[key];
164-
child.kill();
165-
});
166-
}
167169
}
168170

169171
if (process.argv[2] === 'child') {
170-
var receivedMessages = [];
171-
var listenSocket = dgram.createSocket({
172+
const receivedMessages = [];
173+
const listenSocket = dgram.createSocket({
172174
type: 'udp4',
173175
reuseAddr: true
174176
});
175177

176-
listenSocket.on('message', function(buf, rinfo) {
177-
console.error('[CHILD] %s received %s from %j', process.pid,
178-
util.inspect(buf.toString()), rinfo);
178+
listenSocket.on('listening', function() {
179+
listenSocket.addMembership(LOCAL_BROADCAST_HOST);
179180

180-
receivedMessages.push(buf);
181+
listenSocket.on('message', function(buf, rinfo) {
182+
console.error('[CHILD] %s received "%s" from %j', process.pid,
183+
buf.toString(), rinfo);
181184

182-
process.send({ message: buf.toString() });
185+
receivedMessages.push(buf);
183186

184-
if (receivedMessages.length == messages.length) {
185-
// .dropMembership() not strictly needed but here as a sanity check
186-
listenSocket.dropMembership(LOCAL_BROADCAST_HOST);
187-
process.nextTick(function() {
188-
listenSocket.close();
189-
});
190-
}
191-
});
187+
process.send({ message: buf.toString() });
192188

193-
listenSocket.on('close', function() {
194-
//HACK: Wait to exit the process to ensure that the parent
195-
//process has had time to receive all messages via process.send()
196-
//This may be indicitave of some other issue.
197-
setTimeout(function() {
198-
process.exit();
199-
}, 1000);
200-
});
189+
if (receivedMessages.length == messages.length) {
190+
// .dropMembership() not strictly needed but here as a sanity check.
191+
listenSocket.dropMembership(LOCAL_BROADCAST_HOST);
192+
process.nextTick(function() {
193+
listenSocket.close();
194+
});
195+
}
196+
});
201197

202-
listenSocket.on('listening', function() {
198+
listenSocket.on('close', function() {
199+
// HACK: Wait to exit the process to ensure that the parent
200+
// process has had time to receive all messages via process.send()
201+
// This may be indicative of some other issue.
202+
setTimeout(function() {
203+
process.exit();
204+
}, common.platformTimeout(1000));
205+
});
203206
process.send({ listening: true });
204207
});
205208

206209
listenSocket.bind(common.PORT);
207-
208-
listenSocket.on('listening', function() {
209-
listenSocket.addMembership(LOCAL_BROADCAST_HOST);
210-
});
211210
}

0 commit comments

Comments
 (0)