Skip to content

feat(core): lock graph creation when running in another process #29408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/generated/devkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ It only uses language primitives and immutable objects
### Classes

- [AggregateCreateNodesError](../../devkit/documents/AggregateCreateNodesError)
- [StaleProjectGraphCacheError](../../devkit/documents/StaleProjectGraphCacheError)

### Interfaces

Expand Down
144 changes: 144 additions & 0 deletions docs/generated/devkit/StaleProjectGraphCacheError.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Class: StaleProjectGraphCacheError

## Hierarchy

- `Error`

↳ **`StaleProjectGraphCacheError`**

## Table of contents

### Constructors

- [constructor](../../devkit/documents/StaleProjectGraphCacheError#constructor)

### Properties

- [cause](../../devkit/documents/StaleProjectGraphCacheError#cause): unknown
- [message](../../devkit/documents/StaleProjectGraphCacheError#message): string
- [name](../../devkit/documents/StaleProjectGraphCacheError#name): string
- [stack](../../devkit/documents/StaleProjectGraphCacheError#stack): string
- [prepareStackTrace](../../devkit/documents/StaleProjectGraphCacheError#preparestacktrace): Function
- [stackTraceLimit](../../devkit/documents/StaleProjectGraphCacheError#stacktracelimit): number

### Methods

- [captureStackTrace](../../devkit/documents/StaleProjectGraphCacheError#capturestacktrace)

## Constructors

### constructor

• **new StaleProjectGraphCacheError**(): [`StaleProjectGraphCacheError`](../../devkit/documents/StaleProjectGraphCacheError)

#### Returns

[`StaleProjectGraphCacheError`](../../devkit/documents/StaleProjectGraphCacheError)

#### Overrides

Error.constructor

## Properties

### cause

• `Optional` **cause**: `unknown`

#### Inherited from

Error.cause

---

### message

• **message**: `string`

#### Inherited from

Error.message

---

### name

• **name**: `string`

#### Inherited from

Error.name

---

### stack

• `Optional` **stack**: `string`

#### Inherited from

Error.stack

---

### prepareStackTrace

▪ `Static` `Optional` **prepareStackTrace**: (`err`: `Error`, `stackTraces`: `CallSite`[]) => `any`

Optional override for formatting stack traces

**`See`**

https://v8.dev/docs/stack-trace-api#customizing-stack-traces

#### Type declaration

▸ (`err`, `stackTraces`): `any`

##### Parameters

| Name | Type |
| :------------ | :----------- |
| `err` | `Error` |
| `stackTraces` | `CallSite`[] |

##### Returns

`any`

#### Inherited from

Error.prepareStackTrace

---

### stackTraceLimit

▪ `Static` **stackTraceLimit**: `number`

#### Inherited from

Error.stackTraceLimit

## Methods

### captureStackTrace

▸ **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`

Create .stack property on a target object

#### Parameters

| Name | Type |
| :---------------- | :--------- |
| `targetObject` | `object` |
| `constructorOpt?` | `Function` |

#### Returns

`void`

#### Inherited from

Error.captureStackTrace
8 changes: 7 additions & 1 deletion docs/generated/devkit/readCachedProjectGraph.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Function: readCachedProjectGraph

▸ **readCachedProjectGraph**(): [`ProjectGraph`](../../devkit/documents/ProjectGraph)
▸ **readCachedProjectGraph**(`minimumComputedAt?`): [`ProjectGraph`](../../devkit/documents/ProjectGraph)

Synchronously reads the latest cached copy of the workspace's ProjectGraph.

#### Parameters

| Name | Type | Description |
| :------------------- | :------- | :----------------------------------------------------------------------------- |
| `minimumComputedAt?` | `number` | The minimum timestamp that the cached ProjectGraph must have been computed at. |

#### Returns

[`ProjectGraph`](../../devkit/documents/ProjectGraph)
Expand Down
1 change: 1 addition & 0 deletions docs/generated/packages/devkit/documents/nx_devkit.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ It only uses language primitives and immutable objects
### Classes

- [AggregateCreateNodesError](../../devkit/documents/AggregateCreateNodesError)
- [StaleProjectGraphCacheError](../../devkit/documents/StaleProjectGraphCacheError)

### Interfaces

Expand Down
2 changes: 1 addition & 1 deletion packages/devkit/src/executors/parse-target-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function parseTargetString(
targetString: string,
projectGraphOrCtx?: ProjectGraph | ExecutorContext
): Target {
let projectGraph =
let projectGraph: ProjectGraph =
projectGraphOrCtx && 'projectGraph' in projectGraphOrCtx
? projectGraphOrCtx.projectGraph
: (projectGraphOrCtx as ProjectGraph);
Expand Down
2 changes: 2 additions & 0 deletions packages/nx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ assert_fs = "1.0.10"
# This is only used for unit tests
swc_ecma_dep_graph = "0.109.1"
tempfile = "3.13.0"
# We only explicitly use tokio for async tests
tokio = "1.38.0"
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,12 @@ async function processFilesAndCreateAndSerializeProjectGraph(
};
}
}

writeCache(
g.projectFileMapCache,
g.projectGraph,
projectConfigurationsResult.sourceMaps,
errors
);
if (errors.length > 0) {
return {
error: new DaemonProjectGraphError(
Expand All @@ -316,7 +321,6 @@ async function processFilesAndCreateAndSerializeProjectGraph(
serializedSourceMaps: null,
};
} else {
writeCache(g.projectFileMapCache, g.projectGraph);
return g;
}
} catch (err) {
Expand Down
5 changes: 4 additions & 1 deletion packages/nx/src/devkit-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export type {
ProjectsMetadata,
} from './project-graph/plugins';

export { AggregateCreateNodesError } from './project-graph/error-types';
export {
AggregateCreateNodesError,
StaleProjectGraphCacheError,
} from './project-graph/error-types';

export { createNodesFromFiles } from './project-graph/plugins';

Expand Down
13 changes: 6 additions & 7 deletions packages/nx/src/native/db/initialize.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::native::db::connection::NxDbConnection;
use fs4::fs_std::FileExt;
use rusqlite::{Connection, OpenFlags};
use std::fs::{remove_file, File};
use std::path::{Path, PathBuf};
Expand All @@ -12,9 +11,7 @@ pub(super) struct LockFile {

pub(super) fn unlock_file(lock_file: &LockFile) {
if lock_file.path.exists() {
lock_file
.file
.unlock()
fs4::fs_std::FileExt::unlock(&lock_file.file)
.and_then(|_| remove_file(&lock_file.path))
.ok();
}
Expand All @@ -26,8 +23,7 @@ pub(super) fn create_lock_file(db_path: &Path) -> anyhow::Result<LockFile> {
.map_err(|e| anyhow::anyhow!("Unable to create db lock file: {:?}", e))?;

trace!("Getting lock on db lock file");
lock_file
.lock_exclusive()
fs4::fs_std::FileExt::lock_exclusive(&lock_file)
.inspect(|_| trace!("Got lock on db lock file"))
.map_err(|e| anyhow::anyhow!("Unable to lock the db lock file: {:?}", e))?;
Ok(LockFile {
Expand Down Expand Up @@ -77,7 +73,10 @@ pub(super) fn initialize_db(nx_version: String, db_path: &Path) -> anyhow::Resul
Ok(c)
}
Err(reason) => {
trace!("Unable to connect to existing database because: {:?}", reason);
trace!(
"Unable to connect to existing database because: {:?}",
reason
);
trace!("Removing existing incompatible database");
remove_file(db_path)?;

Expand Down
9 changes: 9 additions & 0 deletions packages/nx/src/native/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ export declare class ChildProcess {
onOutput(callback: (message: string) => void): void
}

export declare class FileLock {
locked: boolean
constructor(lockFilePath: string)
unlock(): void
check(): boolean
wait(): Promise<void>
lock(): void
}

export declare class HashPlanner {
constructor(nxJson: NxJson, projectGraph: ExternalObject<ProjectGraph>)
getPlans(taskIds: Array<string>, taskGraph: TaskGraph): Record<string, string[]>
Expand Down
1 change: 1 addition & 0 deletions packages/nx/src/native/native-bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ if (!nativeBinding) {
}

module.exports.ChildProcess = nativeBinding.ChildProcess
module.exports.FileLock = nativeBinding.FileLock
module.exports.HashPlanner = nativeBinding.HashPlanner
module.exports.ImportResult = nativeBinding.ImportResult
module.exports.NxCache = nativeBinding.NxCache
Expand Down
19 changes: 11 additions & 8 deletions packages/nx/src/native/nx.wasi-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,18 @@ function __napi_rs_initialize_modules(__napiInstance) {
__napiInstance.exports['__napi_register__ExternalDependenciesInput_struct_36']?.()
__napiInstance.exports['__napi_register__DepsOutputsInput_struct_37']?.()
__napiInstance.exports['__napi_register__NxJson_struct_38']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_struct_39']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_impl_48']?.()
__napiInstance.exports['__napi_register__WorkspaceErrors_49']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFiles_struct_50']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFilesExternals_struct_51']?.()
__napiInstance.exports['__napi_register__UpdatedWorkspaceFiles_struct_52']?.()
__napiInstance.exports['__napi_register__FileMap_struct_53']?.()
__napiInstance.exports['__napi_register____test_only_transfer_file_map_54']?.()
__napiInstance.exports['__napi_register__FileLock_struct_39']?.()
__napiInstance.exports['__napi_register__FileLock_impl_41']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_struct_42']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_impl_51']?.()
__napiInstance.exports['__napi_register__WorkspaceErrors_52']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFiles_struct_53']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFilesExternals_struct_54']?.()
__napiInstance.exports['__napi_register__UpdatedWorkspaceFiles_struct_55']?.()
__napiInstance.exports['__napi_register__FileMap_struct_56']?.()
__napiInstance.exports['__napi_register____test_only_transfer_file_map_57']?.()
}
export const FileLock = __napiModule.exports.FileLock
export const HashPlanner = __napiModule.exports.HashPlanner
export const ImportResult = __napiModule.exports.ImportResult
export const TaskHasher = __napiModule.exports.TaskHasher
Expand Down
19 changes: 11 additions & 8 deletions packages/nx/src/native/nx.wasi.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,18 @@ function __napi_rs_initialize_modules(__napiInstance) {
__napiInstance.exports['__napi_register__ExternalDependenciesInput_struct_36']?.()
__napiInstance.exports['__napi_register__DepsOutputsInput_struct_37']?.()
__napiInstance.exports['__napi_register__NxJson_struct_38']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_struct_39']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_impl_48']?.()
__napiInstance.exports['__napi_register__WorkspaceErrors_49']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFiles_struct_50']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFilesExternals_struct_51']?.()
__napiInstance.exports['__napi_register__UpdatedWorkspaceFiles_struct_52']?.()
__napiInstance.exports['__napi_register__FileMap_struct_53']?.()
__napiInstance.exports['__napi_register____test_only_transfer_file_map_54']?.()
__napiInstance.exports['__napi_register__FileLock_struct_39']?.()
__napiInstance.exports['__napi_register__FileLock_impl_41']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_struct_42']?.()
__napiInstance.exports['__napi_register__WorkspaceContext_impl_51']?.()
__napiInstance.exports['__napi_register__WorkspaceErrors_52']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFiles_struct_53']?.()
__napiInstance.exports['__napi_register__NxWorkspaceFilesExternals_struct_54']?.()
__napiInstance.exports['__napi_register__UpdatedWorkspaceFiles_struct_55']?.()
__napiInstance.exports['__napi_register__FileMap_struct_56']?.()
__napiInstance.exports['__napi_register____test_only_transfer_file_map_57']?.()
}
module.exports.FileLock = __napiModule.exports.FileLock
module.exports.HashPlanner = __napiModule.exports.HashPlanner
module.exports.ImportResult = __napiModule.exports.ImportResult
module.exports.TaskHasher = __napiModule.exports.TaskHasher
Expand Down
20 changes: 20 additions & 0 deletions packages/nx/src/native/tests/__fixtures__/file-lock.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { FileLock } = require('../../native-bindings.js');
const ora = require('ora');
const tmp = require('os').tmpdir();

(async () => {
const lock = new FileLock(
require('path').join(tmp, 'nx-unit-tests', 'file-lock-fixture')
);
if (lock.locked) {
const s = ora('Waiting for lock').start();
await lock.wait();
s.stop();
console.log('waited for lock');
} else {
await lock.lock();
await new Promise((resolve) => setTimeout(resolve, 5000));
console.log('ran with lock');
await lock.unlock();
}
})();
Loading
Loading