Skip to content

Commit

Permalink
Avoid stack overflow when fetching nested comments, reduce max commen…
Browse files Browse the repository at this point in the history
…t depth to 50 (#5009)

* Avoid stack overflow when fetching deeply nested comments

* add test case

* reduce comment depth, add docs

* decrease

* reduce max comment depth to 50

* fmt

* clippy

* cleanup
  • Loading branch information
Nutomic committed Oct 1, 2024
1 parent 9b5a9ee commit 1ba848f
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 21 deletions.
14 changes: 0 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions api_tests/src/comment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,3 +858,26 @@ test("Dont send a comment reply to a blocked community", async () => {
blockRes = await blockCommunity(beta, newCommunityId, false);
expect(blockRes.blocked).toBe(false);
});

/// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also
/// fetched recursively. Ensure that it works properly.
test("Fetch a deeply nested comment", async () => {
let lastComment;
for (let i = 0; i < 50; i++) {
let commentRes = await createComment(
alpha,
postOnAlphaRes.post_view.post.id,
lastComment?.comment_view.comment.id,
);
expect(commentRes.comment_view.comment).toBeDefined();
lastComment = commentRes;
}

let betaComment = await resolveComment(
beta,
lastComment!.comment_view.comment,
);

expect(betaComment!.comment!.comment).toBeDefined();
expect(betaComment?.comment?.post).toBeDefined();
});
3 changes: 1 addition & 2 deletions crates/api_crud/src/comment/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
MAX_COMMENT_DEPTH_LIMIT,
};

const MAX_COMMENT_DEPTH_LIMIT: usize = 100;

#[tracing::instrument(skip(context))]
pub async fn create_comment(
data: Json<CreateComment>,
Expand Down
19 changes: 14 additions & 5 deletions crates/apub/src/protocol/objects/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ use lemmy_db_schema::{
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
use lemmy_utils::{error::LemmyResult, LemmyErrorType, MAX_COMMENT_DEPTH_LIMIT};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::ops::Deref;
use url::Url;

#[skip_serializing_none]
Expand Down Expand Up @@ -58,9 +57,19 @@ impl Note {
&self,
context: &Data<LemmyContext>,
) -> LemmyResult<(ApubPost, Option<ApubComment>)> {
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
let parent = Box::pin(self.in_reply_to.dereference(context).await?);
match parent.deref() {
// We use recursion here to fetch the entire comment chain up to the top-level parent. This is
// necessary because we need to know the post and parent comment in order to insert a new
// comment. However it can also lead to stack overflow when fetching many comments recursively.
// To avoid this we check the request count against max comment depth, which based on testing
// can be handled without risking stack overflow. This is not a perfect solution, because in
// some cases we have to fetch user profiles too, and reach the limit after only 25 comments
// or so.
// A cleaner solution would be converting the recursion into a loop, but that is tricky.
if context.request_count() > MAX_COMMENT_DEPTH_LIMIT as u32 {
Err(LemmyErrorType::MaxCommentDepthReached)?;
}
let parent = self.in_reply_to.dereference(context).await?;
match parent {
PostOrComment::Post(p) => Ok((p.clone(), None)),
PostOrComment::Comment(c) => {
let post_id = c.post_id;
Expand Down
2 changes: 2 additions & 0 deletions crates/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_secs(60);

pub const CACHE_DURATION_API: Duration = Duration::from_secs(1);

pub const MAX_COMMENT_DEPTH_LIMIT: usize = 50;

#[macro_export]
macro_rules! location_info {
() => {
Expand Down

0 comments on commit 1ba848f

Please sign in to comment.