• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const {
24  mustCall,
25  mustCallAtLeast,
26  platformTimeout,
27} = require('../common');
28const assert = require('assert');
29const fork = require('child_process').fork;
30const net = require('net');
31const debug = require('util').debuglog('test');
32const count = 12;
33
34if (process.argv[2] === 'child') {
35  const needEnd = [];
36  const id = process.argv[3];
37
38  process.on('message', mustCall((m, socket) => {
39    if (!socket) return;
40
41    debug(`[${id}] got socket ${m}`);
42
43    // Will call .end('end') or .write('write');
44    socket[m](m);
45
46    socket.resume();
47
48    socket.on('data', mustCallAtLeast(() => {
49      debug(`[${id}] socket.data ${m}`);
50    }));
51
52    socket.on('end', mustCall(() => {
53      debug(`[${id}] socket.end ${m}`);
54    }));
55
56    // Store the unfinished socket
57    if (m === 'write') {
58      needEnd.push(socket);
59    }
60
61    socket.on('close', mustCall((had_error) => {
62      debug(`[${id}] socket.close ${had_error} ${m}`);
63    }));
64
65    socket.on('finish', mustCall(() => {
66      debug(`[${id}] socket finished ${m}`);
67    }));
68  }));
69
70  process.on('message', mustCall((m) => {
71    if (m !== 'close') return;
72    debug(`[${id}] got close message`);
73    needEnd.forEach((endMe, i) => {
74      debug(`[${id}] ending ${i}/${needEnd.length}`);
75      endMe.end('end');
76    });
77  }));
78
79  process.on('disconnect', mustCall(() => {
80    debug(`[${id}] process disconnect, ending`);
81    needEnd.forEach((endMe, i) => {
82      debug(`[${id}] ending ${i}/${needEnd.length}`);
83      endMe.end('end');
84    });
85  }));
86
87} else {
88
89  const child1 = fork(process.argv[1], ['child', '1']);
90  const child2 = fork(process.argv[1], ['child', '2']);
91  const child3 = fork(process.argv[1], ['child', '3']);
92
93  const server = net.createServer();
94
95  let connected = 0;
96  let closed = 0;
97  server.on('connection', function(socket) {
98    switch (connected % 6) {
99      case 0:
100        child1.send('end', socket); break;
101      case 1:
102        child1.send('write', socket); break;
103      case 2:
104        child2.send('end', socket); break;
105      case 3:
106        child2.send('write', socket); break;
107      case 4:
108        child3.send('end', socket); break;
109      case 5:
110        child3.send('write', socket); break;
111    }
112    connected += 1;
113
114    // TODO(@jasnell): This is not actually being called.
115    // It is not clear if it is needed.
116    socket.once('close', () => {
117      debug(`[m] socket closed, total ${++closed}`);
118    });
119
120    if (connected === count) {
121      closeServer();
122    }
123  });
124
125  let disconnected = 0;
126  server.on('listening', mustCall(() => {
127
128    let j = count;
129    while (j--) {
130      const client = net.connect(server.address().port, '127.0.0.1');
131      client.on('error', () => {
132        // This can happen if we kill the subprocess too early.
133        // The client should still get a close event afterwards.
134        // It likely won't so don't wrap in a mustCall.
135        debug('[m] CLIENT: error event');
136      });
137      client.on('close', mustCall(() => {
138        debug('[m] CLIENT: close event');
139        disconnected += 1;
140      }));
141      client.resume();
142    }
143  }));
144
145  let closeEmitted = false;
146  server.on('close', mustCall(function() {
147    closeEmitted = true;
148
149    child1.kill();
150    child2.kill();
151    child3.kill();
152  }));
153
154  server.listen(0, '127.0.0.1');
155
156  function closeServer() {
157    server.close();
158
159    setTimeout(() => {
160      assert(!closeEmitted);
161      child1.send('close');
162      child2.send('close');
163      child3.disconnect();
164    }, platformTimeout(200));
165  }
166
167  process.on('exit', function() {
168    assert.strictEqual(server._workers.length, 0);
169    assert.strictEqual(disconnected, count);
170    assert.strictEqual(connected, count);
171  });
172}
173