## 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>
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.
### 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>
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>
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>
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`.
### 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>
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
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
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
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.
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.
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.
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.
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
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.
~~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.
### 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.
### 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
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
### 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>
### Motivation
- `WSAStartup` isn't called
### Changes
- Change stuff that I have very little idea about until it works
### Result
WSAStartup is now emitted at the correct location for the linker to be
picked up.
Fixes all warnings when `-require-explicit-sendable` flag is enabled and
enables the flag on macOS CI.
### Motivation:
We want to ensure our public API is either explicitly marked as
`Sendable` or not.
### Modifications:
Marked appropriate public types as `Sendable`, or explicitly defined
their conformance to the `Sendable` protocol as unavailable.
### Result:
We can now enable `-require-explicit-sendable` compiler flag in our
codebase.
Motivation:
The compiler is better at optimising closures wrapped in nominal types
than raw closures when used as generic parameters. CallbackList is used
a lot and stores an optional closure as well as on optional array of
closures. Wrapping these internally should save some allocations.
Modifications:
- Wrap the element stored in the `CallbackList`.
Result:
Fewer allocs
---------
Co-authored-by: Cory Benfield <lukasa@apple.com>
### Motivation:
`inEventLoop` is very much in the performance path of SwiftNIO,
especially these days with Concurrency, `NIOLoopBound` and friends.
Previously, we relied on `pthread_equal(pthread_self(), myPthread)`,
however, this could cause a number of issues.
1. Holding onto a `pthread_t` after `.join` is actually illegal (fixed
in #3297)
2. Potential ABA issues when `pthread_t` pointer are re-used for new
pthreads
3. Fix would require a lock around `myPthread` which makes things (2x
slower, even without contention)
### Modifications:
- New type `SelectableEventLoopUniqueID` which can be packed into a
`UInt`
- Attach them into a C thread local
### Result:
- Even faster than the old, incorrect version
- old: `measuring: el_in_eventloop_100M: 0.257395375, 0.241049208,
0.243188792, 0.259125916, 0.24843225, 0.229690125, 0.244281541,
0.225078834, 0.236395, 0.233305167`
- new: `measuring: el_in_eventloop_100M: 0.175561125, 0.187225625,
0.199269375, 0.19740975, 0.1922695, 0.179850958, 0.177612458,
0.17665125, 0.17897475, 0.18038775`
- More correct
- Groundwork to make #3297 not make things slower
This is a quick simple fix for compiling NIOCore to wasm using the
command `swift build --swift-sdk wasm32-unknown-wasip1-threads --target
NIOCore`.
This fixes all errors for `wasip1`, however there are other errors
remaining in `main` when compiling for `wasi` using the command `swift
build --swift-sdk wasm32-unknown-wasi --target NIOCore`. Those issues
are out of scope for this PR.
### Motivation:
I am working to enable wasm compilation for a wide range of repositories
which depend on NIOCore. See
[here](https://github.com/PassiveLogic/swift-web-examples/issues/1) for
a sneak peak. This change is required to enable compiling several
respositories.
### Modifications:
`.multicastNotSupported` is elided for `os(WASI)` builds in
Channel.swift, however some new code referenced that case without proper
`WASI` guards.
This modification simply adds the appropriate elision guard to fix
wasip1 builds for NIOCore.
### Result:
After making this change, `swift build --swift-sdk
wasm32-unknown-wasip1-threads --target NIOCore` builds again (assuming
you have the appropriate wasm sdk installed).
Fixes#3262 by adding the missing APIs.
### Motivation:
As explained by the issue linked above, having a
non-`Sendable`-requiring variant of the new `scheduleCallback` APIs on
`NIOIsolatedEventLoop` can be useful since we're not always dealing with
`Sendable` types.
### Modifications:
This adds the required `scheduleCallback` APIs to `NIOIsolatedEventLoop`
by wrapping the non-`Sendable` `NIOScheduleCallbackHandler` in a
`NIOLoopBound`-based handler.
### Result:
The two `scheduleCallback`s can be used on `NIOIsolatedEventLoop` too.
---------
Co-authored-by: Fabian Fett <fabianfett@apple.com>
Motivation:
With the new Swift version (6.2) we get access to spans for safely
interacting with the contiguous memory owned by other types. Allow users
write bytes to the ByteBuffer with RawSpans.
Modifications:
* ByteBuffer-core: Overload the setBytes, _setBytes, and
_setBytesAssumingUniqueBufferAccess to use RawSpan instead of
UnsafeRawBufferPointer
* ByteBuffer-aux: Overload the writeBytes to use RawSpan instead of
UnsafeRawBufferPointer
Result:
Users can now write the contiguous memory of other types into ByteBuffer
using the new RawSpan overloads, removing the need for unsafe pointer
handling.
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.
When working on performance in PostgresNIO I noticed a few
`_swift_getGenericMetadata`s. We don't like those. I could trace them
down to `NIOThrowingAsyncSequenceProducer`. All methods on this object
are generic, since the object itself is generic.
Co-authored-by: George Barnett <gbarnett@apple.com>
Fix visionOS builds and tests
### Motivation:
visionOS builds and tests were failing because of missing cases in
availability guards.
### Modifications:
Spell guards in terms of Darwin where appropriate, add visionOS
### Result:
Example run of this working in CI
https://github.com/apple/swift-nio/actions/runs/14876166161/job/41773757638?pr=3220
Moving `PooledRecvBufferAllocator` from NIOPosix to NIOCore, making it
part of its public interface.
### Motivation:
We want to introduce the capability of reusing receiving buffers from a
pool into swift-nio-transport-services project. To prevent duplicating
this functionality there, this change is making the existing
functionality from NIO part of its public API.
### Modifications:
Moved the type `PooledRecvBufferAllocator` from NIOPosix module to
NIOCore and turned it into a public type.
### Result:
`PooledRecvBufferAllocator` is part of NIOCore public API.
---------
Co-authored-by: George Barnett <gbarnett@apple.com>
### Motivation:
`ChannelError` and `NIOConnectionError` should conform
`CustomStringConvertible` so that these errors can be easily logged.
As discussed in https://github.com/apple/swift-nio/issues/3222.
### Modifications:
Added conformance to `ChannelError` and wrote error messages for all
cases.
Added conformance to `NIOConnectionError`.
---------
Co-authored-by: George Barnett <gbarnett@apple.com>
Motivation:
Public static lets can serve a bunch of roles, but one of them is to
store simple constants: integers, and other trivial types, for example.
This is a nice pattern and for internal and private static lets it works
well, but for public ones it produces some inefficient code.
In particular, it has two downsides. First, it allocates storage for
that value. We don't actually need to allocate a few hundred extra
megabytes for the various integers we want to store.
Secondly, it forces calling code to access the address and call the
dispatch_once code in order to get hold of the value. For trivial types
we don't need that cost: they can just know what the value is directly.
Inlinable computed vars avoid all of these costs: they have no size
overhead for storage, and they are visible to all clients so their
values can be directly assembled.
While I'm here, I added a bunch of other inlinable annotations for a few
trivial data types I stumbled onto.
Modifications:
- Added loads of inlinables. Like, loads.
- Swapped many static lets to static vars.
Result:
Better codegen, smaller memory footprints, more attributes.
Motivation:
There's a known compiler issue on the 5.10 toolchain for x86 on Ubuntu
Noble. This issue causes the compile to fail, even though the code is
fine. We shouldn't allow that.
Modifications:
Shuffle the whitespace around to fix the issue.
Suppress the linter issues.
Result:
Compiler is back. Resolves#3223.
### Motivation:
Existing get APIs require passing an explicit index and can be misused,
leading to verbose and error-prone code. Adding peek variants that
automatically use the current readerIndex improves safety and clarity.
This aims to address issue #2034 and issue #2736, and is a continuation
of PR #3157
### Modifications:
Introduced peekSlice(), peekLengthPrefixedSlice(), peekData(),
peekUUIDBytes() and peekWebSocketErrorCode().
Added tests for each peek API covering normal, empty and repeated peek
scenarios.
### Result:
Developers can now use nonmutating peek APIs to inspect ByteBuffer
contents without altering the reader index.
### Motivation:
The current readMultipleIntegers methods are used to consume multiple
integer values at once, but lack an equivalent nonmutating API. This can
lead to unnecessary complexity when users want to inspect values without
advancing readerIndex. THis patch introduces peekMultipleIntegers which
uses the current readerIndex, improving safety. This extends previous
work on peek methods (issue #2034, issue #2736).
### Modifications:
Updated the generation script (.sh file) to produce peekMultipleIntegers
variants for each arity of readMultipleIntegers.
Added new tests mirroring the existing multi-read/write tests
### Result:
Users can now inspect multiple integer values in a ByteBuffer without
consuming them, enabling safer workflows.
---------
Co-authored-by: Cory Benfield <lukasa@apple.com>
Motivation:
NIOLoopBound and NIOLoopBoundBox both have a public-but-underscored
event loop property, i.e. the loop to which the value is bound. It's
often very helpful to be able to access the event loop the value is
isolated to. Having access allows you to execute onto the event loop and
safely access the value.
Modifications:
- Make the property public and add a deprecated shim for the
public-but-underscored variant.
Result:
Can access the event loop of a loop bound value
Co-authored-by: Cory Benfield <lukasa@apple.com>
### Motivation:
Existing get APIs require passing an explicit index and can be misused,
leading to verbose and error-prone code. Adding peek variants that
automatically use the current readerIndex improves safety and clarity.
This aims to address https://github.com/apple/swift-nio/issues/2034 and
https://github.com/apple/swift-nio/issues/2736, and is a continuation of
PR https://github.com/apple/swift-nio/pull/3157
### Modifications:
- Introduced peekBytes(), peekString(), peekNullTerminatedString(),
peekUTF8ValidatedString() and peekDispatchData().
- Added tests for each peek API covering normal, empty, repeated, and
partial read scenarios.
### Result:
Developers can now use nonmutating peek APIs to inspect ByteBuffer
contents without altering the reader index.
***PS**: I have focussed on the ByteBuffer-aux get\* implementations, so
I'm not sure if I am missing any other important/error-prone get\*
elsewhere, if so, please point me to them and I'll be happy to add
respective peek\*'s!*
### Motivation:
The existing getInteger API is often used incorrectly, leading to
verbose or error-prone code as mentioned in
https://github.com/apple/swift-nio/issues/2034 and
https://github.com/apple/swift-nio/issues/2736. Users may
unintentionally access data without correctly managing the reader index.
To address this, I added a peekInteger API that reads integers using the
current readerIndex without modifying it.
### Modifications:
* Added a peekInteger method to ByteBuffer, which provides a nonmutating
way to read integers using the current readerIndex.
* Implemented peekInteger using the existing getInteger function.
* Added comprehensive unit tests to validate correct behavior, including
scenarios for different integer types, insufficient bytes, and
multi-write scenarios.
### Result:
* Users now have access to a safer API for inspecting integers in a
buffer without modifying state.
* Added tests for integer reading scenarios.
---------
Co-authored-by: George Barnett <gbarnett@apple.com>
Fixes NIOCore compiler errors for the `wasm32` architecture.
### Motivation:
When compiling NIOCore for a SwiftWasm target, several compilation
errors such as the following occur:
```
> swift build --swift-sdk wasm32-unknown-wasi --target NIOCore
.../swift-nio/Sources/NIOCore/FileHandle.swift:151:42: error: cannot find type 'TwoUInt32s' in scope
149 | public final class NIOFileHandle: FileDescriptor & Sendable {
150 | private static let descriptorClosed: CInt = CInt.min
151 | private let descriptor: UnsafeAtomic<TwoUInt32s>
| `- error: cannot find type 'TwoUInt32s' in scope
152 |
153 | public var isOpen: Bool {
.../swift-nio/Sources/NIOCore/FileHandle.swift:77:8: error: Unknown architecture
75 | typealias TwoUInt32s = DoubleWord
76 | #else
77 | #error("Unknown architecture")
| `- error: Unknown architecture
78 | #endif
79 |
…
```
The compilation errors were introduced with the following change:
https://github.com/apple/swift-nio/pull/2598/files#diff-a041d0c0d50a8f4fcda2020eeb4450ea994920ef61653cc06014457ccdb3ba0cR70
### Modifications:
The fix is straight-forward and unlikely to affect any existing
compilation targets other than the `wasm32` target.
We simply need to add `arch(wasm32)` to compiler architecture condtions
for a new typedef on line, then NIOCore compiles to wasm32 again.
### Result:
Before: NIOCore wasm build fails ❌
After: NIOCore wasm build succeeds ✅
Co-authored-by: Scott Marchant <scottm@passivelogic.com>
Improve usability of NIOAsyncChannel.executeThenClose function from an
Actor
### Motivation:
When calling executeThenClose from inside an actor, the closure which we
pass is not isolated to the same actor
This makes it hard to use
e.g. the following code won’t compile on Swift 6 because the closure is
`self-isolated` so can’t be passed
```
await withDiscardingTaskGroup { group in
do {
try await serverChannel.executeThenClose { inboundStream, _ in
try await self.handleInboundStream(inboundStream: inboundStream, group: &group)
}
} catch {}
}
```
Making the closure as @Sendable allows it to be passed, but then we
can’t access `group` anymore
### Modifications:
Added actor isolation parameter to the `executeThenClose` function.
### Result:
`executeThenClose` will be able to be asynchronously called from inside
an Actor, as it will now be part of the Actor's isolation domain.
---------
Co-authored-by: Franz Busch <privat@franz-busch.de>