Commit Graph
42 Commits
Author SHA1 Message Date
Fabian Fett 2fdda6c78e Fix benchmark succeeding – even though Benchmark build failed (#3502)
### Motivation:

Currently if there is an error in the Benchmark targets, that prevents
the Benchmark to build correctly, we still get a CI success.

### Modifications:

- Check for the threshold changed error

### Result:

- More reliable CI
2026-02-17 10:30:44 +00:00
Kushal Pisavadia 9b92dcd5c2 Replace ConditionLock with wake-one signalling NIOThreadPoolWorkAvailable (#3507)
**TL;DR** This change leads to a ~90% reduction in observed system CPU
time for some use cases by waking a single thread, instead of all idle
threads.

# Changes

Inlining the commit messages here.

## Add NIOThreadPool submit throughput benchmarks

### Motivation

`NIOThreadPool` had no benchmarks measuring submit overhead. This makes
it difficult to evaluate the cost of signalling changes or to catch
latency regressions.

### Modifications

Add thread pool submit benchmarks, covering use cases with 4-thread and
16-thread pools.

### Result

`NIOThreadPool` submit throughput and context-switch overhead are now
tracked by benchmarks.

### Benchmark Results

<details>

```
NIOThreadPool.serial_wakeup(16 threads)
╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                   │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ Context switches (K)     │        77 │        78 │        79 │        80 │        80 │        92 │        92 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Syscalls (total) (K) *   │       106 │       107 │       108 │       109 │       110 │       116 │       116 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (system CPU) (ms) * │      1649 │      1752 │      1768 │      1795 │      1826 │      1929 │      1929 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (total CPU) (ms) *  │      1701 │      1805 │      1821 │      1849 │      1879 │      1987 │      1987 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (user CPU) (ms) *   │        51 │        52 │        52 │        53 │        53 │        57 │        57 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (wall clock) (ms) * │       167 │       177 │       178 │       181 │       183 │       200 │       200 │        30 │
╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

NIOThreadPool.serial_wakeup(4 threads)
╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                   │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ Context switches (K)     │        44 │        44 │        44 │        45 │        45 │        45 │        45 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Syscalls (total) (K) *   │        65 │        65 │        65 │        66 │        66 │        67 │        67 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (system CPU) (ms) * │       159 │       162 │       163 │       165 │       166 │       169 │       169 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (total CPU) (ms) *  │       178 │       182 │       183 │       185 │       186 │       190 │       190 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (user CPU) (ms) *   │        19 │        19 │        20 │        20 │        20 │        21 │        21 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (wall clock) (ms) * │        76 │        79 │        79 │        80 │        80 │        82 │        82 │        30 │
╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛
```

</details>

## Replace `ConditionLock` with wake-one signalling
`NIOThreadPoolWorkAvailable`

### Motivation

`NIOThreadPool` used `ConditionLock` which calls
`pthread_cond_broadcast` on every state change, waking all threads when
only one work item is enqueued. This causes a thundering-herd problem.

### Modifications

Add `NIOThreadPoolWorkAvailable` in NIOConcurrencyHelpers that uses
`pthread_cond_signal` (wake-one) for work submission and
`pthread_cond_broadcast` only for shutdown. Replace
`ConditionLock<_WorkState>` and the `_WorkState` enum in `NIOThreadPool`
with this new primitive.

### Result

Submitting a work item wakes exactly **one** thread instead of all
threads.

### Benchmark Results

<details>

```
NIOThreadPool.serial_wakeup(16 threads)
╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                   │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ Context switches (K)     │        20 │        20 │        20 │        20 │        20 │        20 │        20 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Syscalls (total) (K) *   │        40 │        40 │        40 │        40 │        40 │        40 │        40 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (system CPU) (ms) * │        47 │        49 │        49 │        50 │        50 │        55 │        55 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (total CPU) (ms) *  │        57 │        58 │        59 │        60 │        61 │        67 │        67 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (user CPU) (ms) *   │        10 │        10 │        10 │        10 │        10 │        12 │        12 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (wall clock) (ms) * │        54 │        55 │        56 │        56 │        57 │        65 │        65 │        30 │
╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

NIOThreadPool.serial_wakeup(4 threads)
╒══════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                   │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞══════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ Context switches (K)     │        20 │        20 │        20 │        20 │        20 │        20 │        20 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Syscalls (total) (K) *   │        40 │        40 │        40 │        40 │        40 │        40 │        40 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (system CPU) (ms) * │        45 │        46 │        46 │        47 │        57 │        75 │        75 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (total CPU) (ms) *  │        54 │        55 │        56 │        57 │        68 │        87 │        87 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (user CPU) (μs) *   │      9055 │      9372 │      9478 │      9765 │     10887 │     12585 │     12585 │        30 │
├──────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (wall clock) (ms) * │        52 │        53 │        53 │        54 │        64 │       124 │       124 │        30 │
╘══════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛
```

</details>
2026-02-10 20:01:35 +00:00
Cory BenfieldandGeorge Barnett 2bc6627d74 Add some benchmarks for UDP and UDP metadata (#3457)
Motivation:

I'm unhappy with AddressedEnvelope.Metadata, and I plan to change it.
Before I change it, I want some way to get a clear picture of whether my
changes make things better or worse.

Modifications:

Added a bunch of benchmarks

Result:

Some data will be available.

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
2025-12-01 13:40:11 +00:00
George Barnett 888c16a7d3 Add some slack to the TCPEcho benchmark (#3451)
Motivation:

TCPEcho has some run-to-run variation which makes CI flaky.

Modifications:

- Add a little slack to TCPEcho; it has a low total alloc count so some
slack is fine as it's less than a per iteration allocation

Result:

CI less flaky
2025-11-20 15:25:04 +00:00
Rick Newton-Rogers 56724a2b6d NIOAsyncTestingChannel local/remote addrs on EmbeddedChannelCore (#3442)
### Motivation:

`NIOAsyncTestingChannel` stored its `localAddress` and `remoteAddress`
in a locked storage on itself for thread safety, however in doing so
left us open to bugs because a handler grabbing the addresses of the
context had no visibility of the values.

### Modifications:

Reach into `EmbeddedChannelCore` for the addresses instead of storing
them on the `NIOAsyncTestinghannel`. I also considered a delegate
approach where the `EmbeddedChannelCore` could offload the
responsibility for storing the values back to the
`NIOAsyncTestingChannel` but it was complicated and of questionable
value.

### Result:

* The correct address values are seen no matter how they are obtained.
* We probably take a performance hit locking the values in this way but
this is testing code so probably not the end of the world.
2025-11-11 15:08:15 +00:00
Fabian Fett 6c114e3c62 Update CI scripts for 6.2 (#3374)
Replaces: #3373
2025-09-19 13:55:48 +01:00
Cory Benfield c9e2ac115f Adjust for SendableMetatype (#3266)
Motivation

With the introduction of isolated conformances, it has become necessary
to start managing the use of metatypes for some of our protocols. In
general, we don't want to force the relevant protocols to only be
conformed in non-isolated forms. Instead, we just want to make the
specific APIs non-usable.

Modifications

- Add shims for SendableMetatype that only use it when it is available.
- Require SendableMetatype where needed, gated by @preconcurrency.

Result

We continue to be safe.
2025-06-13 09:12:25 +01:00
Rick Newton-Rogers 57658e12ff Drop Swift 5.9 (#3228)
Motivation:

Swift 5.9 is no longer supported, we should bump the tools version and
remove it from our CI.

Modifications:

* Bump the Swift tools version to Swift 5.10
* Remove Swift 5.9 jobs where appropriate in main.yml, pull_request.yml

Result:

Code reflects our support window.
2025-05-07 12:24:44 +00:00
Rick Newton-Rogers b8c446368d Add and enable Swift 6.1 workflows (#3196)
Add and enable Swift 6.1 workflows

### Motivation:

Swift 6.1 has been released, we should add it to our CI coverage.

### Modifications:

* Update `appe/swift-nio/scripts/generate_matrix.sh`
* Update reusable adopters of `swift_test_matrix.yml`
* Update end-user adopter workflows of `swift_test_matrix.yml`
  * Copy over test flags from Swift 6.0 jobs

### Result:

NIO tests against Swift 6.1 in CI and downstream repositories can
opt-in.

(Successful CI run with the workflows modified to use the changes on
this branch
https://github.com/apple/swift-nio/actions/runs/14400598096?pr=3196)
2025-04-11 13:45:49 +01:00
Fabian FettandCory Benfield 1d1ce62c8e Add perf test for EL as SerialExecutor (#3178)
This PR adds two benchmarks:

- Jumping 1k times from the global executor to a NIO EL and back using
`el.execute {}` and an `UncheckedContinuation`
- Jumping 1k times from the global executor to a NIO EL and back using
an actor that has a custom executor

Co-authored-by: Cory Benfield <lukasa@apple.com>
2025-04-09 12:54:37 +00:00
Rick Newton-Rogers 8a475ed378 Update nightly main benchmark thresholds (#3187)
### Motivation:

Nightly main benchmarks have improved allocations, we should lock in the
win.

### Modifications:

Update nightly main benchmark thresholds

### Result:

Thresholds reflect gains, passing CI.
2025-04-08 11:20:14 +01:00
Rick Newton-Rogers 26d40ebbda Delete Thresholds/nightly-6.1 symlinks (#3133)
Following on from https://github.com/apple/swift-nio/pull/3126 delete
`Benchmarks/Thresholds/nightly-6.1` and
`IntegrationTests/tests_04_performance/Thresholds/nightly-6.1.json`
which is no longer needed now that the shared benchmarks workflow has
been updated.
2025-03-07 12:09:53 +00:00
Rick Newton-Rogers 517dccac42 rename nightly 6 1 to nightly next (#3126)
Use nightly_next as swift version

see https://github.com/apple/swift-nio/pull/3122

Motivation:
To not have to rename threshold directories when the nightly branch
changes.

Modifications:
* Use nightly_next as swift version in the matrix generation script
which
is picked up by the benchmark script.
* Move nightly-next thresholds and add legacy symlink

Result:

Benchmark thresholds will attempt to find directories named
nightly_next, not nightly_6_1.
2025-03-04 09:37:47 +00:00
Cory Benfield 0c547a717c Fix the HappyEyeballsResolver and core Bootstraps under strict concurrency (#3062)
### Motivation:

The HappyEyeballsResolver, being an old part of our stack, has a lot of
code in it that fails to pass strict concurrency checking. That's deeply
suboptimal.

### Modifications:

- Clean up the happy eyeballs resolver under strict concurrency
- Further cleanups to the bootstraps

### Result:

Another step taken on the road to strict concurrency.
2025-01-23 13:22:18 +00:00
Rick Newton-Rogers 9336e1f259 more 6.0 nightly to 6.1 nightly CI changes (#3078)
### Motivation:

Some changes were missed in #3076, passing through parameters for the
new 6.1 version.

### Modifications:

Pass through more parameters, clean up misleading comments.

### Result:

6.1 nightly runs will be more like 6.0 nightly runs were
2025-01-22 13:38:02 +00:00
Rick Newton-Rogers 27c839f470 GetaddrinfoResolver succeeds futures on eventLoop (#3042)
### Motivation:

`testClientBindWorksOnSocketsBoundToEitherIPv4OrIPv6Only` would fail
sometimes leaking the IPv4 promise in `GetaddrinfoResolver`

`HappyEyeballsConnector` returns the connection when it resolves either
IPv4 of IPv6. It uses the `GetaddrinfoResolver` which holds a promise
for each of the IPv4 and IPv6 resolution; when one is completed the
connection will be returned and it is possible to start tearing down the
test and shutting down the event loop before the other is completed and
we leak the promise.

### Modifications:

Complete both futures on the event loop rather than the dispatch queue.

### Result:

The futures are completed in the same event loop tick meaning that we
cannot continue execution and leak one.
2025-01-08 16:33:33 +00:00
Rick Newton-Rogers 19da487ef2 clean up 5.8 thresholds (#2932)
clean up 5.8 thresholds

### Motivation:

GitHub's merge retained files we don't need now we've dropped 5.8

### Modifications:

remove 5.8 thresholds

### Result:

no unused files
2024-10-17 15:46:55 +02:00
Franz Busch 2fe3d92cf5 Drop support for Swift 5.8 (#2924)
# Motivation

We only support the last three Swift released versions which are at this
time 5.9, 5.10 and 6.

# Modification

This PR drops anything related to Swift 5.8.

# Result

Version support aligned.
2024-10-17 12:22:10 +00:00
Rick Newton-Rogers 06c16b1a26 Add future wait benchmark to catch memory leaks (#2931)
### Motivation:

In the past we introduced a memory leak around the creation of and
waiting on futures - we should protect against leaks in this fundamental
operation.

### Modifications:

Add a new benchmark which would have failed with the previous bug

### Result:

Regression protection for this type of memory leak.
2024-10-17 13:03:52 +01:00
Franz Busch 290b349e2c [CI] Fix python lint (#2925)
# Motivation

Fix the python lint check.

# Modification

This PR fixes all the warnings related to the new python lint check.

# Result

Only green CI.
2024-10-17 09:57:40 +00:00
Si Beaumont ea78d5aadc Add EventLoop APIs for simpler scheduling of callbacks (#2759)
## Motivation

The current `scheduleTask` APIs make use of _both_ callbacks and
promises, which leads to confusing semantics. For example, on
cancellation, users are notified in two ways: once via the promise and
once via the callback. Additionally the way the API is structured
results in unavoidable allocations—for the closures and the
promise—which could be avoided if we structured the API differently.

## Modifications

This PR introduces new protocol requirements on `EventLoop`:

```swift
protocol EventLoop {
    // ...
    @discardableResult
    func scheduleCallback(at deadline: NIODeadline, handler: some NIOScheduledCallbackHandler) throws -> NIOScheduledCallback

    @discardableResult
    func scheduleCallback(in amount: TimeAmount, handler: some NIOScheduledCallbackHandler) throws -> NIOScheduledCallback

    func cancelScheduledCallback(_ scheduledCallback: NIOScheduledCallback)
}
```

Default implementations have been provided that call through to
`EventLoop.scheduleTask(in:_:)` to not break existing `EventLoop`
implementations, although this implementation will be (at least) as slow
as using `scheduleTask(in:_:)` directly.

The API is structured to allow for `EventLoop` implementations to
provide a custom implementation, as an optimization point and this PR
provides a custom implementation for `SelectableEventLoop`, so that
`MultiThreadedEventLoopGroup` can benefit from a faster implementation.

Finally, this PR adds benchmarks to measure the performance of setting a
simple timer using both `scheduleTask(in:_:)` and
`scheduleCallback(in:_:)` APIs using a `MultiThreadedEventLoopGroup`.

## Result

A simpler and more coherent API surface.

There is also a small performance benefit for heavy users of this API,
e.g. protocols that make extensive use of timers: when using MTELG to
repeatedly set a timer with the same handler, switching from
`scheduleTask(in:_:)` to `scheduleCallback(in:_:)` reduces almost all
allocations (and amortizes to zero allocations) and is ~twice as fast.

```
MTELG.scheduleCallback(in:_:)
╒═══════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕
│ Metric                │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │
╞═══════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡
│ Malloc (total) *      │       0 │       0 │       0 │       0 │       0 │       0 │       0 │    1109 │
╘═══════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛

MTELG.scheduleTask(in:_:)
╒═══════════════════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╤═════════╕
│ Metric                │      p0 │     p25 │     p50 │     p75 │     p90 │     p99 │    p100 │ Samples │
╞═══════════════════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╪═════════╡
│ Malloc (total) *      │       4 │       4 │       4 │       4 │       4 │       4 │       4 │     576 │
╘═══════════════════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╧═════════╛
```
2024-10-03 20:14:28 +00:00
Franz Busch e59009da9b Align benchmark scaling and minimum samples (#2839)
# Motivation

We want to have an aligned strategy across all of our benchmarks in how
we use the scaling factor and samples.

# Modification

This PR makes sure that any inner loop is scaled by the scaling factor.
Moreover, we want to run more than 1 iteration. To do this we set an
almost infinite maximum duration and a maximum iteration count.

# Result

More consistent benchmarks
2024-08-15 09:51:52 +00:00
Franz Busch c9756e1083 Adopt swift-format (#2794)
* Apply formatting

* Apply no block comments rule

* Apply OmitExplicitReturns

* Apple OnlyOneTrailingClosureArgument

* Apply NoAssignmentInExpressions

* Fix up DontRepeatTypeInStaticProperties lint errors

* Apply `OrderedImports`

* Apply `ReplaceForEachWithForLoop`

* format file

* Enable the formatting pipeline

* Adopt `AmbiguousTrailingClosureOverload`

* Fix license header

* Fix format check

* Fix `EndOfLineComment`

* Fix CI

* Adapt CI script to check if changes when running formatting

* Separate lint and format into to steps

* Fix format

* Adopt `UseEarlyExits`

* Revert "Adopt `UseEarlyExits`"

This reverts commit d1ac5bbe12.
2024-07-19 11:48:17 +02:00
Johannes Weiss 7948ed2104 ChannelHandler: provide static (un)wrap(In|Out)bound(In|Out) (#2791) 2024-07-18 11:55:48 +01:00
George Barnett 223b0e8eac Add manual control to NIOLockedValueBox (#2786)
Motivation:

NIOLockedValueBox has a 'safer' API than NIOLock as it only provides
scoped access to its boxed value. NIOLock requires users to only access
protected state while the lock is acquired. As such NIOLockedValueBox
should be preferred where possible. However, there are cases where
manual control must be used (such as storing a continuation) and users
must use a NIOLock for this.

There are two downsides to this:

1. All other access to the protected state must use the NIOLock API
   putting the onus on the developer to only access the protected state
   while the lock is held.
2. NIOLock can't store its protected state inline which typically
   results in users storing it on a class.

Modifications:

- Add an 'unsafe' view to NIOLockedValueBox which allows users to
  manually control the lock and access its protected state
- Update NIOAsyncWriter and NIOThrowingAsyncSequenceProducer to use NIOLockedValueBox

Result:

- Safer locking API is used in more places
- Fewer allocations
2024-07-18 01:16:59 -07:00
Franz Busch 8a69bd9d88 [GHA] Cxx interoperability compatibility and integration tests check (#2790)
# Motivation

Another reusable check is to make sure that all library products of a package are successfully building when consumed from a module that has Cxx interoperability enabled. Another check that's missing is running the integration tests.

# Modification

This PR adds two new checks to the reusable workflow. One to check for Cxx interoperability compatibility and another one to run the integration tests. I also fixed a misalgined name for the nightly benchmarks.

# Result

This should be one of the last reusable workflow checks.
2024-07-16 01:22:04 -07:00
Franz Busch 654bf41c03 [GHA] Add license header check (#2781)
* [GHA] Add license header check

# Motivation

We need to make sure all code files have the appropriate license headers in place. This is currently part of the soundness check and we need to replace this with a GH action.

# Modification

Adds a new job to the reusable workflow the check all files for license headers. Since some files are ignored every repo can specify a `.licenseignore` file.

# Result

Last part of the soundness script migrated.

* Review

* Remove default excludes
2024-07-12 03:07:06 -07:00
George Barnett 0f4c110bd7 Combine the two NIOAsyncChannel channel handlers (#2779)
Motivation:

The NIOAsyncChannel allocates 12 times on init. 4 of these allocations
come from creating two channel handlers and two channel handler
contexts. There's no inherent reason that these channel handlers can't
be combined to eliminate two allocations (one handler and one context).

Modifications:

- Combine `NIOAsyncChannelInboundStreamChannelHandler` and
  `NIOAsyncChannelOutboundWriterHandler` into a single
  `NIOAsyncChannelHandler`. Most of this was straightforward as only a
  few handler operations were duplicated across both.
- Add a 'NIOAsyncChannelHandlerWriterDelegate' in place of the
  'NIOAsyncChannelOutboundWriterHandler.Delegate'. One knock on from
  this is that the new delegate stores callbacks rather than the
  concrete type of the handler. This is necessary to prevent the
  generics from the new channel handler bubbling up to the outbound
  writer (which would break API and be somewhat odd).

Result:

Fewer allocations
2024-07-12 01:02:04 -07:00
Franz Busch 86316020a5 [GHA] Benchmark job (#2780) 2024-07-11 03:23:24 -07:00
George Barnett 4d87167a42 Add benchmark for creating NIOAsyncChannel (#2774)
Motivation:

NIOAsyncChannel has a number of allocs associated with it. We should
have a benchmark which tracks the number of allocs.

Modifications:

- Add NIOCoreBenchmarks with a single benchmark

Result:

Better insight
2024-07-10 10:34:04 +01:00
George Barnett cc158ec6c7 Raise minimum Swift version to 5.8 (#2675) 2024-03-11 08:47:57 +00:00
Franz Busch 826fce4fdb Fix NIOAsyncChannel allocation benchmarks (#2622)
# Motivation
We had to disable the benchmarks since they regressed without us noticing and they appear to be flaky.

# Modification
This PR fixes the allocation regression and tries to re-enable them.
2024-01-12 14:23:54 +00:00
David Nadoba 23e995b3af Set SWIFT_VERSION environment variable to resolve to the correct benchmarks thresholds path (#2613)
* Set `SWIFT_VERSION` environment variable to resolve to the correct benchmarks thresholds path

* mallocs have increased

* update benchmark results manually

* update thresholds again

* disable flaky benchmark
2023-12-22 11:07:35 +01:00
Franz Busch 1040927f12 Add closeOnDeinit to the NIOAsyncChannel init (#2592)
* Add `closeOnDeinit` to the `NIOAsyncChannel` init

# Motivation
In my previous PR, I already did the work to add `finishOnDeinit` configuration to the `NIOAsyncWriter` and `NIOAsyncSequenceProducer`. This PR also automatically migrated the `NIOAsyncChanell` to set the `finishOnDeinit = false`. This was intentional since we really want users to not use the deinit based cleanup; however, it also broke all current adopters of this API semantically and they might now run into the preconditions.

# Modification
This PR reverts the change in `NIOAsyncChannel` and does the usual deprecate + new init dance to provide users to configure this behaviour while still nudging them to check that this is really what they want.

# Result
Easier migration without semantically breaking current adopters of `NIOAsyncChannel`.

* Rename to `wrappingChannelSynchronously`
2023-11-15 05:42:53 -08:00
Franz Busch 118de503e2 Add withInboundOutboud to NIOAsyncChannel and deprecate deinit ba… (#2589)
* Add `withInboundOutboud` to `NIOAsyncChannel` and deprecate deinit based cleanup

# Motivation
We just released our new async NIO APIs and have already gotten quite a bunch of feedback from adopters. One of the feedback was that the deinit based closing that we have added to the `NIOAsyncChannel` has caused problems since it leads to unexpected closure of their `Channel`. Furthermore, it makes it impossible to determine how many open sockets a program has at any given time since deinit based clean up relies on the optimizer and can happen at random times.

# Modifications
This PR adds new inits to `NIOAsyncSequenceProducer` and `NIOAsyncWriter` which disable the `deinit` based clean up and instead replace them with an assertion. This allows developers to still catch these issues at debug time. Furthermore, I added a new `withInboundOutbound` scoped access to `NIOAsyncChannel` which will close the channel at the end of the scope. This still gives users a nice API while not having to care much about closing themselves.

# Result
We are no longer using deinit based clean up and bring back one of the core principles of NIO which is deterministic resource usage.

* Review comments

* Internal labels for closure arguments

* Rename to `executeThenCloseChannel`

* Actually call `sinkDeinitialized`

* Change preconditions

* Move logic to deinits

* Rename to `executeThenClose` and review nits
2023-11-13 03:37:54 -08:00
Franz Busch f38b7fd38a Remove SPI from NIOAsyncChannel, new bootstrap methods, protocol negotiation and HTTP upgrade. (#2548)
# Motivation
Over the past months, we have been working on new async bridges to make using NIO's `Channel` from Swift Concurrency possible. Since this work was far reaching we have opted to land all of it as SPI. Now the time has come and we feel confident enough to make the SPI official API. This comes after testing the new APIs in various scenarios such as HTTP 1&2, HTTP upgrades, protocol negotiation and in benchmarks.

# Modification
This PR removes the SPI from the `NIOAsyncChannel`, the bootstrap methods, protocol negotiation and HTTP upgrade.

# Result
Everyone can use the our new APIs🚀
2023-10-17 14:14:02 +01:00
Franz Busch 0fb8cb7947 Add docs for the async NIO APIs (#2549) 2023-10-16 15:02:43 +01:00
Franz Busch 2910d6b203 Call NIOAsyncWriterSinkDelegate outside of the lock (#2547)
* Call `NIOAsyncWriterSinkDelegate` outside of the lock

# Motivation
The current `NIOAsyncWriter` implementation expects that the delegate is called while holding the lock to avoid reentrancy issues. However, this prevents us from executing the delegate calls directly on the `EventLoop` if we are on it already.

# Modification
This moves all of the delegate calls outside of the locks and adds protection against reentrancy into the state machine.

# Result
Less allocations.

Clarify the reentrancy problems in docs and protect against them in the writer

* Code review
2023-10-12 08:33:51 -07:00
Franz Busch 94cc73dfbb Improve performance of NIOAsyncChannel (#2539)
* Implement fast paths for `AsyncChannelInboundStreamChannelHandler`

* Add `_TinyArray` from `swift-certificates`

* Implement single element customization point in `NIOAsyncChannelOutboundWriterHandler`

* Call the single element optimization more often and store suspended producers in `_TinyArray`.

* Update thresholds

* Fix compiler warning
2023-10-09 07:39:25 -07:00
Franz Busch c2d8eed7e6 Add customization point for scheduling ExecutorJobs on EventLoops (#2538)
# Motivation
Currently, the NIO's EventLoop conformance to the `SerialExecutor` protocol always uses `execute` to schedule the actual job. However, the closure for `execute` has to close over the job and the `EventLoop` itself; hence, it always allocates. Since jobs are a very fine grained object in Concurrency that are created a lot this lead to millions of allocations in even small benchmarks.

# Modification
This PR provides a customization point for `EventLoop`s to execute `ExecutorJob`s directly. For `SelectableEventLoop` we store a type erased `UnownedJob` in our `ScheduledTask` and just run it right away.

# Result
No more allocations when NIO's EL is used as a `SerialExecutor`.
2023-10-09 06:00:57 -07:00
Franz Busch 7a9d37ef49 Add NIOAsyncChannel benchmark (#2536)
# Motivation
We want to benchmark our `NIOAsyncChannel` to see how it compares to the synchronous implementation with `ChannelHandler`s

# Modification
This PR adds a new `TCPEchoAsyncChannel` benchmark that mimics the `TCPEcho` benchmark but uses our new async bridges.
Since Swift Concurrency, is normally using a global executor this benchmark would have quite high variation. To reduce this variant I introduced code to hook the global executor and set an `EventLoop` as the executor. In the future, if we get task executors we can change the code to us them instead.

# Result
New baseline benchmarks for the `NIOAsyncChannel`.
2023-10-09 10:45:00 +01:00
Franz Busch d782de84df Adopt package-benchmark (#2534)
# Motivation
We want to migrate our allocation and later on also our performance tests to use the `package-benchmark` plugin. This plugin makes writing benchmarks way easier than our current setup. Furthermore, debugging benchmarks is also possible from within Xcode now.

# Modification
This PR adds the setup for the benchmarking infrastructure and connects it with out

# Result
Allocations tests are more accessible and easier to iterate.
2023-10-08 13:31:22 -07:00