Coverage Report

Created: 2025-09-08 06:42

/src/swift-nio/Sources/NIOCore/ChannelInvoker.swift
Line
Count
Source (jump to first uncovered line)
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
6
// Licensed under Apache License v2.0
7
//
8
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10
//
11
// SPDX-License-Identifier: Apache-2.0
12
//
13
//===----------------------------------------------------------------------===//
14
/// Allows users to invoke an "outbound" operation related to a `Channel` that will flow through the `ChannelPipeline` until
15
/// it will finally be executed by the the `ChannelCore` implementation.
16
public protocol ChannelOutboundInvoker {
17
18
    /// Register on an `EventLoop` and so have all its IO handled.
19
    ///
20
    /// - Parameters:
21
    ///   - promise: the `EventLoopPromise` that will be notified once the operation completes,
22
    ///                or `nil` if not interested in the outcome of the operation.
23
    func register(promise: EventLoopPromise<Void>?)
24
25
    /// Bind to a `SocketAddress`.
26
    /// - Parameters:
27
    ///   - to: the `SocketAddress` to which we should bind the `Channel`.
28
    ///   - promise: the `EventLoopPromise` that will be notified once the operation completes,
29
    ///                or `nil` if not interested in the outcome of the operation.
30
    func bind(to: SocketAddress, promise: EventLoopPromise<Void>?)
31
32
    /// Connect to a `SocketAddress`.
33
    /// - Parameters:
34
    ///   - to: the `SocketAddress` to which we should connect the `Channel`.
35
    ///   - promise: the `EventLoopPromise` that will be notified once the operation completes,
36
    ///                or `nil` if not interested in the outcome of the operation.
37
    func connect(to: SocketAddress, promise: EventLoopPromise<Void>?)
38
39
    /// Write data to the remote peer.
40
    ///
41
    /// Be aware that to be sure that data is really written to the remote peer you need to call `flush` or use `writeAndFlush`.
42
    /// Calling `write` multiple times and then `flush` may allow the `Channel` to `write` multiple data objects to the remote peer with one syscall.
43
    ///
44
    /// - Parameters:
45
    ///   - data: the data to write
46
    ///   - promise: the `EventLoopPromise` that will be notified once the operation completes,
47
    ///                or `nil` if not interested in the outcome of the operation.
48
    @available(
49
        *,
50
        deprecated,
51
        message: "NIOAny is not Sendable. Avoid wrapping the value in NIOAny to silence this warning."
52
    )
53
    func write(_ data: NIOAny, promise: EventLoopPromise<Void>?)
54
55
    /// Flush data that was previously written via `write` to the remote peer.
56
    func flush()
57
58
    /// Shortcut for calling `write` and `flush`.
59
    ///
60
    /// - Parameters:
61
    ///   - data: the data to write
62
    ///   - promise: the `EventLoopPromise` that will be notified once the `write` operation completes,
63
    ///                or `nil` if not interested in the outcome of the operation.
64
    @available(
65
        *,
66
        deprecated,
67
        message: "NIOAny is not Sendable. Avoid wrapping the value in NIOAny to silence this warning."
68
    )
69
    func writeAndFlush(_ data: NIOAny, promise: EventLoopPromise<Void>?)
70
71
    /// Signal that we want to read from the `Channel` once there is data ready.
72
    ///
73
    /// If `ChannelOptions.autoRead` is set for a `Channel` (which is the default) this method is automatically invoked by the transport implementation,
74
    /// otherwise it's the user's responsibility to call this method manually once new data should be read and processed.
75
    ///
76
    func read()
77
78
    /// Close the `Channel` and so the connection if one exists.
79
    ///
80
    /// - Parameters:
81
    ///   - mode: the `CloseMode` that is used
82
    ///   - promise: the `EventLoopPromise` that will be notified once the operation completes,
83
    ///                or `nil` if not interested in the outcome of the operation.
84
    func close(mode: CloseMode, promise: EventLoopPromise<Void>?)
85
86
    /// Trigger a custom user outbound event which will flow through the `ChannelPipeline`.
87
    ///
88
    /// - Parameters:
89
    ///   - event: The event itself.
90
    ///   - promise: the `EventLoopPromise` that will be notified once the operation completes,
91
    ///                or `nil` if not interested in the outcome of the operation.
92
    @preconcurrency
93
    func triggerUserOutboundEvent(_ event: Any & Sendable, promise: EventLoopPromise<Void>?)
94
95
    /// The `EventLoop` which is used by this `ChannelOutboundInvoker` for execution.
96
    var eventLoop: EventLoop { get }
97
}
98
99
/// Extra `ChannelOutboundInvoker` methods. Each method that returns a `EventLoopFuture` will just do the following:
100
///   - create a new `EventLoopPromise<Void>`
101
///   - call the corresponding method that takes a `EventLoopPromise<Void>`
102
///   - return `EventLoopPromise.futureResult`
103
extension ChannelOutboundInvoker {
104
105
    /// Register on an `EventLoop` and so have all its IO handled.
106
    /// - Parameters:
107
    ///   - file: The file this function was called in, for debugging purposes.
108
    ///   - line: The line this function was called on, for debugging purposes.
109
    /// - Returns: the future which will be notified once the operation completes.
110
13.8k
    public func register(file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Void> {
111
13.8k
        let promise = makePromise(file: file, line: line)
112
13.8k
        register(promise: promise)
113
13.8k
        return promise.futureResult
114
13.8k
    }
115
116
    /// Bind to a `SocketAddress`.
117
    /// - Parameters:
118
    ///   - address: the `SocketAddress` to which we should bind the `Channel`.
119
    ///   - file: The file this function was called in, for debugging purposes.
120
    ///   - line: The line this function was called on, for debugging purposes.
121
    /// - Returns: the future which will be notified once the operation completes.
122
    public func bind(
123
        to address: SocketAddress,
124
        file: StaticString = #fileID,
125
        line: UInt = #line
126
0
    ) -> EventLoopFuture<Void> {
127
0
        let promise = makePromise(file: file, line: line)
128
0
        bind(to: address, promise: promise)
129
0
        return promise.futureResult
130
0
    }
131
132
    /// Connect to a `SocketAddress`.
133
    /// - Parameters:
134
    ///   - address: the `SocketAddress` to which we should connect the `Channel`.
135
    ///   - file: The file this function was called in, for debugging purposes.
136
    ///   - line: The line this function was called on, for debugging purposes.
137
    /// - Returns: the future which will be notified once the operation completes.
138
    public func connect(
139
        to address: SocketAddress,
140
        file: StaticString = #fileID,
141
        line: UInt = #line
142
0
    ) -> EventLoopFuture<Void> {
143
0
        let promise = makePromise(file: file, line: line)
144
0
        connect(to: address, promise: promise)
145
0
        return promise.futureResult
146
0
    }
147
148
    /// Write data to the remote peer.
149
    ///
150
    /// Be aware that to be sure that data is really written to the remote peer you need to call `flush` or use `writeAndFlush`.
151
    /// Calling `write` multiple times and then `flush` may allow the `Channel` to `write` multiple data objects to the remote peer with one syscall.
152
    ///
153
    /// - Parameters:
154
    ///   - data: the data to write
155
    ///   - file: The file this function was called in, for debugging purposes.
156
    ///   - line: The line this function was called on, for debugging purposes.
157
    /// - Returns: the future which will be notified once the operation completes.
158
    @available(
159
        *,
160
        deprecated,
161
        message: "NIOAny is not Sendable. Avoid wrapping the value in NIOAny to silence this warning."
162
    )
163
0
    public func write(_ data: NIOAny, file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Void> {
164
0
        let promise = makePromise(file: file, line: line)
165
0
        write(data, promise: promise)
166
0
        return promise.futureResult
167
0
    }
168
169
    /// Shortcut for calling `write` and `flush`.
170
    ///
171
    /// - Parameters:
172
    ///   - data: the data to write
173
    ///   - file: The file this function was called in, for debugging purposes.
174
    ///   - line: The line this function was called on, for debugging purposes.
175
    /// - Returns: the future which will be notified once the `write` operation completes.
176
    @available(
177
        *,
178
        deprecated,
179
        message: "NIOAny is not Sendable. Avoid wrapping the value in NIOAny to silence this warning."
180
    )
181
    public func writeAndFlush(_ data: NIOAny, file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Void>
182
0
    {
183
0
        let promise = makePromise(file: file, line: line)
184
0
        writeAndFlush(data, promise: promise)
185
0
        return promise.futureResult
186
0
    }
187
188
    /// Close the `Channel` and so the connection if one exists.
189
    ///
190
    /// - Parameters:
191
    ///   - mode: the `CloseMode` that is used
192
    ///   - file: The file this function was called in, for debugging purposes.
193
    ///   - line: The line this function was called on, for debugging purposes.
194
    /// - Returns: the future which will be notified once the operation completes.
195
    public func close(mode: CloseMode = .all, file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Void>
196
13.8k
    {
197
13.8k
        let promise = makePromise(file: file, line: line)
198
13.8k
        self.close(mode: mode, promise: promise)
199
13.8k
        return promise.futureResult
200
13.8k
    }
201
202
    /// Trigger a custom user outbound event which will flow through the `ChannelPipeline`.
203
    ///
204
    /// - Parameters:
205
    ///   - event: the event itself.
206
    ///   - file: The file this function was called in, for debugging purposes.
207
    ///   - line: The line this function was called on, for debugging purposes.
208
    /// - Returns: the future which will be notified once the operation completes.
209
    @preconcurrency
210
    public func triggerUserOutboundEvent(
211
        _ event: Any & Sendable,
212
        file: StaticString = #fileID,
213
        line: UInt = #line
214
0
    ) -> EventLoopFuture<Void> {
215
0
        let promise = makePromise(file: file, line: line)
216
0
        triggerUserOutboundEvent(event, promise: promise)
217
0
        return promise.futureResult
218
0
    }
219
220
27.7k
    private func makePromise(file: StaticString = #fileID, line: UInt = #line) -> EventLoopPromise<Void> {
221
27.7k
        eventLoop.makePromise(file: file, line: line)
222
27.7k
    }
223
}
224
225
/// Fire inbound events related to a `Channel` through the `ChannelPipeline` until its end is reached or it's consumed by a `ChannelHandler`.
226
public protocol ChannelInboundInvoker {
227
228
    /// Called once a `Channel` was registered to its `EventLoop` and so IO will be processed.
229
    func fireChannelRegistered()
230
231
    /// Called once a `Channel` was unregistered from its `EventLoop` which means no IO will be handled for a `Channel` anymore.
232
    func fireChannelUnregistered()
233
234
    /// Called once a `Channel` becomes active.
235
    ///
236
    /// What active means depends on the `Channel` implementation and semantics.
237
    /// For example for TCP it means the `Channel` is connected to the remote peer.
238
    func fireChannelActive()
239
240
    /// Called once a `Channel` becomes inactive.
241
    ///
242
    /// What inactive means depends on the `Channel` implementation and semantics.
243
    /// For example for TCP it means the `Channel` was disconnected from the remote peer and closed.
244
    func fireChannelInactive()
245
246
    /// Called once there is some data read for a `Channel` that needs processing.
247
    ///
248
    /// - Parameters:
249
    ///   - data: the data that was read and is ready to be processed.
250
    @available(
251
        *,
252
        deprecated,
253
        message: "NIOAny is not Sendable. Avoid wrapping the value in NIOAny to silence this warning."
254
    )
255
    func fireChannelRead(_ data: NIOAny)
256
257
    /// Called once there is no more data to read immediately on a `Channel`. Any new data received will be handled later.
258
    func fireChannelReadComplete()
259
260
    /// Called when a `Channel`'s writable state changes.
261
    ///
262
    /// The writability state of a Channel depends on watermarks that can be set via `Channel.setOption` and how much data
263
    /// is still waiting to be transferred to the remote peer.
264
    /// You should take care to enforce some kind of backpressure if the channel becomes unwritable which means `Channel.isWritable`
265
    /// will return `false` to ensure you do not consume too much memory due to queued writes. What exactly you should do here depends on the
266
    /// protocol and other semantics. But for example you may want to stop writing to the `Channel` until `Channel.writable` becomes
267
    /// `true` again or stop reading at all.
268
    func fireChannelWritabilityChanged()
269
270
    /// Called when an inbound operation `Error` was caught.
271
    ///
272
    /// Be aware that for inbound operations this method is called while for outbound operations defined in `ChannelOutboundInvoker`
273
    /// the `EventLoopFuture` or `EventLoopPromise` will be notified.
274
    ///
275
    /// - Parameters:
276
    ///   - error: the error we encountered.
277
    func fireErrorCaught(_ error: Error)
278
279
    /// Trigger a custom user inbound event which will flow through the `ChannelPipeline`.
280
    ///
281
    /// - Parameters:
282
    ///   - event: the event itself.
283
    @preconcurrency
284
    func fireUserInboundEventTriggered(_ event: Any & Sendable)
285
}
286
287
/// A protocol that signals that outbound and inbound events are triggered by this invoker.
288
public protocol ChannelInvoker: ChannelOutboundInvoker, ChannelInboundInvoker {}
289
290
/// Specify what kind of close operation is requested.
291
public enum CloseMode: Sendable {
292
    /// Close the output (writing) side of the `Channel` without closing the actual file descriptor.
293
    /// This is an optional mode which means it may not be supported by all `Channel` implementations.
294
    case output
295
296
    /// Close the input (reading) side of the `Channel` without closing the actual file descriptor.
297
    /// This is an optional mode which means it may not be supported by all `Channel` implementations.
298
    case input
299
300
    /// Close the whole `Channel (file descriptor).
301
    case all
302
}