Commit Graph
2140 Commits
Author SHA1 Message Date
Stepan UlyaninandGeorge Barnett be8fdc13c0 Allow to copy files and symlinks while overwriting the destination (#3508)
Adds capability to NIOFS to copy regular files and symlink, allowing to
overwrite the destination.

### Motivation:

Per https://github.com/apple/swift-nio/issues/3403 and
https://github.com/apple/swift-nio/pull/3470 we want to add
`replaceExisting: bool` to `FileSystem.copyItem`.

### Modifications:

1. Adds `replaceExisting: bool` parameter to
`FileSystemProtocol.copyItem`.
2. Adds `replaceExisting: bool` parameter to `FileSystem.copyItem` and
implementation for regular files and symbolic links.
3. Adds tests.

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
2026-02-18 08:57:33 +00:00
Si BeaumontandAgam Dua b0e024792a Add opt-in API for channels to expose their underlying transport (#3509)
## Motivation

NIO channels abstract over an underlying transport mechanisms (sockets,
pipes, etc.), which users typically need not interact with. However,
there are scenarios where users need direct access to the underlying
transport for low-level operations that work outside NIOs abstraction.
One example is performing out out-of-band operations on the underlying
file descriptor for a socket-based channel.

This PR adds a structured way to access the underlying transport of a
channel, for channels that choose to implement it.

## Modifications

- Add a new `public protocol
NIOTransportAccessibleChannelCore<Transport>` which provides a scoped
`withUnsafeTransport(_:)`, used for channel implementations to opt-in.
- Add conformance to
`NIOTransportAccessibleChannel<NIOBSDSocket.Handle>` for
`BaseSocketChannel`, to make this API available for all socket-based
channels, including channels returned from the socket-based bootstrap
public APIs.
- Add public API
`ChannelPipeline.SynchronousOperations.withUnsafeTransportIfAvailable(_:)`

Note that not all channels need to or should their transport, which is
why this was added as an additional protocol that refines `ChannelCore`,
vs. extending `Channel` or `ChannelCore` with a default implementation.

The protocol uses a primary associated type allowing channels to provide
typed access to the transport. E.g. this could be used in NIO Transport
Services to expose the underlying `NWConnection`, if desired.

The method itself is spelled with "unsafe", uses scoped access, and has
clear documentation that users must not violated any of NIOs assumptions
about the state of the underlying transport. It's very much not intended
for every day use. It being on `ChannelCore` should defer users of the
low-level API, since `Channel._channelCore` is marked as for NIO
internal use, and the public `withUnsafeTransportIfAvailable(_:)` will
take care of the runtime checks for channels that have opted into this
API.

## Result

- New opt-in API for channel implementations to expose their underlying
transport
- All socket-based channels from NIOPosix now expose the underlying
socket file descriptor
- New API
`ChannelPipeline.SynchronousOperations.withUnsafeTransportIfAvailable(_:)`
for users

---------

Co-authored-by: Agam Dua <agam_dua@apple.com>
2026-02-17 16:12:58 +00:00
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
Raphael d948192838 Don't send (in)active when channel closes directly (#3497)
### Motivation

Issue #2773 reports a problem where channels can receive
`channelInactive` before `channelActive`. This breaks expectations and
should not happen. Instead of sending them in the wrong order, this PR
suppresses both of them in those cases.

### Modifications

* Add a test to reproduce the problematic behavior.
* Update the state machine to split the active state into two. This
decouples succeeding the promise from sending the signal. Transiting to
closing only sends `channelInactive` when originating from the state
that sent `channelActive`.

### Result

Address #2773.

Edit: Updated modifications to reflect latests commit.
2026-02-11 15:46:19 +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
Stepan Ulyanin db01d87942 Add symlinkat, renameatx_np, and unlinkat system call wrappers (#3505)
Adds three new system call wrappers for the `symlinkat`, `renameatx_np`,
and `unlinkat` system calls.

### Motivation:

Related to https://github.com/apple/swift-nio/issues/3403 and
https://github.com/apple/swift-nio/pull/3470. This PR adds syscall
wrappers needed to atomically overwrite existing files or symlinks at
the destination during copy operations. On Linux, atomic overwrites
require a "copy to temp file, then rename" strategy. We use the `*at`
family of syscalls (which operate relative to directory file
descriptors) to avoid TOCTOU race conditions.

### Modifications:

1. Adds three system call wrappers for the `symlinkat`, `renameatx_np`,
and `unlinkat` system calls.
2. Adds related tests
3. Updates the `FileSystemError` for `symlink` and `unlink` to take in
the system call name to allow for the `*at` names to be passed.
2026-02-09 08:38:25 +00:00
Si Beaumont edad6d8a9b Clarify state and relationship of NIOFileSystem and _NIOFileSystem in API docs (#3504)
### Motivation:

`NIOFileSystem` currently exposes the same API as `_NIOFileSystem`,
which is not API stable. `NIOFileSystem`
was created in error, and its lack of underscore incorrectly implies API
stability. Users who are currently importing `NIOFileSystem` should
ideally move to `_NIOFileSystem`. However this isn't made clear in the
docs:

1. The README talks about the non-underscored `NIOFileSystem`.
2. There are no hosted API docs `NIOFileSystem`.
3. The hosted API docs for `NIO` point to the docs for `NIOFileSystem`
-- results in 404.

### Modifications:

- Update the README to refer to `_NIOFileSystem`.
- Update the hosted API docs for `NIO` to point to docs for
`_NIOFileSystem`.
- Add hosted API docs for `NIOFileSystem` with a statement explaining
the situation and pointing people to the docs
  for `_NIOFileSystem`.

### Result:

Clearer documentation on the state and relationship of the
`NIOFileSystem` and `_NIOFileSystem` modules.
2026-02-06 18:56:42 +00:00
Fabian Fett 6a6f7d7c33 Centralize UnsafeTransfer in NIOCore (#3492)
We have a number of copies of `UnsafeTransfer` and two copies of
`UnsafeMutableTransferBox` in our code base. Before introducing more of
those, lets centralize to just one using a `package` access modifier.
2026-02-03 10:27:30 +00:00
Si Beaumont 37ffc4b8d5 Add API to enable throwing in EmbeddedChannel.getOption and .setOption if channel is closed (#3495)
### Motivation:

Channels based on `BaseSocketChannel` throw in both `getOption` and
`setOption` if the channel has been closed, since the `setsockopt` will
fail. However, the current behavior of `EmbeddedChannel` is options
remain writable and readable on closed channels.

There are situations where we'd like to be able to model the runtime
behaviour of the real channel in tests, e.g. to test this fix:
https://github.com/apple/swift-nio-extras/pull/304.

### Modifications:

- Add API to enable throwing in `EmbeddedChannel.getOption` and
`.setOption` if channel is closed.
- Add a test for this new behavior.

### Result:

- New API to enable throwing in `EmbeddedChannel.getOption` and
`.setOption` if channel is closed.
- No observable change for existing users of `EmbeddedChannel`.
2026-01-27 12:06:55 +00:00
scottmarchant 5bf0267df4 build: Elide NIOPosix for WASI platforms only (#3485)
### Motivation:

Packages that consume NIOPosix should be able to compile to WASI
platforms without special configurations. This change elides the
NIOPosix source from WASI platforms to simplify configuration.

Without this change:

```swift
dependencies: [
    // Without this PR, downstream packages must maintain exhaustive platform list to exclude `.wasi`:
    .product(
        name: "NIOPosix",
        package: "swift-nio",
        condition: .when(platforms: [
            .macOS,
            .macCatalyst,
            .iOS,
            .tvOS,
            .watchOS,
            .visionOS,
            .driverKit,
            .linux,
            .windows,
            .android,
            .openbsd,
            // .wasi // <-- Need to exclude this, because there is no exclusion list api for SPM conditionals
        ])
    ),
]
```

With this change:

```swift
dependencies: [
    // Without this PR, downstream packages can consume NIOPosix simply:
    .product(name: "NIOPosix", package: "swift-nio"),
]
```

### Modifications:

- Add compiler directives (`#if !os(WASI)`) to all source files in
NIOPosix

### Result:

Downstream packages can compile to wasm without manually excluding the
NIOPosix dependency.

### Testing performed

- Verified `swift build --swift-sdk wasm32-unknown-wasip1 --target
NIOPosix` compiles, which demonstrates proper elision of source files in
NIOPosix that aren't wasm-ready.
- Confirmed GitHub [checks
pass](https://github.com/PassiveLogic/swift-nio/actions/runs/21151341738).
2026-01-23 16:39:32 +00:00
scottmarchant 3f80264185 build: Elide NIOEmbedded for WASI platforms only (#3484)
### Motivation:

Packages that consume NIOEmbedded should be able to compile to WASI
platforms without special configurations. This change elides the
NIOEmbedded source from WASI platforms to simplify configuration.

Without this change:

```swift
dependencies: [
    // Without this PR, downstream packages must maintain exhaustive platform list to exclude `.wasi`:
    .product(
        name: "NIOEmbedded",
        package: "swift-nio",
        condition: .when(platforms: [
            .macOS,
            .macCatalyst,
            .iOS,
            .tvOS,
            .watchOS,
            .visionOS,
            .driverKit,
            .linux,
            .windows,
            .android,
            .openbsd,
            // .wasi // <-- Need to exclude this, because there is no exclusion list api for SPM conditionals
        ])
    ),
]
```

With this change:

```swift
dependencies: [
    // Without this PR, downstream packages can consume NIOEmbedded simply:
    .product(name: "NIOEmbedded", package: "swift-nio"),
]
```

### Modifications:

- Fix compiler directive using in AsyncTestingChannel.swift to include
an extension

### Result:

Packages can compile to wasm without manually excluding the NIOEmbedded
dependency.

### Testing performed

- Verified `swift build --swift-sdk wasm32-unknown-wasip1 --target
NIOEmbedded` compiles, which demonstrates proper elision of source files
in NIOEmbedded that aren't wasm-ready.
- Confirmed GitHub [checks
pass](https://github.com/PassiveLogic/swift-nio/actions/runs/21151287289).
2026-01-22 18:58:24 +00:00
scottmarchant c337170d91 chore: Elide mutex variable from Lock class when it is unused (#3483)
### Motivation:

In reviewing a copy of this file in swift-log, @czechboy0
[noticed](https://github.com/apple/swift-log/pull/398#discussion_r2685605800)
that the `mutex` variable is essentially unused and unnecessarily
allocated for certain conditions. It is possible the compiler elided
usage. But given the unconditional variable reference in the `deinit`,
the compiler may not be able to.

This change ensures the `mutex` property is completely elided except in
the conditions where it is used.

### Modifications:

- Adjusted compiler directive pattern to completely elide the `mutex`
property for conditions where it would be unused.
- Adjusted `deinit` to avoid referencing `mutex` unless it is compiled
into the `Lock` class

### Result:

The `mutex` property is no longer allocated or even compiled into the
`Lock` class for unsupported configurations. All unit tests and checks
pass.

### Research:

The `deinit` used to have a blanket call to `mutex.deallocate()`. This
has been moved into the platform-specific checks within the `deinit`.
This allows complete elision of the `mutex` property altogether. The
`_runtime(_multithreaded)` condition [appears to be rooted to this
implementation](https://github.com/swiftlang/swift/blob/ffc51b914602765c5d680241796dbd3c3711fa6b/lib/Basic/LangOptions.cpp#L490).
Diving deeper, all [operating systems in this
list](https://github.com/swiftlang/swift/blob/ffc51b914602765c5d680241796dbd3c3711fa6b/lib/Basic/LangOptions.cpp#L554)
except for UnknownOS and WASI result in `_runtime(_multithreaded)`
returning true. That means that `OpenBSD` (and many other operating
systems besides windows) were almost certainly using the `#elseif
(compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) &&
_runtime(_multithreaded))` condition in the `deinit`. In conclusion,
moving `mutex.deallocate()` up into the conditional clauses inside
`deinit` should be identical to the previous implementation for all
operating systems except `llvm::Triple::UnknownOS`. And
`llvm::Triple::UnknownOS` is likely not to have every compiled anyways
due to the [check on line
32](https://github.com/apple/swift-nio/blob/main/Sources/NIOConcurrencyHelpers/lock.swift#L32).

So in summary, it is expected that this clean up will compile identical
to the previous implementation for all platforms except WASI platforms
that don't have pthread support. For non-pthread WASI platforms, the
unused `mutex` is properly elided from compilation altogether.

### Testing Done:

- Confirmed that unit tests pass locally using Xcode
- Verified [PR checks
pass](https://github.com/PassiveLogic/swift-nio/actions/runs/21149729751)
2026-01-22 17:57:10 +00:00
3405691582 233f61bc2c Initialize the mutexattr before use. (#3482)
As observed by @kkebo, this was missing from the implementation.

### Motivation:

Correct pthread API usage. 

### Modifications:

Initialize the pthread_mutexattr_t with pthread_mutexattr_init before
passed to pthread_mutex_init.

### Result:

Even though this lock API appears to be deprecated, we'll correct the
usage anyway. The non-deprecated NIOLock class doesn't have this
problem.
2026-01-19 18:18:38 +00:00
scottmarchant 6ce9173261 fix: Ensure pthread_mutexattr_t is properly cleaned up with pthread_mutexattr_destroy in Lock (#3480)
### Motivation:

In reviewing a copy of this file in swift-log, @czechboy0 noticed that
the call to `pthread_mutexattr_destroy` is missing. Since
pthread_mutexattr_t is like allocated on the stack rather than heap on
most platforms, this is likely not a memory leak. But for correctness
the missing destroy call should be added.

This change is the start of a resolution
[requested](https://github.com/apple/swift-log/pull/398#discussion_r2689459360)
in a corresponding Lock rollout in swift-log

### Modifications:

- Added missing call to `pthread_mutexattr_destroy`
- Duplicated test for `NIOLock` to also test `Lock`, as a sanity test.

### Result:

The new sanity test passes. If there are other ways this change can be
realistically verified as safe and proper, please feel free to provide
feedback in the PR.
2026-01-19 08:13:02 +00:00
Adam FowlerandCory Benfield 61053a00bb NIOTypedHTTPClientUpgradeHandler will write Upgrade request on handlerAdded if active (#3473)
`NIOTypedHTTPClientUpgradeHandler.handlerAdded` will write upgrade
request if the channel is already active

### Motivation:

This has been added to make it easier to implement a websocket client
that supports proxies. If the upgrade handler is added after the proxy
connect has been processed previously nothing would happen.

### Modifications:

- Added new function
`NIOTypedHTTPClientUpgradeHandler.writeUpgradeRequest()` function. Call
this from `channelActive` and also from `handlerAdded` if the channel is
already active.
- Added test `testUpgradeHappensAfterHandlerAdded`

### Result:

NIOTypedHTTPClientUpgradeHandler now also works if it is added to a
channel after the channel is active

---------

Co-authored-by: Cory Benfield <lukasa@apple.com>
2026-01-14 11:40:00 +00:00
Joseph HeckandCory Benfield 6b33489e64 Updates to present docs on _NIOFileSystem (#3477)
This re-enables documentation lost during refactoring work, under the
target _NIOFileSystem

resolves #3474 

### Motivation:

During the refactoring of NIOFileSystem and recent Swift updates, the
mechanism to "shadow" symbols using `@_exported import` has stopped
working for documentation and imports for symbols, which means that
_NIOFileSystem is the target that needs to host the documentation for
this (for now)

### Modifications:

Moves DocC catalog into _NIOFileSystem target, and updates
disambiguation hashes on overloaded symbols in order to verify no
warnings are presented while generating documentation.
Updates .spi.yml to present _NIOFileSystem instead of NIOFileSystem

### Result:

Previous documentation should be available again, although at a slightly
different URI structure within Swift Package Index.

Co-authored-by: Cory Benfield <lukasa@apple.com>
2026-01-13 14:22:46 +00:00
Karan LokchandaniandCory Benfield 608e511d7a Add homeDirectory accessor to FileSystem (#3471)
Add homeDirectory accessor to FileSystem

### Motivation:
Addresses #3381 by adding a `homeDirectory` property to FileSystem,
equivalent to `FileManager.default.homeDirectoryForCurrentUser`.

### Modifications:
- Added `homeDirectory` property to `FileSystemProtocol` and implemented
in `FileSystem`
- Implementation checks `HOME` environment variable first, falls back to
`USERPROFILE` on Windows, or uses `getpwuid_r(3)` on POSIX systems
- Added system call wrappers (`system_getuid`, `libc_getpwuid_r`) with
proper platform guards
- Added `FileSystemError.getpwuid_r()` error helper

### Result:
Users can now access the home directory via `FileSystem.homeDirectory`,
returning a `FilePath` asynchronously. Follows the same pattern as
`currentWorkingDirectory` and `temporaryDirectory`.

Co-authored-by: Cory Benfield <lukasa@apple.com>
2026-01-12 15:10:25 +00:00
Karan Lokchandani 4a9a971110 docs: String(buffer:) needs to mention that it will always succeed (#3476)
### Motivation:
The documentation for String(buffer:) initializer does not clearly
indicate
that this operation will always succeed, which may leave developers
uncertain
about error handling requirements.
fixes: https://github.com/apple/swift-nio/issues/3449

### Modifications:
Updated the documentation for String(buffer:) to explicitly mention that
the initialization will always succeed.

### Result:
Developers will have clearer understanding that String(buffer:) is a
safe
operation that does not require error handling.

Signed-off-by: Karan <karanlokchandani@protonmail.com>
2026-01-08 15:58:15 +00:00
aryan-25 5dc3b4ba4a Cancel pending in/outbound consumers in EmbeddedChannelCore upon channel close (#3464)
### Motivation:

Currently, pending consumer closures remain in the
`{in}{out}boundBufferConsumer` queues of `EmbeddedChannelCore` even
after the channel closes. It is also possible to enqueue consumers
*after* the channel closes. In these cases, the consumer closures will
never be invoked and this can lead to unfavourable behaviour, as
observed in `NIOAsyncTestingChannel`'s `waitFor{In}{Out}boundWrite`
methods (the only places these queues are currently used).

`NIOAsyncTestingChannel`'s `waitFor{In}{Out}boundWrite` methods complete
a continuation *inside* the consumer closure. In the cases described
above, the continuation never completes and therefore
`waitFor{In}{Out}boundWrite` never returns.

### Modifications:

- Updated the element type in `EmbeddedChannelCore`'s
`{in}{out}boundBufferConsumer` from `(NIOAny) -> Void` to
`(Result<NIOAny, Error>) -> Void`.
- This is so that the `.failure` case can be used to notify the consumer
closure that the channel has closed.
- Changed the visibility of the `{in}{out}boundBufferConsumer`
properties from `internal` to `private` in order to prevent the queues
from being accessed and being appended to without the call site
considering whether the channel has been closed.
- Added new `internal` methods named
`enqueue{In}{Out}boundBufferConsumer(_:)` which take the consumer
closure as an argument and only append to the corresponding queue if the
channel isn't closed.
- If the channel is closed, the consumer closure is invoked immediately
with `.failure(ChannelError.ioOnClosedChannel)`.
- Updated `EmbeddedChannelCore`'s `close0` method to return a
`.failure(ChannelError.ioOnClosedChannel)` result to each closure in
`{in}{out}boundBufferConsumer` and empty both buffers.
- Updated `NIOAsyncTestingChannel`'s `waitFor{In}{Out}boundWrite` to
throw an error in the continuation upon receiving a `.failure` result.
- Added associated test cases.

### Result:

`EmbeddedChannelCore`'s `{in}{out}boundBufferConsumer` queues can be
used more safely: all pending closures will be invoked upon channel
close. As a result, `NIOAsyncTestingChannel`'s
`waitFor{In}{Out}boundWrite` no longer indefinitely blocks when the
channel closes.
2026-01-06 17:52:06 +00:00
e3d5c560e0 Fix coreCount on Linux when using cgroup v2 with CFS throttling disabled (#3462)
Fix coreCount on Linux when using cgroup v2 with CFS throttling disabled

### Motivation:

When using `swift-nio` on Linux with cgroup v2 enabled, but with CFS
throttling disabled, it falls back to attempting to read the cpuset file
at the cgroup v1 path. This does not exist, which in turns falls back to
returning `_SC_NPROCESSORS_ONLN`, which will return the total number of
cores available (ignoring cgroup assignments).

This has unexpected effects, including the default behaviour of starting
the `MultiThreadedEventLoopGroup.singleton` with significantly more
event loops than cores available to the workload.

### Modifications:

- Adds `SystemCalls.statfs`, and associated constants, to determine the
cgroup version.
- Adds `Linux.cgroupVersion()` API to expose cgroup version.
- Adds `Linux.cgroupV2MountPoint` variable to determine the cgroup v2
mount point.
- Adds `Linux.cpuSetPathV1` & `Linux.cpuSetPathV2` (and
`Linux.cpuSetPath` convenience) variables to determine the correct cpu
set path.
- Alters `System.coreCount` to use the appropriate logic from above to
ensure that `cpuset.cpus` is parsed from the correct location.

### Result:

`Linux.coreCount` should correctly parse and return the core count on
Linux cgroup v2 enabled systems (when CFS throttling is disabled), while
maintaining correctness for other configurations.

---------

Co-authored-by: Johannes Weiss <johannesweiss@apple.com>
Co-authored-by: Cory Benfield <lukasa@apple.com>
2026-01-05 14:26:21 +00:00
Johannes Weiss 1268babbc1 Allowlist 6.3 to be allowable for Swift Concurrency takeover (#3467)
Make 6.3 fast
2025-12-19 13:19:04 +00:00
aryan-25 a1605a3303 Use explicit class name in all Self.[un]wrap{In}{Out}bound{In}{Out} calls (#3463)
### Motivation:

Swift does not currently generic specialize static methods on final
classes that have no parent classes when the method is implemented in a
protocol. This means that calling such methods through `Self` will not
involve a generic specialization, whereas using the explicit type name
will.

This pattern manifests in the `[un]wrap{In}{Out}bound{In}{Out}` static
methods defined in the
[`ChannelInboundHandler`](https://github.com/apple/swift-nio/blob/27146d484478b1bb0f150e848758f3a34ed9cbd0/Sources/NIOCore/TypeAssistedChannelHandler.swift#L60)
and
[`ChannelOutboundHandler`](https://github.com/apple/swift-nio/blob/27146d484478b1bb0f150e848758f3a34ed9cbd0/Sources/NIOCore/TypeAssistedChannelHandler.swift#L95)
protocols and their use from all channel handler classes. As such, we
should replace the `Self` part in all
`Self.[un]wrap{In}{Out}bound{In}{Out}` calls with the explicit class
name.

### Modifications:

Replaced all `Self.[un]wrap{In}{Out}bound{In}{Out}` calls to use the
explicit class name.

### Result:

Eliminates unnecessary overhead.
2025-12-15 17:36:11 +00:00
Adam Fowler 888f4affd6 Add ByteBuffer.readableBytesUInt8Span (#3458)
Add  ByteBuffer.readableBytesUInt8Span

### Motivation:

Provide access to readableBytes as a Span<UInt8>

### Modifications:

Added computed property `ByteBuffer.readableBytesUInt8Span`. I also
attempted to add `ByteBuffer.mutableReadableBytesUInt8Span` but due to
an apparently faulty exclusivity issue I couldn't get this to work. See
https://github.com/swiftlang/swift/issues/81218.

### Result:

You can access the readable bytes of a ByteBuffer as a Span<UInt8>
2025-12-05 15:32:17 +00:00
George Barnett 663ddc80f2 Dont hold lock over continuation in TokenBucket (#3455)
Motivation:

The various 'withMumbleContinuation' APIs are supposed to be invoked
synchronously with the caller. This assumption allows a lock to be
acquired before the call and released from the body of the
'withMumbleContinuation' after e.g. storing the continuation. However
this isn't the case and the job may be re-enqueued on the executor
meaning that this is pattern is vulnerable to deadlocks.

Modifications:

- Drop and reacquire the lock in TokenBucket.
- Switch to NIOLockedValueBox

Result:

Lower chance of deadlock
2025-12-01 15:46:46 +00:00
Rafael Cepeda b60d141920 Document when channel.remoteAddress field can be nil (#3456)
Added extra documentation to `channel.remoteAddress` field to capture a
scenario where this field might be `nil`

### Motivation:

It is somewhat common for users to have code like
`channel.remoteAddress!` in their implementation, as it is a reasonable
assumption to think a socket connection will have an associated remote
address. However, in at least one known situation this might not be the
case. When that happens, user's code might crash due to the force unwrap
of the optional field.

### Modifications:

Introduced more documentation to make it clear that users should be
prepared to handle the `nil` scenario.

### Result:

Less frequent mishandling of `channel.remoteAddress`.
2025-12-01 11:09:56 +00:00
3405691582andRick Newton-Rogers 2bc71a6aff Partial OpenBSD support. (#3394)
### Motivation:

Additional partial platform support.

### Modifications: 

* Create a new CNIOBSD module for OpenBSD, for general cleanliness, and
make use of it throughout. Some of the changes are borrowed directly
from CNIOLinux; some may technically be unnecessary; I am erring
somewhat on expediency to functionality.

* Since `malloc_size` is unavailable on OpenBSD, and some of the helpers
make use of ManagedBufferPointer which makes use of it, mark some of
this as unavailable as well.

* Usual pthread optional typing changes, since pthread types are
pointers on this platform.

* Add conditionals to exclude other functionality not available here,
like IP_RECVPKTINFO or IP_PKTINFO.

* d_ino is a backwards-compatibility macro valued token for dirent, so
instead, just expand with a conditional.

* Use kqueue on OpenBSD. This necessitates adding some conditionals for
type and feature compatibility.

* The vsock API is unavailable on OpenBSD.

### Result:

Tested the NIOTCPEchoClient and NIOTCPEchoServer appears to work and
that swift-nio-ssh (hopefully wlog) builds with a local repository with
these changes.

Because NIOFS/_NIOFileSystem depend on some non-portable components,
such as extended attributes, sendfile, non-portable linkat flags, and
renameat2, this is only just partial OpenBSD support, which means that
`swift build` on the full swift-nio project won't build cleanly, but at
least the portable parts can be used to build servers and clients. This
commit will mark the linked bug as fixed; I will open a new bug for file
system support.

Fixes #3383.

---------

Co-authored-by: Rick Newton-Rogers <rnro@apple.com>
2025-11-26 09:07:02 +00:00
George Barnett cda536de37 Dont hold lock over continuation in NIOThrowingAsyncSequenceProducer (#3454)
Motivation:

The various 'withMumbleContinuation' APIs are supposed to be invoked
synchronously with the caller. This assumption allows a lock to be
acquired before the call and released from the body of the
'withMumbleContinuation' after e.g. storing the continuation. However
this isn't the case and the job may be re-enqueued on the executor
meaning that this is pattern is vulnerable to deadlocks.

Modifications:

- Drop and reacquire the lock in the NIOThrowingAsyncSequenceProducer.
- Merge 'next()' and 'next(for:)' methods in the state machine into a
single func; this reduces the amount of duplicated logic across the two
functions.

Result:

Lower chance of deadlock
2025-11-25 09:39:54 +00:00
George Barnett d2feeaa363 Handle cancellation between dropping and reaquiring lock (#3453)
Motivation:

This is a follow up to 5bf841dd to handle the yield task being cancelled
between dropping the lock and re-acquiring it a moment later in the
'withContinuation' block.

Modifications:

- Add an extra cancellation check when yielding with a continuation.

Result:

Fewer issues
2025-11-25 09:16:35 +00:00
George Barnett 5bf841dde5 Drop and reacquire lock over continuation call (#3452)
Motivation:

The various 'withMumbleContinuation' APIs are supposed to be invoked
synchronously with the caller. This assumption allows a lock to be
acquired before the call and released from the body of the
'withMumbleContinuation' after e.g. storing the continuation. However
this isn't the case and the job may be re-enqueued on the executor
meaning that this is pattern is vulnerable to deadlocks.

Modifications:

- Drop and reacquire the lock in the NIOAsyncWriter

Result:

Lower chance of deadlock
2025-11-24 11:13:50 +00:00
Johannes WeissandGeorge Barnett 3eea09220e do not waste time when metrics delegate is not set (#3441)
### Motivation:

Unfortunately, even if no metics delegate has been configured,
significant work is being done _each EL tick_. Concretely:

- 2 `NIODealine.now()` calls which are expensive particularly on some
hypervisors (may cause a VMEXIT!)
- Some unnecessary calculations

### Modifications:

- Only perform the metrics delegate work if a delegate has actually been
set

### Result:

If no delegate is set, each EL tick will now be faster.

Co-authored-by: George Barnett <gbarnett@apple.com>
2025-11-20 16:13:15 +00:00
Max Desiatov 6e02c7a246 Make ByteBuffer.writeBytes(_: RawSpan) broadly available (#3447)
`RawSpan` can be back-deployed, so availability can become consistent
with similar methods like this one
https://github.com/apple/swift-nio/blob/56724a2b6d8e2aed1b2c5f23865b9ea5c43f9977/Sources/NIOCore/ByteBuffer-aux.swift#L1020C3-L1024C38
2025-11-17 09:22:35 +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 45b463c5eb Quality of Life: Add NIOLoopBoundBox.withValue (#3385)
When having a state machine inside the `NIOLoopBoundBox` we currently
check the EL when reading and writing. This is unnecessary. Because of
this, this PR introduces a new `withValue` method, that allows users to read and
write the value inside the NIOLoopBoundBox while only paying the cost
for the EL check once.
2025-11-10 10:08:50 -05:00
Cory Benfield 83d438650a Document the GSO/GRO APIs (#3440) 2025-11-10 08:11:51 +00:00
Cory Benfield 5741df9cc4 Enable per-message GRO on Linux (#3439)
Motivation:

NIO has existing GRO support. This GRO support is applied at Channel
scope, which makes it somewhat less than ideal, as it forces the
datagram sizes to be accessed using socket options. In real applications
this is less desirable than being able to do this on a per-superbuffer
context.

Linux supports this already by using recvmsg/recvmmsg control data. We
just need to wire this up in NIO. We can re-use the existing metadata
fields on AddressedEnvelope.

Modifications:

- Add a new ChannelOption to extra metadata
- Reject setting this option on non-Linux devices.
- On Linux, use this to load the control data.
- Added new tests that validate the behaviour is correct.

Result:

Enable per-message GRO.
2025-11-07 13:24:30 -05:00
Cory Benfield a7da98c3ac Enable per-message GSO on Linux (#3436)
Motivation:

NIO has existing GSO support. This GSO support is applied at Channel
scope, which makes it somewhat less than ideal, as it forces the
datagram sizes to be known statically ahead of time. In real
applications this is less common than being able to do this on a
per-superbuffer
context.

Linux supports this already by using sendmsg/sendmmsg control data. We
just need to wire this up in NIO.

An extension to this is that we can also do per-message GRO in Linux.
I'll tackle this in a later patch to keep the diffs small.

Modifications:

- Adding a new field on AddressedEnvelope Metadata.
- Reject setting this field on non-Linux devices.
- On Linux, use this to set the control data.
- Added new tests that validate the behaviour is correct.

Result:

Enable per-message GSO.
2025-11-07 08:34:33 +00:00
Cory Benfield 88dc99031f Add some spans to ByteBuffer (#3371)
Motivation:

Users would like to be able to access the underlying memory of a
ByteBuffer, as evidenced by the plethora of `withUnsafe*` methods that
ByteBuffer has. As Swift 6.2 has introduced some initial APIs for safe
memory access to underlying storage, we should offer similar APIs on
ByteBuffer to enable users to get safer access to that storage.

For now, the obvious APIs to be able to supplement are:

- withUnsafeReadableBytes
- withUnsafeMutableReadableBytes
- writeWithUnsafeMutableWritableBytes

We can also offer some new APIs to allow initializing a buffer directly
from an OutputSpan.

Note that we can only do this because the Language Steering Group has
pinky promised that they will not break the "Lifetimes" experimental
feature: see

https://forums.swift.org/t/experimental-support-for-lifetime-dependencies-in-swift-6-2-and-beyond/78638
for more details. We are taking them at their word, and so we are
enabling that feature.

Modifications:

Many new methods and tests.

Result:

Safer access.
2025-11-06 11:54:06 -05:00
Cory Benfield fd7c6af4f0 Replace rethrows with typed throws in ByteBuffer (#3372)
Motivation:

Users who want to use typed throws would benefit from us propagating the
thrown error type through our APIs. Most of our surface doesn't allow
for that because it relies on standard library APIs that haven't been
updated, but we can do it for ByteBuffer which is entirely ours.

Modifications:

- Adopt typed rethrows in ByteBuffer

Result:

Users have an easier time working with ByteBuffer
2025-11-05 16:14:30 -05:00
scottmarchant cc38f7abcf feat: Enable NIOFoundationCompat to compile using Swift for WebAssembly. Fix NIOCore WASI compilation issue. (#3422)
Fix Swift for WebAssembly compilation in NIOFoundationCompat.

### Motivation:

NIO is a common dependency, and it is important to enable compiling NIO
using Swift for WebAssembly. This PR fixes compilation for
NIOFoundationCompat.

### Modifications:

- Fix NIOFoundationCompat compilation

### Result:

With these changes, the following build commands succeed:  

```
swift build --swift-sdk wasm32-unknown-wasip1-threads --target NIOCore
swift build --swift-sdk wasm32-unknown-wasip1-threads --target NIOFoundationCompat
```

### Context:

This PR is [part of a larger effort by
PassiveLogic](https://github.com/PassiveLogic/swift-web-examples/issues/1)
to improve Swift for WebAssembly support
2025-11-05 19:55:48 +00:00
George Barnett d963335ba0 Fail on early errors in parallel copy (#3435)
Motivation:

'copyItem' can be used to copy files/directories from a source to a
destination address and fails if the destination already exists. The
parallel version of it can wedge if a directory is being copied and the
directory can't be created at the destination path (for example, if a
file already exists at the destination).

The parallel copy works by feeding tasks (e.g. "copy this item from here
to there") into an async sequence and processing each task in a separate
child task within a task group. When copying directories a new directory
is created at the destination path and then each file within the source
directory is emitted as a separate item to process. Another item is sent
on the async sequence to indicate when the source directory is finished
with which may result in finishing the async sequence.

However, if creating the destination directory fails then that event
isn't sent. This results in the calling code never terminating the async
sequence and causes 'copyItem' to wedge.

Modifications:

- Use non-idempotent directory creation (copy item should fail if the
destination already exists, this was a regression introduced in
7124f096).
- Check whether to continue when a dir can't be created but always emit
the end of dir event
- If terminating when the task group hasn't reached its width limit then
check child tasks for errors

Result:

Parallel copy doesn't wedge
2025-11-05 08:47:05 +00:00
scottmarchant e304a1a363 chore: For WASI builds only, use fatalError in all .wait() calls. Recommend using .get() instead. (#3421)
Use fatalError in all .wait() calls for WASI builds only, and point
developers towards .get() instead.

### Motivation:

While working to adopt NIO in some wasm code, I've commonly ran into
issues any time code calls `.wait()` during wasm runtime. Typically the
executable either traps or crashes any time `.wait()` is called with an
ambiguous error: `Uncaught (in promise) RuntimeError: Atomics.wait
cannot be called in this context`. The error occurs because it is
forbidden to block the main thread for a wasm executable, and the
current implementation of `.wait()` blocks the calling thread.

The fix is straight forward, all calls to `.wait()` need refactor to
`.get()`, which sometimes involves some Swift Concurrency adoption (eg.
`async`) in the process. That change avoids blocking the main thread.

### Modifications:

Added `fatalError` with a descriptive error message to help developers
identify the issue easier.

### Result:

For WASI builds only, changes the trap error message `Uncaught (in
promise) RuntimeError: Atomics.wait cannot be called in this context`
into the following error message instead:

```
NIO's wait() function should not be called on WASI platforms. It will freeze or crash. Use get() instead.
```

### Alternatives considered

- We could instead conditionalize `.wait()` completely out of nio for
WASI builds, to turn runtime errors into compiler errors. That makes
detection of this issue easier, but forces a refactor and breaking
change, and somewhat precludes the possibility that WASI might support
this down the road with a future change.
- We could add a deprecation for WASI only. But long term WASI may be
able to support this blocking call, so deprecation and removal is
communicating the wrong message if the future ends up being reality.

### Context

This PR is [part of a larger effort by
PassiveLogic](https://github.com/PassiveLogic/swift-web-examples/issues/1)
to move Swift for WebAssembly support forward in a large number of
dependencies.
2025-11-03 12:50:23 -05:00
Marc Prud'hommeaux a24771a4c2 Fix the Android CI workflow by adding a post-install step the configure the SDK (#3424)
Fixes the Android CI workflow

### Motivation:

The recently-added Android SDK workflow
[fails](https://github.com/apple/swift-nio/actions/runs/18718487605/job/53384086329?pr=3418)
(as mentioned at
https://github.com/apple/swift-nio/pull/3418#issue-3540969388). The
confusing error message is due to a post-install step that is needed to
download and link the Android NDK into the Swift SDK's artifactbundle.

This PR also adds a one-line fix to
[Sources/NIOPerformanceTester/main.swift](https://github.com/apple/swift-nio/compare/main...swift-android-sdk:swift-nio:main#diff-de179adbbfacb89c9533f4efd21f7f641eedc0c8642d7d650cdfc9d9278a7a55)
that gets it building for Android so the entire swift-nio package can be
built.

### Modifications:

Adds a post-install step to the SDK installation that is needed for the
Swift SDK for Android. This is the same technique used by
https://github.com/swiftlang/github-workflows/pull/172, and is described
as a necessary step in the [Android getting started
guide](https://github.com/swiftlang/swift-org-website/blob/0dc655ce267e7b6bada2a2032cc7855c3b5e19d9/documentation/articles/swift-android-getting-started.md)

### Result:

The "Android Swift SDK" CI check should now pass successfully. See an
example at
https://github.com/swift-android-sdk/swift-nio/actions/runs/18761057590/job/53525442382

### Note:

This PR will _not_ pass the Android check until it has been merged,
because it relies on being able to down the the script
https://raw.githubusercontent.com/apple/swift-nio/main/scripts/install_swift_sdk.sh
from `apple/swift-nio/main`, which will not include the Android fix
until the PR is merged. But once it is merged, the Android SDK CI should
start passing.
2025-10-30 10:04:17 +00:00
Mahdi Bahrami 0469372396 Implement AsyncSequence/splitLines() (#3411)
~~Implement `AsyncSequence/split()` functions similar to
`String/split()` functions in std-lib.~~
Implement `AsyncSequence/splitLines()` functions similar to
`String/split(whereSeparator: \.isNewline)` in std-lib.

### Motivation:

~~Provide an easy way for users to split the data incoming from an async
sequence, using their preferred separator.~~
Provide an easy way for users to split the data incoming from an async
sequence, on new lines.

### Modifications:

Add `internal SplitMessageDecoder: NIOSingleStepByteToMessageDecoder`.
Add `public NIOSplitLinesMessageDecoder:
NIOSingleStepByteToMessageDecoder`.
Add `public
AsyncSequence/splitLines(omittingEmptySubsequences:maximumBufferSize) ->
AsyncSeq<ByteBuffer>`.
Add `public
AsyncSequence/splitUTF8Lines(omittingEmptySubsequences:maximumBufferSize)
-> AsyncSeq<String>`.

### Result:

Users can easily split the data.
2025-10-21 16:13:43 +01:00
Mahdi Bahrami f2ad915d57 [perf] Manually implement lastIndex(where:) in ByteBufferView (#3413)
### Motivation:

`ByteBuffer.lastIndex(where:)` is of suboptimal performance.
The default Collection implementations don't go through any "magic
underscored" functions like `_customIndexOfEquatableElement`.

### Modifications:

Manually implement `lastIndex(where:)`.

### Result:

Basically free performance boost. 2x+ boost even for not big buffers of
a few hundred bytes.

This function is currently used in
`ByteBufferView.trim(limitingElements:)`.

I have no immediate use case for this function, but it's still an issue
worth addressing.
2025-10-21 14:02:55 +01:00
Hamza Hassanain 7124f0963a feat(NIOFileSystem): Add idempotent directory creation behavior (#3404) (#3410)
The createDirectory function will succeed without error if the target
directory already exists.

###  Motivation

This change addresses issue #3404. Currently,
`fileSystem.createDirectory` fails if the target directory already
exists, forcing users to write boilerplate try/catch blocks to handle
this common and expected case.

The goal is to make this function's behavior idempotent.

### Modifications

To achieve this, I've made the following changes:

**(Implementation)** A new private helper function,
`_handleCreateDirectoryFileExists`, was introduced. This function is
responsible for:

1. Performing a `stat` call on the path that failed.
2. Checking if the existing item is a directory (`S_IFDIR`).
3. Returning a success result if it's a directory, or re-throwing the
original `.fileExists` error if it's a file or another type of entity.

**(Logic)** The core `_createDirectory` function was updated to call
this new helper function whenever `Syscall.mkdir` fails with an `EEXIST`
(`.fileExists`) error. This check is applied in both internal loops to
correctly handle cases where either an intermediate directory or the
final target directory already exists.

### Result
With this change, users can now call the function
`fileSystem.createDirectory` and the operation will succeed even if the
directory is already present, leading to cleaner and more predictable
code.
2025-10-20 15:49:26 +01:00
Mahdi Bahrami 767ea9ee09 [perf] Manually implement firstIndex(where:) in ByteBufferView (#3412)
### Motivation:

`ByteBuffer.firsIndex` is of suboptimal performance.
The default Collection implementations don't go through any "magic
underscored" functions like `_customIndexOfEquatableElement`.

### Modifications:

Manually implement `firstIndex(where:)`.

### Result:

Basically free performance boost. 2x+ boost even for not big buffers of
a few hundred bytes.

There are some usage of this function in `BufferedReader`. Those will
become much faster.
Also this function is used in `ByteBufferView.trim(limitingElements:)`.

Also makes #3411 stuff faster. See:
https://github.com/apple/swift-nio/pull/3411#discussion_r2436260691
2025-10-17 07:15:44 +00:00
Mahdi Bahrami 86c5ead5dd Introduce NIODecodedAsyncSequence for easy decoding of async sequences (#3407)
Previous PR: #3405 

Add an API on top of `AsyncSequence<ByteBuffer>` which can dynamically
decode values.
~~Add an API on top of the new `AsyncSequence<ByteBuffer>` APIs which
splits the file based on its content.~~

### Motivation:

Provides a nice API to decode files, instead of users having to go
though manually handling `BufferedReader.read(while:)`.
I struggled with this, as documented in
https://swift-open-source.slack.com/archives/C9MMT6VGB/p1760115481607159

### Modifications:

Add `NIODecodedAsyncSequence` + functions on `AsyncSequence<ByteBuffer>`
to create such a sequence.
~~Add `NIOSplitMessageDecoder` + stdlib-like functions on
`AsyncSequence<ByteBuffer>` to create such a sequence.~~

### Result:

Users can decode an async sequence of `ByteBuffer`s easier.
~~Users can easily split files based on their content.~~

### Checklist

See this comment for a checklist of the remaining things to do:
https://github.com/apple/swift-nio/pull/3407#issuecomment-3403382404
2025-10-16 09:49:55 +00:00
Cory Benfield cdf721f4d6 Avoid zombie pipe channels when receiving writeEOF (#3408)
Motivation

In rare cases where the inbound side of a pipe channel is already closed
(either because it was never open or because it was closed during use),
we can get into trouble if the reader drops the read side of our write
pipe. In that context, the Linux kernel will deliver an EPOLLHUP, but
while we'll close the FD we'll let the channel hang out as a zombie.
This is, obviously, suboptimal.

Modifications

In writeEOF, we check whether the read side is already closed. If it is,
we trigger a close internally to shut the channel down. Added a test for
this as needed, and modified an existing test that would now trip over
this behaviour. Also added a new test for the reverse case to ensure we
don't end up with zombies there (we don't).

Results

No zombie channels.
2025-10-15 10:16:39 +01:00
Fabian FettandCory Benfield a3ed8e10ab Drop Swift 5.10 (#3393)
This PR drops Swift 5.10 and enables Swift 6 language mode.

---------

Co-authored-by: Cory Benfield <lukasa@apple.com>
2025-10-06 10:58:33 +01:00
Mahdi BahramiandCory Benfield 2e22c89844 Add InlineArray helpers to ByteBuffer (#3252)
### Motivation:

Useful for parsing packets, for example `ipv4: InlineArray<4, UInt8>`
and `ipv6: InlineArray<16, UInt8>`.

### Modifications:

For now I've only added a `readInlineArray` function. I know some other
functions are missing, such as `writeInlineArray`.
I wanted to first open up a discussion and see if these changes are
acceptable. I can add those functions too if required, in this PR or
other PRs.

### Result:

Users can read `ByteBuffer` into stack-allocated memory, which can be
more performant than the other alternatives like `Array`, or more
convenient than reading as a tuple like `(UInt8, UInt8, UInt8, UInt8)`.

### Caveats:

Swift 6.2 is required so I've used `#if compiler(>=6.2)`.
Furthermore, `InlineArray` is marked as available on `macOS(9999)` since
the Swift team have yet to update that mark, although `InlineArray` is
planned for Swift 6.2 per the
[proposal](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0453-vector.md).

Edit: to be clear things work fine on Linux and that's where I've been
using this same `readInlineArray` function that I've proposed.

---------

Co-authored-by: Cory Benfield <lukasa@apple.com>
2025-09-24 09:18:00 +00:00