cloudwego / cloudwego/netpoll

expose explicit PauseRead/ResumeRead on Connection for application-driven read backpressure

Open
#422 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
4.6k
Forks
505
PR merge metrics
No merged PRs in 30d

Description

**Is your feature request related to a problem? Please describe.**

Proxies, gateways and high fan-in servers can receive faster than they consume, but netpoll keeps reading into the inbound `LinkBuffer` as long as `EPOLLIN` fires. When consumers fall behind there is no way to stop reading, so memory grows unbounded and the process eventually OOMs. The "overloaded" signal lives in the application (e.g. downstream/outbound queue depth), so the application needs a way to pause/resume reading and let TCP flow control slow the peer.

**Describe the solution you'd like**

Three minimal primitives on the `Connection` interface:

```go
PauseRead() error // stop monitoring EPOLLIN for this connection (idempotent)
ResumeRead() error // resume monitoring EPOLLIN (idempotent)
IsReadPaused() bool // whether read is currently paused
```
Mechanism, not policy — the application decides when to pause/resume based on its own state.

Key implementation points:

Read and write interest become independent dimensions; the poll mask is recomputed from (readPaused, writeWatching) on each transition (adds PollR2N/PollN2R/PollN2W/PollW2N) so toggling read never disturbs a pending write.
Paused with no pending write drops to "no interest" instead of write-only, avoiding a level-triggered EPOLLOUT busy-spin.
Transitions are serialized with a per-connection mutex (the two dimensions are mutated by app vs poller, and epoll_ctl(MOD) sets an absolute mask).
Guarded by IsActive(); epoll + kqueue paths updated. EPOLLIN is level-triggered, so resume is lossless.
Pure addition: no behavior change unless the new APIs are called.

Describe alternatives you've considered

Fixed inbound-buffer threshold (feat/read-throttle / WithReadBufferThreshold). Auto-pauses on inputBuffer.Len() >= threshold. It throttles on the wrong signal — a proxy that drains netpoll's buffer into its own queue never hits the threshold yet still OOMs; it hard-codes one global policy (no hysteresis / per-route control); its implicit resume can stay paused forever; and its PollRW2W pause keeps EPOLLOUT, busy-spinning on receive-mostly conns. The explicit API is the underlying mechanism, on top of which such a threshold could still be built in user space.
App-level sleep/return in OnRequest. Doesn't stop the kernel delivering data, gives no TCP backpressure, and holds the processing lock while sleeping.
Additional context

Prior art: PR #298 / the feat/read-throttle branch, generalized here from a fixed-threshold policy into an explicit mechanism. I have a working implementation (epoll + kqueue) with a TestConnectionPauseResumeRead test and am happy to open a PR #423 .

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.