nx icon indicating copy to clipboard operation
nx copied to clipboard

Previous build is not cancelled on new build trigger

Open cberd1509 opened this issue 9 months ago • 6 comments

Current Behavior

After upgrading to the latest Nx version, i'm seeing that in a NestJS project, when a file change is detected, the previous build is not being cancelled, seems like all the builds happen at the same time and that makes the console to lose format. So if multiple file changes are made and there are build in progress, they will overlap.

Normally it should be something like this

Image

But after a few concurrent restarts the console just starts outputting everything without line breaks.

Image

I've tried adding a debounce timer for the serve executor, or disabling cache and parallelism in the build executor but the issue persists.

For now i've only seen this behavior in NestJS/Webpack build, the monorepo contains an angular project which behaves just fine.

Expected Behavior

The builds should be triggered in sequence in case changes are done in multiple files, other than that, even if they run in parallel, the console should not lose format.

GitHub Repo

No response

Steps to Reproduce

  1. Create a NestJS project
  2. Perform changes in multiple files fast (I reproduced it by adding and removing console logs in a file and saving with Ctrl+Z)

Nx Report

Node           : 20.13.1
OS             : darwin-arm64
Native Target  : aarch64-macos
pnpm           : 10.2.1

nx (global)        : 20.4.4
nx                 : 20.4.2
@nx/js             : 20.4.2
@nx/jest           : 20.4.2
@nx/eslint         : 20.4.2
@nx/workspace      : 20.4.2
@nx/angular        : 20.4.2
@nx/devkit         : 20.4.2
@nx/eslint-plugin  : 20.4.2
@nx/nest           : 20.4.2
@nx/node           : 20.4.2
@nx/web            : 20.4.2
@nx/webpack        : 20.4.2
typescript         : 5.7.3
---------------------------------------
Registered Plugins:
@nx/eslint/plugin
@nx/webpack/plugin
---------------------------------------
Community plugins:
@ngxs/storage-plugin : 19.0.0
@ngxs/store          : 19.0.0
apollo-angular       : 10.0.2
---------------------------------------
Local workspace plugins:
         @corememory/nx-prisma

Failure Logs


Package Manager Version

No response

Operating System

  • [x] macOS
  • [ ] Linux
  • [ ] Windows
  • [ ] Other (Please specify)

Additional Information

This is my current project.json, i added some options to see if i could debounce the builds but so far doesn't seem it is really using the debounce or the dependencies

{
  "name": "corememory-api",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/corememory-api/src",
  "projectType": "application",
  "tags": [],
  "targets": {
    "serve": {
      "executor": "@nx/js:node",
      "defaultConfiguration": "development",
      "options": {
        "buildTarget": "corememory-api:build",
        "watch": true,
        "debounce": 10000,
        "runtimeArgs": [],
        "waitUntilTargets": ["corememory-api:build"]
      },
      "configurations": {
        "development": {
          "buildTarget": "corememory-api:build:development"
        },
        "production": {
          "buildTarget": "corememory-api:build:production"
        }
      }
    },
    "build": {
      "options": {
        "cwd": "apps/corememory-api",
        "args": ["--node-env=production"],
        "command": "webpack-cli build"
      },
      "cache": false,
      "dependsOn": ["^build"],
      "inputs": [
        "production",
        "^production",
        {
          "externalDependencies": ["webpack-cli"]
        }
      ],
      "outputs": ["{workspaceRoot}/dist/apps/corememory-api"],
      "metadata": {
        "technologies": ["webpack"],
        "description": "Runs Webpack build",
        "help": {
          "command": "pnpm exec webpack-cli build --help",
          "example": {
            "options": {
              "json": "stats.json"
            },
            "args": ["--profile"]
          }
        }
      },
      "executor": "nx:run-commands",
      "configurations": {},
      "parallelism": false
    },
    "test": {
      "executor": "@nx/jest:jest",
      "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
      "options": {
        "jestConfig": "apps/corememory-api/jest.config.ts"
      }
    }
  }
}

cberd1509 avatar Mar 03 '25 13:03 cberd1509

+1

iJhefe avatar Mar 10 '25 14:03 iJhefe

Agree, something go wrong with that

Drizzlman avatar Mar 21 '25 13:03 Drizzlman

I've briefly looked at the source code in the @nx/js:node runner (https://github.com/nrwl/nx/blob/master/packages/js/src/executors/node/node.impl.ts), and what I think might be the issue is that the build step which gets called when a file change occurs on line 296, it first does a rebuild and then runs subsequent tasks. The subsequent tasks are debounced, but the build itself is not.

const debouncedRunBuild = debounce(
      runBuild,
      options.debounce ?? 1_000
    );

Adding this, and then calling debouncedRunBuild instead of runBuild on line 296 seems to work in combination with a simplified debouncer. I'm not sure why the pendingPromise logic is in there, probably with a reason, but removing it and just always resetting the debouncer combined with this debouncedRunBuild made it handle multiple file saves correctly for me.

Again, this is based on spending little time on it, but maybe it helps.

Milananas avatar Mar 21 '25 19:03 Milananas

I've briefly looked at the source code in the @nx/js:node runner (https://github.com/nrwl/nx/blob/master/packages/js/src/executors/node/node.impl.ts), and what I think might be the issue is that the build step which gets called when a file change occurs on line 296, it first does a rebuild and then runs subsequent tasks. The subsequent tasks are debounced, but the build itself is not.

const debouncedRunBuild = debounce(
      runBuild,
      options.debounce ?? 1_000
    );

Adding this, and then calling debouncedRunBuild instead of runBuild on line 296 seems to work in combination with a simplified debouncer. I'm not sure why the pendingPromise logic is in there, probably with a reason, but removing it and just always resetting the debouncer combined with this debouncedRunBuild made it handle multiple file saves correctly for me.

Again, this is based on spending little time on it, but maybe it helps.

Pretty interesting 🤔

Drizzlman avatar Mar 21 '25 19:03 Drizzlman

@FrozenPandaz any news on this? It is significantly impacting local development at the moment. I'm open to helping out with creating a PR, or test something if needed.

Milananas avatar May 08 '25 16:05 Milananas

Just verified - this is also an issue with NestJS + rspack (which makes sense, since it is the node executor that is the issue)

Milananas avatar Jun 10 '25 10:06 Milananas

Update - the debouncer has been fixed it seems, the build command is still not being debounced though, so I now apply this patch:

diff --git a/node_modules/@nx/js/src/executors/node/node.impl.js b/node_modules/@nx/js/src/executors/node/node.impl.js
index ad971e9..c25a254 100644
--- a/node_modules/@nx/js/src/executors/node/node.impl.js
+++ b/node_modules/@nx/js/src/executors/node/node.impl.js
@@ -212,6 +212,10 @@ async function* nodeExecutor(options, context) {
                 await addToQueue(childProcess, whenReady);
                 await debouncedProcessQueue();
             };
+            const debouncedRunBuild = debounce(
+              runBuild,
+              options.debounce ?? 1_000
+            );
             if ((0, devkit_1.isDaemonEnabled)()) {
                 additionalExitHandler = await client_1.daemonClient.registerFileWatcher({
                     watchProjects: [context.projectName],
@@ -227,7 +231,7 @@ async function* nodeExecutor(options, context) {
                     else {
                         if (options.watch) {
                             devkit_1.logger.info(`NX File change detected. Restarting...`);
-                            await runBuild();
+                            await debouncedRunBuild();
                         }
                     }
                 });
@@ -235,7 +239,7 @@ async function* nodeExecutor(options, context) {
             else {
                 devkit_1.logger.warn(`NX Daemon is not running. Node process will not restart automatically after file changes.`);
             }
-            await runBuild(); // run first build
+            await debouncedRunBuild(); // run first build
         }
         else {
             // Otherwise, run the build executor, which will not run task dependencies.

Milananas avatar Jul 21 '25 13:07 Milananas

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.

github-actions[bot] avatar Sep 15 '25 00:09 github-actions[bot]