neovim / node-client

Nvim Node.js client and plugin host
https://neovim.io/node-client/
MIT License
492 stars 53 forks source link

unreliable proc.on('close') event #419

Open justinmk opened 1 month ago

justinmk commented 1 month ago

This is a tracking issue for a potential issue noticed in https://github.com/neovim/node-client/pull/414#discussion_r1796637402

Problem

proc.on('close') does not always trigger on nvim.quit(). Based on the failures demonstrated in https://github.com/neovim/node-client/pull/418, it appears that sometimes the child (nvim) does not close the stderr pipe:

    expect(received).toStrictEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Object {
        "errors": 0,
    -   "stderrClosed": true,
    +   "stderrClosed": false,
        "stdoutClosed": true,
      }

      172 |     // TODO: 'close' event sometimes does not emit. #414
      173 |     proc.on('exit', () => {
    > 174 |       expect(r).toStrictEqual({
          |                 ^
      175 |         stdoutClosed: true,
      176 |         stderrClosed: true,
      177 |         errors: 0,

      at ChildProcess.toStrictEqual (src/attach/attach.test.ts:174:17)

Solution

justinmk commented 1 month ago

patch to demo the issue, from https://github.com/neovim/node-client/pull/418

diff --git a/packages/neovim/src/attach/attach.test.ts b/packages/neovim/src/attach/attach.test.ts
index 70d16597..4f0c6942 100644
--- a/packages/neovim/src/attach/attach.test.ts
+++ b/packages/neovim/src/attach/attach.test.ts
@@ -154,14 +154,38 @@ describe('Nvim API', () => {
     expect(newLines).toEqual(['line1', 'line2']);
   });

-  it('emits "disconnect" after quit', done => {
+  it.only('emits "disconnect" after quit', done => {
     const disconnectMock = jest.fn();
     nvim.on('disconnect', disconnectMock);

     nvim.quit();

+    const r = {
+      stdoutClosed: false,
+      stderrClosed: false,
+      errors: 0,
+    };
+    proc.stdout.on('close', () => {
+      r.stdoutClosed = true;
+    });
+    proc.stderr.on('close', () => {
+      r.stderrClosed = true;
+    });
+    proc.on('error', () => {
+      r.errors = r.errors + 1;
+    });
+
     // TODO: 'close' event sometimes does not emit. #414
     proc.on('exit', () => {
+      expect(r).toStrictEqual({
+        stdoutClosed: true,
+        stderrClosed: true,
+        errors: 0,
+      });
+      done();
+    });
+
+    proc.on('close', () => {
       expect(disconnectMock).toHaveBeenCalledTimes(1);
       done();
     });