Skip to content

feat: add ProcessPlatformAdapter for Node.js process environments#627

Open
jumski wants to merge 1 commit into
portable-worker-startfrom
portable-worker-process-adapter
Open

feat: add ProcessPlatformAdapter for Node.js process environments#627
jumski wants to merge 1 commit into
portable-worker-startfrom
portable-worker-process-adapter

Conversation

@jumski
Copy link
Copy Markdown
Contributor

@jumski jumski commented Jun 7, 2026

Adds ProcessPlatformAdapter to support running pgflow workers in Node.js (or any process-based runtime), complementing the existing SupabasePlatformAdapter for Deno/Edge environments.

The adapter handles OS signal registration (SIGTERM, SIGINT, SIGQUIT) for graceful shutdown, including double-signal force-exit behavior. It manages worker lifecycle by calling startOnlyOnce with startMode: 'process', marking the worker stopped in the database on shutdown, and conditionally closing the SQL connection only when the adapter owns it.

A processDeps abstraction is introduced to make the adapter fully testable without a real Node.js process, allowing signal handlers, process.exit, exit codes, and crypto.randomUUID to be injected in tests.

DATABASE_URL is added as a connection resolution option, slotting in between connectionString and EDGE_WORKER_DB_URL in priority order. createAdapter is updated to detect a process environment and return a ProcessPlatformAdapter automatically. CurrentPlatformEnv is broadened from SupabaseEnv to Record<string, string | undefined> to accommodate non-Supabase environments.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 7, 2026

⚠️ No Changeset found

Latest commit: 3cb8c81

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Copy Markdown
Contributor Author

jumski commented Jun 7, 2026

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jun 7, 2026

View your CI Pipeline Execution ↗ for commit 3cb8c81

Command Status Duration Result
nx affected -t lint typecheck test --parallel -... ❌ Failed 1m 45s View ↗
nx run edge-worker:test:integration ❌ Failed 1m View ↗
nx run client:e2e ✅ Succeeded 2m 52s View ↗
nx run core:pgtap ✅ Succeeded 1m 28s View ↗
nx run edge-worker:e2e ✅ Succeeded 49s View ↗
nx run cli:e2e ✅ Succeeded 5s View ↗

💡 Dealing with memory or CPU issues? See memory and CPU details with the resource usage add-on ↗.


☁️ Nx Cloud last updated this comment at 2026-06-07 23:30:39 UTC

Comment on lines +80 to +95
async stopWorker(): Promise<void> {
this.requestShutdown();

try {
if (this.worker) {
await this.worker.stop();
}
if (this.workerId) {
await this.queries.markWorkerStopped(this.workerId);
}
} finally {
if (this.ownsSql) {
await this._platformResources.sql.end();
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: stopWorker() can be called concurrently

If stopWorker() is called manually and then a signal arrives before it completes, handleSignal() will call stopWorker() again since shutdownStarted is only set in handleSignal(), not in stopWorker(). This causes:

  1. worker.stop() called twice (may not be idempotent)
  2. markWorkerStopped() called twice (could fail or create duplicate entries)
  3. sql.end() potentially called twice (will error on second call)

Fix: Add a guard in stopWorker() or set the shutdown flag there as well:

async stopWorker(): Promise<void> {
  if (this.shutdownStarted) {
    return;
  }
  this.shutdownStarted = true;
  this.requestShutdown();
  // ... rest of implementation
}
Suggested change
async stopWorker(): Promise<void> {
this.requestShutdown();
try {
if (this.worker) {
await this.worker.stop();
}
if (this.workerId) {
await this.queries.markWorkerStopped(this.workerId);
}
} finally {
if (this.ownsSql) {
await this._platformResources.sql.end();
}
}
}
async stopWorker(): Promise<void> {
if (this.shutdownStarted) {
return;
}
this.shutdownStarted = true;
this.requestShutdown();
try {
if (this.worker) {
await this.worker.stop();
}
if (this.workerId) {
await this.queries.markWorkerStopped(this.workerId);
}
} finally {
if (this.ownsSql) {
await this._platformResources.sql.end();
}
}
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant