Skip to content

Commit 048335c

Browse files
authored
Merge branch 'main' into openbsd
2 parents 8992361 + 3eea092 commit 048335c

File tree

6 files changed

+191
-31
lines changed

6 files changed

+191
-31
lines changed

.github/workflows/benchmarks.yml

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,37 @@ on:
7171
description: "Boolean to enable the Windows nightly main Swift version matrix job. Currently has no effect!" # TODO: implement Windows benchmarking
7272
default: false
7373

74+
macos_xcode_16_3_enabled:
75+
type: boolean
76+
description: "Boolean to enable the macOS Xcode 16.3 benchmark job. Defaults to false."
77+
default: false
78+
macos_xcode_16_4_enabled:
79+
type: boolean
80+
description: "Boolean to enable the macOS Xcode 16.4 benchmark job. Defaults to false."
81+
default: false
82+
macos_xcode_26_0_enabled:
83+
type: boolean
84+
description: "Boolean to enable the macOS Xcode 26.0 benchmark job. Defaults to false."
85+
default: false
86+
macos_xcode_26_1_enabled:
87+
type: boolean
88+
description: "Boolean to enable the macOS Xcode 26.1 benchmark job. Defaults to false."
89+
default: false
90+
macos_xcode_latest_beta_enabled:
91+
type: boolean
92+
description: "Boolean to enable the macOS Xcode latest beta benchmark job. Defaults to false."
93+
default: false
94+
95+
macos_runner_pool:
96+
type: string
97+
description: "The runner pool which will be requested for macOS jobs."
98+
default: "nightly"
99+
100+
macos_env_vars:
101+
type: string
102+
description: "Environment variables for macOS jobs as JSON (e.g., '{\"DEBUG\":\"1\",\"LOG_LEVEL\":\"info\"}')."
103+
default: "{}"
104+
74105
linux_env_vars:
75106
type: string
76107
description: "Environment variables for Linux jobs as JSON (e.g., '{\"DEBUG\":\"1\",\"LOG_LEVEL\":\"info\"}')."
@@ -82,8 +113,8 @@ on:
82113
default: ""
83114

84115
jobs:
85-
construct-matrix:
86-
name: Construct Benchmarks matrix
116+
construct-matrix-linux:
117+
name: Construct Linux Benchmarks matrix
87118
runs-on: ubuntu-latest
88119
outputs:
89120
benchmarks-matrix: '${{ steps.generate-matrix.outputs.benchmarks-matrix }}'
@@ -115,11 +146,121 @@ jobs:
115146
MATRIX_LINUX_NIGHTLY_NEXT_ENABLED: ${{ inputs.linux_nightly_6_1_enabled && inputs.linux_nightly_next_enabled }}
116147
MATRIX_LINUX_NIGHTLY_MAIN_ENABLED: ${{ inputs.linux_nightly_main_enabled }}
117148

118-
benchmarks:
149+
construct-matrix-macos:
150+
name: Construct macOS Benchmarks matrix
151+
runs-on: ubuntu-latest
152+
if: |
153+
inputs.macos_xcode_16_3_enabled ||
154+
inputs.macos_xcode_16_4_enabled ||
155+
inputs.macos_xcode_26_0_enabled ||
156+
inputs.macos_xcode_26_1_enabled ||
157+
inputs.macos_xcode_latest_beta_enabled
158+
outputs:
159+
macos-matrix: '${{ steps.generate-matrix.outputs.macos-matrix }}'
160+
steps:
161+
- name: Checkout repository
162+
uses: actions/checkout@v4
163+
with:
164+
persist-credentials: false
165+
- id: generate-matrix
166+
run: |
167+
# Validate JSON environment variables
168+
macos_env_vars_json='${{ inputs.macos_env_vars }}'
169+
170+
if ! echo "$macos_env_vars_json" | jq empty 2>/dev/null; then
171+
echo "Error: macos_env_vars is not valid JSON"
172+
exit 1
173+
fi
174+
175+
runner_pool="${MACOS_RUNNER_POOL}"
176+
xcode_16_3_enabled="${MACOS_XCODE_16_3_ENABLED}"
177+
xcode_16_4_enabled="${MACOS_XCODE_16_4_ENABLED}"
178+
xcode_26_0_enabled="${MACOS_XCODE_26_0_ENABLED}"
179+
xcode_26_1_enabled="${MACOS_XCODE_26_1_ENABLED}"
180+
xcode_latest_beta_enabled="${MACOS_XCODE_LATEST_BETA_ENABLED}"
181+
182+
# Create matrix from inputs
183+
matrix='{"config": []}'
184+
185+
if [[ "$xcode_16_3_enabled" == "true" ]]; then
186+
matrix=$(echo "$matrix" | jq -c \
187+
--arg runner_pool "$runner_pool" \
188+
--argjson env_vars "$macos_env_vars_json" \
189+
'.config[.config| length] |= . + { "name": "macOS (Xcode 16.3)", "xcode_version": "16.3", "xcode_app": "Xcode_16.3.app", "os": "sequoia", "arch": "ARM64", "pool": $runner_pool, "env": $env_vars }')
190+
fi
191+
192+
if [[ "$xcode_16_4_enabled" == "true" ]]; then
193+
matrix=$(echo "$matrix" | jq -c \
194+
--arg runner_pool "$runner_pool" \
195+
--argjson env_vars "$macos_env_vars_json" \
196+
'.config[.config| length] |= . + { "name": "macOS (Xcode 16.4)", "xcode_version": "16.4", "xcode_app": "Xcode_16.4.app", "os": "sequoia", "arch": "ARM64", "pool": $runner_pool, "env": $env_vars }')
197+
fi
198+
199+
if [[ "$xcode_26_0_enabled" == "true" ]]; then
200+
matrix=$(echo "$matrix" | jq -c \
201+
--arg runner_pool "$runner_pool" \
202+
--argjson env_vars "$macos_env_vars_json" \
203+
'.config[.config| length] |= . + { "name": "macOS (Xcode 26.0)", "xcode_version": "26.0", "xcode_app": "Xcode_26.0.app", "os": "sequoia", "arch": "ARM64", "pool": $runner_pool, "env": $env_vars }')
204+
fi
205+
206+
if [[ "$xcode_26_1_enabled" == "true" ]]; then
207+
matrix=$(echo "$matrix" | jq -c \
208+
--arg runner_pool "$runner_pool" \
209+
--argjson env_vars "$macos_env_vars_json" \
210+
'.config[.config| length] |= . + { "name": "macOS (Xcode 26.1)", "xcode_version": "26.1", "xcode_app": "Xcode_26.1.app", "os": "sequoia", "arch": "ARM64", "pool": $runner_pool, "env": $env_vars }')
211+
fi
212+
213+
if [[ "$xcode_latest_beta_enabled" == "true" ]]; then
214+
matrix=$(echo "$matrix" | jq -c \
215+
--arg runner_pool "$runner_pool" \
216+
--argjson env_vars "$macos_env_vars_json" \
217+
'.config[.config| length] |= . + { "name": "macOS (Xcode latest beta)", "xcode_version": "latest beta", "xcode_app": "Xcode-latest.app", "os": "sequoia", "arch": "ARM64", "pool": $runner_pool, "env": $env_vars }')
218+
fi
219+
220+
echo "macos-matrix=$matrix" >> "$GITHUB_OUTPUT"
221+
env:
222+
MACOS_RUNNER_POOL: ${{ inputs.macos_runner_pool }}
223+
MACOS_XCODE_16_3_ENABLED: ${{ inputs.macos_xcode_16_3_enabled }}
224+
MACOS_XCODE_16_4_ENABLED: ${{ inputs.macos_xcode_16_4_enabled }}
225+
MACOS_XCODE_26_0_ENABLED: ${{ inputs.macos_xcode_26_0_enabled }}
226+
MACOS_XCODE_26_1_ENABLED: ${{ inputs.macos_xcode_26_1_enabled }}
227+
MACOS_XCODE_LATEST_BETA_ENABLED: ${{ inputs.macos_xcode_latest_beta_enabled }}
228+
229+
benchmarks-linux:
119230
name: Benchmarks
120-
needs: construct-matrix
231+
needs: construct-matrix-linux
121232
# Workaround https://github.com/nektos/act/issues/1875
122233
uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main
123234
with:
124235
name: "Benchmarks"
125-
matrix_string: '${{ needs.construct-matrix.outputs.benchmarks-matrix }}'
236+
matrix_string: '${{ needs.construct-matrix-linux.outputs.benchmarks-matrix }}'
237+
238+
benchmarks-macos:
239+
name: ${{ matrix.config.name }}
240+
needs: construct-matrix-macos
241+
runs-on: [self-hosted, macos, "${{ matrix.config.os }}", "${{ matrix.config.arch }}", "${{ matrix.config.pool }}"]
242+
timeout-minutes: 30
243+
strategy:
244+
fail-fast: false
245+
matrix: ${{ fromJson(needs.construct-matrix-macos.outputs.macos-matrix) }}
246+
steps:
247+
- name: Checkout repository
248+
uses: actions/checkout@v4
249+
with:
250+
persist-credentials: false
251+
submodules: true
252+
- name: Install jemalloc
253+
run: |
254+
brew install jemalloc
255+
- name: Export environment variables
256+
if: ${{ matrix.config.env != '' && matrix.config.env != '{}'}}
257+
run: |
258+
echo "Exporting environment variables from matrix configuration..."
259+
echo '${{ toJSON(matrix.config.env) }}' | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> $GITHUB_ENV
260+
echo '${{ toJSON(matrix.config.env) }}' | jq -r 'to_entries[] | "exporting \(.key)=\(.value)"'
261+
- name: Run benchmarks script
262+
run: |
263+
curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/check_benchmark_thresholds.sh | BENCHMARK_PACKAGE_PATH=${{ inputs.benchmark_package_path }} bash -s -- --disable-sandbox --allow-writing-to-package-directory
264+
env:
265+
DEVELOPER_DIR: "/Applications/${{ matrix.config.xcode_app }}"
266+
SWIFT_VERSION: "Xcode ${{ matrix.config.xcode_version }}"

Benchmarks/Benchmarks/NIOPosixBenchmarks/Benchmarks.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ let benchmarks = {
3131
metrics: defaultMetrics,
3232
scalingFactor: .mega,
3333
maxDuration: .seconds(10_000_000),
34-
maxIterations: 5
34+
maxIterations: 5,
35+
thresholds: [.mallocCountTotal: .init(absolute: [.p90: 50])]
3536
)
3637
) { benchmark in
3738
try runTCPEcho(

Sources/NIOCore/ByteBuffer-aux.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ extension ByteBuffer {
568568
/// - Returns: The number of bytes written or `bytes.byteCount`.
569569
@discardableResult
570570
@inlinable
571-
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
571+
@available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.0, *)
572572
public mutating func writeBytes(_ bytes: RawSpan) -> Int {
573573
let written = self.setBytes(bytes, at: self.writerIndex)
574574
self._moveWriterIndex(forwardBy: written)

Sources/NIOCore/ByteBuffer-core.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ public struct ByteBuffer {
510510

511511
#if compiler(>=6.2)
512512
@inlinable
513-
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
513+
@available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.0, *)
514514
mutating func _setBytes(_ bytes: RawSpan, at index: _Index) -> _Capacity {
515515
let bytesCount = bytes.byteCount
516516
let newEndIndex: _Index = index + _toIndex(bytesCount)
@@ -524,7 +524,7 @@ public struct ByteBuffer {
524524
}
525525

526526
@inlinable
527-
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
527+
@available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.0, *)
528528
mutating func _setBytesAssumingUniqueBufferAccess(_ bytes: RawSpan, at index: _Index) {
529529
let targetPtr = UnsafeMutableRawBufferPointer(
530530
rebasing: self._slicedStorageBuffer.dropFirst(Int(index))
@@ -1122,7 +1122,7 @@ extension ByteBuffer {
11221122
/// Copy `bytes` from a `RawSpan` into the `ByteBuffer` at `index`. Does not move the writer index.
11231123
@discardableResult
11241124
@inlinable
1125-
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
1125+
@available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, visionOS 1.0, *)
11261126
public mutating func setBytes(_ bytes: RawSpan, at index: Int) -> Int {
11271127
Int(self._setBytes(bytes, at: _toIndex(index)))
11281128
}

Sources/NIOPosix/MultiThreadedEventLoopGroup.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
107107
canBeShutdownIndividually: canEventLoopBeShutdownIndividually,
108108
metricsDelegate: metricsDelegate
109109
)
110-
threadSpecificEventLoop.currentValue = loop
110+
Self.threadSpecificEventLoop.currentValue = loop
111111
defer {
112-
threadSpecificEventLoop.currentValue = nil
112+
Self.threadSpecificEventLoop.currentValue = nil
113113
}
114114
callback(loop)
115115
try loop.run()

Sources/NIOPosix/SelectableEventLoop.swift

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,7 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable {
187187
private let promiseCreationStoreLock = NIOLock()
188188
private var _promiseCreationStore: [_NIOEventLoopFutureIdentifier: (file: StaticString, line: UInt)] = [:]
189189

190-
private let metricsDelegate: (any NIOEventLoopMetricsDelegate)?
191-
192-
private var lastTickEndTime: NIODeadline
190+
private var metricsDelegateState: MetricsDelegateState?
193191

194192
@usableFromInline
195193
internal func _promiseCreated(futureIdentifier: _NIOEventLoopFutureIdentifier, file: StaticString, line: UInt) {
@@ -261,7 +259,9 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable {
261259
canBeShutdownIndividually: Bool,
262260
metricsDelegate: NIOEventLoopMetricsDelegate?
263261
) {
264-
self.metricsDelegate = metricsDelegate
262+
self.metricsDelegateState = metricsDelegate.map { delegate in
263+
MetricsDelegateState(metricsDelegate: delegate, lastTickEndime: .now())
264+
}
265265
self._uniqueID = uniqueID
266266
self._parentGroup = parentGroup
267267
self._selector = selector
@@ -270,7 +270,6 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable {
270270
self.msgBufferPool = Pool<PooledMsgBuffer>(maxSize: 16)
271271
self.tasksCopy.reserveCapacity(Self.tasksCopyBatchSize)
272272
self.canBeShutdownIndividually = canBeShutdownIndividually
273-
self.lastTickEndTime = .now()
274273
// note: We are creating a reference cycle here that we'll break when shutting the SelectableEventLoop down.
275274
// note: We have to create the promise and complete it because otherwise we'll hit a loop in `makeSucceededFuture`. This is
276275
// fairly dumb, but it's the only option we have.
@@ -747,21 +746,35 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable {
747746
return nextDeadline
748747
}
749748

750-
private func runLoop(selfIdentifier: ObjectIdentifier) -> NIODeadline? {
751-
let tickStartTime: NIODeadline = .now()
752-
let sleepTime: TimeAmount = tickStartTime - self.lastTickEndTime
749+
private func runOneLoopTick(selfIdentifier: ObjectIdentifier) -> NIODeadline? {
750+
let tickStartInfo:
751+
(
752+
metricsDelegate: any NIOEventLoopMetricsDelegate,
753+
tickStartTime: NIODeadline,
754+
sleepTime: TimeAmount
755+
)? = self.metricsDelegateState.map { metricsDelegateState in
756+
let tickStartTime = NIODeadline.now() // Potentially expensive, only if delegate set.
757+
return (
758+
metricsDelegate: metricsDelegateState.metricsDelegate,
759+
tickStartTime: tickStartTime,
760+
sleepTime: tickStartTime - metricsDelegateState.lastTickEndime
761+
)
762+
}
763+
753764
var tasksProcessedInTick = 0
754765
defer {
755-
let tickEndTime: NIODeadline = .now()
756-
let tickInfo = NIOEventLoopTickInfo(
757-
eventLoopID: selfIdentifier,
758-
numberOfTasks: tasksProcessedInTick,
759-
sleepTime: sleepTime,
760-
startTime: tickStartTime,
761-
endTime: tickEndTime
762-
)
763-
self.metricsDelegate?.processedTick(info: tickInfo)
764-
self.lastTickEndTime = tickEndTime
766+
if let tickStartInfo = tickStartInfo {
767+
let tickEndTime = NIODeadline.now() // Potentially expensive, only if delegate set.
768+
let tickInfo = NIOEventLoopTickInfo(
769+
eventLoopID: selfIdentifier,
770+
numberOfTasks: tasksProcessedInTick,
771+
sleepTime: tickStartInfo.sleepTime,
772+
startTime: tickStartInfo.tickStartTime,
773+
endTime: tickEndTime
774+
)
775+
tickStartInfo.metricsDelegate.processedTick(info: tickInfo)
776+
self.metricsDelegateState?.lastTickEndime = tickEndTime
777+
}
765778
}
766779
while true {
767780
let nextReadyDeadline = self._tasksLock.withLock { () -> NIODeadline? in
@@ -905,7 +918,7 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable {
905918
}
906919
}
907920
}
908-
nextReadyDeadline = runLoop(selfIdentifier: selfIdentifier)
921+
nextReadyDeadline = self.runOneLoopTick(selfIdentifier: selfIdentifier)
909922
}
910923

911924
// This EventLoop was closed so also close the underlying selector.
@@ -1036,6 +1049,11 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable {
10361049
}
10371050
}
10381051

1052+
struct MetricsDelegateState {
1053+
var metricsDelegate: any NIOEventLoopMetricsDelegate
1054+
var lastTickEndime: NIODeadline
1055+
}
1056+
10391057
extension SelectableEventLoop: CustomStringConvertible, CustomDebugStringConvertible {
10401058
@usableFromInline
10411059
var description: String {

0 commit comments

Comments
 (0)