Repository: node
Issue: stream: issues with Duplex streams (especially not closing/destroying properly)
Labels: stream

First, the code:

```js
'use strict';

const { Duplex } = require('stream');

class Foo extends Duplex {
  constructor(isClient) {
    super({ autoDestroy: true });
    this.isClient = isClient;
  }
  _read(n) {}
  _write(data, encoding, cb) {
    this.emit('bar');
    cb();
  }
  _destroy(err, cb) {
    console.log('_destroy', this.isClient ? 'client' : 'server');

    // This is needed in order for the other side to properly close
    this.push(null);

    cb();
  }
  _final(cb) {
    console.log('_final', this.isClient ? 'client' : 'server');
    cb();
  }

  baz() {
    this.push(Buffer.from('foobarbaz'));
  }
}

const client = new Foo(true);
const server = new Foo(false);

client.on('finish', () => console.log('CLIENT FINISH'))
      .on('prefinish', () => console.log('CLIENT PREFINISH'))
      .on('end', () => console.log('CLIENT END'))
      .on('close', () => console.log('CLIENT CLOSE'));
server.on('finish', () => console.log('SERVER FINISH'))
      .on('prefinish', () => console.log('SERVER PREFINISH'))
      .on('end', () => console.log('SERVER END'))
      .on('close', () => console.log('SERVER CLOSE'));

server.on('bar', (req) => {
  console.log('SERVER BAR');
  // Swap between `client.destroy()` and `server.destroy()` here
  client.destroy();
});
client.baz();

client.pipe(server).pipe(client);
```

There are 3 issues I've discovered here, but it's the last one that this issue is mainly about:
  * `this.push(null)` seems to be required in `_destroy()` in order for any connected streams to be properly closed. If a Duplex is being destroyed, shouldn't that destroy/close both sides of the stream (especially with `autoDestroy: true`)?
  * In *some* instances `'close'` is being emitted before `'end'`
  * When a stream destroys itself, it doesn't seem to close any connected streams in newer branches/versions

Now, about that 3rd issue:

With `client.destroy()`, we get the same output for latest v10.x, v12.x, and v14.x:
```
    SERVER BAR
    _destroy client
    CLIENT END
    CLIENT CLOSE
    _final server
    SERVER PREFINISH
    SERVER FINISH
```

With `client.destroy()` on node v16.x we get:
```
    SERVER BAR
    _destroy client
    CLIENT END
    _final server
    SERVER PREFINISH
    CLIENT CLOSE
    SERVER FINISH
```

As you can see, the order of events on 16.x is slightly different than the other branches, but it still calls them in the same logical order (e.g. 'end' being before 'close', etc.)

So everything works fine so far, using `client.destroy()`.

With `server.destroy()`, we get the same output for latest v10.x, v12.x, and v14.0.0 (only):
```
    SERVER BAR
    _destroy server
    SERVER CLOSE
    SERVER END
    _final client
    CLIENT PREFINISH
    CLIENT FINISH
```

Aside from the fact that `'close'` is being emitted before `'end'` (which is a separate bug/issue), everything looks pretty much as expected and is identical (with roles swapped of course) to the `client.destroy()` situation for these same versions.

However, with `server.destroy()` on v14.1.0+ and v16.x we instead simply get:
```
    SERVER BAR
    _destroy server
    SERVER CLOSE
```

As you can see in this case, we're missing quite a few events and I feel like this can't be the correct behavior. I should point out that for the `server.destroy()` scenario, if you remove/comment the `this.push(null)` from the `_destroy()` handler, you will see this exact same output on the other node versions (v10.x, v12.x, v14.0.0), so I suspect it has something to do with that being ignored on v14.1.0+ and v16.x.

/cc @nodejs/streams