/src/swift-nio/Sources/NIOEmbedded/Embedded.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2017-2022 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 | | |
15 | | import Atomics |
16 | | import DequeModule |
17 | | import NIOConcurrencyHelpers |
18 | | import NIOCore |
19 | | import _NIODataStructures |
20 | | |
21 | | #if canImport(Dispatch) |
22 | | import Dispatch |
23 | | #endif |
24 | | |
25 | | #if canImport(Darwin) |
26 | | import Darwin |
27 | | #elseif canImport(Glibc) |
28 | | @preconcurrency import Glibc |
29 | | #elseif canImport(Musl) |
30 | | @preconcurrency import Musl |
31 | | #elseif canImport(Android) |
32 | | @preconcurrency import Android |
33 | | #elseif canImport(WASILibc) |
34 | | @preconcurrency import WASILibc |
35 | | #elseif canImport(WinSDK) |
36 | | @preconcurrency import WinSDK |
37 | | #else |
38 | | #error("Unknown C library.") |
39 | | #endif |
40 | | |
41 | 0 | private func printError(_ string: StaticString) { |
42 | 0 | string.withUTF8Buffer { buf in |
43 | 0 | var buf = buf |
44 | 0 | while buf.count > 0 { |
45 | 0 | // 2 is stderr |
46 | | #if canImport(WinSDK) |
47 | | let rc = _write(2, buf.baseAddress, UInt32(truncatingIfNeeded: buf.count)) |
48 | | let errno = GetLastError() |
49 | | #else |
50 | 0 | let rc = write(2, buf.baseAddress, buf.count) |
51 | | #endif |
52 | 0 | if rc < 0 { |
53 | 0 | let err = errno |
54 | 0 | if err == EINTR { continue } |
55 | 0 | fatalError("Unexpected error writing: \(err)") |
56 | 0 | } |
57 | 0 | buf = .init(rebasing: buf.dropFirst(Int(rc))) |
58 | 0 | } |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | | internal struct EmbeddedScheduledTask { |
63 | | let id: UInt64 |
64 | | let task: () -> Void |
65 | | let failFn: (Error) -> Void |
66 | | let readyTime: NIODeadline |
67 | | let insertOrder: UInt64 |
68 | | |
69 | | init( |
70 | | id: UInt64, |
71 | | readyTime: NIODeadline, |
72 | | insertOrder: UInt64, |
73 | | task: @escaping () -> Void, |
74 | | _ failFn: @escaping (Error) -> Void |
75 | 6.96k | ) { |
76 | 6.96k | self.id = id |
77 | 6.96k | self.readyTime = readyTime |
78 | 6.96k | self.insertOrder = insertOrder |
79 | 6.96k | self.task = task |
80 | 6.96k | self.failFn = failFn |
81 | 6.96k | } |
82 | | |
83 | 0 | func fail(_ error: Error) { |
84 | 0 | self.failFn(error) |
85 | 0 | } |
86 | | } |
87 | | |
88 | | extension EmbeddedScheduledTask: Comparable { |
89 | 0 | static func < (lhs: EmbeddedScheduledTask, rhs: EmbeddedScheduledTask) -> Bool { |
90 | 0 | if lhs.readyTime == rhs.readyTime { |
91 | 0 | return lhs.insertOrder < rhs.insertOrder |
92 | 0 | } else { |
93 | 0 | return lhs.readyTime < rhs.readyTime |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | 0 | static func == (lhs: EmbeddedScheduledTask, rhs: EmbeddedScheduledTask) -> Bool { |
98 | 0 | lhs.id == rhs.id |
99 | 0 | } |
100 | | } |
101 | | |
102 | | /// An `EventLoop` that is embedded in the current running context with no external |
103 | | /// control. |
104 | | /// |
105 | | /// Unlike more complex `EventLoop`s, such as `SelectableEventLoop`, the `EmbeddedEventLoop` |
106 | | /// has no proper eventing mechanism. Instead, reads and writes are fully controlled by the |
107 | | /// entity that instantiates the `EmbeddedEventLoop`. This property makes `EmbeddedEventLoop` |
108 | | /// of limited use for many application purposes, but highly valuable for testing and other |
109 | | /// kinds of mocking. |
110 | | /// |
111 | | /// Time is controllable on an `EmbeddedEventLoop`. It begins at `NIODeadline.uptimeNanoseconds(0)` |
112 | | /// and may be advanced by a fixed amount by using `advanceTime(by:)`, or advanced to a point in |
113 | | /// time with `advanceTime(to:)`. |
114 | | /// |
115 | | /// - warning: Unlike `SelectableEventLoop`, `EmbeddedEventLoop` **is not thread-safe**. This |
116 | | /// is because it is intended to be run in the thread that instantiated it. Users are |
117 | | /// responsible for ensuring they never call into the `EmbeddedEventLoop` in an |
118 | | /// unsynchronized fashion. |
119 | | public final class EmbeddedEventLoop: EventLoop, CustomStringConvertible { |
120 | 71.0k | private var _now: NIODeadline = .uptimeNanoseconds(0) |
121 | | /// The current "time" for this event loop. This is an amount in nanoseconds. |
122 | 0 | public var now: NIODeadline { _now } |
123 | | |
124 | | private enum State { case open, closing, closed } |
125 | 71.0k | private var state: State = .open |
126 | | |
127 | 71.0k | private var scheduledTaskCounter: UInt64 = 0 |
128 | 71.0k | private var scheduledTasks = PriorityQueue<EmbeddedScheduledTask>() |
129 | | |
130 | | /// Keep track of where promises are allocated to ensure we can identify their source if they leak. |
131 | 71.0k | private var _promiseCreationStore: [_NIOEventLoopFutureIdentifier: (file: StaticString, line: UInt)] = [:] |
132 | | |
133 | | // The number of the next task to be created. We track the order so that when we execute tasks |
134 | | // scheduled at the same time, we may do so in the order in which they were submitted for |
135 | | // execution. |
136 | 71.0k | private var taskNumber: UInt64 = 0 |
137 | | |
138 | 71.0k | public let description = "EmbeddedEventLoop" |
139 | | |
140 | | #if canImport(Darwin) || canImport(Glibc) || canImport(Musl) || canImport(Android) |
141 | 71.0k | private let myThread: pthread_t = pthread_self() |
142 | | |
143 | 4.87M | func isCorrectThread() -> Bool { |
144 | 4.87M | pthread_equal(self.myThread, pthread_self()) != 0 |
145 | 4.87M | } |
146 | | #else |
147 | | func isCorrectThread() -> Bool { |
148 | | true // let's be conservative |
149 | | } |
150 | | #endif |
151 | | |
152 | 14.1k | private func nextTaskNumber() -> UInt64 { |
153 | 14.1k | defer { |
154 | 14.1k | self.taskNumber += 1 |
155 | 14.1k | } |
156 | 14.1k | return self.taskNumber |
157 | 14.1k | } |
158 | | |
159 | | /// - see: `EventLoop.inEventLoop` |
160 | 5.00M | public var inEventLoop: Bool { |
161 | 5.00M | self.checkCorrectThread() |
162 | 5.00M | return true |
163 | 5.00M | } |
164 | | |
165 | 9.85M | public func checkCorrectThread() { |
166 | 9.85M | guard self.isCorrectThread() else { |
167 | 0 | if Self.strictModeEnabled { |
168 | 0 | preconditionFailure( |
169 | 0 | "EmbeddedEventLoop is not thread-safe. You can only use it from the thread you created it on." |
170 | 0 | ) |
171 | 0 | } else { |
172 | 0 | printError( |
173 | 0 | """ |
174 | 0 | ERROR: NIO API misuse: EmbeddedEventLoop is not thread-safe. \ |
175 | 0 | You can only use it from the thread you created it on. This problem will be upgraded to a forced \ |
176 | 0 | crash in future versions of SwiftNIO. |
177 | 0 |
|
178 | 0 | """ |
179 | 0 | ) |
180 | 0 | } |
181 | 0 | return |
182 | 9.85M | } |
183 | 9.85M | } |
184 | | |
185 | | /// Initialize a new `EmbeddedEventLoop`. |
186 | 28.2k | public init() {} |
187 | | |
188 | | /// - see: `EventLoop.scheduleTask(deadline:_:)` |
189 | | @discardableResult |
190 | 14.1k | public func scheduleTask<T>(deadline: NIODeadline, _ task: @escaping () throws -> T) -> Scheduled<T> { |
191 | 14.1k | self.checkCorrectThread() |
192 | 14.1k | let promise: EventLoopPromise<T> = makePromise() |
193 | 14.1k | |
194 | 14.1k | switch self.state { |
195 | 14.1k | case .open: |
196 | 14.1k | break |
197 | 14.1k | case .closing, .closed: |
198 | 0 | // If the event loop is shut down, or shutting down, immediately cancel the task. |
199 | 0 | promise.fail(EventLoopError.cancelled) |
200 | 0 | return Scheduled(promise: promise, cancellationTask: {}) |
201 | 14.1k | } |
202 | 14.1k | |
203 | 14.1k | self.scheduledTaskCounter += 1 |
204 | 14.1k | let task = EmbeddedScheduledTask( |
205 | 14.1k | id: self.scheduledTaskCounter, |
206 | 14.1k | readyTime: deadline, |
207 | 14.1k | insertOrder: self.nextTaskNumber(), |
208 | 14.1k | task: { |
209 | 14.1k | do { |
210 | 14.1k | promise.assumeIsolated().succeed(try task()) |
211 | 14.1k | } catch let err { |
212 | 0 | promise.fail(err) |
213 | 14.1k | } |
214 | 14.1k | }, |
215 | 14.1k | promise.fail $s11NIOEmbedded17EmbeddedEventLoopC12scheduleTask8deadline_7NIOCore9ScheduledVyxGAF11NIODeadlineV_xyKctlFys5Error_pYbcAF0cD7PromiseVyxGYbcfu_ Line | Count | Source | 215 | 14.1k | promise.fail |
Unexecuted instantiation: $s11NIOEmbedded17EmbeddedEventLoopC12scheduleTask8deadline_7NIOCore9ScheduledVyxGAF11NIODeadlineV_xyKctlFys5Error_pYbcAF0cD7PromiseVyxGYbcfu_ysAL_pYbcfu0_ |
216 | 14.1k | ) |
217 | 14.1k | |
218 | 14.1k | let taskId = task.id |
219 | 14.1k | let scheduled = Scheduled( |
220 | 14.1k | promise: promise, |
221 | 14.1k | cancellationTask: { |
222 | 0 | self.scheduledTasks.removeFirst { $0.id == taskId } |
223 | 0 | } |
224 | 14.1k | ) |
225 | 14.1k | scheduledTasks.push(task) |
226 | 14.1k | return scheduled |
227 | 14.1k | } |
228 | | |
229 | | /// - see: `EventLoop.scheduleTask(in:_:)` |
230 | | @discardableResult |
231 | 0 | public func scheduleTask<T>(in: TimeAmount, _ task: @escaping () throws -> T) -> Scheduled<T> { |
232 | 0 | self.checkCorrectThread() |
233 | 0 | return self.scheduleTask(deadline: self._now + `in`, task) |
234 | 0 | } |
235 | | |
236 | | @preconcurrency |
237 | | @discardableResult |
238 | | public func scheduleCallback( |
239 | | in amount: TimeAmount, |
240 | | handler: some (NIOScheduledCallbackHandler & Sendable) |
241 | 0 | ) -> NIOScheduledCallback { |
242 | 0 | self.checkCorrectThread() |
243 | 0 | /// Even though this type does not implement a custom `scheduleCallback(at:handler)`, it uses a manual clock so |
244 | 0 | /// it cannot rely on the default implementation of `scheduleCallback(in:handler:)`, which computes the deadline |
245 | 0 | /// as an offset from `NIODeadline.now`. This event loop needs the deadline to be offset from `self._now`. |
246 | 0 | return self.scheduleCallback(at: self._now + amount, handler: handler) |
247 | 0 | } |
248 | | |
249 | | /// On an `EmbeddedEventLoop`, `execute` will simply use `scheduleTask` with a deadline of _now_. This means that |
250 | | /// `task` will be run the next time you call `EmbeddedEventLoop.run`. |
251 | 14.1k | public func execute(_ task: @escaping () -> Void) { |
252 | 14.1k | self.checkCorrectThread() |
253 | 14.1k | self.scheduleTask(deadline: self._now, task) |
254 | 14.1k | } |
255 | | |
256 | | /// Run all tasks that have previously been submitted to this `EmbeddedEventLoop`, either by calling `execute` or |
257 | | /// events that have been enqueued using `scheduleTask`/`scheduleRepeatedTask`/`scheduleRepeatedAsyncTask` and whose |
258 | | /// deadlines have expired. |
259 | | /// |
260 | | /// - seealso: `EmbeddedEventLoop.advanceTime`. |
261 | 23.7k | public func run() { |
262 | 23.7k | self.checkCorrectThread() |
263 | 23.7k | // Execute all tasks that are currently enqueued to be executed *now*. |
264 | 23.7k | self.advanceTime(to: self._now) |
265 | 23.7k | } |
266 | | |
267 | | /// Runs the event loop and moves "time" forward by the given amount, running any scheduled |
268 | | /// tasks that need to be run. |
269 | 0 | public func advanceTime(by increment: TimeAmount) { |
270 | 0 | self.checkCorrectThread() |
271 | 0 | self.advanceTime(to: self._now + increment) |
272 | 0 | } |
273 | | |
274 | | /// Runs the event loop and moves "time" forward to the given point in time, running any scheduled |
275 | | /// tasks that need to be run. |
276 | | /// |
277 | | /// - Note: If `deadline` is before the current time, the current time will not be advanced. |
278 | 23.7k | public func advanceTime(to deadline: NIODeadline) { |
279 | 23.7k | self.checkCorrectThread() |
280 | 23.7k | let newTime = max(deadline, self._now) |
281 | 23.7k | |
282 | 23.7k | while let nextTask = self.scheduledTasks.peek() { |
283 | 14.1k | guard nextTask.readyTime <= newTime else { |
284 | 0 | break |
285 | 14.1k | } |
286 | 14.1k | |
287 | 14.1k | // Now we want to grab all tasks that are ready to execute at the same |
288 | 14.1k | // time as the first. |
289 | 14.1k | var tasks = [EmbeddedScheduledTask]() |
290 | 14.1k | while let candidateTask = self.scheduledTasks.peek(), candidateTask.readyTime == nextTask.readyTime { |
291 | 14.1k | tasks.append(candidateTask) |
292 | 14.1k | self.scheduledTasks.pop() |
293 | 14.1k | } |
294 | 14.1k | |
295 | 14.1k | // Set the time correctly before we call into user code, then |
296 | 14.1k | // call in for all tasks. |
297 | 14.1k | self._now = nextTask.readyTime |
298 | 14.1k | |
299 | 14.1k | for task in tasks { |
300 | 14.1k | task.task() |
301 | 14.1k | } |
302 | 23.7k | } |
303 | 23.7k | |
304 | 23.7k | // Finally ensure we got the time right. |
305 | 23.7k | self._now = newTime |
306 | 23.7k | } |
307 | | |
308 | 6.96k | internal func cancelRemainingScheduledTasks() { |
309 | 6.96k | self.checkCorrectThread() |
310 | 6.96k | while let task = self.scheduledTasks.pop() { |
311 | 0 | task.fail(EventLoopError.cancelled) |
312 | 6.96k | } |
313 | 6.96k | } |
314 | | |
315 | | #if canImport(Dispatch) |
316 | | /// - see: `EventLoop.shutdownGracefully` |
317 | 0 | public func shutdownGracefully(queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) { |
318 | 0 | self.checkCorrectThread() |
319 | 0 | self.state = .closing |
320 | 0 | run() |
321 | 0 | cancelRemainingScheduledTasks() |
322 | 0 | self.state = .closed |
323 | 0 | queue.sync { |
324 | 0 | callback(nil) |
325 | 0 | } |
326 | 0 | } |
327 | | #endif |
328 | | |
329 | 1.98M | public func preconditionInEventLoop(file: StaticString, line: UInt) { |
330 | 1.98M | self.checkCorrectThread() |
331 | 1.98M | // Currently, inEventLoop is always true so this always passes. |
332 | 1.98M | } |
333 | | |
334 | 0 | public func preconditionNotInEventLoop(file: StaticString, line: UInt) { |
335 | 0 | // As inEventLoop always returns true, this must always precondition. |
336 | 0 | preconditionFailure("Always in EmbeddedEventLoop", file: file, line: line) |
337 | 0 | } |
338 | | |
339 | 42.3k | public func _preconditionSafeToWait(file: StaticString, line: UInt) { |
340 | 42.3k | self.checkCorrectThread() |
341 | 42.3k | // EmbeddedEventLoop always allows a wait, as waiting will essentially always block |
342 | 42.3k | // wait() |
343 | 42.3k | return |
344 | 42.3k | } |
345 | | |
346 | 27.8k | public func _promiseCreated(futureIdentifier: _NIOEventLoopFutureIdentifier, file: StaticString, line: UInt) { |
347 | 27.8k | self.checkCorrectThread() |
348 | 27.8k | precondition(_isDebugAssertConfiguration()) |
349 | 27.8k | self._promiseCreationStore[futureIdentifier] = (file: file, line: line) |
350 | 27.8k | } |
351 | | |
352 | | public func _promiseCompleted(futureIdentifier: _NIOEventLoopFutureIdentifier) -> (file: StaticString, line: UInt)? |
353 | 34.8k | { |
354 | 34.8k | self.checkCorrectThread() |
355 | 34.8k | precondition(_isDebugAssertConfiguration()) |
356 | 34.8k | return self._promiseCreationStore.removeValue(forKey: futureIdentifier) |
357 | 34.8k | } |
358 | | |
359 | 0 | public func _preconditionSafeToSyncShutdown(file: StaticString, line: UInt) { |
360 | 0 | self.checkCorrectThread() |
361 | 0 | // EmbeddedEventLoop always allows a sync shutdown. |
362 | 0 | return |
363 | 0 | } |
364 | | |
365 | 0 | public func _executeIsolatedUnsafeUnchecked(_ task: @escaping () -> Void) { |
366 | 0 | // Because of the way EmbeddedEL works, we can just delegate this directly |
367 | 0 | // to execute. |
368 | 0 | self.execute(task) |
369 | 0 | } |
370 | | |
371 | 0 | public func _submitIsolatedUnsafeUnchecked<T>(_ task: @escaping () throws -> T) -> EventLoopFuture<T> { |
372 | 0 | // Because of the way EmbeddedEL works, we can delegate this directly to schedule. Note that I didn't |
373 | 0 | // say submit: we don't have an override of submit here. |
374 | 0 | self.scheduleTask(deadline: self._now, task).futureResult |
375 | 0 | } |
376 | | |
377 | | @discardableResult |
378 | | public func _scheduleTaskIsolatedUnsafeUnchecked<T>( |
379 | | deadline: NIODeadline, |
380 | | _ task: @escaping () throws -> T |
381 | 0 | ) -> Scheduled<T> { |
382 | 0 | // Because of the way EmbeddedEL works, we can delegate this directly to schedule. |
383 | 0 | self.scheduleTask(deadline: deadline, task) |
384 | 0 | } |
385 | | |
386 | | @discardableResult |
387 | | public func _scheduleTaskIsolatedUnsafeUnchecked<T>( |
388 | | in delay: TimeAmount, |
389 | | _ task: @escaping () throws -> T |
390 | 0 | ) -> Scheduled<T> { |
391 | 0 | // Because of the way EmbeddedEL works, we can delegate this directly to schedule. |
392 | 0 | self.scheduleTask(in: delay, task) |
393 | 0 | } |
394 | | |
395 | 28.2k | deinit { |
396 | 28.2k | self.checkCorrectThread() |
397 | 28.2k | precondition(scheduledTasks.isEmpty, "Embedded event loop freed with unexecuted scheduled tasks!") |
398 | 28.2k | } |
399 | | |
400 | | @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) |
401 | 0 | public var executor: any SerialExecutor { |
402 | 0 | fatalError( |
403 | 0 | "EmbeddedEventLoop is not thread safe and cannot be used as a SerialExecutor. Use NIOAsyncTestingEventLoop instead." |
404 | 0 | ) |
405 | 0 | } |
406 | | |
407 | 0 | static let strictModeEnabled: Bool = { |
408 | 0 | for ciVar in ["SWIFTNIO_STRICT", "SWIFTNIO_CI", "SWIFTNIO_STRICT_EMBEDDED"] { |
409 | | #if os(Windows) |
410 | | let env = Windows.getenv("SWIFTNIO_STRICT") |
411 | | #else |
412 | 0 | let env = getenv("SWIFTNIO_STRICT").flatMap { String(cString: $0) } |
413 | | #endif |
414 | 0 | switch env?.lowercased() { |
415 | 0 | case "true", "y", "yes", "on", "1": |
416 | 0 | return true |
417 | 0 | default: |
418 | 0 | () |
419 | 0 | } |
420 | 0 | } |
421 | 0 | return false |
422 | 0 | }() |
423 | | } |
424 | | |
425 | | // EmbeddedEventLoop is extremely _not_ Sendable. However, the EventLoop protocol |
426 | | // requires it to be. We are doing some runtime enforcement of correct use, but |
427 | | // ultimately we can't have the compiler validating this usage. |
428 | | extension EmbeddedEventLoop: @unchecked Sendable {} |
429 | | |
430 | | @usableFromInline |
431 | | class EmbeddedChannelCore: ChannelCore { |
432 | | var isOpen: Bool { |
433 | 13.9k | get { |
434 | 13.9k | self._isOpen.load(ordering: .sequentiallyConsistent) |
435 | 13.9k | } |
436 | 13.9k | set { |
437 | 13.9k | self._isOpen.store(newValue, ordering: .sequentiallyConsistent) |
438 | 13.9k | } |
439 | | } |
440 | | |
441 | | var isActive: Bool { |
442 | 6.96k | get { |
443 | 6.96k | self._isActive.load(ordering: .sequentiallyConsistent) |
444 | 6.96k | } |
445 | 6.96k | set { |
446 | 6.96k | self._isActive.store(newValue, ordering: .sequentiallyConsistent) |
447 | 6.96k | } |
448 | | } |
449 | | |
450 | | var allowRemoteHalfClosure: Bool { |
451 | 0 | get { |
452 | 0 | self._allowRemoteHalfClosure.load(ordering: .sequentiallyConsistent) |
453 | 0 | } |
454 | 0 | set { |
455 | 0 | self._allowRemoteHalfClosure.store(newValue, ordering: .sequentiallyConsistent) |
456 | 0 | } |
457 | | } |
458 | | |
459 | | var allowOptionsWhenClosed: Bool { |
460 | 0 | get { |
461 | 0 | self._allowOptionsWhenClosed.load(ordering: .sequentiallyConsistent) |
462 | 0 | } |
463 | 0 | set { |
464 | 0 | self._allowOptionsWhenClosed.store(newValue, ordering: .sequentiallyConsistent) |
465 | 0 | } |
466 | | } |
467 | | |
468 | 14.1k | private let _isOpen = ManagedAtomic(true) |
469 | 14.1k | private let _isActive = ManagedAtomic(false) |
470 | 14.1k | private let _allowRemoteHalfClosure = ManagedAtomic(false) |
471 | 14.1k | private let _allowOptionsWhenClosed = ManagedAtomic(true) |
472 | | |
473 | | let eventLoop: EventLoop |
474 | | let closePromise: EventLoopPromise<Void> |
475 | | var error: Optional<Error> |
476 | | |
477 | | private let pipeline: ChannelPipeline |
478 | | |
479 | 6.96k | init(pipeline: ChannelPipeline, eventLoop: EventLoop) { |
480 | 6.96k | closePromise = eventLoop.makePromise() |
481 | 6.96k | self.pipeline = pipeline |
482 | 6.96k | self.eventLoop = eventLoop |
483 | 6.96k | self.error = nil |
484 | 6.96k | } |
485 | | |
486 | 14.1k | deinit { |
487 | 14.1k | assert( |
488 | 14.1k | !self.isOpen && !self.isActive, |
489 | 14.1k | "leaked an open EmbeddedChannel, maybe forgot to call channel.finish()?" |
490 | 14.1k | ) |
491 | 14.1k | isOpen = false |
492 | 14.1k | closePromise.succeed(()) |
493 | 14.1k | } |
494 | | |
495 | | /// Contains the flushed items that went into the `Channel` (and on a regular channel would have hit the network). |
496 | | @usableFromInline |
497 | 14.1k | var outboundBuffer: CircularBuffer<NIOAny> = CircularBuffer() |
498 | | |
499 | | /// Contains observers that want to consume the first element that would be appended to the `outboundBuffer` |
500 | 14.1k | private var outboundBufferConsumer: Deque<(Result<NIOAny, Error>) -> Void> = [] |
501 | | |
502 | | /// Contains the unflushed items that went into the `Channel` |
503 | | @usableFromInline |
504 | 14.1k | var pendingOutboundBuffer: MarkedCircularBuffer<(NIOAny, EventLoopPromise<Void>?)> = MarkedCircularBuffer( |
505 | 14.1k | initialCapacity: 16 |
506 | 14.1k | ) |
507 | | |
508 | | /// Contains the items that travelled the `ChannelPipeline` all the way and hit the tail channel handler. On a |
509 | | /// regular `Channel` these items would be lost. |
510 | | @usableFromInline |
511 | 14.1k | var inboundBuffer: CircularBuffer<NIOAny> = CircularBuffer() |
512 | | |
513 | | /// Contains observers that want to consume the first element that would be appended to the `inboundBuffer` |
514 | 14.1k | private var inboundBufferConsumer: Deque<(Result<NIOAny, Error>) -> Void> = [] |
515 | | |
516 | | @usableFromInline |
517 | | internal struct Addresses { |
518 | | var localAddress: SocketAddress? |
519 | | var remoteAddress: SocketAddress? |
520 | | } |
521 | | |
522 | | @usableFromInline |
523 | 14.1k | internal let _addresses: NIOLockedValueBox<Addresses> = NIOLockedValueBox( |
524 | 14.1k | .init(localAddress: nil, remoteAddress: nil) |
525 | 14.1k | ) |
526 | | |
527 | | @usableFromInline |
528 | | var localAddress: SocketAddress? { |
529 | 0 | get { |
530 | 0 | self._addresses.withLockedValue { $0.localAddress } |
531 | 0 | } |
532 | 0 | set { |
533 | 0 | self._addresses.withLockedValue { $0.localAddress = newValue } |
534 | 0 | } |
535 | | } |
536 | | |
537 | | @usableFromInline |
538 | | var remoteAddress: SocketAddress? { |
539 | 0 | get { |
540 | 0 | self._addresses.withLockedValue { $0.remoteAddress } |
541 | 0 | } |
542 | 0 | set { |
543 | 0 | self._addresses.withLockedValue { $0.remoteAddress = newValue } |
544 | 0 | } |
545 | | } |
546 | | |
547 | | @usableFromInline |
548 | 0 | func localAddress0() throws -> SocketAddress { |
549 | 0 | self.eventLoop.preconditionInEventLoop() |
550 | 0 | if let localAddress = self.localAddress { |
551 | 0 | return localAddress |
552 | 0 | } else { |
553 | 0 | throw ChannelError.operationUnsupported |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | | @usableFromInline |
558 | 0 | func remoteAddress0() throws -> SocketAddress { |
559 | 0 | self.eventLoop.preconditionInEventLoop() |
560 | 0 | if let remoteAddress = self.remoteAddress { |
561 | 0 | return remoteAddress |
562 | 0 | } else { |
563 | 0 | throw ChannelError.operationUnsupported |
564 | 0 | } |
565 | 0 | } |
566 | | |
567 | | @usableFromInline |
568 | 14.1k | func close0(error: Error, mode: CloseMode, promise: EventLoopPromise<Void>?) { |
569 | 14.1k | self.eventLoop.preconditionInEventLoop() |
570 | 14.1k | guard self.isOpen else { |
571 | 0 | promise?.fail(ChannelError.alreadyClosed) |
572 | 0 | return |
573 | 14.1k | } |
574 | 14.1k | isOpen = false |
575 | 14.1k | isActive = false |
576 | 14.1k | promise?.succeed(()) |
577 | 14.1k | |
578 | 14.1k | // Return a `.failure` result containing an error to all pending inbound and outbound consumers. |
579 | 14.1k | while let consumer = self.inboundBufferConsumer.popFirst() { |
580 | 0 | consumer(.failure(ChannelError.ioOnClosedChannel)) |
581 | 14.1k | } |
582 | 14.1k | while let consumer = self.outboundBufferConsumer.popFirst() { |
583 | 0 | consumer(.failure(ChannelError.ioOnClosedChannel)) |
584 | 14.1k | } |
585 | 14.1k | |
586 | 14.1k | // As we called register() in the constructor of EmbeddedChannel we also need to ensure we call unregistered here. |
587 | 14.1k | self.pipeline.syncOperations.fireChannelInactive() |
588 | 14.1k | self.pipeline.syncOperations.fireChannelUnregistered() |
589 | 14.1k | |
590 | 14.1k | let loopBoundSelf = NIOLoopBound(self, eventLoop: self.eventLoop) |
591 | 14.1k | |
592 | 14.1k | eventLoop.execute { |
593 | 14.1k | // ensure this is executed in a delayed fashion as the users code may still traverse the pipeline |
594 | 14.1k | let `self` = loopBoundSelf.value |
595 | 14.1k | self.removeHandlers(pipeline: self.pipeline) |
596 | 14.1k | self.closePromise.succeed(()) |
597 | 14.1k | } |
598 | 14.1k | } |
599 | | |
600 | | @usableFromInline |
601 | 0 | func bind0(to address: SocketAddress, promise: EventLoopPromise<Void>?) { |
602 | 0 | self.eventLoop.preconditionInEventLoop() |
603 | 0 | promise?.succeed(()) |
604 | 0 | } |
605 | | |
606 | | @usableFromInline |
607 | 0 | func connect0(to address: SocketAddress, promise: EventLoopPromise<Void>?) { |
608 | 0 | self.eventLoop.preconditionInEventLoop() |
609 | 0 | isActive = true |
610 | 0 | promise?.succeed(()) |
611 | 0 | self.pipeline.syncOperations.fireChannelActive() |
612 | 0 | } |
613 | | |
614 | | @usableFromInline |
615 | 14.1k | func register0(promise: EventLoopPromise<Void>?) { |
616 | 14.1k | self.eventLoop.preconditionInEventLoop() |
617 | 14.1k | promise?.succeed(()) |
618 | 14.1k | self.pipeline.syncOperations.fireChannelRegistered() |
619 | 14.1k | } |
620 | | |
621 | | @usableFromInline |
622 | 0 | func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?) { |
623 | 0 | self.eventLoop.preconditionInEventLoop() |
624 | 0 | isActive = true |
625 | 0 | register0(promise: promise) |
626 | 0 | self.pipeline.syncOperations.fireChannelActive() |
627 | 0 | } |
628 | | |
629 | | @usableFromInline |
630 | 0 | func write0(_ data: NIOAny, promise: EventLoopPromise<Void>?) { |
631 | 0 | self.eventLoop.preconditionInEventLoop() |
632 | 0 | self.pendingOutboundBuffer.append((data, promise)) |
633 | 0 | } |
634 | | |
635 | | @usableFromInline |
636 | 0 | func flush0() { |
637 | 0 | self.eventLoop.preconditionInEventLoop() |
638 | 0 | self.pendingOutboundBuffer.mark() |
639 | 0 |
|
640 | 0 | while self.pendingOutboundBuffer.hasMark, let dataAndPromise = self.pendingOutboundBuffer.popFirst() { |
641 | 0 | self.addToBuffer( |
642 | 0 | buffer: &self.outboundBuffer, |
643 | 0 | consumer: &self.outboundBufferConsumer, |
644 | 0 | data: dataAndPromise.0 |
645 | 0 | ) |
646 | 0 | dataAndPromise.1?.succeed(()) |
647 | 0 | } |
648 | 0 | } |
649 | | |
650 | | @usableFromInline |
651 | 0 | func read0() { |
652 | 0 | self.eventLoop.preconditionInEventLoop() |
653 | 0 | // NOOP |
654 | 0 | } |
655 | | |
656 | 0 | public final func triggerUserOutboundEvent0(_ event: Any, promise: EventLoopPromise<Void>?) { |
657 | 0 | self.eventLoop.preconditionInEventLoop() |
658 | 0 | promise?.fail(ChannelError.operationUnsupported) |
659 | 0 | } |
660 | | |
661 | | @usableFromInline |
662 | 2.38M | func channelRead0(_ data: NIOAny) { |
663 | 2.38M | self.eventLoop.preconditionInEventLoop() |
664 | 2.38M | self.addToBuffer( |
665 | 2.38M | buffer: &self.inboundBuffer, |
666 | 2.38M | consumer: &self.inboundBufferConsumer, |
667 | 2.38M | data: data |
668 | 2.38M | ) |
669 | 2.38M | } |
670 | | |
671 | 13.3k | public func errorCaught0(error: Error) { |
672 | 13.3k | self.eventLoop.preconditionInEventLoop() |
673 | 13.3k | if self.error == nil { |
674 | 13.0k | self.error = error |
675 | 13.0k | } |
676 | 13.3k | } |
677 | | |
678 | | private func addToBuffer( |
679 | | buffer: inout CircularBuffer<NIOAny>, |
680 | | consumer: inout Deque<(Result<NIOAny, Error>) -> Void>, |
681 | | data: NIOAny |
682 | 2.38M | ) { |
683 | 2.38M | self.eventLoop.preconditionInEventLoop() |
684 | 2.38M | if let consume = consumer.popFirst() { |
685 | 0 | consume(.success(data)) |
686 | 2.38M | } else { |
687 | 2.38M | buffer.append(data) |
688 | 2.38M | } |
689 | 2.38M | } |
690 | | |
691 | | /// Enqueue a consumer closure that will be invoked upon the next pending inbound write. |
692 | | /// - Parameter newElement: The consumer closure to enqueue. Returns a `.failure` result if the channel has already |
693 | | /// closed. |
694 | 0 | func _enqueueInboundBufferConsumer(_ newElement: @escaping (Result<NIOAny, Error>) -> Void) { |
695 | 0 | self.eventLoop.preconditionInEventLoop() |
696 | 0 |
|
697 | 0 | // The channel has already closed: there cannot be any further writes. Return a `.failure` result with an error. |
698 | 0 | guard self.isOpen else { |
699 | 0 | newElement(.failure(ChannelError.ioOnClosedChannel)) |
700 | 0 | return |
701 | 0 | } |
702 | 0 |
|
703 | 0 | self.inboundBufferConsumer.append(newElement) |
704 | 0 | } |
705 | | |
706 | | /// Enqueue a consumer closure that will be invoked upon the next pending outbound write. |
707 | | /// - Parameter newElement: The consumer closure to enqueue. Returns a `.failure` result if the channel has already |
708 | | /// closed. |
709 | 0 | func _enqueueOutboundBufferConsumer(_ newElement: @escaping (Result<NIOAny, Error>) -> Void) { |
710 | 0 | self.eventLoop.preconditionInEventLoop() |
711 | 0 |
|
712 | 0 | // The channel has already closed: there cannot be any further writes. Return a `.failure` result with an error. |
713 | 0 | guard self.isOpen else { |
714 | 0 | newElement(.failure(ChannelError.ioOnClosedChannel)) |
715 | 0 | return |
716 | 0 | } |
717 | 0 |
|
718 | 0 | self.outboundBufferConsumer.append(newElement) |
719 | 0 | } |
720 | | } |
721 | | |
722 | | // ChannelCores are basically never Sendable. |
723 | | @available(*, unavailable) |
724 | | extension EmbeddedChannelCore: Sendable {} |
725 | | |
726 | | /// `EmbeddedChannel` is a `Channel` implementation that does neither any |
727 | | /// actual IO nor has a proper eventing mechanism. The prime use-case for |
728 | | /// `EmbeddedChannel` is in unit tests when you want to feed the inbound events |
729 | | /// and check the outbound events manually. |
730 | | /// |
731 | | /// Please remember to call `finish()` when you are no longer using this |
732 | | /// `EmbeddedChannel`. |
733 | | /// |
734 | | /// To feed events through an `EmbeddedChannel`'s `ChannelPipeline` use |
735 | | /// `EmbeddedChannel.writeInbound` which accepts data of any type. It will then |
736 | | /// forward that data through the `ChannelPipeline` and the subsequent |
737 | | /// `ChannelInboundHandler` will receive it through the usual `channelRead` |
738 | | /// event. The user is responsible for making sure the first |
739 | | /// `ChannelInboundHandler` expects data of that type. |
740 | | /// |
741 | | /// `EmbeddedChannel` automatically collects arriving outbound data and makes it |
742 | | /// available one-by-one through `readOutbound`. |
743 | | /// |
744 | | /// - Note: `EmbeddedChannel` is currently only compatible with |
745 | | /// `EmbeddedEventLoop`s and cannot be used with `SelectableEventLoop`s from |
746 | | /// for example `MultiThreadedEventLoopGroup`. |
747 | | /// - warning: Unlike other `Channel`s, `EmbeddedChannel` **is not thread-safe**. This |
748 | | /// is because it is intended to be run in the thread that instantiated it. Users are |
749 | | /// responsible for ensuring they never call into an `EmbeddedChannel` in an |
750 | | /// unsynchronized fashion. `EmbeddedEventLoop`s notes also apply as |
751 | | /// `EmbeddedChannel` uses an `EmbeddedEventLoop` as its `EventLoop`. |
752 | | public final class EmbeddedChannel: Channel { |
753 | | /// `LeftOverState` represents any left-over inbound, outbound, and pending outbound events that hit the |
754 | | /// `EmbeddedChannel` and were not consumed when `finish` was called on the `EmbeddedChannel`. |
755 | | /// |
756 | | /// `EmbeddedChannel` is most useful in testing and usually in unit tests, you want to consume all inbound and |
757 | | /// outbound data to verify they are what you expect. Therefore, when you `finish` an `EmbeddedChannel` it will |
758 | | /// return if it's either `.clean` (no left overs) or that it has `.leftOvers`. |
759 | | public enum LeftOverState { |
760 | | /// The `EmbeddedChannel` is clean, ie. no inbound, outbound, or pending outbound data left on `finish`. |
761 | | case clean |
762 | | |
763 | | /// The `EmbeddedChannel` has inbound, outbound, or pending outbound data left on `finish`. |
764 | | case leftOvers(inbound: [NIOAny], outbound: [NIOAny], pendingOutbound: [NIOAny]) |
765 | | |
766 | | /// `true` if the `EmbeddedChannel` was `clean` on `finish`, ie. there is no unconsumed inbound, outbound, or |
767 | | /// pending outbound data left on the `Channel`. |
768 | 0 | public var isClean: Bool { |
769 | 0 | if case .clean = self { |
770 | 0 | return true |
771 | 0 | } else { |
772 | 0 | return false |
773 | 0 | } |
774 | 0 | } |
775 | | |
776 | | /// `true` if the `EmbeddedChannel` if there was unconsumed inbound, outbound, or pending outbound data left |
777 | | /// on the `Channel` when it was `finish`ed. |
778 | 0 | public var hasLeftOvers: Bool { |
779 | 0 | !self.isClean |
780 | 0 | } |
781 | | } |
782 | | |
783 | | /// `BufferState` represents the state of either the inbound, or the outbound `EmbeddedChannel` buffer. These |
784 | | /// buffers contain data that travelled the `ChannelPipeline` all the way. |
785 | | /// |
786 | | /// If the last `ChannelHandler` explicitly (by calling `fireChannelRead`) or implicitly (by not implementing |
787 | | /// `channelRead`) sends inbound data into the end of the `EmbeddedChannel`, it will be held in the |
788 | | /// `EmbeddedChannel`'s inbound buffer. Similarly for `write` on the outbound side. The state of the respective |
789 | | /// buffer will be returned from `writeInbound`/`writeOutbound` as a `BufferState`. |
790 | | public enum BufferState { |
791 | | /// The buffer is empty. |
792 | | case empty |
793 | | |
794 | | /// The buffer is non-empty. |
795 | | case full([NIOAny]) |
796 | | |
797 | | /// Returns `true` is the buffer was empty. |
798 | 0 | public var isEmpty: Bool { |
799 | 0 | if case .empty = self { |
800 | 0 | return true |
801 | 0 | } else { |
802 | 0 | return false |
803 | 0 | } |
804 | 0 | } |
805 | | |
806 | | /// Returns `true` if the buffer was non-empty. |
807 | 0 | public var isFull: Bool { |
808 | 0 | !self.isEmpty |
809 | 0 | } |
810 | | } |
811 | | |
812 | | /// `WrongTypeError` is throws if you use `readInbound` or `readOutbound` and request a certain type but the first |
813 | | /// item in the respective buffer is of a different type. |
814 | | public struct WrongTypeError: Error, Equatable { |
815 | | /// The type you expected. |
816 | | public let expected: Any.Type |
817 | | |
818 | | /// The type of the actual first element. |
819 | | public let actual: Any.Type |
820 | | |
821 | 0 | public init(expected: Any.Type, actual: Any.Type) { |
822 | 0 | self.expected = expected |
823 | 0 | self.actual = actual |
824 | 0 | } |
825 | | |
826 | 0 | public static func == (lhs: WrongTypeError, rhs: WrongTypeError) -> Bool { |
827 | 0 | lhs.expected == rhs.expected && lhs.actual == rhs.actual |
828 | 0 | } |
829 | | } |
830 | | |
831 | | /// Returns `true` if the `EmbeddedChannel` is 'active'. |
832 | | /// |
833 | | /// An active `EmbeddedChannel` can be closed by calling `close` or `finish` on the `EmbeddedChannel`. |
834 | | /// |
835 | | /// - Note: An `EmbeddedChannel` starts _inactive_ and can be activated, for example by calling `connect`. |
836 | 0 | public var isActive: Bool { channelcore.isActive } |
837 | | |
838 | | @usableFromInline |
839 | 0 | internal var isOpen: Bool { channelcore.isOpen } |
840 | | |
841 | | /// - see: `ChannelOptions.Types.AllowRemoteHalfClosureOption` |
842 | | public var allowRemoteHalfClosure: Bool { |
843 | 0 | get { |
844 | 0 | self.embeddedEventLoop.checkCorrectThread() |
845 | 0 | return channelcore.allowRemoteHalfClosure |
846 | 0 | } |
847 | 0 | set { |
848 | 0 | self.embeddedEventLoop.checkCorrectThread() |
849 | 0 | channelcore.allowRemoteHalfClosure = newValue |
850 | 0 | } |
851 | | } |
852 | | |
853 | | public var allowOptionsWhenClosed: Bool { |
854 | 0 | get { |
855 | 0 | self.embeddedEventLoop.checkCorrectThread() |
856 | 0 | return channelcore.allowOptionsWhenClosed |
857 | 0 | } |
858 | 0 | set { |
859 | 0 | self.embeddedEventLoop.checkCorrectThread() |
860 | 0 | channelcore.allowOptionsWhenClosed = newValue |
861 | 0 | } |
862 | | } |
863 | | |
864 | | /// - see: `Channel.closeFuture` |
865 | 0 | public var closeFuture: EventLoopFuture<Void> { channelcore.closePromise.futureResult } |
866 | | |
867 | | @usableFromInline |
868 | 35.5k | lazy var channelcore: EmbeddedChannelCore = EmbeddedChannelCore( |
869 | 35.5k | pipeline: self._pipeline, |
870 | 35.5k | eventLoop: self.eventLoop |
871 | 35.5k | ) |
872 | | |
873 | | /// - see: `Channel._channelCore` |
874 | 2.43M | public var _channelCore: ChannelCore { |
875 | 2.43M | self.embeddedEventLoop.checkCorrectThread() |
876 | 2.43M | return self.channelcore |
877 | 2.43M | } |
878 | | |
879 | | /// - see: `Channel.pipeline` |
880 | 70.5k | public var pipeline: ChannelPipeline { |
881 | 70.5k | self.embeddedEventLoop.checkCorrectThread() |
882 | 70.5k | return self._pipeline |
883 | 70.5k | } |
884 | | |
885 | | /// - see: `Channel.isWritable` |
886 | 35.5k | public var isWritable: Bool = true |
887 | | |
888 | | @usableFromInline |
889 | 35.5k | internal var _options: [(option: any ChannelOption, value: any Sendable)] = [] |
890 | | |
891 | | /// The `ChannelOption`s set on this channel. |
892 | | /// - see: `Embedded.setOption` |
893 | 0 | public var options: [(option: any ChannelOption, value: any Sendable)] { self._options } |
894 | | |
895 | | /// Synchronously closes the `EmbeddedChannel`. |
896 | | /// |
897 | | /// Errors in the `EmbeddedChannel` can be consumed using `throwIfErrorCaught`. |
898 | | /// |
899 | | /// - Parameters: |
900 | | /// - acceptAlreadyClosed: Whether `finish` should throw if the `EmbeddedChannel` has been previously `close`d. |
901 | | /// - Returns: The `LeftOverState` of the `EmbeddedChannel`. If all the inbound and outbound events have been |
902 | | /// consumed (using `readInbound` / `readOutbound`) and there are no pending outbound events (unflushed |
903 | | /// writes) this will be `.clean`. If there are any unconsumed inbound, outbound, or pending outbound |
904 | | /// events, the `EmbeddedChannel` will returns those as `.leftOvers(inbound:outbound:pendingOutbound:)`. |
905 | 14.1k | public func finish(acceptAlreadyClosed: Bool) throws -> LeftOverState { |
906 | 14.1k | self.embeddedEventLoop.checkCorrectThread() |
907 | 14.1k | do { |
908 | 14.1k | try close().wait() |
909 | 14.1k | } catch let error as ChannelError { |
910 | 0 | guard error == .alreadyClosed && acceptAlreadyClosed else { |
911 | 0 | throw error |
912 | 0 | } |
913 | 14.1k | } |
914 | 14.1k | self.embeddedEventLoop.run() |
915 | 14.1k | self.embeddedEventLoop.cancelRemainingScheduledTasks() |
916 | 14.1k | try throwIfErrorCaught() |
917 | 5.46k | let c = self.channelcore |
918 | 5.46k | if c.outboundBuffer.isEmpty && c.inboundBuffer.isEmpty && c.pendingOutboundBuffer.isEmpty { |
919 | 2.85k | return .clean |
920 | 2.85k | } else { |
921 | 2.61k | return .leftOvers( |
922 | 2.61k | inbound: Array(c.inboundBuffer), |
923 | 2.61k | outbound: Array(c.outboundBuffer), |
924 | 2.61k | pendingOutbound: c.pendingOutboundBuffer.map { $0.0 } |
925 | 2.61k | ) |
926 | 2.61k | } |
927 | 5.46k | } |
928 | | |
929 | | /// Synchronously closes the `EmbeddedChannel`. |
930 | | /// |
931 | | /// This method will throw if the `Channel` hit any unconsumed errors or if the `close` fails. Errors in the |
932 | | /// `EmbeddedChannel` can be consumed using `throwIfErrorCaught`. |
933 | | /// |
934 | | /// - Returns: The `LeftOverState` of the `EmbeddedChannel`. If all the inbound and outbound events have been |
935 | | /// consumed (using `readInbound` / `readOutbound`) and there are no pending outbound events (unflushed |
936 | | /// writes) this will be `.clean`. If there are any unconsumed inbound, outbound, or pending outbound |
937 | | /// events, the `EmbeddedChannel` will returns those as `.leftOvers(inbound:outbound:pendingOutbound:)`. |
938 | 0 | public func finish() throws -> LeftOverState { |
939 | 0 | self.embeddedEventLoop.checkCorrectThread() |
940 | 0 | return try self.finish(acceptAlreadyClosed: false) |
941 | 0 | } |
942 | | |
943 | | private var _pipeline: ChannelPipeline! |
944 | | |
945 | | /// - see: `Channel.allocator` |
946 | 35.5k | public var allocator: ByteBufferAllocator = ByteBufferAllocator() |
947 | | |
948 | | /// - see: `Channel.eventLoop` |
949 | 56.4k | public var eventLoop: EventLoop { |
950 | 56.4k | self.embeddedEventLoop.checkCorrectThread() |
951 | 56.4k | return self.embeddedEventLoop |
952 | 56.4k | } |
953 | | |
954 | | /// Returns the `EmbeddedEventLoop` that this `EmbeddedChannel` uses. This will return the same instance as |
955 | | /// `EmbeddedChannel.eventLoop` but as the concrete `EmbeddedEventLoop` rather than as `EventLoop` existential. |
956 | 35.5k | public var embeddedEventLoop: EmbeddedEventLoop = EmbeddedEventLoop() |
957 | | |
958 | | /// - see: `Channel.localAddress` |
959 | | public var localAddress: SocketAddress? { |
960 | 0 | get { |
961 | 0 | self.embeddedEventLoop.checkCorrectThread() |
962 | 0 | return self.channelcore.localAddress |
963 | 0 | } |
964 | 0 | set { |
965 | 0 | self.embeddedEventLoop.checkCorrectThread() |
966 | 0 | self.channelcore.localAddress = newValue |
967 | 0 | } |
968 | | } |
969 | | |
970 | | /// - see: `Channel.remoteAddress` |
971 | | public var remoteAddress: SocketAddress? { |
972 | 0 | get { |
973 | 0 | self.embeddedEventLoop.checkCorrectThread() |
974 | 0 | return self.channelcore.remoteAddress |
975 | 0 | } |
976 | 0 | set { |
977 | 0 | self.embeddedEventLoop.checkCorrectThread() |
978 | 0 | self.channelcore.remoteAddress = newValue |
979 | 0 | } |
980 | | } |
981 | | |
982 | | /// `nil` because `EmbeddedChannel`s don't have parents. |
983 | 35.5k | public let parent: Channel? = nil |
984 | | |
985 | | /// If available, this method reads one element of type `T` out of the `EmbeddedChannel`'s outbound buffer. If the |
986 | | /// first element was of a different type than requested, `EmbeddedChannel.WrongTypeError` will be thrown, if there |
987 | | /// are no elements in the outbound buffer, `nil` will be returned. |
988 | | /// |
989 | | /// Data hits the `EmbeddedChannel`'s outbound buffer when data was written using `write`, then `flush`ed, and |
990 | | /// then travelled the `ChannelPipeline` all the way too the front. For data to hit the outbound buffer, the very |
991 | | /// first `ChannelHandler` must have written and flushed it either explicitly (by calling |
992 | | /// `ChannelHandlerContext.write` and `flush`) or implicitly by not implementing `write`/`flush`. |
993 | | /// |
994 | | /// - Note: Outbound events travel the `ChannelPipeline` _back to front_. |
995 | | /// - Note: `EmbeddedChannel.writeOutbound` will `write` data through the `ChannelPipeline`, starting with last |
996 | | /// `ChannelHandler`. |
997 | | @inlinable |
998 | 0 | public func readOutbound<T>(as type: T.Type = T.self) throws -> T? { |
999 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1000 | 0 | return try _readFromBuffer(buffer: &channelcore.outboundBuffer) |
1001 | 0 | } |
1002 | | |
1003 | | /// If available, this method reads one element of type `T` out of the `EmbeddedChannel`'s inbound buffer. If the |
1004 | | /// first element was of a different type than requested, `EmbeddedChannel.WrongTypeError` will be thrown, if there |
1005 | | /// are no elements in the outbound buffer, `nil` will be returned. |
1006 | | /// |
1007 | | /// Data hits the `EmbeddedChannel`'s inbound buffer when data was send through the pipeline using `fireChannelRead` |
1008 | | /// and then travelled the `ChannelPipeline` all the way too the back. For data to hit the inbound buffer, the |
1009 | | /// last `ChannelHandler` must have send the event either explicitly (by calling |
1010 | | /// `ChannelHandlerContext.fireChannelRead`) or implicitly by not implementing `channelRead`. |
1011 | | /// |
1012 | | /// - Note: `EmbeddedChannel.writeInbound` will fire data through the `ChannelPipeline` using `fireChannelRead`. |
1013 | | @inlinable |
1014 | 0 | public func readInbound<T>(as type: T.Type = T.self) throws -> T? { |
1015 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1016 | 0 | return try _readFromBuffer(buffer: &channelcore.inboundBuffer) |
1017 | 0 | } |
1018 | | |
1019 | | /// Sends an inbound `channelRead` event followed by a `channelReadComplete` event through the `ChannelPipeline`. |
1020 | | /// |
1021 | | /// The immediate effect being that the first `ChannelInboundHandler` will get its `channelRead` method called |
1022 | | /// with the data you provide. |
1023 | | /// |
1024 | | /// - Parameters: |
1025 | | /// - data: The data to fire through the pipeline. |
1026 | | /// - Returns: The state of the inbound buffer which contains all the events that travelled the `ChannelPipeline` |
1027 | | // all the way. |
1028 | | @inlinable |
1029 | 35.5k | @discardableResult public func writeInbound<T>(_ data: T) throws -> BufferState { |
1030 | 35.5k | self.embeddedEventLoop.checkCorrectThread() |
1031 | 35.5k | self.pipeline.syncOperations.fireChannelRead(NIOAny(data)) |
1032 | 35.5k | self.pipeline.syncOperations.fireChannelReadComplete() |
1033 | 35.5k | try self.throwIfErrorCaught() |
1034 | 24.5k | return self.channelcore.inboundBuffer.isEmpty ? .empty : .full(Array(self.channelcore.inboundBuffer)) |
1035 | 35.5k | } |
1036 | | |
1037 | | /// Sends an outbound `writeAndFlush` event through the `ChannelPipeline`. |
1038 | | /// |
1039 | | /// The immediate effect being that the first `ChannelOutboundHandler` will get its `write` method called |
1040 | | /// with the data you provide. Note that the first `ChannelOutboundHandler` in the pipeline is the _last_ handler |
1041 | | /// because outbound events travel the pipeline from back to front. |
1042 | | /// |
1043 | | /// - Parameters: |
1044 | | /// - data: The data to fire through the pipeline. |
1045 | | /// - Returns: The state of the outbound buffer which contains all the events that travelled the `ChannelPipeline` |
1046 | | // all the way. |
1047 | | @inlinable |
1048 | 0 | @discardableResult public func writeOutbound<T>(_ data: T) throws -> BufferState { |
1049 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1050 | 0 | try self.writeAndFlush(data).wait() |
1051 | 0 | return self.channelcore.outboundBuffer.isEmpty ? .empty : .full(Array(self.channelcore.outboundBuffer)) |
1052 | 0 | } |
1053 | | |
1054 | | /// This method will throw the error that is stored in the `EmbeddedChannel` if any. |
1055 | | /// |
1056 | | /// The `EmbeddedChannel` will store an error some error travels the `ChannelPipeline` all the way past its end. |
1057 | 28.2k | public func throwIfErrorCaught() throws { |
1058 | 28.2k | self.embeddedEventLoop.checkCorrectThread() |
1059 | 28.2k | if let error = channelcore.error { |
1060 | 13.0k | self.channelcore.error = nil |
1061 | 13.0k | throw error |
1062 | 15.1k | } |
1063 | 15.1k | } |
1064 | | |
1065 | | @inlinable |
1066 | 0 | func _readFromBuffer<T>(buffer: inout CircularBuffer<NIOAny>) throws -> T? { |
1067 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1068 | 0 | if buffer.isEmpty { |
1069 | 0 | return nil |
1070 | 0 | } |
1071 | 0 | let elem = buffer.removeFirst() |
1072 | 0 | guard let t = self._channelCore.tryUnwrapData(elem, as: T.self) else { |
1073 | 0 | throw WrongTypeError( |
1074 | 0 | expected: T.self, |
1075 | 0 | actual: type(of: self._channelCore.tryUnwrapData(elem, as: Any.self)!) |
1076 | 0 | ) |
1077 | 0 | } |
1078 | 0 | return t |
1079 | 0 | } |
1080 | | |
1081 | | /// Create a new instance. |
1082 | | /// |
1083 | | /// During creation it will automatically also register itself on the `EmbeddedEventLoop`. |
1084 | | /// |
1085 | | /// - Parameters: |
1086 | | /// - handler: The `ChannelHandler` to add to the `ChannelPipeline` before register or `nil` if none should be added. |
1087 | | /// - loop: The `EmbeddedEventLoop` to use. |
1088 | 14.1k | public convenience init(handler: ChannelHandler? = nil, loop: EmbeddedEventLoop = EmbeddedEventLoop()) { |
1089 | 14.1k | let handlers = handler.map { [$0] } ?? [] |
1090 | 14.1k | self.init(handlers: handlers, loop: loop) |
1091 | 14.1k | self.embeddedEventLoop.checkCorrectThread() |
1092 | 14.1k | } |
1093 | | |
1094 | | /// Create a new instance. |
1095 | | /// |
1096 | | /// During creation it will automatically also register itself on the `EmbeddedEventLoop`. |
1097 | | /// |
1098 | | /// - Parameters: |
1099 | | /// - handlers: The `ChannelHandler`s to add to the `ChannelPipeline` before register. |
1100 | | /// - loop: The `EmbeddedEventLoop` to use. |
1101 | 14.1k | public init(handlers: [ChannelHandler], loop: EmbeddedEventLoop = EmbeddedEventLoop()) { |
1102 | 14.1k | self.embeddedEventLoop = loop |
1103 | 14.1k | self._pipeline = ChannelPipeline(channel: self) |
1104 | 14.1k | |
1105 | 14.1k | try! self._pipeline.syncOperations.addHandlers(handlers) |
1106 | 14.1k | |
1107 | 14.1k | // This will never throw... |
1108 | 14.1k | try! register().wait() |
1109 | 14.1k | self.embeddedEventLoop.checkCorrectThread() |
1110 | 14.1k | } |
1111 | | |
1112 | | /// - see: `Channel.setOption` |
1113 | | @inlinable |
1114 | 0 | public func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> EventLoopFuture<Void> { |
1115 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1116 | 0 | return self.eventLoop.makeCompletedFuture { try self.setOptionSync(option, value: value) } |
1117 | 0 | } |
1118 | | |
1119 | | @inlinable |
1120 | 0 | internal func setOptionSync<Option: ChannelOption>(_ option: Option, value: Option.Value) throws { |
1121 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1122 | 0 |
|
1123 | 0 | guard self.isOpen || self.allowOptionsWhenClosed else { |
1124 | 0 | throw ChannelError.alreadyClosed |
1125 | 0 | } |
1126 | 0 |
|
1127 | 0 | self.addOption(option, value: value) |
1128 | 0 |
|
1129 | 0 | if option is ChannelOptions.Types.AllowRemoteHalfClosureOption { |
1130 | 0 | self.allowRemoteHalfClosure = value as! Bool |
1131 | 0 | return |
1132 | 0 | } |
1133 | 0 | } |
1134 | | |
1135 | | /// - see: `Channel.getOption` |
1136 | | @inlinable |
1137 | 0 | public func getOption<Option: ChannelOption>(_ option: Option) -> EventLoopFuture<Option.Value> { |
1138 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1139 | 0 | return self.eventLoop.makeCompletedFuture { try self.getOptionSync(option) } |
1140 | 0 | } |
1141 | | |
1142 | | @inlinable |
1143 | 0 | internal func getOptionSync<Option: ChannelOption>(_ option: Option) throws -> Option.Value { |
1144 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1145 | 0 |
|
1146 | 0 | guard self.isOpen || self.allowOptionsWhenClosed else { |
1147 | 0 | throw ChannelError.alreadyClosed |
1148 | 0 | } |
1149 | 0 |
|
1150 | 0 | if option is ChannelOptions.Types.AutoReadOption { |
1151 | 0 | return true as! Option.Value |
1152 | 0 | } |
1153 | 0 | if option is ChannelOptions.Types.AllowRemoteHalfClosureOption { |
1154 | 0 | return self.allowRemoteHalfClosure as! Option.Value |
1155 | 0 | } |
1156 | 0 | if option is ChannelOptions.Types.BufferedWritableBytesOption { |
1157 | 0 | let result = self.channelcore.pendingOutboundBuffer.reduce(0) { partialResult, dataAndPromise in |
1158 | 0 | let buffer = self.channelcore.unwrapData(dataAndPromise.0, as: ByteBuffer.self) |
1159 | 0 | return partialResult + buffer.readableBytes |
1160 | 0 | } |
1161 | 0 |
|
1162 | 0 | return result as! Option.Value |
1163 | 0 | } |
1164 | 0 |
|
1165 | 0 | guard let value = optionValue(for: option) else { |
1166 | 0 | fatalError("option \(option) not supported") |
1167 | 0 | } |
1168 | 0 |
|
1169 | 0 | return value |
1170 | 0 | } |
1171 | | |
1172 | | @inlinable |
1173 | 0 | internal func addOption<Option: ChannelOption>(_ option: Option, value: Option.Value) { |
1174 | 0 | if let optionIndex = self._options.firstIndex(where: { $0.option is Option }) { |
1175 | 0 | self._options[optionIndex] = (option: option, value: value) |
1176 | 0 | } else { |
1177 | 0 | self._options.append((option: option, value: value)) |
1178 | 0 | } |
1179 | 0 | } |
1180 | | |
1181 | | @inlinable |
1182 | 0 | internal func optionValue<Option: ChannelOption>(for option: Option) -> Option.Value? { |
1183 | 0 | self.options.first(where: { $0.option is Option })?.value as? Option.Value |
1184 | 0 | } |
1185 | | |
1186 | | /// Fires the (outbound) `bind` event through the `ChannelPipeline`. If the event hits the `EmbeddedChannel` which |
1187 | | /// happens when it travels the `ChannelPipeline` all the way to the front, this will also set the |
1188 | | /// `EmbeddedChannel`'s `localAddress`. |
1189 | | /// |
1190 | | /// - Parameters: |
1191 | | /// - address: The address to fake-bind to. |
1192 | | /// - promise: The `EventLoopPromise` which will be fulfilled when the fake-bind operation has been done. |
1193 | 0 | public func bind(to address: SocketAddress, promise: EventLoopPromise<Void>?) { |
1194 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1195 | 0 | let promise = promise ?? self.embeddedEventLoop.makePromise() |
1196 | 0 | promise.futureResult.whenSuccess { |
1197 | 0 | self.localAddress = address |
1198 | 0 | } |
1199 | 0 | self.pipeline.bind(to: address, promise: promise) |
1200 | 0 | } |
1201 | | |
1202 | | /// Fires the (outbound) `connect` event through the `ChannelPipeline`. If the event hits the `EmbeddedChannel` |
1203 | | /// which happens when it travels the `ChannelPipeline` all the way to the front, this will also set the |
1204 | | /// `EmbeddedChannel`'s `remoteAddress`. |
1205 | | /// |
1206 | | /// - Parameters: |
1207 | | /// - address: The address to fake-bind to. |
1208 | | /// - promise: The `EventLoopPromise` which will be fulfilled when the fake-bind operation has been done. |
1209 | 0 | public func connect(to address: SocketAddress, promise: EventLoopPromise<Void>?) { |
1210 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1211 | 0 | let promise = promise ?? self.embeddedEventLoop.makePromise() |
1212 | 0 | promise.futureResult.whenSuccess { |
1213 | 0 | self.remoteAddress = address |
1214 | 0 | } |
1215 | 0 | self.pipeline.connect(to: address, promise: promise) |
1216 | 0 | } |
1217 | | |
1218 | | /// An overload of `Channel.write` that does not require a Sendable type, as ``EmbeddedEventLoop`` |
1219 | | /// is bound to a single thread. |
1220 | | @inlinable |
1221 | 0 | public func write<T>(_ data: T, promise: EventLoopPromise<Void>?) { |
1222 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1223 | 0 | self.pipeline.syncOperations.write(NIOAny(data), promise: promise) |
1224 | 0 | } |
1225 | | |
1226 | | /// An overload of `Channel.write` that does not require a Sendable type, as ``EmbeddedEventLoop`` |
1227 | | /// is bound to a single thread. |
1228 | | @inlinable |
1229 | 0 | public func write<T>(_ data: T) -> EventLoopFuture<Void> { |
1230 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1231 | 0 | let promise = self.eventLoop.makePromise(of: Void.self) |
1232 | 0 | self.pipeline.syncOperations.write(NIOAny(data), promise: promise) |
1233 | 0 | return promise.futureResult |
1234 | 0 | } |
1235 | | |
1236 | | /// An overload of `Channel.writeAndFlush` that does not require a Sendable type, as ``EmbeddedEventLoop`` |
1237 | | /// is bound to a single thread. |
1238 | | @inlinable |
1239 | 0 | public func writeAndFlush<T>(_ data: T, promise: EventLoopPromise<Void>?) { |
1240 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1241 | 0 | self.pipeline.syncOperations.writeAndFlush(NIOAny(data), promise: promise) |
1242 | 0 | } |
1243 | | |
1244 | | /// An overload of `Channel.writeAndFlush` that does not require a Sendable type, as ``EmbeddedEventLoop`` |
1245 | | /// is bound to a single thread. |
1246 | | @inlinable |
1247 | 0 | public func writeAndFlush<T>(_ data: T) -> EventLoopFuture<Void> { |
1248 | 0 | self.embeddedEventLoop.checkCorrectThread() |
1249 | 0 | let promise = self.eventLoop.makePromise(of: Void.self) |
1250 | 0 | self.pipeline.syncOperations.writeAndFlush(NIOAny(data), promise: promise) |
1251 | 0 | return promise.futureResult |
1252 | 0 | } |
1253 | | } |
1254 | | |
1255 | | extension EmbeddedChannel { |
1256 | | public struct SynchronousOptions: NIOSynchronousChannelOptions { |
1257 | | @usableFromInline |
1258 | | internal let channel: EmbeddedChannel |
1259 | | |
1260 | 0 | fileprivate init(channel: EmbeddedChannel) { |
1261 | 0 | self.channel = channel |
1262 | 0 | } |
1263 | | |
1264 | | @inlinable |
1265 | 0 | public func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) throws { |
1266 | 0 | try self.channel.setOptionSync(option, value: value) |
1267 | 0 | } |
1268 | | |
1269 | | @inlinable |
1270 | 0 | public func getOption<Option: ChannelOption>(_ option: Option) throws -> Option.Value { |
1271 | 0 | try self.channel.getOptionSync(option) |
1272 | 0 | } |
1273 | | } |
1274 | | |
1275 | 0 | public final var syncOptions: NIOSynchronousChannelOptions? { |
1276 | 0 | SynchronousOptions(channel: self) |
1277 | 0 | } |
1278 | | } |
1279 | | |
1280 | | // EmbeddedChannel is extremely _not_ Sendable. However, the Channel protocol |
1281 | | // requires it to be. We are doing some runtime enforcement of correct use, but |
1282 | | // ultimately we can't have the compiler validating this usage. |
1283 | | extension EmbeddedChannel: @unchecked Sendable {} |
1284 | | |
1285 | | @available(*, unavailable) |
1286 | | extension EmbeddedChannel.LeftOverState: @unchecked Sendable {} |
1287 | | |
1288 | | @available(*, unavailable) |
1289 | | extension EmbeddedChannel.BufferState: @unchecked Sendable {} |
1290 | | |
1291 | | @available(*, unavailable) |
1292 | | extension EmbeddedChannel.SynchronousOptions: Sendable {} |