node-pty
node-pty copied to clipboard
EIO when process ends
How to reproduce:
$ node --version
v8.5.0
$ cat package.json | grep node-pty
"node-pty": "^0.7.4",
$ node -e "
const node_pty = require('node-pty');
const pty = node_pty.spawn('bash');
pty.on('error', error => console.error(error));
pty.write('exit\r\n');
"
{ Error: read EIO
at _errnoException (util.js:1026:11)
at Pipe.onread (net.js:606:25) code: 'EIO', errno: 'EIO', syscall: 'read' }
It seems that something wrong with my system (I can't reproduce it using docker run --rm -it node:8.5.0 bash
).
It happens on
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 9.4 (stretch)
Release: 9.4
Codename: stretch
$ node --version
v9.8.0
and on
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.3 LTS
Release: 16.04
Codename: xenial
$ node --version
v8.4.0
and on
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.5 LTS
Release: 14.04
Codename: trusty
$ node --version
v6.2.1
Are you on Windows running through WSL?
No, I use Debian on my laptop and Ubuntun on VPS.
Perhaps some race condition is happening? Does the same occur if you wrap the exit write in a setTimeout(..., 1000)
?
Yes, the following also gets error:
const node_pty = require('node-pty');
const pty = node_pty.spawn('bash');
pty.on('error', error => console.error(error));
setTimeout(function () {
pty.write('exit\r\n');
}, 1000);
"
{ Error: read EIO
at Pipe.onread (net.js:599:25) errno: 'EIO', code: 'EIO', syscall: 'read' }
Well EIO
is a generic I/O error, I would guess it's related to your environment because I've never seen this. Not sure how much more help I can be on this.
I've had at least one user report the issue above and I can also reproduce locally. It happens as part of normal program execution and afterwards exit callback is invoked with (0, 0).
i can reproduce this when the command i run doesn't produce stdout. for example:
pty.spawn('sh', ['-c', 'sleep 1'])
// or
pty.spawn('sh', ['-c', 'echo hi && sleep 1'])
this produces two data
events, one with hi
and another that's empty.
Same error on my server CenOS 8.0.
const nodePty = require('node-pty');
const pty = nodePty.spawn('login', []);
pty.on('data', function(data){
process.stdout.write(data);
});
pty.on('error', function(err){
console.error('error', err);
});
WTF: After adding blocking, there will be no error
const nodePty = require('node-pty');
const pty = nodePty.spawn('login', []);
const now = Date.now();
while(Date.now() < (now + 50)){ // blocking
}
pty.on('data', function(data){
process.stdout.write(data);
});
pty.on('error', function(err){
console.error('error', err);
});
This is because pty'fd is in non-blocking mode. It happened at the end, not bad, the worst happened at the beginning. Just like above.
My solution is: Block in the beginning and ignore the end error.
I improved the blocking code. See below:
// Fixed with my cheap server CenOS 8.0.
const fs = require('fs');
const nodePty = require('node-pty');
const encoding = 'utf-8';
const pty = nodePty.spawn('login', [], { encoding });
pty.on('data', function(data){
process.stdout.write(data);
});
let isClose = false;
pty.on('close', function(){
isClose = true;
console.log('close');
});
pty.on('error', function(err){
setTimeout(() => {
if(isClose && (error.code === "EIO" ||
error.code === "EAGAIN" ||
error.code === "EBADF" ||
error.code === "EWOULDBLOCK")){ // Ignore the end error.
return;
}
console.error('error', err);
});
});
const firstBuffer = blockingWhenUnReadable(pty._fd); // Block in the beginning
pty.emit('data', firstBuffer.toString(encoding));
function blockingWhenUnReadable(fd){
const buffer = Buffer.alloc(1);
let isContinue = true;
while(isContinue){
try {
fs.readSync(fd, buffer);
isContinue = false;
} catch(e){
// console.error('err.code', e.code);
if(e.code === 'EAGAIN' ||
e.code === "EIO" ||
e.code === "EWOULDBLOCK"){
isContinue = true;
} else {
throw e;
}
}
}
return buffer;
}