Skip to content

Commit db631a8

Browse files
authored
Fixed memory leak caused by retain cycle in channel handlers (#354)
fixes: #353 ![CleanShot 2025-06-18 at 13 26 52@2x](https://github.com/user-attachments/assets/38e70e49-45cd-40c7-a0d7-4b0741ce41b5) ![CleanShot 2025-06-18 at 13 27 11@2x](https://github.com/user-attachments/assets/0f47d016-1419-41e6-a4f7-a65cb2576759) Signed-off-by: Kei Kamikawa <Code-Hex@users.noreply.github.com>
1 parent 490d170 commit db631a8

File tree

2 files changed

+52
-18
lines changed

2 files changed

+52
-18
lines changed

Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck
8080
return
8181
}
8282

83-
self.isClosed = true
84-
self.context?.close(promise: nil)
83+
self.closeConnection()
8584
self.responseCallbacks.receiveClose(.canceled, [:], ConnectError.canceled())
8685
}
8786
}
@@ -94,13 +93,21 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck
9493
}
9594
}
9695

96+
private func closeConnection() {
97+
if self.isClosed {
98+
return
99+
}
100+
101+
self.isClosed = true
102+
self.context?.close(promise: nil)
103+
}
104+
97105
// MARK: - ChannelInboundHandler
98106

99107
typealias OutboundOut = NIOHTTP1.HTTPClientRequestPart
100108
typealias InboundIn = NIOHTTP1.HTTPClientResponsePart
101109

102110
func channelActive(context: NIOCore.ChannelHandlerContext) {
103-
self.context = context
104111
if self.isClosed {
105112
return
106113
}
@@ -147,11 +154,23 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck
147154
trailers.map { .fromNIOHeaders($0) } ?? [:],
148155
nil
149156
)
150-
context.close(promise: nil)
151-
self.isClosed = true
157+
self.closeConnection()
152158
}
153159
}
154160

161+
func handlerAdded(context: NIOCore.ChannelHandlerContext) {
162+
self.context = context
163+
}
164+
165+
func handlerRemoved(context: NIOCore.ChannelHandlerContext) {
166+
self.context = nil
167+
}
168+
169+
func channelInactive(context: ChannelHandlerContext) {
170+
self.closeConnection()
171+
context.fireChannelInactive()
172+
}
173+
155174
func errorCaught(context: NIOCore.ChannelHandlerContext, error: Swift.Error) {
156175
if self.isClosed {
157176
return
@@ -162,17 +181,15 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck
162181
[:],
163182
error
164183
)
165-
context.close(promise: nil)
166-
self.isClosed = true
184+
self.closeConnection()
167185
}
168186

169187
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
170188
guard event is NIOCore.IdleStateHandler.IdleStateEvent else {
171189
return context.fireUserInboundEventTriggered(event)
172190
}
173191

174-
self.isClosed = true
175-
context.close(promise: nil)
192+
self.closeConnection()
176193
self.responseCallbacks.receiveClose(.deadlineExceeded, [:], ConnectError.deadlineExceeded())
177194
}
178195
}

Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke
5050
return
5151
}
5252

53-
self.isClosed = true
54-
self.context?.close(promise: nil)
53+
self.closeConnection()
5554
self.onResponse(HTTPResponse(
5655
code: .canceled,
5756
headers: [:],
@@ -82,13 +81,21 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke
8281
)
8382
}
8483

84+
private func closeConnection() {
85+
if self.isClosed {
86+
return
87+
}
88+
89+
self.isClosed = true
90+
self.context?.close(promise: nil)
91+
}
92+
8593
// MARK: - ChannelInboundHandler
8694

8795
typealias OutboundOut = NIOHTTP1.HTTPClientRequestPart
8896
typealias InboundIn = NIOHTTP1.HTTPClientResponsePart
8997

9098
func channelActive(context: NIOCore.ChannelHandlerContext) {
91-
self.context = context
9299
if self.isClosed {
93100
return
94101
}
@@ -138,28 +145,38 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke
138145
self.receivedEnd = trailers
139146
self.onResponse(self.createResponse(error: nil))
140147
self.onMetrics(.init(taskMetrics: nil))
141-
context.close(promise: nil)
142-
self.isClosed = true
148+
self.closeConnection()
143149
}
144150
}
145151

152+
func handlerAdded(context: NIOCore.ChannelHandlerContext) {
153+
self.context = context
154+
}
155+
156+
func handlerRemoved(context: NIOCore.ChannelHandlerContext) {
157+
self.context = nil
158+
}
159+
160+
func channelInactive(context: ChannelHandlerContext) {
161+
self.closeConnection()
162+
context.fireChannelInactive()
163+
}
164+
146165
func errorCaught(context: NIOCore.ChannelHandlerContext, error: Swift.Error) {
147166
if self.isClosed {
148167
return
149168
}
150169

151170
self.onResponse(self.createResponse(error: error))
152-
context.close(promise: nil)
153-
self.isClosed = true
171+
self.closeConnection()
154172
}
155173

156174
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
157175
guard event is NIOCore.IdleStateHandler.IdleStateEvent else {
158176
return context.fireUserInboundEventTriggered(event)
159177
}
160178

161-
self.isClosed = true
162-
context.close(promise: nil)
179+
self.closeConnection()
163180
self.onResponse(.init(
164181
code: .deadlineExceeded,
165182
headers: [:],

0 commit comments

Comments
 (0)