Nodejs 中父子进程对本地文件同时操作的BUG

项目背景:

通过将本地文件压缩打包,将zip转换成buffer上传到数据库

十分简单的一个操作流程,自己在测试的过程中发现再从数据库上面下载下来并且进行转换zip文件时,文件格式的不正确,为此修改了很多方法,正常单元测试都是正确的,唯独运行service的时候出现了问题。

通过一系列的操作,发现最终的问题是在压缩文件还没有完全写完时,父进程就开始进行读取压缩问题,为此,这个问题为什么会发生呢?

两个小疑问

1. 压缩文件什么时候会返回压缩成功的指令?

2. 为什么父子进程可以对一个文件进行同步的读写操作?

问题一: 压缩文件什么时候会返回压缩成功的指令?

通过官方的快速入门代码和注释说明压缩的问题在哪里 archiver - npm

项目是通过使用一个Promise函数,当监听到执行完时,resolve一个值给下一个函数继续执行。

问题就在于什么时候返回,而项目中是在end返回的,通过下述的官方注释,可以看出close和end的不同

'end'end这个信号产生的时候就是资源用完,因为是使用流写入,简单理解就是管道pipe没有数据了
'close'这个信号是在整个文件写完时产生的,就是关闭问题

正常理解觉得管道没有数据和写完应该差不多概念,因为没有资源了就写完了,但是实际上并不是的,pipe资源消耗完代表你要写入的数据已经全部写入,但是不代表整个文件已经完成,因为文件自身也有属性需要写入,所以end只能代表你要写的写完了,但文件还没写完,close才是真正写完。具体体现在通过测试可以发现,end时候对文件进行读出会比close时少1KB(这个操作正常也是不存在的,需要存在父子进行,这是下面问题所在)

// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on('close', function() {
  console.log(archive.pointer() + ' total bytes');
  console.log('archiver has been finalized and the output file descriptor has closed.');
});

// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on('end', function() {
  console.log('Data has been drained');
});

问题二:为什么父子进程可以对一个文件进行同步的读写操作?

子进程使用的是 fs.createWriteStream(), 而父进程使用的是 fs.createReadStream();

在同一个进程中,我们如果同步执行这两个操作对一个问题,系统会自动报错,目前推测是fs这个module自身带有锁

而在两个进程中,也是是父进程,for() 一个子进程时,相互资源独立,那么对于一个文件,子进程写入,父进程也可以读出,并没有任何的进程锁。这是容易忽略的。如果对于两个进程之间怎么对系统资源进行加锁,开多一个进程进行管理文件系统,

当然可以加锁,加锁方案如下:

在NodeJS中利用mkdir实现文件锁。

NodeJS研究笔记,利用目录来实现跨平台文件锁

mkdir是标准的POSIX系统调用,根据标准,它的实现必须是原子性的。任何支持该调用的平台都必须保证它执行的原子性,利用这个特性来构建文件锁的话,代码就能得到很好的跨平台支持,即使是读取网络文件,也能保证文件的一致性不会被破坏。

// 加入锁
var fs = require('fs');
var hasLock = false;
var lockDir = 'config.lock';

exports.lock = function(cb) {
    if (hasLock)  return cb();

    fs.mkdir(lockDir, function(err) {
        if (err) return cb(err);

        fs.writeFile(lockDir + '/' + process.pid, function(err) {
            if (err) console.error(err);
            hasLock = true;
            return cb();
        });
    });
}


// 释放锁
exports.unlock = function(cb) {
    if (!hasLock)  return cb();

    fs.unlink(lockDir + '/' + process.pid, function(err) {
        if (err) return cb(err);

        fs.rmdir(lockDir, function(err) {
            if (err) return cb(err);
            hasLock = false;
            cb();
        }) ;
    });
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值