Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented May 6, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling to prevent creation of multiple active jobs for the same release target, reducing duplicate or concurrent job processing.
  • Refactor
    • Streamlined job status update logic for better reliability and maintainability.
  • Chores
    • Removed outdated job completion and failure callbacks, replacing them with a more robust process for managing release jobs upon job completion.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 6, 2025

Walkthrough

This update introduces new helper functions and modifies job evaluation and update logic to prevent concurrent active jobs for the same release target. It refactors job completion handling, replacing callback-based flows with direct database queries and conditional job creation, and enhances concurrency control within both worker and job update modules.

Changes

File(s) Change Summary
apps/event-worker/src/workers/evaluate-release-target.ts Added helper functions to fetch release target IDs and check for active jobs per release target. Updated worker logic to prevent creating new jobs if an active job for the release target exists, enhancing concurrency control.
packages/job-dispatch/src/job-update.ts Refactored job status transition logic, removed callback-based completion/failure handlers, and introduced new helpers to fetch release targets and latest releases. Now conditionally creates release jobs based on latest release state after job completion.

Sequence Diagram(s)

sequenceDiagram
    participant Worker as evaluateReleaseTargetWorker
    participant DB as Database

    Worker->>DB: getReleaseTargetIdFromRelease(releaseId)
    DB-->>Worker: releaseTargetId

    Worker->>DB: getActiveJobForReleaseTarget(releaseTargetId)
    DB-->>Worker: activeJob or null

    alt Active job exists
        Worker-->>Worker: Return early, no job created
    else No active job
        Worker-->>DB: Proceed to create new job
    end
Loading
sequenceDiagram
    participant updateJob as updateJob
    participant DB as Database

    updateJob->>updateJob: getIsJobJustCompleted(prevJob, updatedJob)
    alt Not just completed
        updateJob-->>updateJob: Return updatedJob
    else Just completed
        updateJob->>DB: getReleaseTargetFromJob(jobId)
        DB-->>updateJob: releaseTarget or null

        alt No releaseTarget
            updateJob-->>updateJob: Log warning, return updatedJob
        else Found releaseTarget
            updateJob->>DB: getLatestReleaseForReleaseTarget(releaseTargetId)
            DB-->>updateJob: latestRelease

            alt No latestRelease or already has releaseJob
                updateJob-->>updateJob: Return updatedJob
            else
                updateJob->>DB: createReleaseJob(latestRelease)
            end
        end
    end
Loading

Possibly related PRs

  • ctrlplanedev/ctrlplane#518: Also modifies evaluate-release-target.ts to address concurrency, adding row-level locks to prevent concurrent modifications.

Suggested reviewers

  • jsbroks

Poem

In the warren, jobs queue in a line,
But now, with new checks, they wait just fine.
No more two bunnies on the same patch—
Only one hops when it’s time to dispatch!
With helpers and queries, the logic is tight—
The release train runs smooth, day and night.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in 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: 1

🧹 Nitpick comments (2)
apps/event-worker/src/workers/evaluate-release-target.ts (1)

96-116: Query returns full joined rows although only the existence check is required

getActiveJobForReleaseTarget fetches complete records from four tables, while the caller only needs to know whether any active job exists. Selecting a single constant (or the job id) is both faster and lighter on the DB-connector’s deserialiser:

-  db
-    .select()
+  db
+    .select({ id: schema.job.id })

This is a micro-optimisation, but given that the function may run very frequently it is worth the two-line change.

packages/job-dispatch/src/job-update.ts (1)

113-129: Row shape assumptions – prefer explicit column list

getLatestReleaseForReleaseTarget relies on Drizzle’s default join-shape, later accessing latestRelease.release and latestRelease.release_job.
While this works today, explicit projection improves readability and guards against future refactors that might rename or add joins:

-  db
-    .select()
+  db
+    .select({
+      release: schema.release,
+      release_job: schema.releaseJob,
+    })

Not mandatory, but it makes downstream property access type-safe.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b5475ea and 488b82f.

📒 Files selected for processing (2)
  • apps/event-worker/src/workers/evaluate-release-target.ts (4 hunks)
  • packages/job-dispatch/src/job-update.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.

  • apps/event-worker/src/workers/evaluate-release-target.ts
  • packages/job-dispatch/src/job-update.ts
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (3)
apps/event-worker/src/workers/evaluate-release-target.ts (2)

212-217: Potential race window between duplicate checks and job insert

The release-target active-job check runs outside the transaction that creates the new releaseJob. Two parallel workers can therefore both read “no active job” and then proceed to insert, violating the one-active-job invariant.

If the invariant must be strict, consider wrapping the “active job check + createReleaseJob” in a single serialisable transaction on the job tables (or acquire a FOR UPDATE lock on release_target here as well).


84-95: ⚠️ Potential issue

getReleaseTargetIdFromRelease may throw when the release row is missing

takeFirst() will throw (or return undefined – depending on the dialect helper) when the release row is not found.
Because the subsequent destructuring { releaseTargetId } is unconditional, the worker would crash with a runtime error instead of cleanly short-circuiting.

A defensive change costs almost nothing and shields us from edge-cases (e.g. the release was deleted between the earlier insert and this query, or a malformed ID gets through).

-    .then(takeFirst)
-    .then(({ releaseTargetId }) => releaseTargetId);
+    .then(takeFirstOrNull)
+    .then((row) => row?.releaseTargetId ?? null);

Call-sites should be prepared to handle a possible null return (currently only one call-site – lines 212-217 – would need a null-check).

⛔ Skipped due to learnings
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#395
File: packages/api/src/router/environment-page/resources/router.ts:40-45
Timestamp: 2025-03-24T18:46:38.894Z
Learning: The `takeFirst` utility function in the codebase (from `@ctrlplane/db`) throws an Error with message "Found non unique or inexistent value" if the result array doesn't contain exactly one element, making additional null/undefined checks unnecessary after its use.
packages/job-dispatch/src/job-update.ts (1)

84-91: Nice, concise helper – one nit

getIsJobJustCompleted is clear and expressive. 👍
(Optional) You could inline the boolean variables to save a couple of lines, but current form is perfectly readable.

Comment on lines +93 to +112
const getReleaseTargetFromJob = (jobId: string) =>
db
.select()
.from(schema.releaseJob)
.innerJoin(
schema.release,
eq(schema.releaseJob.releaseId, schema.release.id),
)
.innerJoin(
schema.versionRelease,
eq(schema.release.versionReleaseId, schema.versionRelease.id),
)
.innerJoin(
schema.releaseTarget,
eq(schema.versionRelease.releaseTargetId, schema.releaseTarget.id),
)
.where(eq(schema.job.id, jobId))
.then(takeFirstOrNull)
.then((result) => result?.release_target ?? null);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect WHERE clause – schema.job not joined, causes invalid SQL

The query filters with eq(schema.job.id, jobId) while the job table is not part of the FROM … JOIN chain. At runtime most SQL engines will raise “column reference ‘job.id’ is ambiguous / missing FROM-clause entry”.

Fix by filtering on the joined releaseJob.jobId, or join the job table explicitly:

-    .where(eq(schema.job.id, jobId))
+    .where(eq(schema.releaseJob.jobId, jobId))

or

+    .innerJoin(schema.job, eq(schema.releaseJob.jobId, schema.job.id))
+    .where(eq(schema.job.id, jobId))

Without this fix updateJob will always fail when it reaches getReleaseTargetFromJob, preventing automatic chaining of jobs.

@adityachoudhari26 adityachoudhari26 deleted the release-target-concurrency branch May 7, 2025 06:40
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