Coverage Report

Created: 2025-07-23 06:54

/src/swift-nio/Sources/NIOCore/NIOLoopBound.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) 2023 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
/// ``NIOLoopBound`` is an always-`Sendable`, value-typed container allowing you access to ``value`` if and only if
16
/// you are accessing it on the right ``EventLoop``.
17
///
18
/// ``NIOLoopBound`` is useful to transport a value of a non-`Sendable` type that needs to go from one place in
19
/// your code to another where you (but not the compiler) know is on one and the same ``EventLoop``. Usually this
20
/// involves `@Sendable` closures. This type is safe because it verifies (using ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``)
21
/// that this is actually true.
22
///
23
/// A ``NIOLoopBound`` can only be constructed, read from or written to when you are provably
24
/// (through ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``) on the ``EventLoop`` associated with the ``NIOLoopBound``. Accessing
25
/// or constructing it from any other place will crash your program with a precondition as it would be undefined
26
/// behaviour to do so.
27
public struct NIOLoopBound<Value>: @unchecked Sendable {
28
    /// The ``EventLoop`` that the value is bound to.
29
    public let eventLoop: EventLoop
30
31
    @available(*, deprecated, renamed: "eventLoop")
32
0
    public var _eventLoop: EventLoop {
33
0
        self.eventLoop
34
0
    }
35
36
    @usableFromInline
37
    var _value: Value
38
39
    /// Initialise a ``NIOLoopBound`` to `value` with the precondition that the code is running on `eventLoop`.
40
    @inlinable
41
180k
    public init(_ value: Value, eventLoop: EventLoop) {
42
180k
        eventLoop.preconditionInEventLoop()
43
180k
        self.eventLoop = eventLoop
44
180k
        self._value = value
45
180k
    }
46
47
    /// Access the `value` with the precondition that the code is running on `eventLoop`.
48
    ///
49
    /// - Note: ``NIOLoopBound`` itself is value-typed, so any writes will only affect the current value.
50
    @inlinable
51
    public var value: Value {
52
118k
        get {
53
118k
            self.eventLoop.preconditionInEventLoop()
54
118k
            return self._value
55
118k
        }
56
0
        _modify {
57
0
            self.eventLoop.preconditionInEventLoop()
58
0
            yield &self._value
59
0
        }
60
    }
61
}
62
63
/// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and
64
/// only if you are accessing it on the right ``EventLoop``.
65
///
66
/// ``NIOLoopBoundBox`` is useful to transport a value of a non-`Sendable` type that needs to go from one place in
67
/// your code to another where you (but not the compiler) know is on one and the same ``EventLoop``. Usually this
68
/// involves `@Sendable` closures. This type is safe because it verifies (using ``EventLoop/preconditionInEventLoop(file:line:)-7ukrq``)
69
/// that this is actually true.
70
///
71
/// A ``NIOLoopBoundBox`` can only be read from or written to when you are provably
72
/// (through ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``) on the ``EventLoop`` associated with the ``NIOLoopBoundBox``. Accessing
73
/// or constructing it from any other place will crash your program with a precondition as it would be undefined
74
/// behaviour to do so.
75
///
76
/// If constructing a ``NIOLoopBoundBox`` with a `value`, it is also required for the program to already be on `eventLoop`
77
/// but if you have a ``NIOLoopBoundBox`` that contains an `Optional` type, you may initialise it _without a value_
78
/// whilst off the ``EventLoop`` by using ``NIOLoopBoundBox/makeEmptyBox(valueType:eventLoop:)``. Any read/write access to ``value``
79
/// afterwards will require you to be on `eventLoop`.
80
public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
81
    /// The ``EventLoop`` that the value is bound to.
82
    public let eventLoop: EventLoop
83
84
    @available(*, deprecated, renamed: "eventLoop")
85
0
    public var _eventLoop: EventLoop {
86
0
        self.eventLoop
87
0
    }
88
89
    @usableFromInline
90
    var _value: Value
91
92
    @inlinable
93
0
    internal init(_value value: Value, uncheckedEventLoop eventLoop: EventLoop) {
94
0
        self.eventLoop = eventLoop
95
0
        self._value = value
96
0
    }
97
98
    /// Initialise a ``NIOLoopBoundBox`` to `value` with the precondition that the code is running on `eventLoop`.
99
    @inlinable
100
0
    public convenience init(_ value: Value, eventLoop: EventLoop) {
101
0
        // This precondition is absolutely required. If not, it were possible to take a non-Sendable `Value` from
102
0
        // _off_ the ``EventLoop`` and transport it _to_ the ``EventLoop``. That would be illegal.
103
0
        eventLoop.preconditionInEventLoop()
104
0
        self.init(_value: value, uncheckedEventLoop: eventLoop)
105
0
    }
106
107
    /// Initialise a ``NIOLoopBoundBox`` that is empty (contains `nil`), this does _not_ require you to be running on `eventLoop`.
108
    public static func makeEmptyBox<NonOptionalValue>(
109
        valueType: NonOptionalValue.Type = NonOptionalValue.self,
110
        eventLoop: EventLoop
111
0
    ) -> NIOLoopBoundBox<Value> where NonOptionalValue? == Value {
112
0
        // Here, we -- possibly surprisingly -- do not precondition being on the EventLoop. This is okay for a few
113
0
        // reasons:
114
0
        // - We write the `Optional.none` value which we know is _not_ a value of the potentially non-Sendable type
115
0
        //   `Value`.
116
0
        // - Because of Swift's Definitive Initialisation (DI), we know that we did write `self._value` before `init`
117
0
        //   returns.
118
0
        // - The only way to ever write (or read indeed) `self._value` is by proving to be inside the `EventLoop`.
119
0
        .init(_value: nil, uncheckedEventLoop: eventLoop)
120
0
    }
121
122
    /// Initialise a ``NIOLoopBoundBox`` by sending a `Sendable` value, validly callable off `eventLoop`.
123
    ///
124
    /// Contrary to ``init(_:eventLoop:)``, this method can be called off `eventLoop` because we know that `value` is `Sendable`.
125
    /// So we don't need to protect `value` itself, we just need to protect the ``NIOLoopBoundBox`` against mutations which we do because the ``value``
126
    /// accessors are checking that we're on `eventLoop`.
127
    public static func makeBoxSendingValue(
128
        _ value: Value,
129
        as: Value.Type = Value.self,
130
        eventLoop: EventLoop
131
0
    ) -> NIOLoopBoundBox<Value> where Value: Sendable {
132
0
        // Here, we -- possibly surprisingly -- do not precondition being on the EventLoop. This is okay for a few
133
0
        // reasons:
134
0
        // - This function only works with `Sendable` values, so we don't need to worry about somebody
135
0
        //   still holding a reference to this.
136
0
        // - Because of Swift's Definitive Initialisation (DI), we know that we did write `self._value` before `init`
137
0
        //   returns.
138
0
        // - The only way to ever write (or read indeed) `self._value` is by proving to be inside the `EventLoop`.
139
0
        .init(_value: value, uncheckedEventLoop: eventLoop)
140
0
    }
141
142
    #if compiler(>=6.0)
143
    // Note: Whitespace changes are used to workaround compiler bug
144
    // Remove when compiler version 5.10 is no longer supported.
145
    // https://github.com/swiftlang/swift/issues/79285
146
    // swift-format-ignore
147
    /// Initialise a ``NIOLoopBoundBox`` by sending a  value, validly callable off `eventLoop`.
148
    ///
149
    /// Contrary to ``init(_:eventLoop:)``, this method can be called off `eventLoop` because `value` is moved into the box and can no longer be accessed outside the box.
150
    /// So we don't need to protect `value` itself, we just need to protect the ``NIOLoopBoundBox`` against mutations which we do because the ``value``
151
    /// accessors are checking that we're on `eventLoop`.
152
    public static func makeBoxSendingValue(_ value: sending Value, as: Value.Type = Value.self, eventLoop: EventLoop) -> NIOLoopBoundBox<Value> {
153
        // Here, we -- possibly surprisingly -- do not precondition being on the EventLoop. This is okay for a few
154
        // reasons:
155
        // - This function takes its value as `sending` so we don't need to worry about somebody
156
        //   still holding a reference to this.
157
        // - Because of Swift's Definitive Initialisation (DI), we know that we did write `self._value` before `init`
158
        //   returns.
159
        // - The only way to ever write (or read indeed) `self._value` is by proving to be inside the `EventLoop`.
160
        .init(_value: value, uncheckedEventLoop: eventLoop)
161
    }
162
    #endif
163
164
    /// Access the `value` with the precondition that the code is running on `eventLoop`.
165
    ///
166
    /// - Note: ``NIOLoopBoundBox`` itself is reference-typed, so any writes will affect anybody sharing this reference.
167
    @inlinable
168
    public var value: Value {
169
0
        get {
170
0
            self.eventLoop.preconditionInEventLoop()
171
0
            return self._value
172
0
        }
173
0
        _modify {
174
0
            self.eventLoop.preconditionInEventLoop()
175
0
            yield &self._value
176
0
        }
177
    }
178
}