Skip to content

Conversation

@ChiragAgg5k
Copy link
Member

@ChiragAgg5k ChiragAgg5k commented Dec 9, 2025

Summary

  • Fixed a resource leak in the Adapter::call() method where cURL handles were not being closed when exceptions were thrown
  • Wrapped cURL operations in a try-finally block to ensure curl_close() is always called

Problem

The call() method in src/Analytics/Adapter.php creates a cURL handle but throws exceptions before reaching curl_close() in two scenarios:

  1. When curl_errno($ch) detects an error (line 173)
  2. When HTTP status code is >= 400 (line 178)

This causes cURL resources to leak over time, leading to memory issues in long-running applications.

Solution

Added a try-finally block around the cURL operations to guarantee that curl_close($ch) is always executed, even when exceptions are thrown.

Test plan

  • Verify existing functionality still works correctly
  • Confirm no regression in error handling behavior
  • Memory profiling would show no resource leaks under repeated error conditions

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced analytics adapter reliability by ensuring guaranteed resource cleanup in all scenarios, including error conditions and early terminations. This improvement prevents resource leaks and enhances overall system stability and performance.

✏️ Tip: You can customize this high-level summary in your review settings.

Added try-finally block to ensure curl_close() is always called, even when exceptions are thrown. Previously, cURL handles would leak when curl_errno() or HTTP error status checks threw exceptions before curl_close() was reached.
@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

The changes refactor curl resource handling in src/Analytics/Adapter.php by wrapping curl operations within a try/finally block. A guaranteed curl_close($ch) call is added to the finally block to ensure resource cleanup occurs regardless of execution path. Existing error handling and response processing logic is preserved, with statements reordered to align with the new control flow structure. The code uses the locally imported Exception class in place of fully qualified names. No public or exported entity declarations are modified.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Curl resource lifecycle verification: Confirm that all curl operations (curl_exec, status checks, header parsing) occur within the try block and that the finally block correctly releases resources in all code paths.
  • Exception handling validation: Verify that exception throwing and catching behavior is preserved and that the locally imported Exception class is correctly applied throughout the modified block.
  • Response processing order: Review the reordering of response parsing and error handling statements to ensure the logic flow remains correct and that response data is properly processed before resource cleanup.
  • Early return paths: Examine any early returns within the try block to ensure they properly trigger finally block execution for resource cleanup.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing a cURL resource leak in the Adapter::call() method by ensuring proper resource cleanup.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-curl-memory-leak

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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 (1)
src/Analytics/Adapter.php (1)

167-171: Edge case: Content-Type without semicolon won't be parsed correctly.

If $responseType is "application/json" (no charset suffix), strpos($responseType, ';') returns false, causing substr() to return an empty string. This would skip JSON decoding.

Consider using explode or str_contains for more robust handling:

-            switch (substr($responseType, 0, strpos($responseType, ';'))) {
-                case 'application/json':
-                    $responseBody = json_decode($responseBody, true);
-                    break;
-            }
+            $mimeType = strstr($responseType, ';', true) ?: $responseType;
+            if ($mimeType === 'application/json') {
+                $responseBody = json_decode($responseBody, true);
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e79067b and aea4e8d.

📒 Files selected for processing (1)
  • src/Analytics/Adapter.php (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Tests
src/Analytics/Adapter.php

[error] 181-181: HTTP 401 Unauthorized from Plausible API: Invalid API key.

🪛 PHPMD (2.15.0)
src/Analytics/Adapter.php

145-145: Avoid unused parameters such as '$curl'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (1)
src/Analytics/Adapter.php (1)

139-188: LGTM! The try-finally pattern correctly ensures cURL resource cleanup.

The fix properly addresses the resource leak by guaranteeing curl_close($ch) executes regardless of whether exceptions are thrown on error paths (curl_errno or HTTP status >= 400).

$responseBody = json_decode($responseBody, true);
break;
}
$responseType = $responseHeaders['Content-Type'] ?? '';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Case mismatch: header key stored lowercase but accessed with mixed case.

Line 153 stores headers with strtolower(), but this line accesses with 'Content-Type'. PHP arrays are case-sensitive, so this will always return the default empty string, preventing JSON response parsing.

-            $responseType = $responseHeaders['Content-Type'] ?? '';
+            $responseType = $responseHeaders['content-type'] ?? '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$responseType = $responseHeaders['Content-Type'] ?? '';
$responseType = $responseHeaders['content-type'] ?? '';
🤖 Prompt for AI Agents
In src/Analytics/Adapter.php around line 164, the code reads
$responseHeaders['Content-Type'] but headers were normalized to lowercase
earlier, so that key will never match; update the access to use the lowercase
key (e.g. 'content-type') or call strtolower on the looked-up key, so the
Content-Type value is retrieved correctly and JSON responses can be parsed.

@loks0n loks0n merged commit ea70885 into main Dec 9, 2025
3 of 4 checks passed
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.

3 participants