Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Apr 22, 2025

Summary by CodeRabbit

  • New Features

    • Introduced distributed mutex locking for compute operations using Redis, improving concurrency control and reliability.
    • Added support for Redis configuration via a new environment variable.
    • New Redis client integration for enhanced backend operations.
  • Bug Fixes

    • Improved error handling and logging during compute operations to ensure mutexes are always released.
  • Chores

    • Updated and standardized dependency management across multiple packages.
    • Added new dependencies to the workspace catalog for Redis support.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 22, 2025

Walkthrough

This change introduces Redis-based distributed mutex locking for selector compute operations in the database package. It adds ioredis, redis-semaphore, and ms as dependencies, configures Redis connection via a new environment variable, and implements a mutex utility for compute operations. The releaseTargets methods in resource builder classes and other selector compute methods are refactored to be asynchronous and now acquire a mutex to prevent concurrent execution within the same workspace. Additionally, several package dependencies across multiple packages are updated to use "catalog:" references, and new dependencies are cataloged in the workspace configuration.

Changes

File(s) Change Summary
apps/event-worker/package.json, packages/events/package.json, apps/pty-proxy/package.json, e2e/package.json, packages/api/package.json, packages/job-dispatch/package.json Updated version specifiers for dependencies (ioredis, lodash, ms, redis-semaphore, @types/ms) to use "catalog:" instead of explicit semantic versions.
pnpm-workspace.yaml Added ioredis (5.4.1), redis-semaphore (5.6.2), ms (2.1.3), and @types/ms (0.7.34) to the package catalog.
packages/db/package.json Added ioredis, ms, and redis-semaphore as dependencies with "catalog:" version specifiers; added @types/ms to devDependencies with "catalog:".
packages/db/src/config.ts Added REDIS_URL environment variable to the server configuration schema for Redis connection URL validation.
packages/db/src/redis.ts Introduced new Redis client module using ioredis, configured with environment variable URL, with connection and error logging, and exported the client instance.
packages/db/src/selectors/compute/mutex.ts Added new mutex module for distributed locking using Redis, including an enum SelectorComputeType, a SelectorComputeMutex class with lock and unlock methods, and a helper function createAndAcquireMutex for acquiring mutexes for selector compute operations.
packages/db/src/selectors/compute/resource-builder.ts Refactored releaseTargets methods in ResourceBuilder and WorkspaceResourceBuilder to be asynchronous, acquire a mutex before execution, and ensure proper error handling and mutex release with logging scoped to "resource-builder".
packages/db/src/selectors/compute/deployment-builder.ts Added mutex-based concurrency control in DeploymentBuilder and WorkspaceDeploymentBuilder for resource selector computations, including workspace validation and error logging. Added getDeployments helper method in DeploymentBuilder.
packages/db/src/selectors/compute/environment-builder.ts Added mutex-based concurrency control in EnvironmentBuilder and WorkspaceEnvironmentBuilder for resource selector computations, including workspace validation and error logging. Added getEnvironments helper method in EnvironmentBuilder.
packages/db/src/selectors/compute/policy-builder.ts Refactored PolicyBuilder and WorkspacePolicyBuilder's releaseTargetSelectors methods to use workspace mutex locking, error logging, and helper methods for better modularity and concurrency control.
apps/pty-proxy/src/controller/agent-socket.ts Modified updateResource method to pass workspaceId separately to upsertResources and transform returned resource metadata array into a key-value object before assignment.
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx Removed unused errors variable assigned from form.formState.errors.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant SelectorComputeMutex
    participant Redis
    participant Builder

    Caller->>Builder: releaseTargets() / resourceSelectors()
    Builder->>SelectorComputeMutex: createAndAcquireMutex(type, workspaceId)
    SelectorComputeMutex->>Redis: Acquire lock
    Redis-->>SelectorComputeMutex: Lock acquired
    SelectorComputeMutex-->>Builder: Mutex instance

    Builder->>Builder: Perform transactional compute logic

    Builder->>SelectorComputeMutex: unlock()
    SelectorComputeMutex->>Redis: Release lock
    Redis-->>SelectorComputeMutex: Lock released
    SelectorComputeMutex-->>Builder: Unlock complete
    Builder-->>Caller: Promise resolved or error thrown
Loading

Possibly related PRs

  • ctrlplanedev/ctrlplane#490: Introduces initial PolicyBuilder and WorkspacePolicyBuilder classes with release target logic, which this PR refactors and extends with mutex locking and error handling.
  • ctrlplanedev/ctrlplane#448: Removes Redis dependencies and mutex code from event-worker, related to Redis usage and dependency management in event-worker.
  • ctrlplanedev/ctrlplane#492: Refactors resource release target computations and selector utilities, related to mutex and concurrency changes in resource builder code.

Poem

In the warren of code, a mutex appears,
Guarding the gates where Redis now steers.
Builders await with their targets in tow,
Only one at a time—no race, just flow.
Catalog dependencies, all neat in a row,
The rabbit applauds as the green carrots grow!
🥕✨


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39fda43 and 57da2a7.

📒 Files selected for processing (1)
  • packages/db/src/config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/db/src/config.ts
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
packages/db/src/config.ts (1)

16-16: Consider providing a default value for REDIS_URL

Unlike POSTGRES_URL, REDIS_URL doesn't have a default value, making it a required environment variable. While this ensures explicit configuration, it could cause issues in development environments if not properly set.

Consider adding a development default for easier local setup:

-    REDIS_URL: z.string().url(),
+    REDIS_URL: z
+      .string()
+      .url()
+      .default("redis://localhost:6379"),
packages/db/src/redis.ts (1)

9-10: Consider configuring retry options more specifically.

The current configuration sets maxRetriesPerRequest to null, which means infinite retries. While this prevents operations from failing due to transient Redis issues, it could potentially cause unexpected behavior if Redis is unreachable for an extended period.

Consider setting a reasonable finite retry limit and adding retry delay configuration:

-const config = { maxRetriesPerRequest: null };
+const config = {
+  maxRetriesPerRequest: 10,  // Reasonable upper limit
+  retryStrategy: (times: number) => {
+    const delay = Math.min(times * 50, 2000); // Exponential backoff with 2s max
+    return delay;
+  }
+};
packages/db/src/selectors/compute/mutex.ts (1)

32-41: Consider documenting timeout implications.

The mutex configuration uses a 30-second timeout for both lock timeout and acquire timeout. While this is reasonable, it would be helpful to document the implications of these timeout values.

Add a comment explaining the timeout choices and their implications:

this.mutex = new RedisMutex(redis, this.key, {
+  // Lock timeout: Maximum time a lock can be held before automatically releasing
+  // Acquire timeout: Maximum time to wait when trying to acquire a lock
+  // Both set to 30s to balance between preventing deadlocks and allowing sufficient
+  // time for compute operations to complete
  lockTimeout: ms("30s"),
  acquireTimeout: ms("30s"),
  onLockLost: (e) =>
    log.warning("Lock lost for selector compute", {
      error: e,
      type,
      workspaceId,
    }),
});
packages/db/src/selectors/compute/resource-builder.ts (2)

7-7: Consider importing each entity separately.

For better maintainability and clarity, consider importing each entity separately rather than importing from a namespace.

-import { createAndAcquireMutex, SelectorComputeType } from "./mutex.js";
+import { createAndAcquireMutex } from "./mutex.js";
+import { SelectorComputeType } from "./mutex.js";

126-140: Consider extracting transaction logic to a separate method.

Since the transaction logic is duplicated between both classes, consider extracting it to a reusable private method to improve maintainability.

Extract the transaction logic to a separate method to reduce duplication:

private async executeReleaseTargetsTransaction(tx: Tx, resourceIds: string[]) {
  await this.deleteExistingReleaseTargets(tx, resourceIds);
  const vals = await this.findMatchingEnvironmentDeploymentPairs(tx, resourceIds);
  if (vals.length === 0) return [];

  const results = await tx
    .insert(SCHEMA.releaseTarget)
    .values(vals)
    .onConflictDoNothing()
    .returning();

  await this.recomputePolicyReleaseTargets(tx);
  
  return results;
}

Then modify both methods to use this extracted method with appropriate parameters.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4909c07 and 8d5a939.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • apps/event-worker/package.json (1 hunks)
  • packages/db/package.json (1 hunks)
  • packages/db/src/config.ts (1 hunks)
  • packages/db/src/redis.ts (1 hunks)
  • packages/db/src/selectors/compute/mutex.ts (1 hunks)
  • packages/db/src/selectors/compute/resource-builder.ts (3 hunks)
  • packages/events/package.json (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/redis.ts
  • packages/db/src/config.ts
  • packages/db/src/selectors/compute/mutex.ts
  • packages/db/src/selectors/compute/resource-builder.ts
🧬 Code Graph Analysis (3)
packages/db/src/redis.ts (2)
packages/logger/src/index.ts (1)
  • logger (48-48)
packages/db/src/config.ts (1)
  • env (5-20)
packages/db/src/selectors/compute/mutex.ts (2)
packages/logger/src/index.ts (1)
  • logger (48-48)
packages/db/src/redis.ts (1)
  • redis (10-10)
packages/db/src/selectors/compute/resource-builder.ts (3)
packages/logger/src/index.ts (1)
  • logger (48-48)
packages/db/src/selectors/compute/mutex.ts (1)
  • createAndAcquireMutex (17-24)
packages/db/src/selectors/compute/compute.ts (1)
  • resources (51-53)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (12)
pnpm-workspace.yaml (1)

35-36: Appropriate Redis dependencies added to catalog

The addition of redis-semaphore and ioredis to the package catalog supports the implementation of distributed mutex locking for compute operations. The versions specified follow the caret versioning pattern used by other dependencies in the catalog.

packages/db/package.json (1)

49-49: Redis dependencies properly added to package

Redis dependencies are correctly added to support the implementation of mutex functionality, and they appropriately reference the versions defined in the workspace catalog.

Also applies to: 52-52

packages/events/package.json (1)

29-29: Good dependency management practice

Changing the ioredis dependency to use the catalog reference improves consistency across packages and centralizes version management, which is a good practice for dependency management.

packages/db/src/redis.ts (2)

12-13: LGTM! Good error handling with appropriate logging.

The event listeners for connection and error events are well implemented with proper logging.


10-10:

❓ Verification inconclusive

Ensure Redis URL is validated.

The code uses env.REDIS_URL without any fallback mechanism. If the environment variable is not set correctly, this will cause runtime errors when the application starts.

Based on the context from packages/db/src/config.ts, it appears that REDIS_URL is required in the environment configuration. Make sure this is documented in deployment instructions and verify that the application fails gracefully if the Redis URL is missing.


🏁 Script executed:

#!/bin/bash
# Check if REDIS_URL is properly documented in README or environment setup files
fd -t f "README.md|.env.example" -x grep -l "REDIS_URL"

Length of output: 55


I've added a script to:

  1. Find and display packages/db/src/config.ts to see how env vars are validated.
  2. Search the entire repo for any mentions of REDIS_URL.

This will confirm if REDIS_URL is already required/checked in code or documented in examples. Once we have the results, we can finalize the review comment.

apps/event-worker/package.json (1)

46-46: Consistent dependency versioning.

Updating these dependencies to use "catalog:" aligns with the project's dependency management approach across packages. This change helps maintain consistency across the monorepo and simplifies version management.

Also applies to: 50-51

packages/db/src/selectors/compute/mutex.ts (4)

10-15: LGTM! Well-structured enum for compute types.

Clear enumeration of different selector compute types that provides type safety and readability.


17-24: LGTM! Helpful utility function for mutex acquisition.

The createAndAcquireMutex function provides a convenient way to create and immediately acquire a mutex in a single step.


44-47: LGTM! Good validation before lock acquisition.

The code correctly checks if the mutex is already locked before attempting to acquire it, which prevents logical errors.


49-52: LGTM! Good validation before lock release.

The code correctly checks if the mutex is actually locked before attempting to release it, which prevents logical errors.

packages/db/src/selectors/compute/resource-builder.ts (2)

119-149: LGTM! Properly implemented mutex pattern with error handling.

The code correctly acquires a mutex before performing database operations and ensures it's released in a finally block. The error handling is comprehensive with proper logging of the error context.


249-284: LGTM! Consistent implementation of mutex pattern in both classes.

The same mutex pattern is consistently applied in the WorkspaceResourceBuilder class, maintaining the same error handling approach and ensuring the mutex is properly released.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
packages/db/src/selectors/compute/environment-builder.ts (2)

49-51: Avoid executing the same query twice

getEnvironments() is called once to build workspaceIds (lines 72‑78) and again inside the transaction through findMatchingResourcesForEnvironments() (lines 49‑51).
You already have the first result set; passing it down eliminates an extra round‑trip and simplifies testing.

Also applies to: 84-99


100-107: Protect against mutex.unlock() throwing

redis‑semaphore can propagate network errors on unlock(). An exception here would mask the original failure and leave the lock dangling.
Wrap it in its own try/catch or use finally { void mutex.unlock().catch(err => log.warn(...)) }.

-    } finally {
-      await mutex.unlock();
-    }
+    } finally {
+      try {
+        await mutex.unlock();
+      } catch (unlockErr) {
+        log.warn("Failed to release mutex", { unlockErr, workspaceId });
+      }
+    }
packages/db/src/selectors/compute/deployment-builder.ts (1)

99-101: Guard unlock() against Redis/network errors

Same rationale as in EnvironmentBuilder.

-    } finally {
-      await mutex.unlock();
-    }
+    } finally {
+      try {
+        await mutex.unlock();
+      } catch (unlockErr) {
+        log.warn("Failed to release mutex", { unlockErr, workspaceId });
+      }
+    }
packages/db/src/selectors/compute/policy-builder.ts (1)

118-119: Wrap mutex.unlock() in try/catch

Same concern as the other builders – an unlock failure would overshadow the transaction result and potentially leave a stuck lock.

Also applies to: 221-222

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9530ac6 and 6aeb5f5.

📒 Files selected for processing (3)
  • packages/db/src/selectors/compute/deployment-builder.ts (4 hunks)
  • packages/db/src/selectors/compute/environment-builder.ts (4 hunks)
  • packages/db/src/selectors/compute/policy-builder.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/selectors/compute/environment-builder.ts
  • packages/db/src/selectors/compute/deployment-builder.ts
  • packages/db/src/selectors/compute/policy-builder.ts
🧬 Code Graph Analysis (1)
packages/db/src/selectors/compute/environment-builder.ts (4)
packages/logger/src/index.ts (1)
  • logger (48-48)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/selectors/compute/compute.ts (1)
  • environments (31-33)
packages/db/src/selectors/query/builder.ts (1)
  • environments (38-43)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (1)
packages/db/src/selectors/compute/policy-builder.ts (1)

153-190: Null‑selector handling removed – may generate invalid SQL

WorkspacePolicyBuilder.findMatchingReleaseTargetsForTargets no longer checks
resourceSelector/deploymentSelector/environmentSelector for null before passing them to QueryBuilder.where().
If QueryBuilder does not internally coerce null into a tautology, the generated SQL becomes invalid and the whole compute fails.

Please either reinstate the early‑return guard used in PolicyBuilder or confirm that QueryBuilder.where(null) is a no‑op.

@adityachoudhari26 adityachoudhari26 merged commit 482a634 into main Apr 22, 2025
9 of 10 checks passed
@adityachoudhari26 adityachoudhari26 deleted the use-mutex-for-builders branch April 22, 2025 20:30
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.

2 participants