You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js无法检测到新建文件:信号量防重复任务失效求助

解决Node.js中重复通知的信号量失效问题

嘿,我完全懂你遇到的坑——明明用文件当信号量阻止重复任务,磁盘上都能看到锁文件了,Node还是照样执行后续操作。这十有八九是没搞定JavaScript异步模型的时序问题,毕竟异步I/O和事件循环很容易让我们的“锁逻辑”失效。

问题根源分析

你之前的信号量逻辑大概率犯了这两个错误之一:

  • 非原子性操作:比如先检查文件是否存在(fs.existsSync/fs.access),再创建文件。这两步之间有时间差,多个异步通知同时进来时,可能都通过了“文件不存在”的检查,然后都创建了文件(或者覆盖了),导致锁完全没起作用。
  • 异步操作未等待:如果用了异步的文件操作(比如fs.writeFile),但没等它完成就去判断锁状态,那后续代码会先执行,自然拦不住重复任务。

解决方案:原子化的异步锁

要解决这个问题,核心是用原子性的文件操作来实现锁——也就是让“检查锁是否存在”和“创建锁”变成一个不可分割的操作。Node.js的fs.open方法支持wx模式(创建文件,若已存在则报错),这就是个天然的原子操作。

下面给你一个可直接复用的实现:

const fs = require('fs').promises;
const path = require('path');

// 定义锁文件路径,你可以根据自己的需求调整
const TASK_LOCK_PATH = path.join(__dirname, '.notification-task-lock');

/**
 * 尝试获取锁
 * @returns {boolean} 是否成功获取到锁
 */
async function acquireTaskLock() {
  try {
    // wx模式:创建新文件,文件已存在则抛出EEXIST错误(原子操作)
    await fs.open(TASK_LOCK_PATH, 'wx');
    return true;
  } catch (err) {
    // 只有当文件已存在时,才返回false表示锁被持有
    if (err.code === 'EEXIST') {
      return false;
    }
    // 其他未知错误,直接抛出
    throw err;
  }
}

/**
 * 释放锁
 */
async function releaseTaskLock() {
  try {
    await fs.unlink(TASK_LOCK_PATH);
  } catch (err) {
    // 如果锁文件已经被删除(比如之前的任务崩溃后手动删了),忽略这个错误
    if (err.code !== 'ENOENT') {
      throw err;
    }
  }
}

// 处理通知的核心函数
async function handleNotification(event) {
  // 先尝试获取锁
  const hasLock = await acquireTaskLock();
  if (!hasLock) {
    console.log(`[跳过] 重复通知,事件ID:${event.id}`);
    return;
  }

  try {
    // 这里放你的任务逻辑:比如调用API、处理数据、读写文件等
    console.log(`[开始] 处理事件:${event.id}`);
    // 模拟异步任务(替换成你的实际代码)
    await new Promise(resolve => setTimeout(resolve, 3000));
    console.log(`[完成] 处理事件:${event.id}`);
  } finally {
    // 不管任务成功还是失败,一定要释放锁!
    await releaseTaskLock();
  }
}

关键细节说明

  1. 原子性保障:fs.open('wx')是操作系统层面的原子操作,多个异步请求同时尝试创建锁文件时,只有第一个能成功,其他都会立刻得到EEXIST错误,从根本上避免了竞态条件。
  2. finally释放锁:用finally块确保锁一定会被释放,哪怕任务中途抛出错误或者崩溃(当然,如果进程直接被杀掉,锁文件会残留,你可以在脚本启动时检查并清理旧的锁文件)。
  3. 错误区分:只在文件已存在时返回“锁被持有”,其他错误(比如权限不足)直接抛出,方便排查问题。

额外优化建议

  • 锁超时机制:如果你的任务可能长时间运行甚至卡住,可以给锁加个超时时间——比如在创建锁文件时写入当前时间,每次获取锁前检查文件的创建时间,超时就强制删除锁文件。
  • 进程级锁补充:如果你的脚本是多进程运行的,这个文件锁依然有效,但如果是分布式场景,就得用Redis之类的分布式锁了。

内容的提问来源于stack exchange,提问作者blackbird

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:19:05