Skip to content

fix: fail open realtime publishing#11607

Merged
loks0n merged 1 commit into1.9.xfrom
fix/failed-open-realtime-publishing
Mar 23, 2026
Merged

fix: fail open realtime publishing#11607
loks0n merged 1 commit into1.9.xfrom
fix/failed-open-realtime-publishing

Conversation

@loks0n
Copy link
Member

@loks0n loks0n commented Mar 22, 2026

What does this PR do?

(Provide a description of what this PR does and why it's needed.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)

Related PRs and Issues

  • (Related PR or issue)

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

The change adds defensive error handling to realtime event transmission in src/Appwrite/Event/Realtime.php. A Utopia\Console import is added, and each realtime->send() call is wrapped in a try/catch block. Caught exceptions are logged via Console::error() with the exception message, allowing the loop to continue processing remaining projectIds. The trigger() method continues to return true regardless of whether individual send operations succeed or fail.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is the standard contribution template with no meaningful content filled in regarding the changes, motivation, or testing. Provide a detailed description of the change, the motivation for making the code fail open instead of failing closed, and the test plan to validate this behavior.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: fail open realtime publishing' clearly relates to the main change: wrapping realtime send calls in try/catch to handle exceptions gracefully rather than failing the entire operation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/failed-open-realtime-publishing

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.

Tip

CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.

Add a configuration file to your project to customize how CodeRabbit runs biome.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR wraps the $this->realtime->send() call in Realtime::trigger() with a try-catch block so that a failure to publish a realtime event does not propagate and disrupt the broader event pipeline (fail-open pattern). The change is small and targeted, and the intent is sound — however, the catch clause only catches \Exception, not PHP's \Error hierarchy, which means certain low-level failures (e.g., a TypeError from a null $register global inside getPubSubPool()) would still bubble up uncaught, undermining the fail-open guarantee.

  • Catch scope too narrow: catch (\Exception $e) should be catch (\Throwable $e) to also handle PHP \Error subtypes and fully honour the fail-open contract.
  • Limited error logging: Only getMessage() is recorded; including the exception class and stack trace would significantly aid production debugging.

Confidence Score: 3/5

  • Safe to merge with low risk, but the catch clause should be widened to \Throwable before shipping to fully achieve the stated fail-open goal.
  • The change is minimal and well-intentioned. The only functional concern is that catch (\Exception $e) does not cover PHP \Error types, so the fail-open behaviour is incomplete. No data loss or security risk is introduced.
  • src/Appwrite/Event/Realtime.php — the catch type should be widened to \Throwable.

Important Files Changed

Filename Overview
src/Appwrite/Event/Realtime.php Adds a try-catch around $this->realtime->send() to implement fail-open behaviour for realtime publishing; however, it only catches \Exception rather than \Throwable, meaning PHP \Error types (e.g. TypeError from a null $register global) will still propagate and break the fail-open guarantee.

Reviews (1): Last reviewed commit: "fix: fail open realtime publishing" | Re-trigger Greptile

Comment on lines +112 to +114
} catch (\Exception $e) {
Console::error('Realtime send failed: '.$e->getMessage());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 Catch \Throwable instead of \Exception for true fail-open

The catch block only handles \Exception but not PHP's \Error hierarchy (e.g., TypeError, ValueError, Error). The getPubSubPool() method accesses a global $register and calls chained methods on it — if $register is null or ->get('pools') returns null, PHP 8 will throw a \TypeError (which extends \Error, not \Exception), bypassing this catch block entirely and propagating the exception upward. This defeats the stated "fail open" intent of the PR.

The standard PHP pattern for a true fail-open is to catch \Throwable, which covers both \Exception and \Error:

Suggested change
} catch (\Exception $e) {
Console::error('Realtime send failed: '.$e->getMessage());
}
} catch (\Throwable $e) {
Console::error('Realtime send failed: '.$e->getMessage());
}

]
);
} catch (\Exception $e) {
Console::error('Realtime send failed: '.$e->getMessage());
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Log stack trace for easier debugging

Only getMessage() is captured. Without the stack trace, it can be very difficult to determine the root cause of a failure in production (e.g., which layer of the PubSub/pool code failed). Consider including getTraceAsString() or at least the exception class:

Suggested change
Console::error('Realtime send failed: '.$e->getMessage());
Console::error('Realtime send failed: ' . get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString());

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Appwrite/Event/Realtime.php`:
- Around line 112-114: Replace the catch block to catch \Throwable instead of
\Exception so errors like TypeError are handled (locate the try surrounding
$this->realtime->send()), and update the Console::error call to follow PSR-12
spacing for concatenation (the string + $e->getMessage() expression used with
Console::error). Ensure the catch uses catch (\Throwable $e) and the message
concatenation uses spaces around the . operator while still calling
Console::error('Realtime send failed: ' . $e->getMessage()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7211b9c3-08ee-4d34-af43-48827f93edea

📥 Commits

Reviewing files that changed from the base of the PR and between 6e59e13 and be76990.

📒 Files selected for processing (1)
  • src/Appwrite/Event/Realtime.php

Comment on lines +112 to +114
} catch (\Exception $e) {
Console::error('Realtime send failed: '.$e->getMessage());
}
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In PHP 8.x, does catch (\Exception $e)catch\Error/\TypeError, or is catch (\Throwable $e) required to catch both?

💡 Result:

In PHP 8.x, catch (\Exception $e) does NOT catch \Error or \TypeError. You must use catch (\Throwable $e) to catch both Exceptions and Errors (including TypeError, which extends Error). Throwable is the base interface implemented by both Exception and Error. Error (and its subclasses like TypeError) do not extend Exception, so a catch block specific to Exception will not match them. This behavior has been consistent since PHP 7, and remains the same in PHP 8.x.

Citations:


🏁 Script executed:

# Check if the file exists and view the context around lines 112-114
if [ -f "src/Appwrite/Event/Realtime.php" ]; then
  echo "=== File exists, showing lines 100-125 ==="
  sed -n '100,125p' src/Appwrite/Event/Realtime.php | cat -n
else
  echo "File not found, searching for similar files..."
  find . -name "Realtime.php" -type f
fi

Repository: appwrite/appwrite

Length of output: 917


🏁 Script executed:

# Search for other catch (\Exception $e) patterns in the codebase to see if this is a broader issue
rg 'catch\s*\(\s*\\Exception\s*\$' --type php | head -20

Repository: appwrite/appwrite

Length of output: 881


Use catch (\Throwable $e) instead of catch (\Exception $e) to handle all error types.

In PHP, \Exception and \Error are separate hierarchies under the common \Throwable interface. The current code only catches \Exception, missing \Error and \TypeError. This means a TypeError or other Error thrown from $this->realtime->send() will bypass the error handler and propagate up, defeating the fail-open intent. The fix also improves PSR-12 compliance with operator spacing.

Proposed fix
-            } catch (\Exception $e) {
-                Console::error('Realtime send failed: '.$e->getMessage());
+            } catch (\Throwable $e) {
+                Console::error('Realtime send failed: ' . $e->getMessage());
             }
📝 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
} catch (\Exception $e) {
Console::error('Realtime send failed: '.$e->getMessage());
}
} catch (\Throwable $e) {
Console::error('Realtime send failed: ' . $e->getMessage());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Event/Realtime.php` around lines 112 - 114, Replace the catch
block to catch \Throwable instead of \Exception so errors like TypeError are
handled (locate the try surrounding $this->realtime->send()), and update the
Console::error call to follow PSR-12 spacing for concatenation (the string +
$e->getMessage() expression used with Console::error). Ensure the catch uses
catch (\Throwable $e) and the message concatenation uses spaces around the .
operator while still calling Console::error('Realtime send failed: ' .
$e->getMessage()).

@github-actions
Copy link

github-actions bot commented Mar 22, 2026

🔄 PHP-Retry Summary

Flaky tests detected across commits:

Commit be76990 - 4 flaky tests
Test Retries Total Time Details
UsageTest::testFunctionsStats 1 10.13s Logs
UsageTest::testPrepareSitesStats 1 7ms Logs
LegacyCustomClientTest::testUniqueIndexDuplicate 1 240.34s Logs
LegacyTransactionsCustomServerTest::testCreateDocument 1 240.50s Logs

@github-actions
Copy link

✨ Benchmark results

  • Requests per second: 1,584
  • Requests with 200 status code: 285,107
  • P99 latency: 0.109094617

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 1,584 1,106
200 285,107 199,260
P99 0.109094617 0.205754617

@loks0n loks0n merged commit 9d976fd into 1.9.x Mar 23, 2026
48 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.

1 participant