Skip to content

Close a UDP proxy backend that becomes active after eviction#2019

Open
devops-thiago wants to merge 1 commit into
apple:mainfrom
devops-thiago:fix-udp-proxy-socket-leak
Open

Close a UDP proxy backend that becomes active after eviction#2019
devops-thiago wants to merge 1 commit into
apple:mainfrom
devops-thiago:fix-udp-proxy-socket-leak

Conversation

@devops-thiago

Copy link
Copy Markdown

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Motivation and Context

Fixes #2015.

UDPProxyFrontend keeps at most 256 backends in an LRU cache and closes whichever one is evicted. UDPProxyBackend.close() returned early when the backend channel had not finished binding:

func close() {
    guard let channel = state.channel else {
        self.log?.warning("backend - close on inactive channel")
        return
    }
    _ = channel.close()
}

state.channel is nil from construction until channelActive runs, which happens after bind completes. A backend evicted inside that window is never closed. channelActive then adopts the channel, and because the ProxyContext has already been dropped from the cache, no reference remains that could close it. The socket stays open for the lifetime of the process.

The existing warning shows the case was anticipated, but the early return leaves the socket open rather than deferring the close.

This change records the close request in the backend state and has channelActive honour it, closing the channel instead of adopting it and discarding any queued payloads. Both paths run on the event loop the backend is bootstrapped on, since DatagramBootstrap(group: context.eventLoop) and NIOLoopBound(proxy, eventLoop: context.eventLoop) bind them to that loop, so the flag needs no further synchronisation.

UDPProxyBackend changes from file-private to internal so the test can drive the ordering directly. Nothing outside the module references it.

Testing

  • Tested locally
  • Added/updated tests
  • Added/updated docs

testBackendClosedBeforeChannelActiveClosesTheChannel calls close() before the channel becomes active, then activates it through an EmbeddedChannel and asserts the channel is no longer active. The test fails against the current code and passes with this change. testUDPForwarder and testLRUCache continue to pass.

UDPProxyBackend.close() returned early when the backend channel had not
finished binding, leaving the socket open. The frontend calls close() on
whichever backend the LRU cache evicts, so a backend evicted inside the
bind window was never closed: channelActive then adopted the channel, and
because the ProxyContext had already been dropped from the cache, no
reference remained that could close it. The socket stayed open for the
lifetime of the process.

Record the close request in the backend state and have channelActive
honour it, closing the channel instead of adopting it. Both paths run on
the event loop the backend is bootstrapped on, so the flag needs no
additional synchronisation.

UDPProxyBackend becomes internal so the regression test can drive the
eviction ordering directly.

Fixes apple#2015
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.

[Bug]: UDP proxy socket leaks when a backend is evicted before its channel becomes active

1 participant