Skip to content

perf: use sendfile to forward#2686

Merged
phanen merged 1 commit intomainfrom
sendfile
Apr 21, 2026
Merged

perf: use sendfile to forward#2686
phanen merged 1 commit intomainfrom
sendfile

Conversation

@phanen
Copy link
Copy Markdown
Collaborator

@phanen phanen commented Apr 20, 2026

Not sure will this be faster

Summary by CodeRabbit

  • Refactor
    • Improved RPC data transfer for more reliable and efficient streaming, with better handling of end-of-stream and cleaner shutdown behavior.
    • Linux-specific I/O optimizations to enhance throughput and reduce stalls during long-running transfers.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

Platform-aware RPC reader added in lua/fzf-lua/rpc.lua: introduces _is_linux, a shared finish() helper to close sockets and exit, and a Linux-specific loop using uv.fs_sendfile to stream from receive_socket to stdout; non-Linux path retains read_start callback with adjusted EOF handling.

Changes

Cohort / File(s) Summary
RPC I/O and lifecycle
lua/fzf-lua/rpc.lua
Added _is_linux platform check and a shared finish() to close receive_socket/server_socket and call os.exit(0). On Linux, replaced callback stream handling with an infinite loop using uv.fs_sendfile to copy from socket to stdout, treating len == 0 as EOF and calling finish(). Non-Linux path keeps receive_socket:read_start but only invokes finish() when data is falsy; otherwise it writes data to stdout.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ReceiveSocket
  participant RPCProcess
  participant Stdout
  participant ServerSocket

  Client->>ReceiveSocket: send data
  ReceiveSocket->>RPCProcess: data available
  alt Linux (_is_linux)
    RPCProcess->>ReceiveSocket: uv.fs_sendfile -> stdout (fd 1)
    loop repeat until EOF
      ReceiveSocket-->>Stdout: file-send chunks
    end
    RPCProcess->>RPCProcess: detect len == 0 (EOF)
  else Non-Linux (callback)
    ReceiveSocket->>RPCProcess: read_start callback with data
    RPCProcess->>Stdout: io.write(data)
    alt data falsy
      RPCProcess->>RPCProcess: detect EOF
    end
  end
  RPCProcess->>ServerSocket: close
  RPCProcess->>ReceiveSocket: close
  RPCProcess->>RPCProcess: os.exit(0)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble bytes from pipe to sun,

uv sends, the chunks do run,
When zero comes I fold my paws,
Close the doors and end the cause,
Hop gone—RPC's work is done.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'perf: use sendfile to forward' is vague and incomplete, failing to clearly convey the actual change of adding Linux-specific sendfile optimization for socket data forwarding. Consider revising to be more specific, such as 'perf: use sendfile for Linux socket forwarding' or clarify what 'forward' refers to in the context of the implementation.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 sendfile

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
Copy Markdown

@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 `@lua/fzf-lua/rpc.lua`:
- Around line 37-49: The current tight loop calling uv.fs_sendfile synchronously
must be replaced with an async stream-based forwarder: stop calling
uv.fs_sendfile inside the while loop and instead call
receive_socket:read_start(callback) to receive chunks, then write those chunks
to server_socket (using server_socket:write or uv.write) inside the callback; on
nil/EOF from the read callback close receive_socket and server_socket and then
exit (or call uv.stop()/os.exit as appropriate), and ensure the libuv event loop
is driven (uv.run()) so the callbacks run; update the code paths around
uv.fs_sendfile, receive_socket, and server_socket to use this async
read_start/write pattern instead of the synchronous loop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ebc3eb43-7f42-4868-a1c0-3791eb85878f

📥 Commits

Reviewing files that changed from the base of the PR and between 8342463 and 323e271.

📒 Files selected for processing (1)
  • lua/fzf-lua/rpc.lua

Comment thread lua/fzf-lua/rpc.lua Outdated
@phanen
Copy link
Copy Markdown
Collaborator Author

phanen commented Apr 20, 2026

Seems hang on non-linux... I abort the cij

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
lua/fzf-lua/rpc.lua (1)

48-60: ⚠️ Potential issue | 🔴 Critical

Avoid the fs_sendfile tight loop until its fd/error behavior is proven safe.

If uv.fs_sendfile returns nil, err, len == 0 is false and this loop retries forever, hanging the RPC subprocess. Since receive_socket is an accepted libuv pipe/stream handle, the safe path is to keep the existing read_start forwarding unless this fd combination is explicitly verified.

🐛 Proposed safe fallback
-    if _is_linux then
-      while true do
-        local len = uv.fs_sendfile(1, receive_socket:fileno(), 0, 1024 * 1024)
-        local eof = len == 0
-        if eof then finish() end
-      end
-    end
-
     receive_socket:read_start(function(err, data)
-      assert(not err)
-      if not data then finish() end
+      assert(not err, err)
+      if not data then return finish() end
       io.write(data)
     end)

To verify the dependency behavior, check the official docs/man page for the Neovim/libuv version in use:

#!/bin/bash
set -euo pipefail

# Verify libuv's documented fs_sendfile signature/behavior.
curl -fsSL https://docs.libuv.org/en/v1.x/fs.html | grep -n -A8 -B4 'uv_fs_sendfile'

# Verify Linux sendfile fd restrictions and error modes.
curl -fsSL https://man7.org/linux/man-pages/man2/sendfile.2.html | grep -n -A35 -B8 'ERRORS'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lua/fzf-lua/rpc.lua` around lines 48 - 60, The tight loop using
uv.fs_sendfile inside the _is_linux branch can hang if uv.fs_sendfile returns
nil, err or an unexpected value; replace it with a safe attempt that checks the
return values and falls back to the existing read-based forwarding: call
uv.fs_sendfile once (or in a guarded loop) and if it returns an error (nil, err)
or a non-positive length (len == 0) break out and invoke finish or switch to
receive_socket:read_start forwarding, ensuring you reference _is_linux,
uv.fs_sendfile, receive_socket:fileno(), read_start and finish so the code only
uses fs_sendfile when its fd/error behavior is verified and otherwise relies on
the existing read_start path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@lua/fzf-lua/rpc.lua`:
- Around line 48-60: The tight loop using uv.fs_sendfile inside the _is_linux
branch can hang if uv.fs_sendfile returns nil, err or an unexpected value;
replace it with a safe attempt that checks the return values and falls back to
the existing read-based forwarding: call uv.fs_sendfile once (or in a guarded
loop) and if it returns an error (nil, err) or a non-positive length (len == 0)
break out and invoke finish or switch to receive_socket:read_start forwarding,
ensuring you reference _is_linux, uv.fs_sendfile, receive_socket:fileno(),
read_start and finish so the code only uses fs_sendfile when its fd/error
behavior is verified and otherwise relies on the existing read_start path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dad7654f-0fcc-42ea-9d64-544a20bacf97

📥 Commits

Reviewing files that changed from the base of the PR and between 323e271 and 69f9876.

📒 Files selected for processing (1)
  • lua/fzf-lua/rpc.lua

@phanen
Copy link
Copy Markdown
Collaborator Author

phanen commented Apr 21, 2026

sendfile 44s, io.write 50s... not sure if this meaningful

local uv = vim.uv
require("fzf-lua").fzf_exec(function(cb)
	coroutine.wrap(function()
		local co = coroutine.running()
		for i = 1, 20000000 do
			cb(tostring(i), function()
				coroutine.resume(co)
			end)
			coroutine.yield()
		end
		cb(nil)
	end)()
end, {
	multiprocess = false,
	keymap = { fzf = { load = "abort" } },
	winopts = {
		on_create = function(e)
			_G.start = uv.hrtime()
		end,
		on_close = function()
			print((uv.hrtime() - _G.start) * 1e-9)
		end,
	},
})

@phanen phanen merged commit beaa4e8 into main Apr 21, 2026
11 checks passed
@phanen phanen deleted the sendfile branch April 21, 2026 10:02
@ibhagwan
Copy link
Copy Markdown
Owner

sendfile 44s, io.write 50s... not sure if this meaningful

6ms is still something :)

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