Skip to content

Commit c623001

Browse files
committed
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: nodejs#2474
1 parent d1000b4 commit c623001

File tree

1 file changed

+136
-139
lines changed

1 file changed

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

17108
if (process.argv[2] !== 'child') {
18109
var workers = {},
19110
listeners = 3,
20111
listening = 0,
21112
dead = 0,
22113
i = 0,
23-
done = 0,
24-
timer = null;
114+
done = 0;
25115

26116
//exit the test if it doesn't succeed within TIMEOUT
27-
timer = setTimeout(function() {
117+
var timer = setTimeout(function() {
28118
console.error('[PARENT] Responses were not received within %d ms.',
29119
TIMEOUT);
30120
console.error('[PARENT] Fail');
@@ -36,90 +126,7 @@ if (process.argv[2] !== 'child') {
36126

37127
//launch child processes
38128
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);
129+
launchChildProcess(x);
123130
}
124131

125132
var sendSocket = dgram.createSocket('udp4');
@@ -141,7 +148,7 @@ if (process.argv[2] !== 'child') {
141148
});
142149

143150
sendSocket.sendNext = function() {
144-
var buf = messages[i++];
151+
const buf = messages[i++];
145152

146153
if (!buf) {
147154
try { sendSocket.close(); } catch (e) {}
@@ -151,61 +158,51 @@ if (process.argv[2] !== 'child') {
151158
sendSocket.send(buf, 0, buf.length,
152159
common.PORT, LOCAL_BROADCAST_HOST, function(err) {
153160
if (err) throw err;
154-
console.error('[PARENT] sent %s to %s:%s',
155-
util.inspect(buf.toString()),
161+
console.error('[PARENT] sent "%s" to %s:%s',
162+
buf.toString(),
156163
LOCAL_BROADCAST_HOST, common.PORT);
157164
process.nextTick(sendSocket.sendNext);
158165
});
159166
};
160-
161-
function killChildren(children) {
162-
Object.keys(children).forEach(function(key) {
163-
var child = children[key];
164-
child.kill();
165-
});
166-
}
167167
}
168168

169169
if (process.argv[2] === 'child') {
170-
var receivedMessages = [];
171-
var listenSocket = dgram.createSocket({
170+
const receivedMessages = [];
171+
const listenSocket = dgram.createSocket({
172172
type: 'udp4',
173173
reuseAddr: true
174174
});
175175

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

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

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

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-
});
185+
process.send({ message: buf.toString() });
192186

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-
});
187+
if (receivedMessages.length == messages.length) {
188+
// .dropMembership() not strictly needed but here as a sanity check
189+
listenSocket.dropMembership(LOCAL_BROADCAST_HOST);
190+
process.nextTick(function() {
191+
listenSocket.close();
192+
});
193+
}
194+
});
201195

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

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

0 commit comments

Comments
 (0)