Skip to content

Conversation

@roman380
Copy link
Contributor

@roman380 roman380 commented Aug 13, 2025

… sent buffers; fixed ownership over buffer array

Summary by CodeRabbit

  • New Features

    • Per-send user context for stream sends — callers can attach a context pointer to each send, and it is returned with the send-complete event.
    • Stream-close preserves prior behavior when no per-send context is provided.
  • Documentation

    • API docs updated to describe the new per-send context parameter and its propagation to send-complete events.

@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

Adds a per-send void* user_context to stream sends: updates wtf_stream_send signature, extends SEND_COMPLETE event payload to include user_context, propagates the context through the internal send context and types, adjusts buffer lifetime handling, and updates call sites (e.g., wtf_stream_close).

Changes

Cohort / File(s) Summary of changes
Public API: header
include/wtf.h
Added void* user_context parameter to wtf_stream_send; extended wtf_stream_event_t SEND_COMPLETE payload with user_context; updated related API comments.
Stream send implementation
src/stream.c
wtf_stream_send signature changed to accept user_context; allocates send context with embedded buffer and QUIC_BUFFER array, stores user_context, copies input buffers into internal storage, passes quic_buffers and send context to QUIC API, includes user_context in SEND_COMPLETE events, and updated wtf_stream_close to call wtf_stream_send(..., NULL).
Internal types
src/types.h
Added void* user_context to wtf_internal_send_context (inserted after uint32_t count), moving QUIC_BUFFER* quic_buffers to follow it.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Stream as wtf_stream_send
  participant SendCtx as send_context
  participant Transport as QUIC_Send
  participant App as EventCallback

  Caller->>Stream: wtf_stream_send(buffers, count, fin, user_context)
  Stream->>SendCtx: allocate send_ctx, copy buffers, store user_context
  Stream->>Transport: submit send (send_ctx->quic_buffers, ClientContext=send_ctx)
  Transport-->>Stream: send completion
  Stream-->>App: SEND_COMPLETE{buffers, count, cancelled, user_context}
  Stream->>SendCtx: cleanup
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I hop with bytes and secrets tucked,
A tiny tag on every pluck.
Each send I carry, neat and swift,
To tell the app just who to gift.
Thump-thump — delivery, a clever trick! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a242fe0 and 790b7d3.

📒 Files selected for processing (2)
  • src/stream.c (3 hunks)
  • src/types.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types.h
🔇 Additional comments (7)
src/stream.c (7)

287-287: LGTM! User context correctly propagated to send completion event.

The send completion event now includes the per-send user context from the send context, which aligns with the API enhancement objectives.


567-567: LGTM! Function signature correctly updated with user context parameter.

The wtf_stream_send function signature now includes the void* user_context parameter as intended by the PR objectives.


574-576: Excellent memory allocation strategy with trailing region design.

The single allocation approach with a trailing region for buffers and QUIC_BUFFER arrays is efficient and eliminates the ownership issues from the previous implementation. This addresses the past review concerns about buffer lifetime management.


581-585: Well-designed pointer arithmetic for internal buffer layout.

The pointer arithmetic correctly sets up the internal buffer layout:

  • send_ctx->buffers points to the wtf_buffer_t array in the trailing region
  • send_ctx->quic_buffers points to the QUIC_BUFFER array after the wtf_buffer_t array
  • send_ctx->user_context stores the per-send context

588-592: Buffer copying logic is correct and addresses ownership concerns.

The loop correctly:

  1. Copies each input buffer to the internal buffer array for completion reporting
  2. Sets up the corresponding QUIC_BUFFER entries pointing to the original data
  3. Maintains the original data pointers (shallow copy as documented)

This resolves the previous ownership issues while preserving performance.


601-601: Correct usage of internal QUIC buffers for MsQuic API.

The StreamSend call now uses send_ctx->quic_buffers instead of the original buffers pointer, which addresses the use-after-scope risk identified in the previous review.


669-669: Stream close correctly updated for new API signature.

The wtf_stream_close function correctly passes NULL as the user context when calling wtf_stream_send, preserving backward compatibility semantics.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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

@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 (4)
include/wtf.h (3)

263-264: Two user_context fields in wtf_stream_event_t may confuse API consumers

Top-level event->user_context is the stream-level context (wtf_stream_set_context), and send_complete.user_context is the per-send context (wtf_stream_send). Consider clarifying naming in docs and examples to avoid misuse.

Apply this minimal doc tweak to reduce ambiguity:

-    void* user_context;            //! User-provided context data (wtf_stream_set_context)
+    void* user_context;            //! Stream-level user context (set via wtf_stream_set_context)

560-561: New user_context parameter docs are clear

The parameter description reads well and conveys the behavior. Consider also stating that the library doesn't own/free this pointer and that its lifetime must extend through the send completion.

-//! @param user_context an opaque context to be passed back to the caller on completion
+//! @param user_context an opaque context to be passed back to the caller on completion.
+//!        The library does not take ownership and will not free this pointer. It must remain
+//!        valid until the WTF_STREAM_EVENT_SEND_COMPLETE callback fires.

577-579: Doc note is misleading given buffer array lifetime management

The current note says "passes the original buffers directly to the QUIC layer without modification or copying." With the new internal array to fix ownership over the buffer array, we should clarify that no data copying occurs, but the array headers may be shallow-copied.

-//! @note The function passes the original buffers directly to the QUIC layer without
-//! modification or copying. No protocol headers are added for stream data.
+//! @note No protocol headers are added for stream data. The buffer data pointers are not copied.
+//!       The library may shallow-copy the buffer descriptors to manage their lifetime across
+//!       the asynchronous send completion.
src/stream.c (1)

172-183: Initialize user_context for internal (header) sends to avoid accidental use

For internal header sends, user_context is currently uninitialized. While it's not read for internal sends today, future changes could inadvertently access it. Initialize to NULL.

     send_ctx->buffers = header_buffer;
     send_ctx->count = 1;
+    send_ctx->user_context = NULL;
     send_ctx->internal_send = 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 9d0a455 and 8c6ec13.

📒 Files selected for processing (3)
  • include/wtf.h (4 hunks)
  • src/stream.c (5 hunks)
  • src/types.h (1 hunks)
🔇 Additional comments (5)
src/types.h (1)

325-328: Per-send user_context addition is fine; make sure all internal send contexts initialize it

Adding the per-send user_context to wtf_internal_send_context is aligned with the PR objective. Ensure every allocation path for this struct explicitly initializes user_context (NULL for internal sends) to avoid accidental use of uninitialized memory in future changes.

include/wtf.h (2)

273-277: SEND_COMPLETE payload enriched with per-send context — good

Exposing the per-send user_context in the send_complete payload matches the API extension and unblocks apps from correlating completions. No issues found.


580-581: API break: wtf_stream_send signature change

No internal uses of wtf_stream_send were found in .c, .cc, or .cpp files in this repo. This update still introduces a source-breaking change for external consumers:

  • Location: include/wtf.h lines 580–581 (now takes an extra void* user_context parameter)
  • Ensure all consumer call sites are updated to pass the new user_context argument
  • Consider adding a temporary compatibility shim for the 4-argument overload
  • Document this change in release notes and bump the library’s version
src/stream.c (2)

273-289: Per-send user_context is correctly propagated to SEND_COMPLETE

The event now includes send_ctx->user_context, enabling callers to correlate completions without relying on external state. This matches the API update.


668-669: wtf_stream_close updated to pass NULL per-send context — good

Preserves previous semantics while conforming to the new API. Looks good.

… sent buffers; fixed ownership over buffer array
@roman380 roman380 force-pushed the SendMemoryOwnership branch from 8c6ec13 to a242fe0 Compare August 13, 2025 13:31
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: 0

🔭 Outside diff range comments (3)
include/wtf.h (3)

259-276: Disambiguate per-stream vs per-send context fields in wtf_stream_event_t

There are two user_context pointers in this struct: one at the top-level (stream-scoped) and one in send_complete (per-send). Rename the latter to avoid ambiguity in client code.

 typedef struct {
     wtf_stream_event_type_t type;  //! Type of stream event
     wtf_stream_t* stream;          //! Stream that generated the event
-    void* user_context;            //! User-provided context data (wtf_stream_set_context)
+    void* user_context;            //! Stream-scoped context (wtf_stream_set_context)
 
     union {
         struct {
             wtf_buffer_t* buffers;  //! Array of received data buffers
             uint32_t buffer_count;  //! Number of buffers
             bool fin;               //! True if this is the final data
         } data_received;
 
         struct {
             wtf_buffer_t* buffers;  //! Array of sent data buffers
             uint32_t buffer_count;  //! Number of buffers sent
-            void* user_context;     //! User-provided context data (wtf_stream_send)
+            void* send_user_context; //! Per-send context (from wtf_stream_send)
             bool cancelled;         //! True if send was cancelled
         } send_complete;

563-579: Clarify lifetime of buffer descriptors in wtf_stream_send docs

The stream send implementation allocates and copies only the array of wtf_buffer_t descriptors (not the buffer data) into an internal array, then frees that internal array on send completion. Update the docs to reflect that callers may free their own buffers array immediately, but must keep each buffers[i].data valid until the WTF_STREAM_EVENT_SEND_COMPLETE event.

• Location: include/wtf.h, around wtf_stream_send doc (lines ~563–579)
• Replace the ambiguous note with:

- //! @note The function passes the original buffers directly to the QUIC layer without
- //! modification or copying. No protocol headers are added for stream data.
+ //! @note The function copies your array of buffer descriptors (pointers and lengths)
+ //! into an internal array but does not copy the actual data. You may free the
+ //! `buffers` array itself immediately after calling `wtf_stream_send`. However,
+ //! each `buffers[i].data` pointer must remain valid until the library emits
+ //! `WTF_STREAM_EVENT_SEND_COMPLETE`, at which point the internal descriptors
+ //! are freed. No protocol headers are added for stream data.

555-581: Preserve public API compatibility: introduce wtf_stream_send_ex and retain an inline wtf_stream_send wrapper

The new five-parameter signature for wtf_stream_send is an ABI break. To maintain the existing four-parameter API, rename the implementation to wtf_stream_send_ex, provide an inline wrapper for wtf_stream_send that passes NULL for user_context, and update the docs accordingly.

Files to update:

  • include/wtf.h (around line 579): rename the API entry, insert the _ex variant and inline wrapper, and adjust the @param comments.
  • src/stream.c (around line 568): rename the definition from wtf_stream_send to wtf_stream_send_ex.
  • samples/echo_server.c: existing calls to wtf_stream_send(...) will continue to work via the inline wrapper.
  • README.md (examples at lines 92 and 118): ensure calls omit user_context or add a note about the new _ex variant.

Example diff:

 //! @param fin true if this is the final data
-//! @param user_context an opaque context to be passed back to the caller on completion
+//! @param user_context an opaque context to be passed back to the caller on completion (…_ex only)
 //! @return WTF_SUCCESS on success, error code on failure
 …
-WTF_API wtf_result_t wtf_stream_send(wtf_stream_t* stream, const wtf_buffer_t* buffers,
-                                     uint32_t buffer_count, bool fin, void* user_context);
+// Extended variant with per-send context.
+WTF_API wtf_result_t wtf_stream_send_ex(wtf_stream_t* stream, const wtf_buffer_t* buffers,
+                                        uint32_t buffer_count, bool fin, void* user_context);
+// Backward-compatible inline wrapper preserving the original signature.
+static inline wtf_result_t wtf_stream_send(wtf_stream_t* stream, const wtf_buffer_t* buffers,
+                                           uint32_t buffer_count, bool fin) {
+    return wtf_stream_send_ex(stream, buffers, buffer_count, fin, NULL);
+}

Please confirm whether maintaining source compatibility is required for this release; if not, document this breaking change in the release notes and bump the major version.

🧹 Nitpick comments (1)
src/types.h (1)

321-328: Const-correctness and ownership note for internal send context

Good addition of per-send user_context. Two improvements:

  • Make buffers const to prevent accidental mutation of caller data.
  • Document that user_context is opaque and not owned/freed by the library.

Apply:

 typedef struct {
-    wtf_buffer_t* buffers;
+    const wtf_buffer_t* buffers;
     uint32_t count;
-    void* user_context;
+    void* user_context; // opaque; lifetime managed by the caller; not owned/freed by the library
     wtf_session* session;
     bool internal_send;
 } wtf_internal_send_context;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c6ec13 and a242fe0.

📒 Files selected for processing (3)
  • include/wtf.h (4 hunks)
  • src/stream.c (5 hunks)
  • src/types.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/stream.c

@andrewmd5
Copy link
Owner

I’m going to hold off merging this for the moment as I have upstream patches I need to make to msquic

@roman380
Copy link
Contributor Author

No problem, I am sure these changes will survive upstream merge and send part is perfect now. We are likely to extend similarly to datagrams when it comes to audio. I'd say the bigger problem is #21 now, but we'll also have some time to find a solution as context/server instance bandaided as a singleton without destruction seems to be sufficient to make acceptable experience integrating this further.

@guest271314
Copy link

Does this fix the double free() issue, and echo server erroring and aborting after close of 1 stream?

@roman380
Copy link
Contributor Author

Does this fix the double free() issue, and echo server erroring and aborting after close of 1 stream?

Hard to tell, we had to put this on pause a bit and so I just don't remember already. We did have this working pretty well as a prototype though.

@guest271314
Copy link

I think if line 547 in echo_server.c is commented the double free(), error and abort go away.

I pulled this code and tested. Can't tell the difference between this PR and main after commenting that free() - and adding an exit() in the signal handler, otherwsie CTRL+C doesn't work. See #24 (comment).

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