Compare commits

...

34 Commits
2.49.0 ... main

Author SHA1 Message Date
dnrops 8bdc286847 Update Package.swift 2024-08-27 21:03:31 +08:00
Cory Benfield 48e2fa746e
Adopt the Swift CoC (#2440)
Motivation:

We're centralizing on the Swift code of conduct, so we'll x-reference
that instead of holding our own.

Modifications:

Hyperlink out to Swift.

Result:

Shared CoC across the projects.
2023-06-06 11:04:04 -07:00
Franz Busch 46c0538253
Add NIOAsyncChannel based connect methods to ClientBootstrap (#2437)
* Add NIOAsyncChannel based connect methods to ClientBootstrap

# Motivation
In my previous PR, I added new `bind` methods to `ServerBootstrap` that vend `NIOAsyncChannel` or support an async protocol negotiation. This PR focuses on adding new `connect` methods to `ClientBootstrap` which offer the same functionality.

# Modification
This PR adds new `connect` methods that either vend a `NIOAsyncChannel` or an asynchronous protocol negotiation result. To make this work I had to change the `HappyEyeballs` resolver so that it can return a generic value on resolving. Lastly, I adapted the bootstrap tests to use the new `ClientBootstrap` capabilities which now demonstrate a client/server protocol negotiation dance.

# Result
We can now bootstrap TCP clients with `NIOAsyncChannel`s

* Reduce code duplication

* Create a new set of APIs to tunnel an arbitrary Sendable payload through the inits

* Pass EL to closure

* Fix documentation
2023-06-06 03:36:53 -07:00
ser 6213ba7a06
Pooled control message storage. (#2422) 2023-05-31 06:06:23 -07:00
George Barnett d5519dba0b
Fix happy eyeballs races with custom resolver (#2436)
Motivation:

The HappyEyeballs connector synchronises state on an event loop but
calls out to a 'Resolver' to do DNS lookups. The resolver returns
results as a future which may be on a different loop than the connector.
The connector does not hop back to its own event loop before processing
the results.

For client bootstraps, if no resolver is specified then the default
resolver uses the same event loop as the connector so in many cases this
is not an issue. However, if a custom resolver is used this guarantee is
lost and data races are much more likely.

Modifications:

- Hop back to the connector's event loop after calling the resolver.
- Add a test.

Result:

Fewer data races.
2023-05-31 11:28:51 +01:00
Franz Busch b22575ac96
Fix flaky test in NIOAsyncWriter (#2431)
* Fix flaky test in NIOAsyncWriter

# Motivation
We had flaky test in the writer where we were relying on some timing.

# Modification
Change the test so that it is deterministic.

# Result
Fixes https://github.com/apple/swift-nio/issues/2427

* Update allocation counters
2023-05-31 09:53:54 +01:00
Gwynne Raskind 5768317b70
Add test coverage of WebSocketMaskingKey.random() (#2433)
Solves the NIO side of #2432
2023-05-24 02:23:41 -07:00
Franz Busch 27620851c4
Add narrative documentation for NIO's concurrency bridges (#2423)
* Add narrative documentation for NIO's concurrency bridges

# Motivation
We started to add bridges from NIO to Swift Concurrency and we should provide some narrative documentation for our users how to adopt them.

# Modification
Adds a new article that explains the NIO concurrency bridges.

* Address smaller review comments

* Rearrange sections and introduce general guidance section

* remove some commas

* Add missing catch
2023-05-22 18:40:16 +01:00
Felix Schlegel 47b6289d93
`Embedded`: `getOption(ChannelOptions.allowRemoteHalfClosure)` should not `fatalError` (#2429)
* Embedded: getOption(.allowRemoteHalfClosure) -> OK

Motivation:

In `swift-nio-ssl`, I am currently working on allowing half-closures
which relies on querying the underlying channel if
`ChannelOptions.Types.AllowRemoteHalfClosureOption` is enabled. As a lot of
`swift-nio-ssl`'s tests rely on `EmbeddedChannel` and it did not support
this option, a lot of the tests failed.

Modifications:

* add a `public var allowRemoteHalfClosure` to `EmbeddedChannel`
* enable setting/getting
  `ChannelOptions.Types.AllowRemoteHalfClosureOption` in
`EmbeddedChannel` (only modifies the `allowRemoteHalfClosure` variable
* add test for new behaviour

* AsyncTestingChannel: getOption(.allowRemoteHalfClosure) -> OK

Motivation:

`AsyncTestingChannel` interface should be in step with `EmbeddedChannel`
interface. Therefore also add support for the
`AllowRemoteHalfClosureOption`

Modifications:

* add a `public var allowRemoteHalfClosure` to `AsyncTestingChannel`
* enable setting/getting
  `ChannelOptions.Types.AllowRemoteHalfClosureOption` in `AsyncTestingChannel`
  (only modifies the `allowRemoteHalfClosure` variable
* add tests for new behaviour

* Synchronize access to allowRemoteHalfClosure

Modifications:

* add `ManagedAtomic` property `_allowRemoteHalfClosure` to
  `EmbeddedChannelCore`
* make sure that access to `allowRemoteHalfClosure` from
  `AsyncTestingChannel` and `EmbeddedChannel` is synchronized by
  accessing underlying atomic value in `channelcore`

* Update allocation limits
2023-05-19 09:47:47 -07:00
Felix Schlegel a22a35a0ff
Update update-alloc-limits script (#2430)
Motivation:

Running the script failed as it looked for a CI build for Swift 5.5,
which is not run by our CI anymore.

Modifications:

* don't look for CI builds for Swift 5.5
* additionally, also check for CI build for Swift 5.9
2023-05-19 09:22:53 -07:00
Johannes Weiss 7a0ec436a1
include relevant versions (kernel & Swift) in test output (#2425) 2023-05-16 02:55:18 -07:00
Cory Benfield 2d8e6ca36f
Tolerate sending data after close(mode: .output) (#2421)
Motivation

We shouldn't crash on somewhat likely user error.

Modifications

Pass on writes after close(mode: .output) instead of crashing.

Result

User code is more robust to weird edge cases.
2023-05-09 10:52:48 +01:00
dkz2 7f4d10bd94
addition of assertSuccess() and assertFailure() on EventLoopFuture (#2417)
* added assertSuccess() and assertFailure()

* test functions for assertSuccess() and assertFailure()

* removed callbacks approach and add precondition variants

* added test for precondition variants

* self.always to handle future w/o creating a promise, added fileID and line for debug

* updated test for asserts on EventLoopFuture
2023-05-04 17:25:18 +01:00
Fabian Fett 546eaa261e
Add unprocessedBytes property on NIOSingleStepByteToMessageProcessor (#2419) 2023-05-03 17:19:13 +01:00
Gwynne Raskind 014812aab6
Make ByteBuffer CustomDebugStringConvertible (#2418) 2023-05-03 03:18:34 -07:00
Fabian Fett d1690f8541
NIOThrowingAsyncSequenceProducer throws when cancelled (#2415)
* NIOThrowingAsyncSequenceProducer throws when cancelled

* PR review
2023-04-28 08:21:15 -07:00
Cory Benfield 5f8b0647e4
Handle close(output) in the pipeline handler. (#2414)
Motivation:

Currently the server pipeline handler ignores close. This generally works,
except in the rare case that the user calls close(mode: .output). In this
instance they have signalled that they'll never write again, and they're
likely expecting a final close shortly after.

However, it is possible that the pipeline handler has suspended reads
at the same time. On Linux this isn't an issue, because we'll still be told
about the eventual socket close. However, on Apple platforms we won't: we've
masked off the reads, and we can't listen to EVFILT_EXCEPT due to some
other issues. This means that on Apple platforms the server pipeline handler
can accidentally wedge the Channel open and prevent it from closing.

We should take this opportunity to have the server pipeline handler be smart
about close(mode: .output). What _should_ happen here is that the pipeline
handler should immediately refuse to deliver further requests on the Channel.
If one is in-flight, it can continue, but everything else should be dropped.
This is because the server cannot possibly respond to further requests.

Modifications:

- Add new states to the server pipeline handler
- Drop buffered requests and new data after close(mode: .output)
- Add tests

Result:

Server pipeline handler behaves way better.
2023-04-28 06:22:07 -07:00
George Barnett 77d35d2f43
Bind to port 0 in a few more places (#2413)
Motivation:

Follow up to #2412 which changed the async channel bootstrap tests to
bind to port 0 instead of a fixed port. A few instances were missed,
this fixes up the remainder.

Modifications:

- Bind to port 0

Result:

Tests should run in parallel without failures.
2023-04-28 02:11:25 -07:00
Franz Busch e3497ffdc5
AsyncChannelBootstrapTests bind to 0 instead (#2412)
# Motivation

The `AsyncChannelBootstrapTests` were failing when running parallel since they were all using the same port.

# Modification

This PR uses port `0` to let the system assign a port instead. It also runs the formatter on this file.

# Result

No more tests failures.
2023-04-27 03:24:11 -07:00
Cory Benfield fd35cd9e52
Extend the integration test harness to track FDs (#2411)
* Extend the integration test harness to track FDs

Motivation

This patch extends the NIO integration test harness to track
file descriptors, in particular to search for leaks. This
change has been validated on Linux and Darwin, and in both cases
correctly diagnoses FD leaks.

The goal is to enable us to regression test for things like

Modifications

- Add support for hooking socket and close calls.
- Wire up this support into the test harness.
- Extend the test harness to handle the logging.
- Add new regression test for #2047.

Results

We can write regression tests for FD leaks.

* Disable FD checking in most builds.

I'm doing this for speed reasons

* Always print the leaked fds number
2023-04-26 08:38:18 -07:00
Franz Busch d836d6bef5
Add `AsyncChannel` based `ServerBootstrap.bind()` methods (#2403)
* Add `AsyncChannel` based `ServerBootstrap.bind()` methods

# Motivation
In my previous PR, we added a new async bridge from a NIO `Channel` to Swift Concurrency primitives in the from of the `NIOAsyncChannel`. This type alone is already helpful in bridging `Channel`s to Concurrency; however, it is hard to use since it requires to wrap the `Channel` at the right time otherwise we will drop reads. Furthermore, in the case of protocol negotiation this becomes even trickier since we need to wait until it finishes and then wrap the `Channel`.

# Modification
This PR introduces a few things:
1. New methods on the `ServerBootstrap` which allow the creation of `NIOAsyncChannel` based channels. This can be used in all cases where no protocol negotiation is involved.
2. A new protocol and type called `NIOProtocolNegotiationHandler` and `NIOProtocolNegotiationResult` which is used to identify channel handlers that are doing protocol negotiation.
3. New methods on the `ServerBootstrap` that are aware of protocol negotiation.

# Result
We can now easily and safely create new `AsyncChannel`s from the `ServerBootstrap`

* Code review

* Fix typo

* Fix up tests

* Stop finishing the writer when an error is caught

* Code review

* Fix up writer tests

* Introduce shared protocol negotiation handler state machine

* Correctly handle multi threaded event loops

* Adapt test to assert the channel was closed correctly.

* Code review
2023-04-26 07:17:07 -07:00
Cory Benfield f7c4655298
Avoid double-closing on fcntl failures (#2409)
Motivation:

The fix provided in #2407 was subtly wrong. ignoreSIGPIPE, which throws
the error in question, closes the FD on error _except_ on EINVAL from
fcntl, where it instead does not. This inconsistent behaviour is the
source of the bug. Because this behaviour is inconsistent, the fix from
PR #2407 is also inconsistent and can in some cases double-close the
socket.

The actual issue is not as old as I expected: the code can be observed
by reviewing the change in #1598, which incorrectly inserted the error
transformation before the call to close.

Modifications:

- Revert the change from #2407.
- Move the close in ignoreSIGPIPE to before the error check, rather than
  after, so we unconditionally execute it.

Result:

More resilient fix.
2023-04-20 12:40:39 +01:00
Cory Benfield 003fbadf51
Don't have channels stop reading on errors they tolerate. (#2408)
Motivation:

When an error is hit during a read loop, a channel is able to tolerate
that error without closing. This is done for a number of reasons, but
the most important one is accepting sockets for already-closed
connections, which can trigger all kinds of errors on the read path.

Unfortunately, there was an edge-case in the code for handling this
case. If one or more reads in the loop had succeeded before the error
was caught, the inner code would be expecting a call to readIfNeeded,
but the outer code wouldn't make it. This would lead to autoRead
channels being wedged open.

Modifications:

This patch extends the Syscall Abstraction Layer to add support for
server sockets. It adds two tests: one for the basic accept flow, and
then one for the case discussed above.

This patch also refactors the code in BaseSocketChannel.readable0 to
more clearly show the path through the error case. There were a number
of early returns and partial conditionals that led to us checking the
same condition in a number of places. This refactor makes it clearer
that it is possible to exit this code in the happy path, with a
tolerated error, which should be considered the same as reading
_something_.

Result:

Harder to wedge a channel open.
2023-04-20 11:09:02 +01:00
Cory Benfield ad859ae82e
Close accepted FDs if we fail to create Socket (#2407)
Motivation:

In some circumstances we can accept a socket that is already closed. In
those cases, creating the underlying Socket type will fail, as
attempting to ignore SIGPIPE will fail. On Apple platforms, this causes
us to leak the accepted socket, and can lead to file descriptor
exhaustion.

Modifications:

- Close the accepted socket if we fail to create a Socket class

Result:

No FD leaks
2023-04-19 18:26:55 +01:00
David Nadoba 6720612111
Drop Swift 5.5 (#2406)
* Drop Swift 5.5

* Use `swift-atomics` 1.1.0 with `Sendable` adoption
2023-04-17 08:40:35 +01:00
Franz Busch a2fd8ad077
Handle reentranct reads in ALPNHandler (#2402)
# Motivation
I spotted a bug in the ALPNHandler where it doesn't properly unbuffer reentrant reads. This can lead to dropped reads.

# Modification
Instead of buffering into an array we are now buffering into a Deque and unbuffer as long as there are reads in the Deque.

# Result
No more dropped reads.
2023-04-13 05:06:15 -07:00
Yim Lee b4ebd5a64a
Add docker-compose file for Swift 5.9 (#2404)
Co-authored-by: Cory Benfield <lukasa@apple.com>
2023-04-13 12:41:45 +01:00
Cory Benfield 20cc037d23
Fix broken docs. (#2405)
The docs broke again.
2023-04-13 10:49:05 +01:00
David Nadoba e0cc6dd6ff
Throw `CancellationError` instead of returning `nil` during early cancellation. (#2401)
### Motivation:
Follow up PR for https://github.com/apple/swift-nio/pull/2399

We currently still return `nil` if the current `Task` is canceled before the first call to `NIOThrowingAsyncSequenceProducer.AsyncIterator.next()` but it should throw `CancellationError` too.

In addition, the generic `Failure` type turns out to be a problem. Just throwing a `CancellationError` without checking that `Failure` type is `any Swift.Error` or `CancellationError` introduced a type safety violation as we throw an unrelated type.

### Modifications:

- throw `CancellationError` on eager cancellation
-  deprecates the generic `Failure` type of `NIOThrowingAsyncSequenceProducer`. It now must always be `any Swift.Error`. For backward compatibility we will still return nil if `Failure` is not `any Swift.Error` or `CancellationError`.

### Result:

`CancellationError` is now correctly thrown instead of returning `nil` on eager cancelation. Generic `Failure` type is deprecated.
2023-04-11 08:58:01 -07:00
Cory Benfield a7c36a7654
Clean up and regression check the docs. (#2400)
Motivation:

Up until recently, it has not been possible to regression check our
documentation. However, in recent releases of the DocC plugin it has
become possible to make warnings into errors, making it possible for us
to CI our docs.

This patch adds support for doing that, and also cleans up our
documentation so that it successfully passes the check.

Along the way I accidentally wrote an `index.md` for `NIOCore` so I
figure we may as well keep it.

Modifications:

- Structure the documentation for NIOCore
- Fix up DocC issues
- Add `check-docs.sh` script to check the docs cleanly build
- Wire things up to our docker-compose scripts.

Result:

We can CI our docs.

Co-authored-by: George Barnett <gbarnett@apple.com>
2023-04-11 09:05:22 +01:00
Franz Busch e7e83d6aa4
Land `NIOAsyncChannel` as SPI (#2397)
* Land `NIOAsyncChannel` as SPI

# Motivation

We want to provide bridges from NIO `Channel`s to Swift Concurrency. In previous PRs, we already landed the building blocks namely `NIOAsyncSequenceProducer` and `NIOAsyncWriter`. These two types are highly performant bridges between synchronous and asynchronous code that respect back-pressure.
The next step is to build convenience methods that wrap a `Channel` with these two types.

# Modification
This PR adds a new type called `NIOAsyncChannel` that is capable of wrapping a `Channel`. This is done by adding two handlers to the channel pipeline that are bridging to the `NIOAsyncSequenceProducer` and `NIOAsyncWriter`.
The new `NIOAsyncChannel` type exposes three properties. The underlying `Channel`, a `NIOAsyncChannelInboundStream` and a `NIOAsyncChannelOutboundWriter`. Using these three types the user a able to read/write into the channel using `async` methods.

Importantly, we are landing all of this behind the `@_spi(AsyncChannel`. This allows us to merge PRs while we are still working on the remaining parts such as protocol negotiation.

# Result
We have the first part necessary for our async bridges. Follow up PRs will include the following things:
1.  Bootstrap support
2. Protocol negotiation support
3. Example with documentation

* Add AsyncSequence bridge to NIOAsyncChannelOutboundWriter

* Code review

* Prefix temporary spi public method

* Rename writeAndFlush to write
2023-04-06 05:26:32 -07:00
David Nadoba 75cea45e61
Throw `CancellationError` if `NIOThrowingAsyncSequenceProducer.AsyncIterator.next()` is cancelled instead of returning `nil` (#2399)
* Throw `CancellationError` if `NIOThrowingAsyncSequenceProducer.AsyncIterator.next()` is cancelled instead of returning `nil`

* Update doc comment

* Fix typo
2023-04-05 17:37:43 +01:00
Cory Benfield 4f7b78202d
Update links in NIO docs index (#2396)
These links have fallen out-of-date. Bring them back up to speed.
2023-04-03 05:52:00 -07:00
Cory Benfield 8423040a9b
Mildly rework the NIOLock storage (#2395)
Motivation:

NIOLock uses a storage object constructed from a ManagedBuffer. In
general this is fine, but it's a tricky API to use safely and we want to
avoid violating any of its guarantees.

Modifications:

- Store the value in the header and the lock in the elements
- Add debug assertions on alignment

Result:

We'll be a bit more confident of the use of NIOLock
2023-03-27 16:37:09 +01:00
206 changed files with 6396 additions and 6058 deletions

View File

@ -1,38 +1,5 @@
# Code of Conduct
To be a truly great community, the SwiftNIO project needs to welcome developers from all walks of life, with different backgrounds, and with a wide range of experience. A diverse and friendly community will have more great ideas, more unique perspectives, and produce more great code. We will work diligently to make the SwiftNIO community welcoming to everyone.
The code of conduct for this project can be found at https://swift.org/code-of-conduct.
To give clarity of what is expected of our members, this code of conduct is based on [contributor-covenant.org](http://contributor-covenant.org). This document is used across many open source communities, and we think it articulates our values well.
### Contributor Code of Conduct v1.4
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language (e.g., prefer non-gendered words like “folks” to “guys”, non-ableist words like “soundness check” to “sanity check”, etc.)
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
This Code of Conduct applies within all project spaces managed by the SwiftNIO project, including (but not limited to) source code repositories, bug trackers, web sites, documentation, and online forums. It also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [swift-server-conduct@group.apple.com](mailto:swift-server-conduct@group.apple.com) or by flagging the behavior for moderation (e.g., in the Forums), whether you are the target of that behavior or not. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. The site of the disputed behavior is usually not an acceptable place to discuss moderation decisions, and moderators may move or remove any such discussion.
Project maintainers are held to a higher standard, and project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the projects leadership.
If you disagree with a moderation action, you can appeal to the Core Team (or individual Core Team members) privately.
This policy is adapted from the Contributor Code of Conduct [version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/).
<!-- Copyright (c) 2023 Apple Inc and the Swift Project authors. All Rights Reserved. -->

View File

@ -72,7 +72,7 @@ For this reason, whenever you add new tests **you have to run a script** that ge
### Make sure your patch works for all supported versions of swift
The CI will do this for you. You can use the docker-compose files included if you wish to check locally. Currently all versions of swift >= 5.5.2 are supported. For example usage of docker compose see the main [README](./README.md#an-alternative-using-docker-compose)
The CI will do this for you. You can use the docker-compose files included if you wish to check locally. Currently all versions of swift >= 5.6 are supported. For example usage of docker compose see the main [README](./README.md#an-alternative-using-docker-compose)
### Make sure your code is performant

View File

@ -29,4 +29,14 @@ void add_malloc_bytes_counter(intptr_t v);
void reset_malloc_bytes_counter(void);
intptr_t read_malloc_bytes_counter(void);
typedef struct {
size_t count;
int *leaked;
} LeakedFDs;
void begin_tracking_fds(void);
void track_open_fd(int fd);
void track_closed_fd(int fd);
LeakedFDs stop_tracking_fds(void);
#endif

View File

@ -2,7 +2,7 @@
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2017-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@ -12,7 +12,13 @@
//
//===----------------------------------------------------------------------===//
#include <atomic-counter.h>
#include <assert.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define MAKE_COUNTER(name) /*
*/ _Atomic long g_ ## name ## _counter = ATOMIC_VAR_INIT(0); /*
@ -34,3 +40,127 @@
MAKE_COUNTER(free)
MAKE_COUNTER(malloc)
MAKE_COUNTER(malloc_bytes)
// This section covers tracking leaked FDs.
//
// We do this by recording which FD has been set in a queue. A queue is a bad data structure here,
// but using a better one requires writing too much code, and the performance impact here is not
// going to be too bad.
typedef struct {
size_t capacity;
size_t count;
int *allocatedFDs;
} FDTracker;
static _Bool FDTracker_search_fd(const FDTracker *tracker, int fd, size_t *foundIndex) {
if (tracker == NULL) { return false; }
for (size_t i = 0; i < tracker->count; i++) {
if (tracker->allocatedFDs[i] == fd) {
if (foundIndex != NULL) {
*foundIndex = i;
}
return true;
}
}
return false;
}
static void FDTracker_remove_at_index(FDTracker *tracker, size_t index) {
assert(tracker != NULL);
assert(index < tracker->count);
// Shuffle everything down by 1 from index onwards.
const size_t lastValidTargetIndex = tracker->count - 1;
for (size_t i = index; i < lastValidTargetIndex; i++) {
tracker->allocatedFDs[i] = tracker->allocatedFDs[i + 1];
}
tracker->count--;
}
_Atomic _Bool is_tracking = ATOMIC_VAR_INIT(false);
pthread_mutex_t tracker_lock = PTHREAD_MUTEX_INITIALIZER;
FDTracker tracker = { 0 };
void begin_tracking_fds(void) {
int rc = pthread_mutex_lock(&tracker_lock);
assert(rc == 0);
assert(tracker.capacity == 0);
assert(tracker.count == 0);
assert(tracker.allocatedFDs == NULL);
tracker.allocatedFDs = calloc(1024, sizeof(int));
tracker.capacity = 1024;
atomic_store_explicit(&is_tracking, true, memory_order_release);
rc = pthread_mutex_unlock(&tracker_lock);
assert(rc == 0);
}
void track_open_fd(int fd) {
bool should_track = atomic_load_explicit(&is_tracking, memory_order_acquire);
if (!should_track) { return; }
int rc = pthread_mutex_lock(&tracker_lock);
assert(rc == 0);
// We need to not be tracking this FD already, or there's a correctness error.
assert(!FDTracker_search_fd(&tracker, fd, NULL));
// We want to append to the queue.
if (tracker.capacity == tracker.count) {
// Wuh-oh, resize. We do this by doubling.
assert((tracker.capacity * sizeof(int)) < (SIZE_MAX / 2));
size_t newCapacity = tracker.capacity * 2;
int *new = realloc(tracker.allocatedFDs, newCapacity * sizeof(int));
assert(new != NULL);
tracker.allocatedFDs = new;
tracker.capacity = newCapacity;
}
tracker.allocatedFDs[tracker.count] = fd;
tracker.count++;
rc = pthread_mutex_unlock(&tracker_lock);
assert(rc == 0);
}
void track_closed_fd(int fd) {
bool should_track = atomic_load_explicit(&is_tracking, memory_order_acquire);
if (!should_track) { return; }
int rc = pthread_mutex_lock(&tracker_lock);
assert(rc == 0);
size_t index;
if (FDTracker_search_fd(&tracker, fd, &index)) {
// We're tracking this FD, let's remove it.
FDTracker_remove_at_index(&tracker, index);
}
rc = pthread_mutex_unlock(&tracker_lock);
assert(rc == 0);
}
LeakedFDs stop_tracking_fds(void) {
int rc = pthread_mutex_lock(&tracker_lock);
assert(rc == 0);
LeakedFDs result = {
.count = tracker.count,
.leaked = tracker.allocatedFDs
};
// Clear the tracker.
tracker.allocatedFDs = NULL;
tracker.capacity = 0;
tracker.count = 0;
atomic_store_explicit(&is_tracking, false, memory_order_release);
rc = pthread_mutex_unlock(&tracker_lock);
assert(rc == 0);
return result;
}

View File

@ -16,6 +16,8 @@
#define HOOKED_FREE
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#if __APPLE__
# include <malloc/malloc.h>
#endif
@ -27,6 +29,10 @@ void *replacement_realloc(void *ptr, size_t size);
void *replacement_reallocf(void *ptr, size_t size);
void *replacement_valloc(size_t size);
int replacement_posix_memalign(void **memptr, size_t alignment, size_t size);
int replacement_socket(int domain, int type, int protocol);
int replacement_accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
int replacement_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
int replacement_close(int fildes);
#if __APPLE__
void *replacement_malloc_zone_malloc(malloc_zone_t *zone, size_t size);

View File

@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//
#include <assert.h>
#if __APPLE__
#define _GNU_SOURCE
@ -23,6 +24,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@ -148,6 +150,37 @@ int replacement_posix_memalign(void **memptr, size_t alignment, size_t size) {
JUMP_INTO_LIBC_FUN(posix_memalign, memptr, alignment, size);
}
int replacement_socket(int domain, int type, int protocol) {
int fd = socket(domain, type, protocol);
if (fd < 0) {
return fd;
}
track_open_fd(fd);
return fd;
}
int replacement_accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
int fd = accept(socket, address, address_len);
if (fd < 0) {
return fd;
}
track_open_fd(fd);
return fd;
}
int replacement_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
// Should never be called.
assert(false);
}
int replacement_close(int fildes) {
track_closed_fd(fildes);
JUMP_INTO_LIBC_FUN(close, fildes);
}
DYLD_INTERPOSE(replacement_free, free)
DYLD_INTERPOSE(replacement_malloc, malloc)
DYLD_INTERPOSE(replacement_realloc, realloc)
@ -161,4 +194,7 @@ DYLD_INTERPOSE(replacement_malloc_zone_valloc, malloc_zone_valloc)
DYLD_INTERPOSE(replacement_malloc_zone_realloc, malloc_zone_realloc)
DYLD_INTERPOSE(replacement_malloc_zone_memalign, malloc_zone_memalign)
DYLD_INTERPOSE(replacement_malloc_zone_free, malloc_zone_free)
DYLD_INTERPOSE(replacement_socket, socket)
DYLD_INTERPOSE(replacement_accept, accept)
DYLD_INTERPOSE(replacement_close, close)
#endif

View File

@ -41,16 +41,28 @@ static _Atomic ptrdiff_t g_recursive_malloc_next_free_ptr = ATOMIC_VAR_INIT(0);
static __thread bool g_in_malloc = false;
static __thread bool g_in_realloc = false;
static __thread bool g_in_free = false;
static __thread bool g_in_socket = false;
static __thread bool g_in_accept = false;
static __thread bool g_in_accept4 = false;
static __thread bool g_in_close = false;
/* The types of the variables holding the libc function pointers. */
typedef void *(*type_libc_malloc)(size_t);
typedef void *(*type_libc_realloc)(void *, size_t);
typedef void (*type_libc_free)(void *);
typedef int (*type_libc_socket)(int, int, int);
typedef int (*type_libc_accept)(int, struct sockaddr*, socklen_t *);
typedef int (*type_libc_accept4)(int, struct sockaddr *, socklen_t *, int);
typedef int (*type_libc_close)(int);
/* The (atomic) globals holding the pointer to the original libc implementation. */
_Atomic type_libc_malloc g_libc_malloc;
_Atomic type_libc_realloc g_libc_realloc;
_Atomic type_libc_free g_libc_free;
_Atomic type_libc_socket g_libc_socket;
_Atomic type_libc_accept g_libc_accept;
_Atomic type_libc_accept4 g_libc_accept4;
_Atomic type_libc_close g_libc_close;
// this is called if malloc is called whilst trying to resolve libc's realloc.
// we just vend out pointers to a large block in the BSS (which we never free).
@ -93,6 +105,30 @@ static void recursive_free(void *ptr) {
abort();
}
// this is called if socket is called whilst trying to resolve libc's socket.
static int recursive_socket(int domain, int type, int protocol) {
// not possible
abort();
}
// this is called if accept is called whilst trying to resolve libc's accept.
static int recursive_accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
// not possible
abort();
}
// this is called if accept4 is called whilst trying to resolve libc's accept4.
static int recursive_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
// not possible
abort();
}
// this is called if close is called whilst trying to resolve libc's close.
static int recursive_close(int fildes) {
// not possible
abort();
}
/* On Apple platforms getting to the original libc function from a hooked
* function is easy. On other UNIX systems this is slightly harder because we
* have to look up the function with the dynamic linker. Because that isn't
@ -192,4 +228,53 @@ int replacement_posix_memalign(void **memptr, size_t alignment, size_t size) {
}
}
static int socket_thunk(int domain, int type, int protocol) {
JUMP_INTO_LIBC_FUN(socket, domain, type, protocol);
}
int replacement_socket(int domain, int type, int protocol) {
int fd = socket_thunk(domain, type, protocol);
if (fd < 0) {
return fd;
}
track_open_fd(fd);
return fd;
}
static int accept_thunk(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
JUMP_INTO_LIBC_FUN(accept, socket, address, address_len);
}
int replacement_accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
int fd = accept_thunk(socket, address, address_len);
if (fd < 0) {
return fd;
}
track_open_fd(fd);
return fd;
}
static int accept4_thunk(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
JUMP_INTO_LIBC_FUN(accept4, sockfd, addr, addrlen, flags);
}
int replacement_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
int fd = accept4_thunk(sockfd, addr, addrlen, flags);
if (fd < 0) {
return fd;
}
track_open_fd(fd);
return fd;
}
int replacement_close(int fildes) {
track_closed_fd(fildes);
JUMP_INTO_LIBC_FUN(close, fildes);
}
#endif

View File

@ -41,6 +41,18 @@ void *valloc(size_t size) {
int posix_memalign(void **memptr, size_t alignment, size_t size) {
return replacement_posix_memalign(memptr, alignment, size);
}
int socket(int domain, int type, int protocol) {
return replacement_socket(domain, type, protocol);
}
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
return replacement_accept(socket, address, address_len);
}
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) {
return replacement_accept4(sockfd, addr, addrlen, flags);
}
int close(int fildes) {
return replacement_close(fildes);
}
#endif
void swift_main(void);

View File

@ -47,11 +47,47 @@ func waitForThreadsToQuiesce(shouldReachZero: Bool) {
} while true
}
func measureAll(_ fn: () -> Int) -> [[String: Int]] {
func measureOne(throwAway: Bool = false, _ fn: () -> Int) -> [String: Int]? {
struct Measurement {
var totalAllocations: Int
var totalAllocatedBytes: Int
var remainingAllocations: Int
var leakedFDs: [CInt]
}
extension Array where Element == Measurement {
private func printIntegerMetric(_ keyPath: KeyPath<Measurement, Int>, description desc: String, metricName k: String) {
let vs = self.map { $0[keyPath: keyPath] }
print("\(desc).\(k): \(vs.min() ?? -1)")
}
func printTotalAllocations(description: String) {
self.printIntegerMetric(\.totalAllocations, description: description, metricName: "total_allocations")
}
func printTotalAllocatedBytes(description: String) {
self.printIntegerMetric(\.totalAllocatedBytes, description: description, metricName: "total_allocated_bytes")
}
func printRemainingAllocations(description: String) {
self.printIntegerMetric(\.remainingAllocations, description: description, metricName: "remaining_allocations")
}
func printLeakedFDs(description desc: String) {
let vs = self.map { $0.leakedFDs }.filter { !$0.isEmpty }
print("\(desc).leaked_fds: \(vs.first.map { $0.count } ?? 0)")
}
}
func measureAll(trackFDs: Bool, _ fn: () -> Int) -> [Measurement] {
func measureOne(throwAway: Bool = false, trackFDs: Bool, _ fn: () -> Int) -> Measurement? {
AtomicCounter.reset_free_counter()
AtomicCounter.reset_malloc_counter()
AtomicCounter.reset_malloc_bytes_counter()
if trackFDs {
AtomicCounter.begin_tracking_fds()
}
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
autoreleasepool {
_ = fn()
@ -63,46 +99,56 @@ func measureAll(_ fn: () -> Int) -> [[String: Int]] {
let frees = AtomicCounter.read_free_counter()
let mallocs = AtomicCounter.read_malloc_counter()
let mallocedBytes = AtomicCounter.read_malloc_bytes_counter()
var leakedFDs: [CInt] = []
if trackFDs {
let leaks = AtomicCounter.stop_tracking_fds()
defer {
free(leaks.leaked)
}
leakedFDs = Array(UnsafeBufferPointer(start: leaks.leaked, count: leaks.count))
}
if mallocs - frees < 0 {
print("WARNING: negative remaining allocation count, skipping.")
return nil
}
return [
"total_allocations": mallocs,
"total_allocated_bytes": mallocedBytes,
"remaining_allocations": mallocs - frees
]
return Measurement(
totalAllocations: mallocs,
totalAllocatedBytes: mallocedBytes,
remainingAllocations: mallocs - frees,
leakedFDs: leakedFDs
)
}
_ = measureOne(throwAway: true, fn) /* pre-heat and throw away */
_ = measureOne(throwAway: true, trackFDs: trackFDs, fn) /* pre-heat and throw away */
var measurements: [[String: Int]] = []
var measurements: [Measurement] = []
for _ in 0..<10 {
if let results = measureOne(fn) {
if let results = measureOne(trackFDs: trackFDs, fn) {
measurements.append(results)
}
}
return measurements
}
func measureAndPrint(desc: String, fn: () -> Int) -> Void {
let measurements = measureAll(fn)
for k in measurements[0].keys {
let vs = measurements.map { $0[k]! }
print("\(desc).\(k): \(vs.min() ?? -1)")
}
func measureAndPrint(desc: String, trackFDs: Bool, fn: () -> Int) -> Void {
let measurements = measureAll(trackFDs: trackFDs, fn)
measurements.printTotalAllocations(description: desc)
measurements.printRemainingAllocations(description: desc)
measurements.printTotalAllocatedBytes(description: desc)
measurements.printLeakedFDs(description: desc)
print("DEBUG: \(measurements)")
}
public func measure(identifier: String, _ body: () -> Int) {
measureAndPrint(desc: identifier) {
public func measure(identifier: String, trackFDs: Bool = false, _ body: () -> Int) {
measureAndPrint(desc: identifier, trackFDs: trackFDs) {
return body()
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
func measureAll(_ fn: @escaping () async -> Int) -> [[String: Int]] {
func measureOne(throwAway: Bool = false, _ fn: @escaping () async -> Int) -> [String: Int]? {
func measureAll(trackFDs: Bool, _ fn: @escaping () async -> Int) -> [Measurement] {
func measureOne(throwAway: Bool = false, trackFDs: Bool, _ fn: @escaping () async -> Int) -> Measurement? {
func run(_ fn: @escaping () async -> Int) {
let group = DispatchGroup()
group.enter()
@ -112,9 +158,15 @@ func measureAll(_ fn: @escaping () async -> Int) -> [[String: Int]] {
}
group.wait()
}
if trackFDs {
AtomicCounter.begin_tracking_fds()
}
AtomicCounter.reset_free_counter()
AtomicCounter.reset_malloc_counter()
AtomicCounter.reset_malloc_bytes_counter()
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
autoreleasepool {
run(fn)
@ -126,22 +178,31 @@ func measureAll(_ fn: @escaping () async -> Int) -> [[String: Int]] {
let frees = AtomicCounter.read_free_counter()
let mallocs = AtomicCounter.read_malloc_counter()
let mallocedBytes = AtomicCounter.read_malloc_bytes_counter()
var leakedFDs: [CInt] = []
if trackFDs {
let leaks = AtomicCounter.stop_tracking_fds()
defer {
free(leaks.leaked)
}
leakedFDs = Array(UnsafeBufferPointer(start: leaks.leaked, count: leaks.count))
}
if mallocs - frees < 0 {
print("WARNING: negative remaining allocation count, skipping.")
return nil
}
return [
"total_allocations": mallocs,
"total_allocated_bytes": mallocedBytes,
"remaining_allocations": mallocs - frees
]
return Measurement(
totalAllocations: mallocs,
totalAllocatedBytes: mallocedBytes,
remainingAllocations: mallocs - frees,
leakedFDs: leakedFDs
)
}
_ = measureOne(throwAway: true, fn) /* pre-heat and throw away */
_ = measureOne(throwAway: true, trackFDs: trackFDs, fn) /* pre-heat and throw away */
var measurements: [[String: Int]] = []
var measurements: [Measurement] = []
for _ in 0..<10 {
if let results = measureOne(fn) {
if let results = measureOne(trackFDs: trackFDs, fn) {
measurements.append(results)
}
}
@ -149,16 +210,15 @@ func measureAll(_ fn: @escaping () async -> Int) -> [[String: Int]] {
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
func measureAndPrint(desc: String, fn: @escaping () async -> Int) -> Void {
let measurements = measureAll(fn)
for k in measurements[0].keys {
let vs = measurements.map { $0[k]! }
print("\(desc).\(k): \(vs.min() ?? -1)")
}
print("DEBUG: \(measurements)")
func measureAndPrint(desc: String, trackFDs: Bool, fn: @escaping () async -> Int) -> Void {
let measurements = measureAll(trackFDs: trackFDs, fn)
measurements.printTotalAllocations(description: desc)
measurements.printRemainingAllocations(description: desc)
measurements.printTotalAllocatedBytes(description: desc)
measurements.printLeakedFDs(description: desc)
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public func measure(identifier: String, _ body: @escaping () async -> Int) {
measureAndPrint(desc: identifier, fn: body)
public func measure(identifier: String, trackFDs: Bool = false, _ body: @escaping () async -> Int) {
measureAndPrint(desc: identifier, trackFDs: trackFDs, fn: body)
}

View File

@ -22,7 +22,7 @@ function make_package() {
fi
cat > "$tmpdir/syscallwrapper/Package.swift" <<"EOF"
// swift-tools-version:5.5
// swift-tools-version:5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

View File

@ -35,13 +35,16 @@ for test in "${all_tests[@]}"; do
test_case=${test_case#test_*}
total_allocations=$(grep "^test_$test_case.total_allocations:" "$tmp/output" | cut -d: -f2 | sed 's/ //g')
not_freed_allocations=$(grep "^test_$test_case.remaining_allocations:" "$tmp/output" | cut -d: -f2 | sed 's/ //g')
leaked_fds=$(grep "^test_$test_case.leaked_fds:" "$tmp/output" | cut -d: -f2 | sed 's/ //g')
max_allowed_env_name="MAX_ALLOCS_ALLOWED_$test_case"
info "$test_case: allocations not freed: $not_freed_allocations"
info "$test_case: total number of mallocs: $total_allocations"
info "$test_case: leaked fds: $leaked_fds"
assert_less_than "$not_freed_allocations" 5 # allow some slack
assert_greater_than "$not_freed_allocations" -5 # allow some slack
assert_less_than "$leaked_fds" 1 # No slack allowed here though
if [[ -z "${!max_allowed_env_name+x}" ]]; then
if [[ -z "${!max_allowed_env_name+x}" ]]; then
warn "no reference number of allocations set (set to \$$max_allowed_env_name)"

View File

@ -0,0 +1,44 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
import NIOPosix
func run(identifier: String) {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try! group.syncShutdownGracefully()
}
let serverConnection = try! ServerBootstrap(group: group)
.bind(host: "localhost", port: 0)
.wait()
let serverAddress = serverConnection.localAddress!
let clientBootstrap = ClientBootstrap(group: group)
measure(identifier: identifier, trackFDs: true) {
let iterations = 1000
for _ in 0..<iterations {
let conn = clientBootstrap.connect(to: serverAddress)
let _: Void? = try? conn.flatMap { channel in
(channel as! SocketOptionProvider).setSoLinger(linger(l_onoff: 1, l_linger: 0)).flatMap {
channel.close()
}
}.wait()
}
return iterations
}
}

View File

@ -68,7 +68,7 @@ var targets: [PackageDescription.Target] = [
name: "CNIOLLHTTP",
cSettings: [.define("LLHTTP_STRICT_MODE")]
),
.target(name: "NIOTLS", dependencies: ["NIO", "NIOCore"]),
.target(name: "NIOTLS", dependencies: ["NIO", "NIOCore", swiftCollections]),
.executableTarget(name: "NIOChatServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
@ -100,11 +100,11 @@ var targets: [PackageDescription.Target] = [
.executableTarget(name: "NIOAsyncAwaitDemo",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1"]),
.testTarget(name: "NIOCoreTests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOFoundationCompat"]),
dependencies: ["NIOCore", "NIOEmbedded", "NIOFoundationCompat", swiftAtomics]),
.testTarget(name: "NIOEmbeddedTests",
dependencies: ["NIOConcurrencyHelpers", "NIOCore", "NIOEmbedded"]),
.testTarget(name: "NIOPosixTests",
dependencies: ["NIOPosix", "NIOCore", "NIOFoundationCompat", "NIOTestUtils", "NIOConcurrencyHelpers", "NIOEmbedded", "CNIOLinux"]),
dependencies: ["NIOPosix", "NIOCore", "NIOFoundationCompat", "NIOTestUtils", "NIOConcurrencyHelpers", "NIOEmbedded", "CNIOLinux", "NIOTLS"]),
.testTarget(name: "NIOConcurrencyHelpersTests",
dependencies: ["NIOConcurrencyHelpers", "NIOCore"]),
.testTarget(name: "NIODataStructuresTests",
@ -112,7 +112,7 @@ var targets: [PackageDescription.Target] = [
.testTarget(name: "NIOHTTP1Tests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOPosix", "NIOHTTP1", "NIOFoundationCompat", "NIOTestUtils"]),
.testTarget(name: "NIOTLSTests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOTLS", "NIOFoundationCompat"]),
dependencies: ["NIOCore", "NIOEmbedded", "NIOTLS", "NIOFoundationCompat", "NIOTestUtils"]),
.testTarget(name: "NIOWebSocketTests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOWebSocket"]),
.testTarget(name: "NIOTestUtilsTests",
@ -139,9 +139,9 @@ let package = Package(
.library(name: "NIOTestUtils", targets: ["NIOTestUtils"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://gitlink.org.cn/dnrops/swift-atomics.git", from: "1.1.0"),
.package(url: "https://gitlink.org.cn/dnrops/swift-collections.git", from: "1.0.2"),
.package(url: "https://gitlink.org.cn/dnrops/swift-docc-plugin", from: "1.0.0"),
],
targets: targets
)

View File

@ -1,145 +0,0 @@
// swift-tools-version:5.5
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import PackageDescription
let swiftAtomics: PackageDescription.Target.Dependency = .product(name: "Atomics", package: "swift-atomics")
let swiftCollections: PackageDescription.Target.Dependency = .product(name: "DequeModule", package: "swift-collections")
var targets: [PackageDescription.Target] = [
.target(name: "NIOCore",
dependencies: ["NIOConcurrencyHelpers", "CNIOLinux", "CNIOWindows", swiftCollections, swiftAtomics]),
.target(name: "_NIODataStructures"),
.target(name: "NIOEmbedded",
dependencies: ["NIOCore",
"NIOConcurrencyHelpers",
"_NIODataStructures",
swiftAtomics]),
.target(name: "NIOPosix",
dependencies: ["CNIOLinux",
"CNIODarwin",
"CNIOWindows",
"NIOConcurrencyHelpers",
"NIOCore",
"_NIODataStructures",
swiftAtomics]),
.target(name: "NIO",
dependencies: ["NIOCore",
"NIOEmbedded",
"NIOPosix"]),
.target(name: "_NIOConcurrency",
dependencies: ["NIO", "NIOCore"]),
.target(name: "NIOFoundationCompat", dependencies: ["NIO", "NIOCore"]),
.target(name: "CNIOAtomics", dependencies: []),
.target(name: "CNIOSHA1", dependencies: []),
.target(name: "CNIOLinux", dependencies: []),
.target(name: "CNIODarwin", dependencies: [], cSettings: [.define("__APPLE_USE_RFC_3542")]),
.target(name: "CNIOWindows", dependencies: []),
.target(name: "NIOConcurrencyHelpers",
dependencies: ["CNIOAtomics"]),
.target(name: "NIOHTTP1",
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOLLHTTP"]),
.executableTarget(name: "NIOEchoServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.executableTarget(name: "NIOEchoClient",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.executableTarget(name: "NIOHTTP1Server",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.executableTarget(name: "NIOHTTP1Client",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.target(
name: "CNIOLLHTTP",
cSettings: [.define("LLHTTP_STRICT_MODE")]
),
.target(name: "NIOTLS", dependencies: ["NIO", "NIOCore"]),
.executableTarget(name: "NIOChatServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.executableTarget(name: "NIOChatClient",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.target(name: "NIOWebSocket",
dependencies: ["NIO", "NIOCore", "NIOHTTP1", "CNIOSHA1"]),
.executableTarget(name: "NIOWebSocketServer",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOWebSocket"],
exclude: ["README.md"]),
.executableTarget(name: "NIOWebSocketClient",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOWebSocket"],
exclude: ["README.md"]),
.executableTarget(name: "NIOPerformanceTester",
dependencies: ["NIOPosix", "NIOCore", "NIOEmbedded", "NIOHTTP1", "NIOFoundationCompat", "NIOWebSocket"]),
.executableTarget(name: "NIOMulticastChat",
dependencies: ["NIOPosix", "NIOCore"]),
.executableTarget(name: "NIOUDPEchoServer",
dependencies: ["NIOPosix", "NIOCore"],
exclude: ["README.md"]),
.executableTarget(name: "NIOUDPEchoClient",
dependencies: ["NIOPosix", "NIOCore"],
exclude: ["README.md"]),
.target(name: "NIOTestUtils",
dependencies: ["NIOPosix", "NIOCore", "NIOEmbedded", "NIOHTTP1", swiftAtomics]),
.executableTarget(name: "NIOCrashTester",
dependencies: ["NIOPosix", "NIOCore", "NIOEmbedded", "NIOHTTP1", "NIOWebSocket", "NIOFoundationCompat"]),
.executableTarget(name: "NIOAsyncAwaitDemo",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1"]),
.testTarget(name: "NIOCoreTests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOFoundationCompat"]),
.testTarget(name: "NIOEmbeddedTests",
dependencies: ["NIOConcurrencyHelpers", "NIOCore", "NIOEmbedded"]),
.testTarget(name: "NIOPosixTests",
dependencies: ["NIOPosix", "NIOCore", "NIOFoundationCompat", "NIOTestUtils", "NIOConcurrencyHelpers", "NIOEmbedded"]),
.testTarget(name: "NIOConcurrencyHelpersTests",
dependencies: ["NIOConcurrencyHelpers", "NIOCore"]),
.testTarget(name: "NIODataStructuresTests",
dependencies: ["_NIODataStructures"]),
.testTarget(name: "NIOHTTP1Tests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOPosix", "NIOHTTP1", "NIOFoundationCompat", "NIOTestUtils"]),
.testTarget(name: "NIOTLSTests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOTLS", "NIOFoundationCompat"]),
.testTarget(name: "NIOWebSocketTests",
dependencies: ["NIOCore", "NIOEmbedded", "NIOWebSocket"]),
.testTarget(name: "NIOTestUtilsTests",
dependencies: ["NIOTestUtils", "NIOCore", "NIOEmbedded", "NIOPosix"]),
.testTarget(name: "NIOFoundationCompatTests",
dependencies: ["NIOCore", "NIOFoundationCompat"]),
.testTarget(name: "NIOTests",
dependencies: ["NIO"]),
]
let package = Package(
name: "swift-nio",
products: [
.library(name: "NIOCore", targets: ["NIOCore"]),
.library(name: "NIO", targets: ["NIO"]),
.library(name: "NIOEmbedded", targets: ["NIOEmbedded"]),
.library(name: "NIOPosix", targets: ["NIOPosix"]),
.library(name: "_NIOConcurrency", targets: ["_NIOConcurrency"]),
.library(name: "NIOTLS", targets: ["NIOTLS"]),
.library(name: "NIOHTTP1", targets: ["NIOHTTP1"]),
.library(name: "NIOConcurrencyHelpers", targets: ["NIOConcurrencyHelpers"]),
.library(name: "NIOFoundationCompat", targets: ["NIOFoundationCompat"]),
.library(name: "NIOWebSocket", targets: ["NIOWebSocket"]),
.library(name: "NIOTestUtils", targets: ["NIOTestUtils"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.2"),
],
targets: targets
)

View File

@ -11,7 +11,7 @@ It's like [Netty](https://netty.io), but written for Swift.
The SwiftNIO project is split across multiple repositories:
Repository | NIO 2 (Swift 5.5.2+)
Repository | NIO 2 (Swift 5.6+)
--- | ---
[https://github.com/apple/swift-nio][repo-nio] <br> SwiftNIO core | `from: "2.0.0"`
[https://github.com/apple/swift-nio-ssl][repo-nio-ssl] <br> TLS (SSL) support | `from: "2.0.0"`
@ -70,14 +70,15 @@ Redis | ✅ | ❌ | [mordil/swift-redi-stack](https://gitlab.com/Mordil/swift-re
This is the current version of SwiftNIO and will be supported for the foreseeable future.
The most recent versions of SwiftNIO support Swift 5.5.2 and newer. The minimum Swift version supported by SwiftNIO releases are detailed below:
The most recent versions of SwiftNIO support Swift 5.6 and newer. The minimum Swift version supported by SwiftNIO releases are detailed below:
SwiftNIO | Minimum Swift Version
--------------------|----------------------
`2.0.0 ..< 2.30.0` | 5.0
`2.30.0 ..< 2.40.0` | 5.2
`2.40.0 ..< 2.43.0` | 5.4
`2.43.0 ...` | 5.5.2
`2.43.0 ..< 2.51.0` | 5.5.2
`2.51.0 ...` | 5.6
### SwiftNIO 1
SwiftNIO 1 is considered end of life - it is strongly recommended that you move to a newer version. The Core NIO team does not actively work on this version. No new features will be added to this version but PRs which fix bugs or security vulnerabilities will be accepted until the end of May 2022.
@ -331,7 +332,7 @@ have a few prerequisites installed on your system.
### Linux
- Swift 5.5.2 or newer from [swift.org/download](https://swift.org/download/#releases). We always recommend to use the latest released version.
- Swift 5.6 or newer from [swift.org/download](https://swift.org/download/#releases). We always recommend to use the latest released version.
- netcat (for integration tests only)
- lsof (for integration tests only)
- shasum (for integration tests only)

View File

@ -17,8 +17,10 @@ team would create the following patch releases:
Swift 5.2 and 5.3
* NIO 2.42. + plus next patch release to address the issue for projects that support
Swift 5.4 and later
* mainline + plus next patch release to address the issue for projects that support
* NIO 2.50. + plus next patch release to address the issue for projects that support
Swift 5.5.2 and later
* mainline + plus next patch release to address the issue for projects that support
Swift 5.6 and later
SwiftNIO 1.x is considered end of life and will not receive any security patches.

View File

@ -8,7 +8,7 @@ SwiftNIO is a cross-platform asynchronous event-driven network application frame
It's like Netty, but written for Swift.
## Repository organization
### Repository organization
The SwiftNIO project is split across multiple repositories:
@ -21,13 +21,13 @@ Repo | Usage
[swift-nio-transport-services][repo-nio-transport-services] | First-class support for macOS, iOS, tvOS, and watchOS
[swift-nio-ssh][repo-nio-ssh] | SSH support
## Modules
### Modules
SwiftNIO has a number of products that provide different functionality. This package includes the following products:
- ``NIO``. This is an umbrella module exporting [NIOCore][module-core], [NIOEmbedded][module-embedded] and [NIOPosix][module-posix].
- [NIOCore][module-core]. This provides the core abstractions and types for using SwiftNIO (see ["Conceptual Overview"](#conceptual-overview) for more details). Most NIO extension projects that provide things like new [`EventLoop`s][el] and [`Channel`s][c] or new protocol implementations should only need to depend on [NIOCore][module-core].
- [NIOPosix][module-posix]. This provides the primary [`EventLoopGroup`], [`EventLoop`][el], and [`Channel`s][c] for use on POSIX-based systems. This is our high performance core I/O layer. In general, this should only be imported by projects that plan to do some actual I/O, such as high-level protocol implementations or applications.
- [NIOCore][module-core]. This provides the core abstractions and types for using SwiftNIO (see ["Conceptual Overview"](#Conceptual-Overview) for more details). Most NIO extension projects that provide things like new [`EventLoop`s][el] and [`Channel`s][c] or new protocol implementations should only need to depend on [NIOCore][module-core].
- [NIOPosix][module-posix]. This provides the primary [`EventLoopGroup`][elg], [`EventLoop`][el], and [`Channel`s][c] for use on POSIX-based systems. This is our high performance core I/O layer. In general, this should only be imported by projects that plan to do some actual I/O, such as high-level protocol implementations or applications.
- [NIOEmbedded][module-embedded]. This provides [`EmbeddedChannel`][ec] and [`EmbeddedEventLoop`][eel], implementations of the [NIOCore][module-core] abstractions that provide fine-grained control over their execution. These are most often used for testing, but can also be used to drive protocol implementations in a way that is decoupled from networking altogether.
- [NIOConcurrencyHelpers][module-concurrency-helpers]. This provides a few low-level concurrency primitives that are used by NIO implementations, such as locks and atomics.
- [NIOFoundationCompat][module-foundation-compatibility]. This extends a number of NIO types for better interoperation with Foundation data types. If you are working with Foundation data types such as `Data`, you should import this.
@ -36,7 +36,7 @@ SwiftNIO has a number of products that provide different functionality. This pac
- [NIOWebSocket][module-websocket]. This provides a low-level WebSocket protocol implementation.
- [NIOTestUtils][module-test-utilities]. This provides a number of helpers for testing projects that use SwiftNIO.
## Conceptual Overview
### Conceptual Overview
SwiftNIO is fundamentally a low-level tool for building high-performance networking applications in Swift. It particularly targets those use-cases where using a "thread-per-connection" model of concurrency is inefficient or untenable. This is a common limitation when building servers that use a large number of relatively low-utilization connections, such as HTTP servers.
@ -46,7 +46,7 @@ SwiftNIO does not aim to provide high-level solutions like, for example, web fra
The following sections will describe the low-level tools that SwiftNIO provides, and provide a quick overview of how to work with them. If you feel comfortable with these concepts, then you can skip right ahead to the other sections of this document.
### Basic Architecture
#### Basic Architecture
The basic building blocks of SwiftNIO are the following 8 types of objects:
@ -61,7 +61,7 @@ The basic building blocks of SwiftNIO are the following 8 types of objects:
All SwiftNIO applications are ultimately constructed of these various components.
#### EventLoops and EventLoopGroups
##### EventLoops and EventLoopGroups
The basic I/O primitive of SwiftNIO is the event loop. The event loop is an object that waits for events (usually I/O related events, such as "data received") to happen and then fires some kind of callback when they do. In almost all SwiftNIO applications there will be relatively few event loops: usually only one or two per CPU core the application wants to use. Generally speaking event loops run for the entire lifetime of your application, spinning in an endless loop dispatching events.
@ -71,7 +71,7 @@ In SwiftNIO today there is one [`EventLoopGroup`][elg] implementation, and two [
[`EventLoop`][el]s have a number of important properties. Most vitally, they are the way all work gets done in SwiftNIO applications. In order to ensure thread-safety, any work that wants to be done on almost any of the other objects in SwiftNIO must be dispatched via an [`EventLoop`][el]. [`EventLoop`][el] objects own almost all the other objects in a SwiftNIO application, and understanding their execution model is critical for building high-performance SwiftNIO applications.
#### Channels, Channel Handlers, Channel Pipelines, and Channel Contexts
##### Channels, Channel Handlers, Channel Pipelines, and Channel Contexts
While [`EventLoop`][el]s are critical to the way SwiftNIO works, most users will not interact with them substantially beyond asking them to create [`EventLoopPromise`][elp]s and to schedule work. The parts of a SwiftNIO application most users will spend the most time interacting with are [`Channel`][c]s and [`ChannelHandler`][ch]s.
@ -93,7 +93,7 @@ SwiftNIO ships with many [`ChannelHandler`][ch]s built in that provide useful fu
Additionally, SwiftNIO ships with a few [`Channel`][c] implementations. In particular, it ships with `ServerSocketChannel`, a [`Channel`][c] for sockets that accept inbound connections; `SocketChannel`, a [`Channel`][c] for TCP connections; and `DatagramChannel`, a [`Channel`][c] for UDP sockets. All of these are provided by the [NIOPosix][module-posix] module. It also provides[`EmbeddedChannel`][ec], a [`Channel`][c] primarily used for testing, provided by the [NIOEmbedded][module-embedded] module.
##### A Note on Blocking
###### A Note on Blocking
One of the important notes about [`ChannelPipeline`][cp]s is that they are thread-safe. This is very important for writing SwiftNIO applications, as it allows you to write much simpler [`ChannelHandler`][ch]s in the knowledge that they will not require synchronization.
@ -101,15 +101,15 @@ However, this is achieved by dispatching all code on the [`ChannelPipeline`][cp]
This is a common concern while writing SwiftNIO applications. If it is useful to write code in a blocking style, it is highly recommended that you dispatch work to a different thread when you're done with it in your pipeline.
#### Bootstrap
##### Bootstrap
While it is possible to configure and register [`Channel`][c]s with [`EventLoop`][el]s directly, it is generally more useful to have a higher-level abstraction to handle this work.
For this reason, SwiftNIO ships a number of `Bootstrap` objects whose purpose is to streamline the creation of channels. Some `Bootstrap` objects also provide other functionality, such as support for Happy Eyeballs for making TCP connection attempts.
Currently SwiftNIO ships with three `Bootstrap` objects in the [NIOPosix][module-posix] module: [`ServerBootstrap`](./NIOPosix/Classes/ServerBootstrap.html), for bootstrapping listening channels; [`ClientBootstrap`](./NIOPosix/Classes/ClientBootstrap.html), for bootstrapping client TCP channels; and [`DatagramBootstrap`](./NIOPosix/Classes/DatagramBootstrap.html) for bootstrapping UDP channels.
Currently SwiftNIO ships with three `Bootstrap` objects in the [NIOPosix][module-posix] module: [`ServerBootstrap`][sb], for bootstrapping listening channels; [`ClientBootstrap`][cb], for bootstrapping client TCP channels; and [`DatagramBootstrap`][db] for bootstrapping UDP channels.
#### ByteBuffer
##### ByteBuffer
The majority of the work in a SwiftNIO application involves shuffling buffers of bytes around. At the very least, data is sent and received to and from the network in the form of buffers of bytes. For this reason it's very important to have a high-performance data structure that is optimized for the kind of work SwiftNIO applications perform.
@ -121,7 +121,7 @@ In general, it is highly recommended that you use the [`ByteBuffer`][bb] in its
For more details on the API of [`ByteBuffer`][bb], please see our API documentation, linked below.
#### Promises and Futures
##### Promises and Futures
One major difference between writing concurrent code and writing synchronous code is that not all actions will complete immediately. For example, when you write data on a channel, it is possible that the event loop will not be able to immediately flush that write out to the network. For this reason, SwiftNIO provides [`EventLoopPromise<T>`][elp] and [`EventLoopFuture<T>`][elf] to manage operations that complete *asynchronously*. These types are provided by the [NIOCore][module-core] module.
@ -133,7 +133,7 @@ Another important topic for consideration is the difference between how the prom
There are several functions for applying callbacks to [`EventLoopFuture<T>`][elf], depending on how and when you want them to execute. Details of these functions is left to the API documentation.
### Design Philosophy
#### Design Philosophy
SwiftNIO is designed to be a powerful tool for building networked applications and frameworks, but it is not intended to be the perfect solution for all levels of abstraction. SwiftNIO is tightly focused on providing the basic I/O primitives and protocol implementations at low levels of abstraction, leaving more expressive but slower abstractions to the wider community to build. The intention is that SwiftNIO will be a building block for server-side applications, not necessarily the framework those applications will use directly.
@ -151,29 +151,32 @@ The core SwiftNIO repository will contain a few extremely important protocol imp
[repo-nio-transport-services]: https://github.com/apple/swift-nio-transport-services
[repo-nio-ssh]: https://github.com/apple/swift-nio-ssh
[module-core]: ./NIOCore
[module-posix]: ./NIOPosix
[module-embedded]: ./NIOEmbedded
[module-concurrency-helpers]: ./NIOConcurrencyHelpers
[module-embedded]: ./NIOEmbedded
[module-foundation-compatibility]: ./NIOFoundationCompat
[module-http1]: ./NIOHTTP1
[module-tls]: ./NIOTLS
[module-websocket]: ./NIOWebSocket
[module-test-utilities]: ./NIOTestUtils
[module-core]: ./niocore
[module-posix]: ./nioposix
[module-embedded]: ./nioembedded
[module-concurrency-helpers]: ./nioconcurrencyhelpers
[module-embedded]: ./nioembedded
[module-foundation-compatibility]: ./niofoundationcompat
[module-http1]: ./niohttp1
[module-tls]: ./niotls
[module-websocket]: ./niowebsocket
[module-test-utilities]: ./niotestutils
[ch]: ./NIOCore/Protocols/ChannelHandler.html
[c]: ./NIOCore/Protocols/Channel.html
[chc]: ./NIOCore/Classes/ChannelHandlerContext.html
[ec]: ./NIOCore/Classes/EmbeddedChannel.html
[el]: ./NIOCore/Protocols/EventLoop.html
[eel]: ./NIOCore/Classes/EmbeddedEventLoop.html
[elg]: ./NIOCore/Protocols/EventLoopGroup.html
[bb]: ./NIOCore/Structs/ByteBuffer.html
[elf]: ./NIOCore/Classes/EventLoopFuture.html
[elp]: ./NIOCore/Structs/EventLoopPromise.html
[cp]: ./NIOCore/Classes/ChannelPipeline.html
[mtelg]: ./NIOPosix/Classes/MultiThreadedEventLoopGroup.html
[ch]: ./niocore/channelhandler
[c]: ./niocore/channel
[chc]: ./niocore/channelhandlercontext
[ec]: ./nioembedded/embeddedchannel
[el]: ./niocore/eventloop
[eel]: ./nioembedded/embeddedeventloop
[elg]: ./niocore/eventloopgroup
[bb]: ./niocore/bytebuffer
[elf]: ./niocore/eventloopfuture
[elp]: ./niocore/eventlooppromise
[cp]: ./niocore/channelpipeline
[mtelg]: ./nioposix/multithreadedeventloopgroup
[sb]: ./nioposix/serverbootstrap
[cb]: ./nioposix/clientbootstrap
[db]: ./nioposix/datagrambootstrap
[pthreads]: https://en.wikipedia.org/wiki/POSIX_Threads
[kqueue]: https://en.wikipedia.org/wiki/Kqueue
[epoll]: https://en.wikipedia.org/wiki/Epoll

View File

@ -35,6 +35,8 @@ enum LockOperations { }
extension LockOperations {
@inlinable
static func create(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
mutex.assertValidAlignment()
#if os(Windows)
InitializeSRWLock(mutex)
#else
@ -51,6 +53,8 @@ extension LockOperations {
@inlinable
static func destroy(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
mutex.assertValidAlignment()
#if os(Windows)
// SRWLOCK does not need to be free'd
#else
@ -61,6 +65,8 @@ extension LockOperations {
@inlinable
static func lock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
mutex.assertValidAlignment()
#if os(Windows)
AcquireSRWLockExclusive(mutex)
#else
@ -71,6 +77,8 @@ extension LockOperations {
@inlinable
static func unlock(_ mutex: UnsafeMutablePointer<LockPrimitive>) {
mutex.assertValidAlignment()
#if os(Windows)
ReleaseSRWLockExclusive(mutex)
#else
@ -83,19 +91,43 @@ extension LockOperations {
// Tail allocate both the mutex and a generic value using ManagedBuffer.
// Both the header pointer and the elements pointer are stable for
// the class's entire lifetime.
//
// However, for safety reasons, we elect to place the lock in the "elements"
// section of the buffer instead of the head. The reasoning here is subtle,
// so buckle in.
//
// _As a practical matter_, the implementation of ManagedBuffer ensures that
// the pointer to the header is stable across the lifetime of the class, and so
// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader`
// the value of the header pointer will be the same. This is because ManagedBuffer uses
// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure
// that it does not invoke any weird Swift accessors that might copy the value.
//
// _However_, the header is also available via the `.header` field on the ManagedBuffer.
// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends
// do not interact with Swift's exclusivity model. That is, the various `with` functions do not
// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because
// there's literally no other way to perform the access, but for `.header` it's entirely possible
// to accidentally recursively read it.
//
// Our implementation is free from these issues, so we don't _really_ need to worry about it.
// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive
// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry,
// and future maintainers will be happier that we were cautious.
//
// See also: https://github.com/apple/swift/pull/40000
@usableFromInline
final class LockStorage<Value>: ManagedBuffer<LockPrimitive, Value> {
final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
@inlinable
static func create(value: Value) -> Self {
let buffer = Self.create(minimumCapacity: 1) { _ in
return LockPrimitive()
return value
}
let storage = unsafeDowncast(buffer, to: Self.self)
storage.withUnsafeMutablePointers { lockPtr, valuePtr in
storage.withUnsafeMutablePointers { _, lockPtr in
LockOperations.create(lockPtr)
valuePtr.initialize(to: value)
}
return storage
@ -103,36 +135,35 @@ final class LockStorage<Value>: ManagedBuffer<LockPrimitive, Value> {
@inlinable
func lock() {
self.withUnsafeMutablePointerToHeader { lockPtr in
self.withUnsafeMutablePointerToElements { lockPtr in
LockOperations.lock(lockPtr)
}
}
@inlinable
func unlock() {
self.withUnsafeMutablePointerToHeader { lockPtr in
self.withUnsafeMutablePointerToElements { lockPtr in
LockOperations.unlock(lockPtr)
}
}
@inlinable
deinit {
self.withUnsafeMutablePointers { lockPtr, valuePtr in
self.withUnsafeMutablePointerToElements { lockPtr in
LockOperations.destroy(lockPtr)
valuePtr.deinitialize(count: 1)
}
}
@inlinable
func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
try self.withUnsafeMutablePointerToHeader { lockPtr in
try self.withUnsafeMutablePointerToElements { lockPtr in
return try body(lockPtr)
}
}
@inlinable
func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
try self.withUnsafeMutablePointers { lockPtr, valuePtr in
try self.withUnsafeMutablePointers { valuePtr, lockPtr in
LockOperations.lock(lockPtr)
defer { LockOperations.unlock(lockPtr) }
return try mutate(&valuePtr.pointee)
@ -209,3 +240,10 @@ extension NIOLock {
}
extension NIOLock: Sendable {}
extension UnsafeMutablePointer {
@inlinable
func assertValidAlignment() {
assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0)
}
}

View File

@ -263,7 +263,7 @@ extension AsyncSequence where Element: RandomAccessCollection, Element.Element =
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension AsyncSequence where Element == ByteBuffer {
/// Accumulates an ``Swift/AsyncSequence`` of ``ByteBuffer``s into a single `accumulationBuffer`.
/// Accumulates an `AsyncSequence` of ``ByteBuffer``s into a single `accumulationBuffer`.
/// - Parameters:
/// - accumulationBuffer: buffer to write all the elements of `self` into
/// - maxBytes: The maximum number of bytes this method is allowed to write into `accumulationBuffer`
@ -285,7 +285,7 @@ extension AsyncSequence where Element == ByteBuffer {
}
}
/// Accumulates an ``Swift/AsyncSequence`` of ``ByteBuffer``s into a single ``ByteBuffer``.
/// Accumulates an `AsyncSequence` of ``ByteBuffer``s into a single ``ByteBuffer``.
/// - Parameters:
/// - maxBytes: The maximum number of bytes this method is allowed to accumulate
/// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`.

View File

@ -0,0 +1,241 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// Wraps a NIO ``Channel`` object into a form suitable for use in Swift Concurrency.
///
/// ``NIOAsyncChannel`` abstracts the notion of a NIO ``Channel`` into something that
/// can safely be used in a structured concurrency context. In particular, this exposes
/// the following functionality:
///
/// - reads are presented as an `AsyncSequence`
/// - writes can be written to with async functions on a writer, providing backpressure
/// - channels can be closed seamlessly
///
/// This type does not replace the full complexity of NIO's ``Channel``. In particular, it
/// does not expose the following functionality:
///
/// - user events
/// - traditional NIO backpressure such as writability signals and the ``Channel/read()`` call
///
/// Users are encouraged to separate their ``ChannelHandler``s into those that implement
/// protocol-specific logic (such as parsers and encoders) and those that implement business
/// logic. Protocol-specific logic should be implemented as a ``ChannelHandler``, while business
/// logic should use ``NIOAsyncChannel`` to consume and produce data to the network.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@_spi(AsyncChannel)
public final class NIOAsyncChannel<Inbound: Sendable, Outbound: Sendable>: Sendable {
/// The underlying channel being wrapped by this ``NIOAsyncChannel``.
@_spi(AsyncChannel)
public let channel: Channel
/// The stream of inbound messages.
@_spi(AsyncChannel)
public let inboundStream: NIOAsyncChannelInboundStream<Inbound>
/// The writer for writing outbound messages.
@_spi(AsyncChannel)
public let outboundWriter: NIOAsyncChannelOutboundWriter<Outbound>
/// Initializes a new ``NIOAsyncChannel`` wrapping a ``Channel``.
///
/// - Important: This **must** be called on the channel's event loop otherwise this init will crash. This is necessary because
/// we must install the handlers before any other event in the pipeline happens otherwise we might drop reads.
///
/// - Parameters:
/// - channel: The ``Channel`` to wrap.
/// - backpressureStrategy: The backpressure strategy of the ``NIOAsyncChannel/inboundStream``.
/// - isOutboundHalfClosureEnabled: If outbound half closure should be enabled. Outbound half closure is triggered once
/// the ``NIOAsyncChannelWriter`` is either finished or deinitialized.
/// - inboundType: The ``NIOAsyncChannel/inboundStream`` message's type.
/// - outboundType: The ``NIOAsyncChannel/outboundWriter`` message's type.
@inlinable
@_spi(AsyncChannel)
public init(
synchronouslyWrapping channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark? = nil,
isOutboundHalfClosureEnabled: Bool = false,
inboundType: Inbound.Type = Inbound.self,
outboundType: Outbound.Type = Outbound.self
) throws {
channel.eventLoop.preconditionInEventLoop()
self.channel = channel
(self.inboundStream, self.outboundWriter) = try channel._syncAddAsyncHandlers(
backpressureStrategy: backpressureStrategy,
isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled
)
}
/// Initializes a new ``NIOAsyncChannel`` wrapping a ``Channel`` where the outbound type is `Never`.
///
/// This initializer will finish the ``NIOAsyncChannel/outboundWriter`` immediately.
///
/// - Important: This **must** be called on the channel's event loop otherwise this init will crash. This is necessary because
/// we must install the handlers before any other event in the pipeline happens otherwise we might drop reads.
///
/// - Parameters:
/// - channel: The ``Channel`` to wrap.
/// - backpressureStrategy: The backpressure strategy of the ``NIOAsyncChannel/inboundStream``.
/// - isOutboundHalfClosureEnabled: If outbound half closure should be enabled. Outbound half closure is triggered once
/// the ``NIOAsyncChannelWriter`` is either finished or deinitialized.
/// - inboundType: The ``NIOAsyncChannel/inboundStream`` message's type.
@inlinable
@_spi(AsyncChannel)
public init(
synchronouslyWrapping channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark? = nil,
isOutboundHalfClosureEnabled: Bool = false,
inboundType: Inbound.Type = Inbound.self
) throws where Outbound == Never {
channel.eventLoop.preconditionInEventLoop()
self.channel = channel
(self.inboundStream, self.outboundWriter) = try channel._syncAddAsyncHandlers(
backpressureStrategy: backpressureStrategy,
isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled
)
self.outboundWriter.finish()
}
@inlinable
@_spi(AsyncChannel)
public init(
channel: Channel,
inboundStream: NIOAsyncChannelInboundStream<Inbound>,
outboundWriter: NIOAsyncChannelOutboundWriter<Outbound>
) {
channel.eventLoop.preconditionInEventLoop()
self.channel = channel
self.inboundStream = inboundStream
self.outboundWriter = outboundWriter
}
@inlinable
@_spi(AsyncChannel)
public static func wrapAsyncChannelForBootstrapBind(
synchronouslyWrapping channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark? = nil,
isOutboundHalfClosureEnabled: Bool = false,
transformationClosure: @escaping (Channel) -> EventLoopFuture<Inbound>
) throws -> NIOAsyncChannel<Inbound, Outbound> where Outbound == Never {
channel.eventLoop.preconditionInEventLoop()
let (inboundStream, outboundWriter): (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) = try channel._syncAddAsyncHandlersForBootstrapBind(
backpressureStrategy: backpressureStrategy,
isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled,
transformationClosure: transformationClosure
)
outboundWriter.finish()
return .init(
channel: channel,
inboundStream: inboundStream,
outboundWriter: outboundWriter
)
}
@inlinable
@_spi(AsyncChannel)
public static func wrapAsyncChannelForBootstrapBindWithProtocolNegotiation(
synchronouslyWrapping channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark? = nil,
isOutboundHalfClosureEnabled: Bool = false,
transformationClosure: @escaping (Channel) -> EventLoopFuture<Inbound>
) throws -> NIOAsyncChannel<Inbound, Outbound> where Outbound == Never {
channel.eventLoop.preconditionInEventLoop()
let (inboundStream, outboundWriter): (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) = try channel._syncAddAsyncHandlersForBootstrapProtocolNegotiation(
backpressureStrategy: backpressureStrategy,
isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled,
transformationClosure: transformationClosure
)
outboundWriter.finish()
return .init(
channel: channel,
inboundStream: inboundStream,
outboundWriter: outboundWriter
)
}
}
extension Channel {
// TODO: We need to remove the public and spi here once we make the AsyncChannel methods public
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@inlinable
@_spi(AsyncChannel)
public func _syncAddAsyncHandlers<Inbound: Sendable, Outbound: Sendable>(
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
isOutboundHalfClosureEnabled: Bool
) throws -> (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) {
self.eventLoop.assertInEventLoop()
let closeRatchet = CloseRatchet(isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled)
let inboundStream = try NIOAsyncChannelInboundStream<Inbound>.makeWrappingHandler(
channel: self,
backpressureStrategy: backpressureStrategy,
closeRatchet: closeRatchet
)
let writer = try NIOAsyncChannelOutboundWriter<Outbound>(
channel: self,
closeRatchet: closeRatchet
)
return (inboundStream, writer)
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@inlinable
@_spi(AsyncChannel)
public func _syncAddAsyncHandlersForBootstrapBind<Inbound: Sendable, Outbound: Sendable>(
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
isOutboundHalfClosureEnabled: Bool,
transformationClosure: @escaping (Channel) -> EventLoopFuture<Inbound>
) throws -> (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) {
self.eventLoop.assertInEventLoop()
let closeRatchet = CloseRatchet(isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled)
let inboundStream = try NIOAsyncChannelInboundStream<Inbound>.makeBindingHandler(
channel: self,
backpressureStrategy: backpressureStrategy,
closeRatchet: closeRatchet,
transformationClosure: transformationClosure
)
let writer = try NIOAsyncChannelOutboundWriter<Outbound>(
channel: self,
closeRatchet: closeRatchet
)
return (inboundStream, writer)
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@inlinable
@_spi(AsyncChannel)
public func _syncAddAsyncHandlersForBootstrapProtocolNegotiation<Inbound: Sendable, Outbound: Sendable>(
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
isOutboundHalfClosureEnabled: Bool,
transformationClosure: @escaping (Channel) -> EventLoopFuture<Inbound>
) throws -> (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) {
self.eventLoop.assertInEventLoop()
let closeRatchet = CloseRatchet(isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled)
let inboundStream = try NIOAsyncChannelInboundStream<Inbound>.makeProtocolNegotiationHandler(
channel: self,
backpressureStrategy: backpressureStrategy,
closeRatchet: closeRatchet,
transformationClosure: transformationClosure
)
let writer = try NIOAsyncChannelOutboundWriter<Outbound>(
channel: self,
closeRatchet: closeRatchet
)
return (inboundStream, writer)
}
}

View File

@ -0,0 +1,151 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// The inbound message asynchronous sequence of a ``NIOAsyncChannel``.
///
/// This is a unicast async sequence that allows a single iterator to be created.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@_spi(AsyncChannel)
public struct NIOAsyncChannelInboundStream<Inbound: Sendable>: Sendable {
@usableFromInline
typealias Producer = NIOThrowingAsyncSequenceProducer<Inbound, Error, NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark, NIOAsyncChannelInboundStreamChannelHandlerProducerDelegate>
/// The underlying async sequence.
@usableFromInline let _producer: Producer
@inlinable
init<HandlerInbound: Sendable>(
channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
closeRatchet: CloseRatchet,
handler: NIOAsyncChannelInboundStreamChannelHandler<HandlerInbound, Inbound>
) throws {
channel.eventLoop.preconditionInEventLoop()
let strategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark
if let userProvided = backpressureStrategy {
strategy = userProvided
} else {
// Default strategy. These numbers are fairly arbitrary, but they line up with the default value of
// maxMessagesPerRead.
strategy = .init(lowWatermark: 2, highWatermark: 10)
}
let sequence = Producer.makeSequence(
backPressureStrategy: strategy,
delegate: NIOAsyncChannelInboundStreamChannelHandlerProducerDelegate(handler: handler)
)
handler.source = sequence.source
try channel.pipeline.syncOperations.addHandler(handler)
self._producer = sequence.sequence
}
/// Creates a new ``NIOAsyncChannelInboundStream`` which is used when the pipeline got synchronously wrapped.
@inlinable
static func makeWrappingHandler(
channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
closeRatchet: CloseRatchet
) throws -> NIOAsyncChannelInboundStream {
let handler = NIOAsyncChannelInboundStreamChannelHandler<Inbound, Inbound>.makeWrappingHandler(
eventLoop: channel.eventLoop,
closeRatchet: closeRatchet
)
return try .init(
channel: channel,
backpressureStrategy: backpressureStrategy,
closeRatchet: closeRatchet,
handler: handler
)
}
/// Creates a new ``NIOAsyncChannelInboundStreamChannelHandler`` which is used in the bootstrap for the ServerChannel.
@inlinable
static func makeBindingHandler(
channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
closeRatchet: CloseRatchet,
transformationClosure: @escaping (Channel) -> EventLoopFuture<Inbound>
) throws -> NIOAsyncChannelInboundStream {
let handler = NIOAsyncChannelInboundStreamChannelHandler<Channel, Inbound>.makeBindingHandler(
eventLoop: channel.eventLoop,
closeRatchet: closeRatchet,
transformationClosure: transformationClosure
)
return try .init(
channel: channel,
backpressureStrategy: backpressureStrategy,
closeRatchet: closeRatchet,
handler: handler
)
}
/// Creates a new ``NIOAsyncChannelInboundStreamChannelHandler`` which is used in the bootstrap for the ServerChannel when the child
/// channel does protocol negotiation.
@inlinable
static func makeProtocolNegotiationHandler(
channel: Channel,
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?,
closeRatchet: CloseRatchet,
transformationClosure: @escaping (Channel) -> EventLoopFuture<Inbound>
) throws -> NIOAsyncChannelInboundStream {
let handler = NIOAsyncChannelInboundStreamChannelHandler<Channel, Inbound>.makeProtocolNegotiationHandler(
eventLoop: channel.eventLoop,
closeRatchet: closeRatchet,
transformationClosure: transformationClosure
)
return try .init(
channel: channel,
backpressureStrategy: backpressureStrategy,
closeRatchet: closeRatchet,
handler: handler
)
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension NIOAsyncChannelInboundStream: AsyncSequence {
@_spi(AsyncChannel)
public typealias Element = Inbound
@_spi(AsyncChannel)
public struct AsyncIterator: AsyncIteratorProtocol {
@usableFromInline var _iterator: Producer.AsyncIterator
@inlinable
init(_ iterator: Producer.AsyncIterator) {
self._iterator = iterator
}
@inlinable @_spi(AsyncChannel)
public mutating func next() async throws -> Element? {
return try await self._iterator.next()
}
}
@inlinable
@_spi(AsyncChannel)
public func makeAsyncIterator() -> AsyncIterator {
return AsyncIterator(self._producer.makeAsyncIterator())
}
}
/// The ``NIOAsyncChannelInboundStream/AsyncIterator`` MUST NOT be shared across `Task`s. With marking this as
/// unavailable we are explicitly declaring this.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(*, unavailable)
extension NIOAsyncChannelInboundStream.AsyncIterator: Sendable {}

View File

@ -0,0 +1,373 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A ``ChannelHandler`` that is used to transform the inbound portion of a NIO
/// ``Channel`` into an asynchronous sequence that supports back-pressure.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@usableFromInline
internal final class NIOAsyncChannelInboundStreamChannelHandler<InboundIn: Sendable, ProducerElement: Sendable>: ChannelDuplexHandler {
@usableFromInline
enum _ProducingState {
// Not .stopProducing
case keepProducing
// .stopProducing but not read()
case producingPaused
// .stopProducing and read()
case producingPausedWithOutstandingRead
}
@usableFromInline
typealias OutboundIn = Any
@usableFromInline
typealias OutboundOut = Any
@usableFromInline
typealias Source = NIOThrowingAsyncSequenceProducer<
ProducerElement,
Error,
NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark,
NIOAsyncChannelInboundStreamChannelHandlerProducerDelegate
>.Source
/// The source of the asynchronous sequence.
@usableFromInline
var source: Source?
/// The channel handler's context.
@usableFromInline
var context: ChannelHandlerContext?
/// An array of reads which will be yielded to the source with the next channel read complete.
@usableFromInline
var buffer: [ProducerElement] = []
/// The current producing state.
@usableFromInline
var producingState: _ProducingState = .keepProducing
/// The event loop.
@usableFromInline
let eventLoop: EventLoop
/// The shared `CloseRatchet` between this handler and the writer handler.
@usableFromInline
let closeRatchet: CloseRatchet
/// A type indicating what kind of transformation to apply to reads.
@usableFromInline
enum Transformation {
/// A synchronous transformation is applied to incoming reads. This is used when sync wrapping a channel.
case syncWrapping((InboundIn) -> ProducerElement)
/// This is used in the ServerBootstrap since we require to wrap the child channel on it's event loop but yield it on the parent's loop.
case bind((InboundIn) -> EventLoopFuture<ProducerElement>)
/// In the case of protocol negotiation we are applying a future based transformation where we wait for the transformation
/// to finish before we yield it to the source.
case protocolNegotiation((InboundIn) -> EventLoopFuture<ProducerElement>)
}
/// The transformation applied to incoming reads.
@usableFromInline
let transformation: Transformation
@inlinable
init(
eventLoop: EventLoop,
closeRatchet: CloseRatchet,
transformation: Transformation
) {
self.eventLoop = eventLoop
self.closeRatchet = closeRatchet
self.transformation = transformation
}
/// Creates a new ``NIOAsyncChannelInboundStreamChannelHandler`` which is used when the pipeline got synchronously wrapped.
@inlinable
static func makeWrappingHandler(
eventLoop: EventLoop,
closeRatchet: CloseRatchet
) -> NIOAsyncChannelInboundStreamChannelHandler where InboundIn == ProducerElement {
return .init(
eventLoop: eventLoop,
closeRatchet: closeRatchet,
transformation: .syncWrapping { $0 }
)
}
/// Creates a new ``NIOAsyncChannelInboundStreamChannelHandler`` which is used in the bootstrap for the ServerChannel.
@inlinable
static func makeBindingHandler(
eventLoop: EventLoop,
closeRatchet: CloseRatchet,
transformationClosure: @escaping (Channel) -> EventLoopFuture<ProducerElement>
) -> NIOAsyncChannelInboundStreamChannelHandler where InboundIn == Channel {
return .init(
eventLoop: eventLoop,
closeRatchet: closeRatchet,
transformation: .bind(transformationClosure)
)
}
/// Creates a new ``NIOAsyncChannelInboundStreamChannelHandler`` which is used in the bootstrap for the ServerChannel when the child
/// channel does protocol negotiation.
@inlinable
static func makeProtocolNegotiationHandler(
eventLoop: EventLoop,
closeRatchet: CloseRatchet,
transformationClosure: @escaping (Channel) -> EventLoopFuture<ProducerElement>
) -> NIOAsyncChannelInboundStreamChannelHandler where InboundIn == Channel {
return .init(
eventLoop: eventLoop,
closeRatchet: closeRatchet,
transformation: .protocolNegotiation(transformationClosure)
)
}
@inlinable
func handlerAdded(context: ChannelHandlerContext) {
self.context = context
}
@inlinable
func handlerRemoved(context: ChannelHandlerContext) {
self._finishSource(context: context)
self.context = nil
}
@inlinable
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let unwrapped = self.unwrapInboundIn(data)
switch self.transformation {
case .syncWrapping(let transformation):
self.buffer.append(transformation(unwrapped))
// We forward on reads here to enable better channel composition.
context.fireChannelRead(data)
case .bind(let transformation):
// The unsafe transfers here are required because we need to use self in whenComplete
// We are making sure to be on our event loop so we can safely use self in whenComplete
let unsafeSelf = NIOLoopBound(self, eventLoop: context.eventLoop)
let unsafeContext = NIOLoopBound(context, eventLoop: context.eventLoop)
transformation(unwrapped)
.hop(to: context.eventLoop)
.whenComplete { result in
unsafeSelf.value._transformationCompleted(context: unsafeContext.value, result: result)
// We forward the read only after the transformation has been completed. This is super important
// since we are setting up the NIOAsyncChannel handlers in the transformation and
// we must make sure to only generate reads once they are setup. Reads can only
// happen after the child channels hit `channelRead0` that's why we are holding the read here.
context.fireChannelRead(data)
}
case .protocolNegotiation(let protocolNegotiation):
// The unsafe transfers here are required because we need to use self in whenComplete
// We are making sure to be on our event loop so we can safely use self in whenComplete
let unsafeSelf = NIOLoopBound(self, eventLoop: context.eventLoop)
let unsafeContext = NIOLoopBound(context, eventLoop: context.eventLoop)
protocolNegotiation(unwrapped)
.hop(to: context.eventLoop)
.whenComplete { result in
unsafeSelf.value._transformationCompleted(context: unsafeContext.value, result: result)
}
// We forwarding the read here right away since protocol negotiation often needs reads to progress.
// In this case, we expect the user to synchronously wrap the child channel into a NIOAsyncChannel
// hence we don't have the timing issue as in the `.bind` case.
context.fireChannelRead(data)
}
}
@inlinable
func _transformationCompleted(
context: ChannelHandlerContext,
result: Result<ProducerElement, Error>
) {
context.eventLoop.preconditionInEventLoop()
switch result {
case .success(let transformed):
self.buffer.append(transformed)
// We are delivering out of band here since the future can complete at any point
self._deliverReads(context: context)
case .failure:
// Transformation failed. Nothing to really do here this must be handled in the transformation
// futures themselves.
break
}
}
@inlinable
func channelReadComplete(context: ChannelHandlerContext) {
self._deliverReads(context: context)
context.fireChannelReadComplete()
}
@inlinable
func channelInactive(context: ChannelHandlerContext) {
self._finishSource(context: context)
context.fireChannelInactive()
}
@inlinable
func errorCaught(context: ChannelHandlerContext, error: Error) {
self._finishSource(with: error, context: context)
context.fireErrorCaught(error)
}
@inlinable
func read(context: ChannelHandlerContext) {
switch self.producingState {
case .keepProducing:
context.read()
case .producingPaused:
self.producingState = .producingPausedWithOutstandingRead
case .producingPausedWithOutstandingRead:
break
}
}
@inlinable
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
switch event {
case ChannelEvent.inputClosed:
self._finishSource(context: context)
default:
break
}
context.fireUserInboundEventTriggered(event)
}
@inlinable
func _finishSource(with error: Error? = nil, context: ChannelHandlerContext) {
guard let source = self.source else {
return
}
// We need to deliver the reads first to buffer them in the source.
self._deliverReads(context: context)
if let error = error {
source.finish(error)
} else {
source.finish()
}
// We can nil the source here, as we're no longer going to use it.
self.source = nil
}
@inlinable
func _deliverReads(context: ChannelHandlerContext) {
if self.buffer.isEmpty {
return
}
guard let source = self.source else {
self.buffer.removeAll()
return
}
let result = source.yield(contentsOf: self.buffer)
switch result {
case .produceMore, .dropped:
break
case .stopProducing:
if self.producingState != .producingPausedWithOutstandingRead {
self.producingState = .producingPaused
}
}
self.buffer.removeAll(keepingCapacity: true)
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension NIOAsyncChannelInboundStreamChannelHandler {
@inlinable
func _didTerminate() {
self.eventLoop.preconditionInEventLoop()
self.source = nil
// Wedges the read open forever, we'll never read again.
self.producingState = .producingPausedWithOutstandingRead
switch self.closeRatchet.closeRead() {
case .nothing:
break
case .close:
self.context?.close(promise: nil)
}
}
@inlinable
func _produceMore() {
self.eventLoop.preconditionInEventLoop()
switch self.producingState {
case .producingPaused:
self.producingState = .keepProducing
case .producingPausedWithOutstandingRead:
self.producingState = .keepProducing
self.context?.read()
case .keepProducing:
break
}
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@usableFromInline
struct NIOAsyncChannelInboundStreamChannelHandlerProducerDelegate: @unchecked Sendable, NIOAsyncSequenceProducerDelegate {
@usableFromInline
let eventLoop: EventLoop
@usableFromInline
let _didTerminate: () -> Void
@usableFromInline
let _produceMore: () -> Void
@inlinable
init<InboundIn, ProducerElement>(handler: NIOAsyncChannelInboundStreamChannelHandler<InboundIn, ProducerElement>) {
self.eventLoop = handler.eventLoop
self._didTerminate = handler._didTerminate
self._produceMore = handler._produceMore
}
@inlinable
func didTerminate() {
self.eventLoop.execute {
self._didTerminate()
}
}
@inlinable
func produceMore() {
self.eventLoop.execute {
self._produceMore()
}
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(*, unavailable)
extension NIOAsyncChannelInboundStreamChannelHandler: Sendable {}

View File

@ -0,0 +1,91 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A ``NIOAsyncChannelWriter`` is used to write and flush new outbound messages in a channel.
///
/// The writer acts as a bridge between the Concurrency and NIO world. It allows to write and flush messages into the
/// underlying ``Channel``. Furthermore, it respects back-pressure of the channel by suspending the calls to write until
/// the channel becomes writable again.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@_spi(AsyncChannel)
public struct NIOAsyncChannelOutboundWriter<OutboundOut: Sendable>: Sendable {
@usableFromInline
typealias _Writer = NIOAsyncChannelOutboundWriterHandler<OutboundOut>.Writer
@usableFromInline
let _outboundWriter: _Writer
@inlinable
init(
channel: Channel,
closeRatchet: CloseRatchet
) throws {
let handler = NIOAsyncChannelOutboundWriterHandler<OutboundOut>(
eventLoop: channel.eventLoop,
closeRatchet: closeRatchet
)
let writer = _Writer.makeWriter(
elementType: OutboundOut.self,
isWritable: true,
delegate: .init(handler: handler)
)
handler.sink = writer.sink
try channel.pipeline.syncOperations.addHandler(handler)
self._outboundWriter = writer.writer
}
@inlinable
init(outboundWriter: NIOAsyncChannelOutboundWriterHandler<OutboundOut>.Writer) {
self._outboundWriter = outboundWriter
}
/// Send a write into the ``ChannelPipeline`` and flush it right away.
///
/// This method suspends if the underlying channel is not writable and will resume once the it becomes writable again.
@inlinable
@_spi(AsyncChannel)
public func write(_ data: OutboundOut) async throws {
try await self._outboundWriter.yield(data)
}
/// Send a sequence of writes into the ``ChannelPipeline`` and flush them right away.
///
/// This method suspends if the underlying channel is not writable and will resume once the it becomes writable again.
@inlinable
@_spi(AsyncChannel)
public func write<Writes: Sequence>(contentsOf sequence: Writes) async throws where Writes.Element == OutboundOut {
try await self._outboundWriter.yield(contentsOf: sequence)
}
/// Send a sequence of writes into the ``ChannelPipeline`` and flush them right away.
///
/// This method suspends if the underlying channel is not writable and will resume once the it becomes writable again.
@inlinable
@_spi(AsyncChannel)
public func write<Writes: AsyncSequence>(contentsOf sequence: Writes) async throws where Writes.Element == OutboundOut {
for try await data in sequence {
try await self._outboundWriter.yield(data)
}
}
/// Finishes the writer.
///
/// This might trigger a half closure if the ``NIOAsyncChannel`` was configured to support it.
@_spi(AsyncChannel)
public func finish() {
self._outboundWriter.finish()
}
}

View File

@ -0,0 +1,168 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import DequeModule
/// A ``ChannelHandler`` that is used to write the outbound portion of a NIO
/// ``Channel`` from Swift Concurrency with back-pressure support.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@usableFromInline
internal final class NIOAsyncChannelOutboundWriterHandler<OutboundOut: Sendable>: ChannelDuplexHandler {
@usableFromInline typealias InboundIn = Any
@usableFromInline typealias InboundOut = Any
@usableFromInline typealias OutboundIn = Any
@usableFromInline typealias OutboundOut = OutboundOut
@usableFromInline
typealias Writer = NIOAsyncWriter<
OutboundOut,
NIOAsyncChannelOutboundWriterHandler<OutboundOut>.Delegate
>
@usableFromInline
typealias Sink = Writer.Sink
/// The sink of the ``NIOAsyncWriter``.
@usableFromInline
var sink: Sink?
/// The channel handler context.
@usableFromInline
var context: ChannelHandlerContext?
/// The event loop.
@usableFromInline
let eventLoop: EventLoop
/// The shared `CloseRatchet` between this handler and the inbound stream handler.
@usableFromInline
let closeRatchet: CloseRatchet
@inlinable
init(
eventLoop: EventLoop,
closeRatchet: CloseRatchet
) {
self.eventLoop = eventLoop
self.closeRatchet = closeRatchet
}
@inlinable
func _didYield(sequence: Deque<OutboundOut>) {
// This is always called from an async context, so we must loop-hop.
// Because we always loop-hop, we're always at the top of a stack frame. As this
// is the only source of writes for us, and as this channel handler doesn't implement
// func write(), we cannot possibly re-entrantly write. That means we can skip many of the
// awkward re-entrancy protections NIO usually requires, and can safely just do an iterative
// write.
self.eventLoop.preconditionInEventLoop()
guard let context = self.context else {
// Already removed from the channel by now, we can stop.
return
}
self._doOutboundWrites(context: context, writes: sequence)
}
@inlinable
func _didTerminate(error: Error?) {
self.eventLoop.preconditionInEventLoop()
switch self.closeRatchet.closeWrite() {
case .nothing:
break
case .closeOutput:
self.context?.close(mode: .output, promise: nil)
case .close:
self.context?.close(promise: nil)
}
self.sink = nil
}
@inlinable
func _doOutboundWrites(context: ChannelHandlerContext, writes: Deque<OutboundOut>) {
for write in writes {
context.write(self.wrapOutboundOut(write), promise: nil)
}
context.flush()
}
@inlinable
func handlerAdded(context: ChannelHandlerContext) {
self.context = context
}
@inlinable
func handlerRemoved(context: ChannelHandlerContext) {
self.context = nil
self.sink = nil
}
@inlinable
func channelInactive(context: ChannelHandlerContext) {
self.sink?.finish()
context.fireChannelInactive()
}
@inlinable
func channelWritabilityChanged(context: ChannelHandlerContext) {
self.sink?.setWritability(to: context.channel.isWritable)
context.fireChannelWritabilityChanged()
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension NIOAsyncChannelOutboundWriterHandler {
@usableFromInline
struct Delegate: @unchecked Sendable, NIOAsyncWriterSinkDelegate {
@usableFromInline
typealias Element = OutboundOut
@usableFromInline
let eventLoop: EventLoop
@usableFromInline
let handler: NIOAsyncChannelOutboundWriterHandler<OutboundOut>
@inlinable
init(handler: NIOAsyncChannelOutboundWriterHandler<OutboundOut>) {
self.eventLoop = handler.eventLoop
self.handler = handler
}
@inlinable
func didYield(contentsOf sequence: Deque<OutboundOut>) {
// This always called from an async context, so we must loop-hop.
self.eventLoop.execute {
self.handler._didYield(sequence: sequence)
}
}
@inlinable
func didTerminate(error: Error?) {
// This always called from an async context, so we must loop-hop.
self.eventLoop.execute {
self.handler._didTerminate(error: error)
}
}
}
}
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(*, unavailable)
extension NIOAsyncChannelOutboundWriterHandler: Sendable {}

View File

@ -0,0 +1,93 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A helper type that lets ``NIOAsyncChannelAdapterHandler`` and ``NIOAsyncChannelWriterHandler`` collude
/// to ensure that the ``Channel`` they share is closed appropriately.
///
/// The strategy of this type is that it keeps track of which side has closed, so that the handlers can work out
/// which of them was "last", in order to arrange closure.
@usableFromInline
final class CloseRatchet {
@usableFromInline
enum State {
case notClosed(isOutboundHalfClosureEnabled: Bool)
case readClosed
case writeClosed
case bothClosed
@inlinable
mutating func closeRead() -> CloseReadAction {
switch self {
case .notClosed:
self = .readClosed
return .nothing
case .writeClosed:
self = .bothClosed
return .close
case .readClosed, .bothClosed:
preconditionFailure("Duplicate read closure")
}
}
@inlinable
mutating func closeWrite() -> CloseWriteAction {
switch self {
case .notClosed(let isOutboundHalfClosureEnabled):
self = .writeClosed
if isOutboundHalfClosureEnabled {
return .closeOutput
} else {
return .nothing
}
case .readClosed:
self = .bothClosed
return .close
case .writeClosed, .bothClosed:
preconditionFailure("Duplicate write closure")
}
}
}
@usableFromInline
var _state: State
@inlinable
init(isOutboundHalfClosureEnabled: Bool) {
self._state = .notClosed(isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled)
}
@usableFromInline
enum CloseReadAction {
case nothing
case close
}
@inlinable
func closeRead() -> CloseReadAction {
return self._state.closeRead()
}
@usableFromInline
enum CloseWriteAction {
case nothing
case close
case closeOutput
}
@inlinable
func closeWrite() -> CloseWriteAction {
return self._state.closeWrite()
}
}

View File

@ -79,7 +79,7 @@ public protocol NIOAsyncSequenceProducerDelegate: Sendable {
func didTerminate()
}
/// This is an ``Swift/AsyncSequence`` that supports a unicast ``Swift/AsyncIterator``.
/// This is an `AsyncSequence` that supports a unicast `AsyncIterator`.
///
/// The goal of this sequence is to produce a stream of elements from the _synchronous_ world
/// (e.g. elements from a ``Channel`` pipeline) and vend it to the _asynchronous_ world for consumption.
@ -103,7 +103,7 @@ public struct NIOAsyncSequenceProducer<
/// This struct contains two properties:
/// 1. The ``source`` which should be retained by the producer and is used
/// to yield new elements to the sequence.
/// 2. The ``sequence`` which is the actual ``Swift/AsyncSequence`` and
/// 2. The ``sequence`` which is the actual `AsyncSequence` and
/// should be passed to the consumer.
public struct NewSequence {
/// The source of the ``NIOAsyncSequenceProducer`` used to yield and finish.
@ -147,9 +147,8 @@ public struct NIOAsyncSequenceProducer<
backPressureStrategy: Strategy,
delegate: Delegate
) -> NewSequence {
let newSequence = NIOThrowingAsyncSequenceProducer.makeSequence(
let newSequence = NIOThrowingAsyncSequenceProducer.makeNonThrowingSequence(
elementType: Element.self,
failureType: Never.self,
backPressureStrategy: backPressureStrategy,
delegate: delegate
)
@ -198,7 +197,8 @@ extension NIOAsyncSequenceProducer {
@inlinable
public func next() async -> Element? {
return try! await self._throwingIterator.next()
// this call will only throw if cancelled and we want to just return nil in that case
return try? await self._throwingIterator.next()
}
}
}
@ -253,7 +253,7 @@ extension NIOAsyncSequenceProducer {
/// The result of a call to ``NIOAsyncSequenceProducer/Source/yield(_:)``.
public enum YieldResult: Hashable {
/// Indicates that the caller should produce more elements for now. The delegate's ``NIOAsyncSequenceProducerDelegate/produceMore()``
/// will **NOT** get called, since the demand was already signalled through this ``YieldResult``
/// will **NOT** get called, since the demand was already signalled through this ``NIOAsyncSequenceProducer/Source/YieldResult``.
case produceMore
/// Indicates that the caller should stop producing elements. The delegate's ``NIOAsyncSequenceProducerDelegate/produceMore()``
/// will get called once production should be resumed.

View File

@ -15,7 +15,7 @@
import DequeModule
import NIOConcurrencyHelpers
/// This is an ``Swift/AsyncSequence`` that supports a unicast ``Swift/AsyncIterator``.
/// This is an `AsyncSequence` that supports a unicast `AsyncIterator`.
///
/// The goal of this sequence is to produce a stream of elements from the _synchronous_ world
/// (e.g. elements from a ``Channel`` pipeline) and vend it to the _asynchronous_ world for consumption.
@ -35,12 +35,12 @@ public struct NIOThrowingAsyncSequenceProducer<
Strategy: NIOAsyncSequenceProducerBackPressureStrategy,
Delegate: NIOAsyncSequenceProducerDelegate
>: Sendable {
/// Simple struct for the return type of ``NIOThrowingAsyncSequenceProducer/makeSequence(elementType:failureType:backPressureStrategy:delegate:)``.
/// Simple struct for the return type of ``NIOThrowingAsyncSequenceProducer/makeSequence(elementType:failureType:backPressureStrategy:delegate:)-8qauq``.
///
/// This struct contains two properties:
/// 1. The ``source`` which should be retained by the producer and is used
/// to yield new elements to the sequence.
/// 2. The ``sequence`` which is the actual ``Swift/AsyncSequence`` and
/// 2. The ``sequence`` which is the actual `AsyncSequence` and
/// should be passed to the consumer.
public struct NewSequence {
/// The source of the ``NIOThrowingAsyncSequenceProducer`` used to yield and finish.
@ -98,6 +98,7 @@ public struct NIOThrowingAsyncSequenceProducer<
/// - backPressureStrategy: The back-pressure strategy of the sequence.
/// - delegate: The delegate of the sequence
/// - Returns: A ``NIOThrowingAsyncSequenceProducer/Source`` and a ``NIOThrowingAsyncSequenceProducer``.
@available(*, deprecated, message: "Support for a generic Failure type is deprecated. Failure type must be `any Swift.Error`.")
@inlinable
public static func makeSequence(
elementType: Element.Type = Element.self,
@ -113,6 +114,51 @@ public struct NIOThrowingAsyncSequenceProducer<
return .init(source: source, sequence: sequence)
}
/// Initializes a new ``NIOThrowingAsyncSequenceProducer`` and a ``NIOThrowingAsyncSequenceProducer/Source``.
///
/// - Important: This method returns a struct containing a ``NIOThrowingAsyncSequenceProducer/Source`` and
/// a ``NIOThrowingAsyncSequenceProducer``. The source MUST be held by the caller and
/// used to signal new elements or finish. The sequence MUST be passed to the actual consumer and MUST NOT be held by the
/// caller. This is due to the fact that deiniting the sequence is used as part of a trigger to terminate the underlying source.
///
/// - Parameters:
/// - elementType: The element type of the sequence.
/// - failureType: The failure type of the sequence. Must be `Swift.Error`
/// - backPressureStrategy: The back-pressure strategy of the sequence.
/// - delegate: The delegate of the sequence
/// - Returns: A ``NIOThrowingAsyncSequenceProducer/Source`` and a ``NIOThrowingAsyncSequenceProducer``.
@inlinable
public static func makeSequence(
elementType: Element.Type = Element.self,
failureType: Failure.Type = Error.self,
backPressureStrategy: Strategy,
delegate: Delegate
) -> NewSequence where Failure == Error {
let sequence = Self(
backPressureStrategy: backPressureStrategy,
delegate: delegate
)
let source = Source(storage: sequence._storage)
return .init(source: source, sequence: sequence)
}
/// only used internally by``NIOAsyncSequenceProducer`` to reuse most of the code
@inlinable
internal static func makeNonThrowingSequence(
elementType: Element.Type = Element.self,
backPressureStrategy: Strategy,
delegate: Delegate
) -> NewSequence where Failure == Never {
let sequence = Self(
backPressureStrategy: backPressureStrategy,
delegate: delegate
)
let source = Source(storage: sequence._storage)
return .init(source: source, sequence: sequence)
}
@inlinable
/* private */ internal init(
@ -465,6 +511,21 @@ extension NIOThrowingAsyncSequenceProducer {
return nil
}
case .returnCancellationError:
self._lock.unlock()
// We have deprecated the generic Failure type in the public API and Failure should
// now be `Swift.Error`. However, if users have not migrated to the new API they could
// still use a custom generic Error type and this cast might fail.
// In addition, we use `NIOThrowingAsyncSequenceProducer` in the implementation of the
// non-throwing variant `NIOAsyncSequenceProducer` where `Failure` will be `Never` and
// this cast will fail as well.
// Everything is marked @inlinable and the Failure type is known at compile time,
// therefore this cast should be optimised away in release build.
if let error = CancellationError() as? Failure {
throw error
}
return nil
case .returnNil:
self._lock.unlock()
return nil
@ -498,8 +559,21 @@ extension NIOThrowingAsyncSequenceProducer {
return delegate
case .resumeContinuationWithNilAndCallDidTerminate(let continuation):
continuation.resume(returning: nil)
case .resumeContinuationWithCancellationErrorAndCallDidTerminate(let continuation):
// We have deprecated the generic Failure type in the public API and Failure should
// now be `Swift.Error`. However, if users have not migrated to the new API they could
// still use a custom generic Error type and this cast might fail.
// In addition, we use `NIOThrowingAsyncSequenceProducer` in the implementation of the
// non-throwing variant `NIOAsyncSequenceProducer` where `Failure` will be `Never` and
// this cast will fail as well.
// Everything is marked @inlinable and the Failure type is known at compile time,
// therefore this cast should be optimised away in release build.
if let failure = CancellationError() as? Failure {
continuation.resume(throwing: failure)
} else {
continuation.resume(returning: nil)
}
let delegate = self._delegate
self._delegate = nil
@ -544,6 +618,9 @@ extension NIOThrowingAsyncSequenceProducer {
failure: Failure?
)
/// The state once a call to next has been cancelled. Cancel the source when entering this state.
case cancelled(iteratorInitialized: Bool)
/// The state once there can be no outstanding demand. This can happen if:
/// 1. The ``NIOThrowingAsyncSequenceProducer/AsyncIterator`` was deinited
/// 2. The underlying source finished and all buffered elements have been consumed
@ -585,7 +662,8 @@ extension NIOThrowingAsyncSequenceProducer {
switch self._state {
case .initial(_, iteratorInitialized: false),
.streaming(_, _, _, _, iteratorInitialized: false),
.sourceFinished(_, iteratorInitialized: false, _):
.sourceFinished(_, iteratorInitialized: false, _),
.cancelled(iteratorInitialized: false):
// No iterator was created so we can transition to finished right away.
self._state = .finished(iteratorInitialized: false)
@ -593,7 +671,8 @@ extension NIOThrowingAsyncSequenceProducer {
case .initial(_, iteratorInitialized: true),
.streaming(_, _, _, _, iteratorInitialized: true),
.sourceFinished(_, iteratorInitialized: true, _):
.sourceFinished(_, iteratorInitialized: true, _),
.cancelled(iteratorInitialized: true):
// An iterator was created and we deinited the sequence.
// This is an expected pattern and we just continue on normal.
return .none
@ -614,6 +693,7 @@ extension NIOThrowingAsyncSequenceProducer {
case .initial(_, iteratorInitialized: true),
.streaming(_, _, _, _, iteratorInitialized: true),
.sourceFinished(_, iteratorInitialized: true, _),
.cancelled(iteratorInitialized: true),
.finished(iteratorInitialized: true):
// Our sequence is a unicast sequence and does not support multiple AsyncIterator's
fatalError("NIOThrowingAsyncSequenceProducer allows only a single AsyncIterator to be created")
@ -635,6 +715,10 @@ extension NIOThrowingAsyncSequenceProducer {
iteratorInitialized: true
)
case .cancelled(iteratorInitialized: false):
// An iterator needs to be initialized before we can be cancelled.
preconditionFailure("Internal inconsistency")
case .sourceFinished(let buffer, false, let failure):
// The first and only iterator was initialized.
self._state = .sourceFinished(
@ -668,13 +752,15 @@ extension NIOThrowingAsyncSequenceProducer {
switch self._state {
case .initial(_, iteratorInitialized: false),
.streaming(_, _, _, _, iteratorInitialized: false),
.sourceFinished(_, iteratorInitialized: false, _):
.sourceFinished(_, iteratorInitialized: false, _),
.cancelled(iteratorInitialized: false):
// An iterator needs to be initialized before it can be deinitialized.
preconditionFailure("Internal inconsistency")
case .initial(_, iteratorInitialized: true),
.streaming(_, _, _, _, iteratorInitialized: true),
.sourceFinished(_, iteratorInitialized: true, _):
.sourceFinished(_, iteratorInitialized: true, _),
.cancelled(iteratorInitialized: true):
// An iterator was created and deinited. Since we only support
// a single iterator we can now transition to finish and inform the delegate.
self._state = .finished(iteratorInitialized: true)
@ -802,7 +888,7 @@ extension NIOThrowingAsyncSequenceProducer {
return .init(shouldProduceMore: shouldProduceMore)
case .sourceFinished, .finished:
case .cancelled, .sourceFinished, .finished:
// If the source has finished we are dropping the elements.
return .returnDropped
@ -854,7 +940,7 @@ extension NIOThrowingAsyncSequenceProducer {
return .none
case .sourceFinished, .finished:
case .cancelled, .sourceFinished, .finished:
// If the source has finished, finishing again has no effect.
return .none
@ -868,9 +954,9 @@ extension NIOThrowingAsyncSequenceProducer {
enum CancelledAction {
/// Indicates that ``NIOAsyncSequenceProducerDelegate/didTerminate()`` should be called.
case callDidTerminate
/// Indicates that the continuation should be resumed with `nil` and
/// Indicates that the continuation should be resumed with a `CancellationError` and
/// that ``NIOAsyncSequenceProducerDelegate/didTerminate()`` should be called.
case resumeContinuationWithNilAndCallDidTerminate(CheckedContinuation<Element?, Error>)
case resumeContinuationWithCancellationErrorAndCallDidTerminate(CheckedContinuation<Element?, Error>)
/// Indicates that nothing should be done.
case none
}
@ -880,23 +966,43 @@ extension NIOThrowingAsyncSequenceProducer {
switch self._state {
case .initial(_, let iteratorInitialized):
// This can happen if the `Task` that calls `next()` is already cancelled.
self._state = .finished(iteratorInitialized: iteratorInitialized)
return .callDidTerminate
// We have deprecated the generic Failure type in the public API and Failure should
// now be `Swift.Error`. However, if users have not migrated to the new API they could
// still use a custom generic Error type and this cast might fail.
// In addition, we use `NIOThrowingAsyncSequenceProducer` in the implementation of the
// non-throwing variant `NIOAsyncSequenceProducer` where `Failure` will be `Never` and
// this cast will fail as well.
// Everything is marked @inlinable and the Failure type is known at compile time,
// therefore this cast should be optimised away in release build.
if let failure = CancellationError() as? Failure {
self._state = .sourceFinished(
buffer: .init(),
iteratorInitialized: iteratorInitialized,
failure: failure
)
} else {
self._state = .finished(iteratorInitialized: iteratorInitialized)
}
return .none
case .streaming(_, _, .some(let continuation), _, let iteratorInitialized):
// We have an outstanding continuation that needs to resumed
// and we can transition to finished here and inform the delegate
self._state = .finished(iteratorInitialized: iteratorInitialized)
return .resumeContinuationWithNilAndCallDidTerminate(continuation)
return .resumeContinuationWithCancellationErrorAndCallDidTerminate(continuation)
case .streaming(_, _, continuation: .none, _, let iteratorInitialized):
self._state = .finished(iteratorInitialized: iteratorInitialized)
// We may have elements in the buffer, which is why we have no continuation
// waiting. We must store the cancellation error to hand it out on the next
// next() call.
self._state = .cancelled(iteratorInitialized: iteratorInitialized)
return .callDidTerminate
case .sourceFinished, .finished:
case .cancelled, .sourceFinished, .finished:
// If the source has finished, finishing again has no effect.
return .none
@ -916,6 +1022,8 @@ extension NIOThrowingAsyncSequenceProducer {
/// Indicates that the `Failure` should be returned to the caller and
/// that ``NIOAsyncSequenceProducerDelegate/didTerminate()`` should be called.
case returnFailureAndCallDidTerminate(Failure?)
/// Indicates that the next call to AsyncSequence got cancelled
case returnCancellationError
/// Indicates that the `nil` should be returned to the caller.
case returnNil
/// Indicates that the `Task` of the caller should be suspended.
@ -999,6 +1107,10 @@ extension NIOThrowingAsyncSequenceProducer {
return .returnFailureAndCallDidTerminate(failure)
}
case .cancelled(let iteratorInitialized):
self._state = .finished(iteratorInitialized: iteratorInitialized)
return .returnCancellationError
case .finished:
return .returnNil
@ -1043,7 +1155,7 @@ extension NIOThrowingAsyncSequenceProducer {
return .none
}
case .streaming(_, _, .some(_), _, _), .sourceFinished, .finished:
case .streaming(_, _, .some(_), _, _), .sourceFinished, .finished, .cancelled:
preconditionFailure("This should have already been handled by `next()`")
case .modifying:

View File

@ -863,7 +863,7 @@ public struct ByteBuffer {
}
}
extension ByteBuffer: CustomStringConvertible {
extension ByteBuffer: CustomStringConvertible, CustomDebugStringConvertible {
/// A `String` describing this `ByteBuffer`. Example:
///
/// ByteBuffer { readerIndex: 0, writerIndex: 4, readableBytes: 4, capacity: 512, storageCapacity: 1024, slice: 256..<768, storage: 0x0000000103001000 (1024 bytes)}

View File

@ -161,7 +161,7 @@ extension ByteBufferView: RangeReplaceableCollection {
/// Reserves enough space in the underlying `ByteBuffer` such that this view can
/// store the specified number of bytes without reallocation.
///
/// See the documentation for ``ByteBuffer.reserveCapacity(_:)`` for more details.
/// See the documentation for ``ByteBuffer/reserveCapacity(_:)`` for more details.
@inlinable
public mutating func reserveCapacity(_ minimumCapacity: Int) {
let additionalCapacity = minimumCapacity - self.count

View File

@ -102,7 +102,7 @@ public protocol ChannelCore: AnyObject {
/// passed to or returned by the operations are used to retrieve the result of an operation after it has completed.
///
/// A `Channel` owns its `ChannelPipeline` which handles all I/O events and requests associated with the `Channel`.
public protocol Channel: AnyObject, ChannelOutboundInvoker, NIOPreconcurrencySendable {
public protocol Channel: AnyObject, ChannelOutboundInvoker, _NIOPreconcurrencySendable {
/// The `Channel`'s `ByteBuffer` allocator. This is _the only_ supported way of allocating `ByteBuffer`s to be used with this `Channel`.
var allocator: ByteBufferAllocator { get }

View File

@ -343,3 +343,30 @@ extension RemovableChannelHandler {
context.leavePipeline(removalToken: removalToken)
}
}
/// The result of protocol negotiation.
@_spi(AsyncChannel)
public enum NIOProtocolNegotiationResult<NegotiationResult> {
/// Indicates that the protocol negotiation finished.
case finished(NegotiationResult)
/// Indicates that protocol negotiation has been deferred to the next handler.
case deferredResult(EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>>)
}
@_spi(AsyncChannel)
extension NIOProtocolNegotiationResult: Equatable where NegotiationResult: Equatable {}
@_spi(AsyncChannel)
extension NIOProtocolNegotiationResult: Sendable where NegotiationResult: Sendable {}
/// A ``ProtocolNegotiationHandler`` is a ``ChannelHandler`` that is responsible for negotiating networking protocols.
///
/// Typically these handlers are at the tail of the pipeline and wait until the peer indicated what protocol should be used. Once, the protocol
/// has been negotiated the handlers allow user code to configure the pipeline.
@_spi(AsyncChannel)
public protocol NIOProtocolNegotiationHandler: ChannelHandler {
associatedtype NegotiationResult
/// The future which gets succeeded with the protocol negotiation result.
var protocolNegotiationResult: EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>> { get }
}

View File

@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
/// A configuration option that can be set on a `Channel` to configure different behaviour.
public protocol ChannelOption: Equatable, NIOPreconcurrencySendable {
public protocol ChannelOption: Equatable, _NIOPreconcurrencySendable {
/// The type of the `ChannelOption`'s value.
associatedtype Value
}
@ -437,6 +437,4 @@ extension ChannelOptions {
}
}
#if swift(>=5.6)
extension ChannelOptions.Storage: @unchecked Sendable {}
#endif

View File

@ -0,0 +1,173 @@
# ``NIOCore``
The core abstractions that make up SwiftNIO.
## Overview
``NIOCore`` contains the fundamental abstractions that are used in all SwiftNIO programs. The goal of this module is to
be platform-independent, and to be the most-common building block used for NIO protocol implementations.
More specialized modules provide concrete implementations of many of the abstractions defined in NIOCore.
## Topics
### Articles
- <doc:swift-concurrency>
### Event Loops and Event Loop Groups
- ``EventLoopGroup``
- ``EventLoop``
- ``NIOEventLoopGroupProvider``
- ``EventLoopIterator``
- ``Scheduled``
- ``RepeatedTask``
- ``NIOLoopBound``
- ``NIOLoopBoundBox``
### Channels and Channel Handlers
- ``Channel``
- ``MulticastChannel``
- ``ChannelHandler``
- ``ChannelOutboundHandler``
- ``ChannelInboundHandler``
- ``ChannelDuplexHandler``
- ``ChannelHandlerContext``
- ``ChannelPipeline``
- ``RemovableChannelHandler``
- ``NIOAny``
- ``ChannelEvent``
- ``CloseMode``
- ``ChannelShouldQuiesceEvent``
### Buffers and Files
- ``ByteBuffer``
- ``ByteBufferView``
- ``ByteBufferAllocator``
- ``Endianness``
- ``NIOFileHandle``
- ``FileDescriptor``
- ``FileRegion``
- ``NIOPOSIXFileMode``
- ``IOData``
### Futures and Promises
- ``EventLoopFuture``
- ``EventLoopPromise``
### Configuring Channels
- ``ChannelOption``
- ``NIOSynchronousChannelOptions``
- ``ChannelOptions``
- ``SocketOptionProvider``
- ``RecvByteBufferAllocator``
- ``AdaptiveRecvByteBufferAllocator``
- ``FixedSizeRecvByteBufferAllocator``
- ``AllocatorOption``
- ``AllowRemoteHalfClosureOption``
- ``AutoReadOption``
- ``BacklogOption``
- ``ConnectTimeoutOption``
- ``DatagramVectorReadMessageCountOption``
- ``MaxMessagesPerReadOption``
- ``RecvAllocatorOption``
- ``SocketOption``
- ``SocketOptionLevel``
- ``SocketOptionName``
- ``SocketOptionValue``
- ``WriteBufferWaterMarkOption``
- ``WriteBufferWaterMark``
- ``WriteSpinOption``
### Message Oriented Protocol Helpers
- ``AddressedEnvelope``
- ``NIOPacketInfo``
- ``NIOExplicitCongestionNotificationState``
### Generic Bootstraps
- ``NIOClientTCPBootstrap``
- ``NIOClientTCPBootstrapProtocol``
- ``NIOClientTLSProvider``
- ``NIOInsecureNoTLS``
### Simple Message Handling
- ``ByteToMessageDecoder``
- ``WriteObservingByteToMessageDecoder``
- ``DecodingState``
- ``ByteToMessageHandler``
- ``NIOSingleStepByteToMessageDecoder``
- ``NIOSingleStepByteToMessageProcessor``
- ``MessageToByteEncoder``
- ``MessageToByteHandler``
### Core Channel Handlers
- ``AcceptBackoffHandler``
- ``BackPressureHandler``
- ``NIOCloseOnErrorHandler``
- ``IdleStateHandler``
### Async Sequences
- ``NIOAsyncSequenceProducer``
- ``NIOThrowingAsyncSequenceProducer``
- ``NIOAsyncSequenceProducerBackPressureStrategy``
- ``NIOAsyncSequenceProducerBackPressureStrategies``
- ``NIOAsyncSequenceProducerDelegate``
- ``NIOAsyncWriter``
- ``NIOAsyncWriterSinkDelegate``
### Time
- ``TimeAmount``
- ``NIODeadline``
### Circular Buffers
- ``CircularBuffer``
- ``MarkedCircularBuffer``
### Operating System State
- ``System``
- ``NIONetworkDevice``
- ``NIONetworkInterface``
- ``SocketAddress``
- ``NIOBSDSocket``
- ``NIOIPProtocol``
### Implementing Core Abstractions
- ``ChannelCore``
- ``ChannelInvoker``
- ``ChannelInboundInvoker``
- ``ChannelOutboundInvoker``
### Sendable Helpers
- ``NIOSendable``
- ``NIOPreconcurrencySendable``
### Error Types
- ``ByteToMessageDecoderError``
- ``ChannelError``
- ``ChannelPipelineError``
- ``DatagramChannelError``
- ``EventLoopError``
- ``IOError``
- ``NIOAsyncWriterError``
- ``NIOAttemptedToRemoveHandlerMultipleTimesError``
- ``NIOMulticastNotImplementedError``
- ``NIOMulticastNotSupportedError``
- ``NIOTooManyBytesError``
- ``SocketAddressError``

View File

@ -0,0 +1,287 @@
# NIO and Swift Concurrency
This article explains how to interface between NIO and Swift Concurrency.
NIO was created before native Concurrency support in Swift existed, hence, NIO had to solve
a few problems that have solutions in the language today. Since the introduction of Swift Concurrency,
NIO has added numerous features to make the interop between NIO's ``Channel`` eventing system and Swift's
Concurrency primitives as easy as possible.
### EventLoopFuture bridges
The first bridges that NIO introduced added methods on ``EventLoopFuture`` and ``EventLoopPromise``
to enable communication between Concurrency and NIO. These methods are ``EventLoopFuture/get()`` and ``EventLoopPromise/completeWithTask(_:)``.
> Warning: The future ``EventLoopFuture/get()`` method does not support task cancellation.
Here is a small example of how these work:
```swift
let eventLoop: EventLoop
let promise = eventLoop.makePromise(of: Bool.self)
promise.completeWithTask {
try await Task.sleep(for: .seconds(1))
return true
}
let result = try await promise.futureResult.get()
```
> Note: The `completeWithTask` method creates an unstructured task under the hood.
### Channel bridges
The ``EventLoopFuture`` and ``EventLoopPromise`` bridges already allow async code to interact with
some of NIO's types. However, they only work where we have request-response-like interfaces.
On the other hand, NIO's ``Channel`` type contains a ``ChannelPipeline`` which can be roughly
described as a bi-directional streaming pipeline. To bridge such a pipeline into Concurrency required
new types. Importantly, these types need to uphold the channel's back-pressure and writability guarantees.
NIO introduced the ``NIOThrowingAsyncSequenceProducer``, ``NIOAsyncSequenceProducer`` and the ``NIOAsyncWriter``
which form the foundation to bridge a ``Channel``.
On top of these foundational types, NIO provides the `NIOAsyncChannel` which is used to wrap a
``Channel`` to produce an interface that can be consumed directly from Swift Concurrency. The following
sections cover the details of the foundational types and how the `NIOAsyncChannel` works.
#### NIOThrowingAsyncSequenceProducer and NIOAsyncSequenceProducer
The ``NIOThrowingAsyncSequenceProducer`` and ``NIOAsyncSequenceProducer`` are asynchronous sequences
similar to Swift's `AsyncStream`. Their purpose is to provide a back-pressured bridge between a
synchronous producer and an asynchronous consumer. These types are highly configurable and generic which
makes them usable in a lot of places with very good performance; however, at the same time they are
not the easiest types to hold. We recommend that you **never** expose them in public API but rather
wrap them in your own async sequence.
#### NIOAsyncWriter
The ``NIOAsyncWriter`` is used for bridging from an asynchronous producer to a synchronous consumer.
It also has back-pressure support which allows the consumer to stop the producer by suspending the
``NIOAsyncWriter/yield(contentsOf:)`` method.
> Important: Everything below this is currently not public API but can be tested it by using `@_spi(AsyncChannel) import`.
The APIs might change until they become publicly available.
#### NIOAsyncChannel
The above types are used to bridge both the read and write side of a ``Channel`` into Swift Concurrency.
This can be done by wrapping a ``Channel`` via the `NIOAsyncChannel/init(synchronouslyWrapping:backpressureStrategy:isOutboundHalfClosureEnabled:inboundType:outboundType:)`
initializer. Under the hood, this initializer adds two channel handlers to the end of the channel's pipeline.
These handlers bridge the read and write side of the channel. Additionally, the handlers work together
to close the channel once both the reading and the writing have finished.
This is how you can wrap an existing channel into a `NIOAsyncChannel`, consume the inbound data and
echo it back outbound.
```swift
let channel = ...
let asyncChannel = try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: ByteBuffer.self, outboundType: ByteBuffer.self)
for try await inboundData in asyncChannel.inboundStream {
try await asyncChannel.outboundWriter.write(inboundData)
}
```
The above code works nicely; however, you must be very careful at what point you wrap your channel
otherwise you might lose some reads. For example your channel might be created by a `ServerBootstrap`
for a new inbound connection. The channel might start to produce reads as soon as it registered its
IO which happens after your channel initializer ran. To avoid potentially losing reads the channel
must be wrapped before it registered its IO.
Another example is when the channel contains a handler that does protocol negotiation. Protocol negotiation handlers
are usually waiting for some data to be exchanged before deciding what protocol to chose. Afterwards, they
often modify the channel's pipeline and add the protocol appropriate handlers to it. This is another
case where wrapping of the `Channel` into a `NIOAsyncChannel` needs to happen at the right time to avoid
losing reads.
### Async bootstrap
NIO offers three different kind of bootstraps `ServerBootstrap`, `ClientBootstrap` and `DatagramBootstrap`.
The next section is going to focus on how to use the methods of these three types to bootstrap connections
using `NIOAsyncChannel`.
#### ServerBootstrap
The server bootstrap is used to create a new TCP based server. Once any of the bind methods on the `ServerBootstrap`
is called, a new listening socket is created to handle new inbound TCP connections. Let's take a look
at the new `NIOAsyncChannel` based bind methods.
```swift
let serverChannel = try await ServerBootstrap(group: eventLoopGroup)
.bind(
host: "127.0.0.1",
port: 0,
childChannelInboundType: ByteBuffer.self,
childChannelOutboundType: ByteBuffer.self
)
try await withThrowingDiscardingTaskGroup { group in
for try await connectionChannel in serverChannel.inboundStream {
group.addTask {
do {
for try await inboundData in connectionChannel.inboundStream {
try await connectionChannel.outboundWriter.write(inboundData)
}
} catch {
// Handle errors
}
}
}
}
```
In the above code, we are bootstrapping a new TCP server which we assign to `serverChannel`.
The `serverChannel` is a `NIOAsyncChannel` whose inbound type is a `NIOAsyncChannel` and whose
outbound type is `Never`. This is due to the fact that each inbound connection gets its own separate child channel.
The inbound and outbound types of each inbound connection is `ByteBuffer` as specified in the bootstrap.
Afterwards, we handle each inbound connection in separate child tasks and echo the data back.
> Important: Make sure to use discarding task groups which automatically reap finished child tasks.
Normal task groups will result in a memory leak since they do not reap their child tasks automatically.
#### ClientBootstrap
The client bootstrap is used to create a new TCP based client. Let's take a look at the new
`NIOAsyncChannel` based connect methods.
```swift
let clientChannel = try await ClientBootstrap(group: eventLoopGroup)
.connect(
host: "127.0.0.1",
port: 0,
channelInboundType: ByteBuffer.self,
channelOutboundType: ByteBuffer.self
)
clientChannel.outboundWriter.write(ByteBuffer(string: "hello"))
for try await inboundData in clientChannel.inboundStream {
print(inboundData)
}
```
#### DatagramBootstrap
> Important: Support for `DatagramBootstrap` with `NIOAsyncChannel` hasn't landed yet.
#### Protocol negotiation
The above bootstrap methods work great in the case where we know the types of the resulting channels
at compile time. However, as mentioned previously protocol negotiation is another case where the timing
of wrapping the ``Channel`` is important that we haven't covered with the `bind` methods that take
an inbound and outbound type yet.
To solve the problem of protocol negotiation, NIO introduced a new ``ChannelHandler`` protocol called
`NIOProtocolNegotiationHandler`. This protocol requires a single future property `NIOProtocolNegotiationHandler/protocolNegotiationResult`
that is completed once the handler is finished with protocol negotiation. In the successful case,
the future can either indicate that protocol negotiation is fully done by returning `NIOProtocolNegotiationResult/finished(_:)` or
indicate that further protocol negotiation needs to be done by returning `NIOProtocolNegotiationResult/deferredResult(_:)`.
Additionally, the various bootstraps provide another set of `bind()`/`connect()` methods that handle protocol negotiation.
Let's walk through how to setup a `ServerBootstrap` with protocol negotiation.
First, we have to define our negotiation result. For this example, we are negotiating between a
`String` based and `UInt8` based channel. Additionally, we also need an error that we can throw
if protocol negotiation failed.
```swift
enum NegotiationResult {
case string(NIOAsyncChannel<String, String>)
case byte(NIOAsyncChannel<UInt8, UInt8>)
}
struct ProtocolNegotiationError: Error {}
```
Next, we have to setup our bootstrap. We are adding a `NIOTypedApplicationProtocolNegotiationHandler`
to each child channel's pipeline. This handler listens for user inbound events of the type `TLSUserEvent`
and then calls the provided closure with the result. In our example, we are handling either `string`
or `byte` application protocols. Importantly, we now have to wrap the channel into a `NIOAsyncChannel` ourselves and
return the finished `NIOProtocolNegotiationResult`.
```swift
let serverBoostrap = try await ServerBootstrap(group: eventLoopGroup)
.childChannelInitializer { channel in
channel.eventLoop.makeCompletedFuture {
let negotiationHandler = NIOTypedApplicationProtocolNegotiationHandler<NegotiationResult>(eventLoop: channel.eventLoop) { alpnResult, channel in
switch alpnResult {
case .negotiated(let alpn):
switch alpn {
case "string":
return channel.eventLoop.makeCompletedFuture {
let asyncChannel = try NIOAsyncChannel(
synchronouslyWrapping: channel,
isOutboundHalfClosureEnabled: true,
inboundType: String.self,
outboundType: String.self
)
return NIOProtocolNegotiationResult.finished(NegotiationResult.string(asyncChannel))
}
case "byte":
return channel.eventLoop.makeCompletedFuture {
let asyncChannel = try NIOAsyncChannel(
synchronouslyWrapping: channel,
isOutboundHalfClosureEnabled: true,
inboundType: UInt8.self,
outboundType: UInt8.self
)
return NIOProtocolNegotiationResult.finished(NegotiationResult.byte(asyncChannel))
}
default:
return channel.eventLoop.makeFailedFuture(ProtocolNegotiationError())
}
case .fallback:
return channel.eventLoop.makeFailedFuture(ProtocolNegotiationError())
}
}
try channel.pipeline.syncOperations.addHandler(negotiationHandler)
}
}
```
Lastly, we can now bind the `serverChannel` and handle the incoming connections. In the code below,
you can see that our server channel is now a `NIOAsyncChannel` of `NegotiationResult`s instead of
child channels.
```swift
let serverChannel = serverBootstrap.bind(
host: "127.0.0.1",
port: 1995,
protocolNegotiationHandlerType: NIOTypedApplicationProtocolNegotiationHandler<NegotiationResult>.self
)
try await withThrowingDiscardingTaskGroup { group in
for try await negotiationResult in serverChannel.inboundStream {
group.addTask {
do {
switch negotiationResult {
case .string(let channel):
for try await inboundData in channel.inboundStream {
try await channel.outboundWriter.write(inboundData)
}
case .byte(let channel):
for try await value in channel.inboundStream {
try await channel.outboundWriter.write(inboundData)
}
}
} catch {
// Handle errors
}
}
}
}
```
### General guidance
#### Where should your code live?
Before the introduction of Swift Concurrency both implementations of network protocols and business logic
were often written inside ``ChannelHandler``s. This made it easier to get started; however, it came with
some downsides. First, implementing business logic inside channel handlers requires the business logic to
also handle all of the invariants that the ``ChannelHandler`` protocol brings with it. This often requires
writing complex state machines. Additionally, the business logic becomes very tied to NIO and hard to
port between different systems.
Because of the above reasons we recommend to implement your business logic using Swift Concurrency primitives and the
`NIOAsyncChannel` based bootstraps. Network protocol implementation should still be implemented as
``ChannelHandler``s.

View File

@ -1153,7 +1153,7 @@ extension EventLoop {
}
/// Provides an endless stream of `EventLoop`s to use.
public protocol EventLoopGroup: AnyObject, NIOPreconcurrencySendable {
public protocol EventLoopGroup: AnyObject, _NIOPreconcurrencySendable {
/// Returns the next `EventLoop` to use, this is useful for load balancing.
///
/// The algorithm that is used to select the next `EventLoop` is specific to each `EventLoopGroup`. A common choice

View File

@ -13,7 +13,6 @@
//===----------------------------------------------------------------------===//
extension EventLoopFuture {
#if swift(>=5.6)
/// When the current `EventLoopFuture<Value>` is fulfilled, run the provided callback,
/// which will provide a new `EventLoopFuture` alongside the `EventLoop` associated with this future.
///
@ -43,45 +42,6 @@ extension EventLoopFuture {
@inlinable
@preconcurrency
public func flatMapWithEventLoop<NewValue>(_ callback: @escaping @Sendable (Value, EventLoop) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
self._flatMapWithEventLoop(callback)
}
@usableFromInline typealias FlatMapWithEventLoopCallback<NewValue> = @Sendable (Value, EventLoop) -> EventLoopFuture<NewValue>
#else
/// When the current `EventLoopFuture<Value>` is fulfilled, run the provided callback,
/// which will provide a new `EventLoopFuture` alongside the `EventLoop` associated with this future.
///
/// This allows you to dynamically dispatch new asynchronous tasks as phases in a
/// longer series of processing steps. Note that you can use the results of the
/// current `EventLoopFuture<Value>` when determining how to dispatch the next operation.
///
/// This works well when you have APIs that already know how to return `EventLoopFuture`s.
/// You can do something with the result of one and just return the next future:
///
/// ```
/// let d1 = networkRequest(args).future()
/// let d2 = d1.flatMapWithEventLoop { t, eventLoop -> EventLoopFuture<NewValue> in
/// eventLoop.makeSucceededFuture(t + 1)
/// }
/// d2.whenSuccess { u in
/// NSLog("Result of second request: \(u)")
/// }
/// ```
///
/// Note: In a sense, the `EventLoopFuture<NewValue>` is returned before it's created.
///
/// - parameters:
/// - callback: Function that will receive the value of this `EventLoopFuture` and return
/// a new `EventLoopFuture`.
/// - returns: A future that will receive the eventual value.
@inlinable
public func flatMapWithEventLoop<NewValue>(_ callback: @escaping (Value, EventLoop) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
self._flatMapWithEventLoop(callback)
}
@usableFromInline typealias FlatMapWithEventLoopCallback<NewValue> = (Value, EventLoop) -> EventLoopFuture<NewValue>
#endif
@inlinable
func _flatMapWithEventLoop<NewValue>(_ callback: @escaping FlatMapWithEventLoopCallback<NewValue>) -> EventLoopFuture<NewValue> {
let next = EventLoopPromise<NewValue>.makeUnleakablePromise(eventLoop: self.eventLoop)
self._whenComplete { [eventLoop = self.eventLoop] in
switch self._value! {
@ -102,7 +62,6 @@ extension EventLoopFuture {
return next.futureResult
}
#if swift(>=5.6)
/// When the current `EventLoopFuture<Value>` is in an error state, run the provided callback, which
/// may recover from the error by returning an `EventLoopFuture<NewValue>`. The callback is intended to potentially
/// recover from the error by returning a new `EventLoopFuture` that will eventually contain the recovered
@ -117,30 +76,6 @@ extension EventLoopFuture {
@inlinable
@preconcurrency
public func flatMapErrorWithEventLoop(_ callback: @escaping @Sendable (Error, EventLoop) -> EventLoopFuture<Value>) -> EventLoopFuture<Value> {
self._flatMapErrorWithEventLoop(callback)
}
@usableFromInline typealias FlatMapWithErrorWithEventLoopCallback = @Sendable (Error, EventLoop) -> EventLoopFuture<Value>
#else
/// When the current `EventLoopFuture<Value>` is in an error state, run the provided callback, which
/// may recover from the error by returning an `EventLoopFuture<NewValue>`. The callback is intended to potentially
/// recover from the error by returning a new `EventLoopFuture` that will eventually contain the recovered
/// result.
///
/// If the callback cannot recover it should return a failed `EventLoopFuture`.
///
/// - parameters:
/// - callback: Function that will receive the error value of this `EventLoopFuture` and return
/// a new value lifted into a new `EventLoopFuture`.
/// - returns: A future that will receive the recovered value.
@inlinable
public func flatMapErrorWithEventLoop(_ callback: @escaping (Error, EventLoop) -> EventLoopFuture<Value>) -> EventLoopFuture<Value> {
self._flatMapErrorWithEventLoop(callback)
}
@usableFromInline typealias FlatMapWithErrorWithEventLoopCallback = (Error, EventLoop) -> EventLoopFuture<Value>
#endif
@inlinable
func _flatMapErrorWithEventLoop(_ callback: @escaping FlatMapWithErrorWithEventLoopCallback) -> EventLoopFuture<Value> {
let next = EventLoopPromise<Value>.makeUnleakablePromise(eventLoop: self.eventLoop)
self._whenComplete { [eventLoop = self.eventLoop] in
switch self._value! {
@ -161,7 +96,6 @@ extension EventLoopFuture {
return next.futureResult
}
#if swift(>=5.6)
/// Returns a new `EventLoopFuture` that fires only when this `EventLoopFuture` and
/// all the provided `futures` complete. It then provides the result of folding the value of this
/// `EventLoopFuture` with the values of all the provided `futures`.
@ -197,62 +131,6 @@ extension EventLoopFuture {
return body
}
if self.eventLoop.inEventLoop {
return fold0(eventLoop: self.eventLoop)
} else {
let promise = self.eventLoop.makePromise(of: Value.self)
self.eventLoop.execute { [eventLoop = self.eventLoop] in
fold0(eventLoop: eventLoop).cascade(to: promise)
}
return promise.futureResult
}
}
@usableFromInline typealias FoldWithEventLoop<OtherValue> = @Sendable (Value, OtherValue, EventLoop) -> EventLoopFuture<Value>
#else
/// Returns a new `EventLoopFuture` that fires only when this `EventLoopFuture` and
/// all the provided `futures` complete. It then provides the result of folding the value of this
/// `EventLoopFuture` with the values of all the provided `futures`.
///
/// This function is suited when you have APIs that already know how to return `EventLoopFuture`s.
///
/// The returned `EventLoopFuture` will fail as soon as the a failure is encountered in any of the
/// `futures` (or in this one). However, the failure will not occur until all preceding
/// `EventLoopFutures` have completed. At the point the failure is encountered, all subsequent
/// `EventLoopFuture` objects will no longer be waited for. This function therefore fails fast: once
/// a failure is encountered, it will immediately fail the overall EventLoopFuture.
///
/// - parameters:
/// - futures: An array of `EventLoopFuture<NewValue>` to wait for.
/// - with: A function that will be used to fold the values of two `EventLoopFuture`s and return a new value wrapped in an `EventLoopFuture`.
/// - returns: A new `EventLoopFuture` with the folded value whose callbacks run on `self.eventLoop`.
@inlinable
public func foldWithEventLoop<OtherValue>(
_ futures: [EventLoopFuture<OtherValue>],
with combiningFunction: @escaping (Value, OtherValue, EventLoop) -> EventLoopFuture<Value>
) -> EventLoopFuture<Value> {
self._foldWithEventLoop(futures, with: combiningFunction)
}
@usableFromInline typealias FoldWithEventLoop<OtherValue> = (Value, OtherValue, EventLoop) -> EventLoopFuture<Value>
#endif
@inlinable
func _foldWithEventLoop<OtherValue>(
_ futures: [EventLoopFuture<OtherValue>],
with combiningFunction: @escaping FoldWithEventLoop<OtherValue>
) -> EventLoopFuture<Value> {
func fold0(eventLoop: EventLoop) -> EventLoopFuture<Value> {
let body = futures.reduce(self) { (f1: EventLoopFuture<Value>, f2: EventLoopFuture<OtherValue>) -> EventLoopFuture<Value> in
let newFuture = f1.and(f2).flatMap { (args: (Value, OtherValue)) -> EventLoopFuture<Value> in
let (f1Value, f2Value) = args
self.eventLoop.assertInEventLoop()
return combiningFunction(f1Value, f2Value, eventLoop)
}
assert(newFuture.eventLoop === self.eventLoop)
return newFuture
}
return body
}
if self.eventLoop.inEventLoop {
return fold0(eventLoop: self.eventLoop)
} else {

View File

@ -1233,7 +1233,7 @@ extension EventLoopFuture {
/// threads: it is primarily useful for testing, or for building interfaces between blocking
/// and non-blocking code.
///
/// This is also forbidden in async contexts: prefer ``EventLoopFuture/get``.
/// This is also forbidden in async contexts: prefer ``EventLoopFuture/get()``.
///
/// - returns: The value of the `EventLoopFuture` when it completes.
/// - throws: The error value of the `EventLoopFuture` if it errors.
@ -2187,6 +2187,88 @@ extension EventLoopFuture {
}
}
// MARK: assertion
extension EventLoopFuture {
/// Attaches a callback to the `EventLoopFuture` that asserts the original future's success.
///
/// If the original future fails, it triggers an assertion failure, causing a runtime error during development.
/// The assertion failure will include the file and line of the calling site.
///
/// - parameters:
/// - file: The file this function was called in, for debugging purposes.
/// - line: The line this function was called on, for debugging purposes.
@inlinable
public func assertSuccess(file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Value> {
return self.always { result in
switch result {
case .success:
()
case .failure(let error):
assertionFailure("Expected success, but got failure: \(error)", file: file, line: line)
}
}
}
/// Attaches a callback to the `EventLoopFuture` that asserts the original future's failure.
///
/// If the original future succeeds, it triggers an assertion failure, causing a runtime error during development.
/// The assertion failure will include the file and line of the calling site.
///
/// - parameters:
/// - file: The file this function was called in, for debugging purposes.
/// - line: The line this function was called on, for debugging purposes.
@inlinable
public func assertFailure(file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Value> {
return self.always { result in
switch result {
case .success(let value):
assertionFailure("Expected failure, but got success: \(value)", file: file, line: line)
case .failure:
()
}
}
}
/// Attaches a callback to the `EventLoopFuture` that preconditions the original future's success.
///
/// If the original future fails, it triggers a precondition failure, causing a runtime error during development.
/// The precondition failure will include the file and line of the calling site.
///
/// - parameters:
/// - file: The file this function was called in, for debugging purposes.
/// - line: The line this function was called on, for debugging purposes.
@inlinable
public func preconditionSuccess(file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Value> {
return self.always { result in
switch result {
case .success:
()
case .failure(let error):
Swift.preconditionFailure("Expected success, but got failure: \(error)", file: file, line: line)
}
}
}
/// Attaches a callback to the `EventLoopFuture` that preconditions the original future's failure.
///
/// If the original future succeeds, it triggers a precondition failure, causing a runtime error during development.
/// The precondition failure will include the file and line of the calling site.
///
/// - parameters:
/// - file: The file this function was called in, for debugging purposes.
/// - line: The line this function was called on, for debugging purposes.
@inlinable
public func preconditionFailure(file: StaticString = #fileID, line: UInt = #line) -> EventLoopFuture<Value> {
return self.always { result in
switch result {
case .success(let value):
Swift.preconditionFailure("Expected failure, but got success: \(value)", file: file, line: line)
case .failure:
()
}
}
}
}
/// An opaque identifier for a specific `EventLoopFuture`.
///

View File

@ -82,10 +82,8 @@ public struct FileRegion {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension FileRegion: Sendable {}
#endif
extension FileRegion {
/// Create a new `FileRegion` forming a complete file.

View File

@ -30,10 +30,8 @@ public enum IOData {
/// `IOData` objects are comparable just like the values they wrap.
extension IOData: Equatable {}
#if swift(>=5.6)
@available(*, unavailable)
extension IOData: Sendable {}
#endif
/// `IOData` provide a number of readable bytes.
extension IOData {

View File

@ -257,10 +257,8 @@ public struct NIOAny {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIOAny: Sendable {}
#endif
extension NIOAny: CustomStringConvertible {
public var description: String {

View File

@ -12,16 +12,16 @@
//
//===----------------------------------------------------------------------===//
/// ``NIOLoopBound`` is an always-``Sendable``, value-typed container allowing you access to ``Value`` if and only if
/// you are accessing it on the right EventLoop``.
/// ``NIOLoopBound`` is an always-`Sendable`, value-typed container allowing you access to ``value`` if and only if
/// you are accessing it on the right ``EventLoop``.
///
/// ``NIOLoopBound`` is useful to transport a value of a non-``Sendable`` type that needs to go from one place in
/// ``NIOLoopBound`` is useful to transport a value of a non-`Sendable` type that needs to go from one place in
/// your code to another where you (but not the compiler) know is on one and the same ``EventLoop``. Usually this
/// involves `@Sendable` closures. This type is safe because it verifies (using `eventLoop.preconditionInEventLoop()`)
/// involves `@Sendable` closures. This type is safe because it verifies (using ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``)
/// that this is actually true.
///
/// A ``NIOLoopBound`` can only be constructed, read from or written to when you are provably
/// (through `eventLoop.preconditionInEventLoop()`) on the ``EventLoop`` associated with the ``NIOLoopBound``. Accessing
/// (through ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``) on the ``EventLoop`` associated with the ``NIOLoopBound``. Accessing
/// or constructing it from any other place will crash your program with a precondition as it would be undefined
/// behaviour to do so.
public struct NIOLoopBound<Value>: @unchecked Sendable {
@ -54,22 +54,22 @@ public struct NIOLoopBound<Value>: @unchecked Sendable {
}
}
/// ``NIOLoopBoundBox`` is an always-``Sendable``, reference-typed container allowing you access to ``Value`` if and
/// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and
/// only if you are accessing it on the right EventLoop``.
///
/// ``NIOLoopBoundBox`` is useful to transport a value of a non-``Sendable`` type that needs to go from one place in
/// ``NIOLoopBoundBox`` is useful to transport a value of a non-`Sendable` type that needs to go from one place in
/// your code to another where you (but not the compiler) know is on one and the same ``EventLoop``. Usually this
/// involves `@Sendable` closures. This type is safe because it verifies (using `eventLoop.preconditionInEventLoop()`)
/// involves `@Sendable` closures. This type is safe because it verifies (using ``EventLoop/preconditionInEventLoop(file:line:)-7ukrq``)
/// that this is actually true.
///
/// A ``NIOLoopBoundBox`` can only be read from or written to when you are provably
/// (through `eventLoop.preconditionInEventLoop()`) on the ``EventLoop`` associated with the ``NIOLoopBoundBox``. Accessing
/// (through ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``) on the ``EventLoop`` associated with the ``NIOLoopBoundBox``. Accessing
/// or constructing it from any other place will crash your program with a precondition as it would be undefined
/// behaviour to do so.
///
/// If constructing a ``NIOLoopBoundBox`` with a `value`, it is also required for the program to already be on `eventLoop`
/// but if you have a ``NIOLoopBoundBox`` that contains an ``Optional`` type, you may initialise it _without a value_
/// whilst off the ``EventLoop`` by using ``NIOLoopBoundBox.makeEmptyBox``. Any read/write access to `value`
/// but if you have a ``NIOLoopBoundBox`` that contains an `Optional` type, you may initialise it _without a value_
/// whilst off the ``EventLoop`` by using ``NIOLoopBoundBox/makeEmptyBox(valueType:eventLoop:)``. Any read/write access to ``value``
/// afterwards will require you to be on `eventLoop`.
public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
public let _eventLoop: EventLoop

View File

@ -15,11 +15,10 @@
@available(*, deprecated, renamed: "Sendable")
public typealias NIOSendable = Swift.Sendable
#if swift(>=5.6)
@preconcurrency public protocol NIOPreconcurrencySendable: Sendable {}
#else
public protocol NIOPreconcurrencySendable {}
#endif
@preconcurrency public protocol _NIOPreconcurrencySendable: Sendable {}
@available(*, deprecated, message: "use @preconcurrency and Sendable directly")
public typealias NIOPreconcurrencySendable = _NIOPreconcurrencySendable
/// ``UnsafeTransfer`` can be used to make non-`Sendable` values `Sendable`.
/// As the name implies, the usage of this is unsafe because it disables the sendable checking of the compiler.

View File

@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
/// Allocates `ByteBuffer`s to be used to read bytes from a `Channel` and records the number of the actual bytes that were used.
public protocol RecvByteBufferAllocator: NIOPreconcurrencySendable {
public protocol RecvByteBufferAllocator: _NIOPreconcurrencySendable {
/// Allocates a new `ByteBuffer` that will be used to read bytes from a `Channel`.
func buffer(allocator: ByteBufferAllocator) -> ByteBuffer

View File

@ -281,6 +281,12 @@ extension NIOSingleStepByteToMessageProcessor: Sendable {}
// MARK: NIOSingleStepByteToMessageProcessor Public API
extension NIOSingleStepByteToMessageProcessor {
/// The number of bytes that are currently not processed by the ``process(buffer:_:)`` method. Having unprocessed
/// bytes may result from receiving only partial messages or from receiving multiple messages at once.
public var unprocessedBytes: Int {
self._buffer?.readableBytes ?? 0
}
/// Feed data into the `NIOSingleStepByteToMessageProcessor`
///
/// - parameters:

View File

@ -47,7 +47,7 @@ import WinSDK
///
/// - note: Like the `Channel` protocol, all methods in this protocol are
/// thread-safe.
public protocol SocketOptionProvider: NIOPreconcurrencySendable {
public protocol SocketOptionProvider: _NIOPreconcurrencySendable {
/// The `EventLoop` which is used by this `SocketOptionProvider` for execution.
var eventLoop: EventLoop { get }

View File

@ -206,24 +206,24 @@ extension System {
#if os(Linux)
/// Returns true if the platform supports 'UDP_SEGMENT' (GSO).
///
/// The option can be enabled by setting the ``DatagramSegmentSize`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option.
public static let supportsUDPSegmentationOffload: Bool = CNIOLinux_supports_udp_segment()
#else
/// Returns true if the platform supports 'UDP_SEGMENT' (GSO).
///
/// The option can be enabled by setting the ``DatagramSegmentSize`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option.
public static let supportsUDPSegmentationOffload: Bool = false
#endif
#if os(Linux)
/// Returns true if the platform supports 'UDP_GRO'.
///
/// The option can be enabled by setting the ``DatagramReceiveOffload`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option.
public static let supportsUDPReceiveOffload: Bool = CNIOLinux_supports_udp_gro()
#else
/// Returns true if the platform supports 'UDP_GRO'.
///
/// The option can be enabled by setting the ``DatagramReceiveOffload`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option.
public static let supportsUDPReceiveOffload: Bool = false
#endif
}

View File

@ -170,6 +170,16 @@ public final class NIOAsyncTestingChannel: Channel {
/// - note: An ``NIOAsyncTestingChannel`` starts _inactive_ and can be activated, for example by calling `connect`.
public var isActive: Bool { return channelcore.isActive }
/// - see: `ChannelOptions.Types.AllowRemoteHalfClosureOption`
public var allowRemoteHalfClosure: Bool {
get {
return channelcore.allowRemoteHalfClosure
}
set {
channelcore.allowRemoteHalfClosure = newValue
}
}
/// - see: `Channel.closeFuture`
public var closeFuture: EventLoopFuture<Void> { return channelcore.closePromise.futureResult }
@ -362,7 +372,7 @@ public final class NIOAsyncTestingChannel: Channel {
try self._readFromBuffer(buffer: &self.channelcore.outboundBuffer)
}
}
/// This method is similar to ``NIOAsyncTestingChannel/readOutbound(as:)`` but will wait if the outbound buffer is empty.
/// If available, this method reads one element of type `T` out of the ``NIOAsyncTestingChannel``'s outbound buffer. If the
/// first element was of a different type than requested, ``WrongTypeError`` will be thrown, if there
@ -412,7 +422,7 @@ public final class NIOAsyncTestingChannel: Channel {
try self._readFromBuffer(buffer: &self.channelcore.inboundBuffer)
}
}
/// This method is similar to ``NIOAsyncTestingChannel/readInbound(as:)`` but will wait if the inbound buffer is empty.
/// If available, this method reads one element of type `T` out of the ``NIOAsyncTestingChannel``'s inbound buffer. If the
/// first element was of a different type than requested, ``WrongTypeError`` will be thrown, if there
@ -499,7 +509,7 @@ public final class NIOAsyncTestingChannel: Channel {
throw error
}
}
@inlinable
func _readFromBuffer<T>(buffer: inout CircularBuffer<NIOAny>) throws -> T? {
@ -510,7 +520,7 @@ public final class NIOAsyncTestingChannel: Channel {
}
return try self._cast(buffer.removeFirst(), to: T.self)
}
@inlinable
func _cast<T>(_ element: NIOAny, to: T.Type = T.self) throws -> T {
guard let t = self._channelCore.tryUnwrapData(element, as: T.self) else {
@ -532,8 +542,12 @@ public final class NIOAsyncTestingChannel: Channel {
@inlinable
internal func setOptionSync<Option: ChannelOption>(_ option: Option, value: Option.Value) {
// No options supported
fatalError("no options supported")
if option is ChannelOptions.Types.AllowRemoteHalfClosureOption {
self.allowRemoteHalfClosure = value as! Bool
return
}
// No other options supported
fatalError("option not supported")
}
/// - see: `Channel.getOption`
@ -551,6 +565,9 @@ public final class NIOAsyncTestingChannel: Channel {
if option is ChannelOptions.Types.AutoReadOption {
return true as! Option.Value
}
if option is ChannelOptions.Types.AllowRemoteHalfClosureOption {
return self.allowRemoteHalfClosure as! Option.Value
}
fatalError("option \(option) not supported")
}

View File

@ -255,8 +255,18 @@ class EmbeddedChannelCore: ChannelCore {
}
}
var allowRemoteHalfClosure: Bool {
get {
return self._allowRemoteHalfClosure.load(ordering: .sequentiallyConsistent)
}
set {
self._allowRemoteHalfClosure.store(newValue, ordering: .sequentiallyConsistent)
}
}
private let _isOpen = ManagedAtomic(true)
private let _isActive = ManagedAtomic(false)
private let _allowRemoteHalfClosure = ManagedAtomic(false)
let eventLoop: EventLoop
let closePromise: EventLoopPromise<Void>
@ -281,7 +291,7 @@ class EmbeddedChannelCore: ChannelCore {
/// Contains the flushed items that went into the `Channel` (and on a regular channel would have hit the network).
@usableFromInline
var outboundBuffer: CircularBuffer<NIOAny> = CircularBuffer()
/// Contains observers that want to consume the first element that would be appended to the `outboundBuffer`
@usableFromInline
var outboundBufferConsumer: Deque<(NIOAny) -> Void> = []
@ -294,7 +304,7 @@ class EmbeddedChannelCore: ChannelCore {
/// regular `Channel` these items would be lost.
@usableFromInline
var inboundBuffer: CircularBuffer<NIOAny> = CircularBuffer()
/// Contains observers that want to consume the first element that would be appended to the `inboundBuffer`
@usableFromInline
var inboundBufferConsumer: Deque<(NIOAny) -> Void> = []
@ -551,6 +561,16 @@ public final class EmbeddedChannel: Channel {
/// - note: An `EmbeddedChannel` starts _inactive_ and can be activated, for example by calling `connect`.
public var isActive: Bool { return channelcore.isActive }
/// - see: `ChannelOptions.Types.AllowRemoteHalfClosureOption`
public var allowRemoteHalfClosure: Bool {
get {
return channelcore.allowRemoteHalfClosure
}
set {
channelcore.allowRemoteHalfClosure = newValue
}
}
/// - see: `Channel.closeFuture`
public var closeFuture: EventLoopFuture<Void> { return channelcore.closePromise.futureResult }
@ -749,7 +769,7 @@ public final class EmbeddedChannel: Channel {
let handlers = handler.map { [$0] } ?? []
self.init(handlers: handlers, loop: loop)
}
/// Create a new instance.
///
/// During creation it will automatically also register itself on the `EmbeddedEventLoop`.
@ -776,8 +796,12 @@ public final class EmbeddedChannel: Channel {
@inlinable
internal func setOptionSync<Option: ChannelOption>(_ option: Option, value: Option.Value) {
// No options supported
fatalError("no options supported")
if option is ChannelOptions.Types.AllowRemoteHalfClosureOption {
self.allowRemoteHalfClosure = value as! Bool
return
}
// No other options supported
fatalError("option not supported")
}
/// - see: `Channel.getOption`
@ -791,6 +815,9 @@ public final class EmbeddedChannel: Channel {
if option is ChannelOptions.Types.AutoReadOption {
return true as! Option.Value
}
if option is ChannelOptions.Types.AllowRemoteHalfClosureOption {
return self.allowRemoteHalfClosure as! Option.Value
}
fatalError("option \(option) not supported")
}
@ -848,7 +875,5 @@ extension EmbeddedChannel {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension EmbeddedChannel.SynchronousOptions: Sendable {}
#endif

View File

@ -744,10 +744,8 @@ public final class HTTPDecoder<In, Out>: ByteToMessageDecoder, HTTPDecoderDelega
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension HTTPDecoder: Sendable {}
#endif
/// Strategy to use when a HTTPDecoder is removed from a pipeline after a HTTP upgrade was detected.
public enum RemoveAfterUpgradeStrategy: Sendable {

View File

@ -166,10 +166,8 @@ public final class HTTPRequestEncoder: ChannelOutboundHandler, RemovableChannelH
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension HTTPRequestEncoder: Sendable {}
#endif
/// A `ChannelOutboundHandler` that can serialize HTTP responses.
///
@ -203,10 +201,8 @@ public final class HTTPResponseEncoder: ChannelOutboundHandler, RemovableChannel
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension HTTPResponseEncoder: Sendable {}
#endif
private extension ByteBuffer {
private mutating func write(status: HTTPResponseStatus) {

View File

@ -88,10 +88,8 @@ public final class NIOHTTPResponseHeadersValidator: ChannelOutboundHandler, Remo
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIOHTTPRequestHeadersValidator: Sendable {}
@available(*, unavailable)
extension NIOHTTPResponseHeadersValidator: Sendable {}
#endif

View File

@ -82,11 +82,22 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
/// to wait for the request to complete, but won't block anything.
case requestEndPending
/// The server has closed the output partway through a request. The server will never
/// act again, but this may not be in error, so we'll forward the rest of this request to the server.
case sentCloseOutputRequestEndPending
/// The server has closed the output, and a complete request has been delivered.
/// It's never going to act again. Generally we expect this to be closely followed
/// by read EOF, but we need to keep reading to make that possible, so we
/// never suppress reads again.
case sentCloseOutput
mutating func requestHeadReceived() {
switch self {
case .idle:
self = .requestAndResponseEndPending
case .requestAndResponseEndPending, .responseEndPending, .requestEndPending:
case .requestAndResponseEndPending, .responseEndPending, .requestEndPending,
.sentCloseOutputRequestEndPending, .sentCloseOutput:
preconditionFailure("received request head in state \(self)")
}
}
@ -100,6 +111,10 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
// We got a response while still receiving a request, which we have to
// wait for.
self = .requestEndPending
case .sentCloseOutput, .sentCloseOutputRequestEndPending:
// This is a user error: they have sent close(mode: .output), but are continuing to write.
// The write will fail, so we can allow it to pass.
()
case .requestEndPending, .idle:
preconditionFailure("Unexpectedly received a response in state \(self)")
}
@ -114,10 +129,25 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
// We got a request and the response isn't done, wait for the
// response.
self = .responseEndPending
case .responseEndPending, .idle:
case .sentCloseOutputRequestEndPending:
// Got the request end we were waiting for.
self = .sentCloseOutput
case .responseEndPending, .idle, .sentCloseOutput:
preconditionFailure("Received second request")
}
}
mutating func closeOutputSent() {
switch self {
case .idle, .responseEndPending:
self = .sentCloseOutput
case .requestEndPending, .requestAndResponseEndPending:
self = .sentCloseOutputRequestEndPending
case .sentCloseOutput, .sentCloseOutputRequestEndPending:
// Weird to duplicate fail, but we tolerate it in both cases.
()
}
}
}
/// The events that this handler buffers while waiting for the server to
@ -182,6 +212,11 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
()
}
if self.state == .sentCloseOutput {
// Drop all events in this state.
return
}
if self.eventBuffer.count != 0 || self.state == .responseEndPending {
self.eventBuffer.append(.channelRead(data))
return
@ -252,18 +287,20 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
// we're not in the middle of a request, let's just shut the door
self.lifecycleState = .quiescingLastRequestEndReceived
self.eventBuffer.removeAll()
case .idle:
case .idle, .sentCloseOutput:
// we're completely idle, let's just close
self.lifecycleState = .quiescingCompleted
self.eventBuffer.removeAll()
context.close(promise: nil)
case .requestEndPending, .requestAndResponseEndPending:
// we're in the middle of a request, we'll need to keep accepting events until we see the .end
case .requestEndPending, .requestAndResponseEndPending, .sentCloseOutputRequestEndPending:
// we're in the middle of a request, we'll need to keep accepting events until we see the .end.
// It's ok for us to forget we saw close output here, the lifecycle event will close for us.
self.lifecycleState = .quiescingWaitingForRequestEnd
}
case ChannelEvent.inputClosed:
// We only buffer half-close if there are request parts we're waiting to send.
// Otherwise we deliver the half-close immediately.
// Otherwise we deliver the half-close immediately. Note that we deliver this
// even if the server has sent close output, as it's useful information.
if case .responseEndPending = self.state, self.eventBuffer.count > 0 {
self.eventBuffer.append(.halfClose)
} else {
@ -414,6 +451,32 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
context.fireChannelInactive()
}
public func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
var shouldRead = false
if mode == .output {
// We need to do special handling here. If the server is closing output they don't intend to write anymore.
// That means we want to drop anything up to the end of the in-flight request.
self.dropAllButInFlightRequest()
self.state.closeOutputSent()
// If there's a read pending, we should deliver it after we forward the close on.
shouldRead = self.readPending
}
context.close(mode: mode, promise: promise)
// Double-check readPending here in case something weird happened.
//
// Note that because of the state transition in closeOutputSent() above we likely won't actually
// forward any further reads to the user, unless they belong to a request currently streaming in.
// Any reads past that point will be dropped in channelRead().
if shouldRead && self.readPending {
self.readPending = false
context.read()
}
}
/// A response has been sent: we can now start passing reads through
/// again if there are no further pending requests, and send any read()
/// call we may have swallowed.
@ -469,9 +532,32 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
context.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
}
}
private func dropAllButInFlightRequest() {
// We're going to walk the request buffer up to the next `.head` and drop from there.
let maybeFirstHead = self.eventBuffer.firstIndex(where: { element in
switch element {
case .channelRead(let read):
switch self.unwrapInboundIn(read) {
case .head:
return true
case .body, .end:
return false
}
case .error, .halfClose:
// Leave these where they are, if they're before the next .head we still want to deliver them.
// If they're after the next .head, we don't care.
return false
}
})
guard let firstHead = maybeFirstHead else {
return
}
self.eventBuffer.removeSubrange(firstHead...)
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension HTTPServerPipelineHandler: Sendable {}
#endif

View File

@ -72,7 +72,5 @@ public final class HTTPServerProtocolErrorHandler: ChannelDuplexHandler, Removab
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension HTTPServerProtocolErrorHandler: Sendable {}
#endif

View File

@ -301,10 +301,8 @@ public final class NIOHTTPServerRequestAggregator: ChannelInboundHandler, Remova
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIOHTTPServerRequestAggregator: Sendable {}
#endif
/// A `ChannelInboundHandler` that handles HTTP chunked `HTTPClientResponsePart`
/// messages by aggregating individual message chunks into a single
@ -403,7 +401,5 @@ public final class NIOHTTPClientResponseAggregator: ChannelInboundHandler, Remov
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIOHTTPClientResponseAggregator: Sendable {}
#endif

View File

@ -19,7 +19,7 @@ import Atomics
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
final class NIOAsyncSequenceProducerBenchmark: AsyncBenchmark, NIOAsyncSequenceProducerDelegate, @unchecked Sendable {
fileprivate typealias SequenceProducer = NIOThrowingAsyncSequenceProducer<Int, Never, NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark, NIOAsyncSequenceProducerBenchmark>
fileprivate typealias SequenceProducer = NIOThrowingAsyncSequenceProducer<Int, Error, NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark, NIOAsyncSequenceProducerBenchmark>
private let iterations: Int
private var iterator: SequenceProducer.AsyncIterator!

View File

@ -1091,36 +1091,40 @@ class BaseSocketChannel<SocketType: BaseSocketProtocol>: SelectableChannel, Chan
// peer closed / shutdown the connection.
if let channelErr = err as? ChannelError, channelErr == ChannelError.eof {
readStreamState = .eof
// Directly call getOption0 as we are already on the EventLoop and so not need to create an extra future.
// getOption0 can only fail if the channel is not active anymore but we assert further up that it is. If
// that's not the case this is a precondition failure and we would like to know.
if self.lifecycleManager.isActive, try! self.getOption0(ChannelOptions.allowRemoteHalfClosure) {
// If we want to allow half closure we will just mark the input side of the Channel
// as closed.
assert(self.lifecycleManager.isActive)
if self.lifecycleManager.isActive {
// Directly call getOption0 as we are already on the EventLoop and so not need to create an extra future.
//
// getOption0 can only fail if the channel is not active anymore but we assert further up that it is. If
// that's not the case this is a precondition failure and we would like to know.
let allowRemoteHalfClosure = try! self.getOption0(ChannelOptions.allowRemoteHalfClosure)
// For EOF, we always fire read complete.
self.pipeline.syncOperations.fireChannelReadComplete()
if self.shouldCloseOnReadError(err) {
self.close0(error: err, mode: .input, promise: nil)
if allowRemoteHalfClosure {
// If we want to allow half closure we will just mark the input side of the Channel
// as closed.
if self.shouldCloseOnReadError(err) {
self.close0(error: err, mode: .input, promise: nil)
}
self.readPending = false
return .eof
}
self.readPending = false
return .eof
}
} else {
readStreamState = .error
self.pipeline.syncOperations.fireErrorCaught(err)
}
// Call before triggering the close of the Channel.
if readStreamState != .error, self.lifecycleManager.isActive {
self.pipeline.syncOperations.fireChannelReadComplete()
}
if self.shouldCloseOnReadError(err) {
self.close0(error: err, mode: .all, promise: nil)
return readStreamState
} else {
// This is non-fatal, so continue as normal.
// This constitutes "some" as we did get at least an error from the socket.
readResult = .some
}
return readStreamState
}
// This assert needs to be disabled for io_uring, as the io_uring backend does not have the implicit synchronisation between
// modifications to the poll mask and the actual returned events on the completion queue that kqueue and epoll has.

File diff suppressed because it is too large Load Diff

View File

@ -26,30 +26,46 @@ import CNIOWindows
struct UnsafeControlMessageStorage: Collection {
let bytesPerMessage: Int
var buffer: UnsafeMutableRawBufferPointer
private let deallocateBuffer: Bool
/// Initialise which includes allocating memory
/// parameter:
/// - bytesPerMessage: How many bytes have been allocated for each supported message.
/// - buffer: The memory allocated to use for control messages.
private init(bytesPerMessage: Int, buffer: UnsafeMutableRawBufferPointer) {
/// - deallocateBuffer: buffer owning indicator
private init(bytesPerMessage: Int, buffer: UnsafeMutableRawBufferPointer, deallocateBuffer: Bool) {
self.bytesPerMessage = bytesPerMessage
self.buffer = buffer
self.deallocateBuffer = deallocateBuffer
}
// Guess that 4 Int32 payload messages is enough for anyone.
static var bytesPerMessage: Int { NIOBSDSocketControlMessage.space(payloadSize: MemoryLayout<Int32>.stride) * 4 }
/// Allocate new memory - Caller must call `deallocate` when no longer required.
/// parameter:
/// - msghdrCount: How many `msghdr` structures will be fed from this buffer - we assume 4 Int32 cmsgs for each.
static func allocate(msghdrCount: Int) -> UnsafeControlMessageStorage {
// Guess that 4 Int32 payload messages is enough for anyone.
let bytesPerMessage = NIOBSDSocketControlMessage.space(payloadSize: MemoryLayout<Int32>.stride) * 4
let bytesPerMessage = Self.bytesPerMessage
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bytesPerMessage * msghdrCount,
alignment: MemoryLayout<cmsghdr>.alignment)
return UnsafeControlMessageStorage(bytesPerMessage: bytesPerMessage, buffer: buffer)
alignment: MemoryLayout<cmsghdr>.alignment)
return UnsafeControlMessageStorage(bytesPerMessage: bytesPerMessage, buffer: buffer, deallocateBuffer: true)
}
/// Create an instance not owning the buffer
/// parameter:
/// - bytesPerMessage: How many bytes have been allocated for each supported message.
/// - buffer: The memory allocated to use for control messages.
static func makeNotOwning(bytesPerMessage: Int, buffer: UnsafeMutableRawBufferPointer) -> UnsafeControlMessageStorage {
precondition(buffer.count >= bytesPerMessage)
return UnsafeControlMessageStorage(bytesPerMessage: bytesPerMessage, buffer: buffer, deallocateBuffer: false)
}
mutating func deallocate() {
self.buffer.deallocate()
self.buffer = UnsafeMutableRawBufferPointer(start: UnsafeMutableRawPointer(bitPattern: 0x7eadbeef), count: 0)
if self.deallocateBuffer {
self.buffer.deallocate()
self.buffer = UnsafeMutableRawBufferPointer(start: UnsafeMutableRawPointer(bitPattern: 0x7eadbeef), count: 0)
}
}
/// Get the part of the buffer for use with a message.
@ -65,7 +81,6 @@ struct UnsafeControlMessageStorage: Collection {
func index(after: Int) -> Int {
return after + 1
}
}
/// Representation of a `cmsghdr` and associated data.

View File

@ -141,7 +141,10 @@ private struct TargetIterator: IteratorProtocol {
///
/// This class's private API is *not* thread-safe, and expects to be called from the
/// event loop thread of the `loop` it is passed.
internal class HappyEyeballsConnector {
///
/// The `ChannelBuilderResult` generic type can used to tunnel an arbitrary type
/// from the `channelBuilderCallback` to the `resolve` methods return value.
internal final class HappyEyeballsConnector<ChannelBuilderResult> {
/// An enum for keeping track of connection state.
private enum ConnectionState {
/// Initial state. No work outstanding.
@ -223,7 +226,7 @@ internal class HappyEyeballsConnector {
/// than intended.
///
/// The channel builder callback takes an event loop and a protocol family as arguments.
private let channelBuilderCallback: (EventLoop, NIOBSDSocket.ProtocolFamily) -> EventLoopFuture<Channel>
private let channelBuilderCallback: (EventLoop, NIOBSDSocket.ProtocolFamily) -> EventLoopFuture<(Channel, ChannelBuilderResult)>
/// The amount of time to wait for an AAAA response to come in after a A response is
/// received. By default this is 50ms.
@ -250,7 +253,7 @@ internal class HappyEyeballsConnector {
private var timeoutTask: Optional<Scheduled<Void>>
/// The promise that will hold the final connected channel.
private let resolutionPromise: EventLoopPromise<Channel>
private let resolutionPromise: EventLoopPromise<(Channel, ChannelBuilderResult)>
/// Our state machine state.
private var state: ConnectionState
@ -263,7 +266,7 @@ internal class HappyEyeballsConnector {
///
/// This is kept to ensure that we can clean up after ourselves once a connection succeeds,
/// and throw away all pending connection attempts that are no longer needed.
private var pendingConnections: [EventLoopFuture<Channel>] = []
private var pendingConnections: [EventLoopFuture<(Channel, ChannelBuilderResult)>] = []
/// The number of DNS resolutions that have returned.
///
@ -274,6 +277,7 @@ internal class HappyEyeballsConnector {
/// An object that holds any errors we encountered.
private var error: NIOConnectionError
@inlinable
init(resolver: Resolver,
loop: EventLoop,
host: String,
@ -281,7 +285,7 @@ internal class HappyEyeballsConnector {
connectTimeout: TimeAmount,
resolutionDelay: TimeAmount = .milliseconds(50),
connectionDelay: TimeAmount = .milliseconds(250),
channelBuilderCallback: @escaping (EventLoop, NIOBSDSocket.ProtocolFamily) -> EventLoopFuture<Channel>) {
channelBuilderCallback: @escaping (EventLoop, NIOBSDSocket.ProtocolFamily) -> EventLoopFuture<(Channel, ChannelBuilderResult)>) {
self.resolver = resolver
self.loop = loop
self.host = host
@ -303,10 +307,34 @@ internal class HappyEyeballsConnector {
self.connectionDelay = connectionDelay
}
@inlinable
convenience init(
resolver: Resolver,
loop: EventLoop,
host: String,
port: Int,
connectTimeout: TimeAmount,
resolutionDelay: TimeAmount = .milliseconds(50),
connectionDelay: TimeAmount = .milliseconds(250),
channelBuilderCallback: @escaping (EventLoop, NIOBSDSocket.ProtocolFamily) -> EventLoopFuture<Channel>
) where ChannelBuilderResult == Void {
self.init(
resolver: resolver,
loop: loop,
host: host,
port: port,
connectTimeout: connectTimeout,
resolutionDelay: resolutionDelay,
connectionDelay: connectionDelay) { loop, protocolFamily in
channelBuilderCallback(loop, protocolFamily).map { ($0, ()) }
}
}
/// Initiate a DNS resolution attempt using Happy Eyeballs 2.
///
/// returns: An `EventLoopFuture` that fires with a connected `Channel`.
public func resolveAndConnect() -> EventLoopFuture<Channel> {
@inlinable
func resolveAndConnect() -> EventLoopFuture<(Channel, ChannelBuilderResult)> {
// We dispatch ourselves onto the event loop, rather than do all the rest of our processing from outside it.
self.loop.execute {
self.timeoutTask = self.loop.scheduleTask(in: self.connectTimeout) { self.processInput(.connectTimeoutElapsed) }
@ -315,6 +343,14 @@ internal class HappyEyeballsConnector {
return resolutionPromise.futureResult
}
/// Initiate a DNS resolution attempt using Happy Eyeballs 2.
///
/// returns: An `EventLoopFuture` that fires with a connected `Channel`.
@inlinable
func resolveAndConnect() -> EventLoopFuture<Channel> where ChannelBuilderResult == Void {
self.resolveAndConnect().map { $0.0 }
}
/// Spin the state machine.
///
/// - parameters:
@ -433,8 +469,14 @@ internal class HappyEyeballsConnector {
// The two queries SHOULD be made as soon after one another as possible,
// with the AAAA query made first and immediately followed by the A
// query.
whenAAAALookupComplete(future: resolver.initiateAAAAQuery(host: host, port: port))
whenALookupComplete(future: resolver.initiateAQuery(host: host, port: port))
//
// We hop back to `self.loop` because there's no guarantee the resolver runs
// on our event loop.
let aaaaLookup = self.resolver.initiateAAAAQuery(host: self.host, port: self.port).hop(to: self.loop)
self.whenAAAALookupComplete(future: aaaaLookup)
let aLookup = self.resolver.initiateAQuery(host: self.host, port: self.port).hop(to: self.loop)
self.whenALookupComplete(future: aLookup)
}
/// Called when the A query has completed before the AAAA query.
@ -534,11 +576,11 @@ internal class HappyEyeballsConnector {
let channelFuture = channelBuilderCallback(self.loop, target.protocol)
pendingConnections.append(channelFuture)
channelFuture.whenSuccess { channel in
channelFuture.whenSuccess { (channel, result) in
// If we are in the complete state then we want to abandon this channel. Otherwise, begin
// connecting.
if case .complete = self.state {
self.pendingConnections.remove(element: channelFuture)
self.pendingConnections.removeAll { $0 === channelFuture }
channel.close(promise: nil)
} else {
channel.connect(to: target).map {
@ -546,13 +588,13 @@ internal class HappyEyeballsConnector {
// Otherwise, fire the channel connected event. Either way we don't want the channel future to
// be in our list of pending connections, so we don't either double close or close the connection
// we want to use.
self.pendingConnections.remove(element: channelFuture)
self.pendingConnections.removeAll { $0 === channelFuture }
if case .complete = self.state {
channel.close(promise: nil)
} else {
self.processInput(.connectSuccess)
self.resolutionPromise.succeed(channel)
self.resolutionPromise.succeed((channel, result))
}
}.whenFailure { err in
// The connection attempt failed. If we're in the complete state then there's nothing
@ -561,7 +603,7 @@ internal class HappyEyeballsConnector {
assert(self.pendingConnections.firstIndex { $0 === channelFuture } == nil, "failed but was still in pending connections")
} else {
self.error.connectionErrors.append(SingleConnectionFailure(target: target, error: err))
self.pendingConnections.remove(element: channelFuture)
self.pendingConnections.removeAll { $0 === channelFuture }
self.processInput(.connectFailed)
}
}
@ -569,7 +611,7 @@ internal class HappyEyeballsConnector {
}
channelFuture.whenFailure { error in
self.error.connectionErrors.append(SingleConnectionFailure(target: target, error: error))
self.pendingConnections.remove(element: channelFuture)
self.pendingConnections.removeAll { $0 === channelFuture }
self.processInput(.connectFailed)
}
}
@ -601,7 +643,7 @@ internal class HappyEyeballsConnector {
let connections = self.pendingConnections
self.pendingConnections = []
for connection in connections {
connection.whenSuccess { channel in channel.close(promise: nil) }
connection.whenSuccess { (channel, _) in channel.close(promise: nil) }
}
}

View File

@ -383,7 +383,6 @@ final class PendingDatagramWritesManager: PendingWritesManager {
private let bufferPool: Pool<PooledBuffer>
private let msgBufferPool: Pool<PooledMsgBuffer>
private let controlMessageStorage: UnsafeControlMessageStorage
private var state = PendingDatagramWritesState()
@ -400,13 +399,10 @@ final class PendingDatagramWritesManager: PendingWritesManager {
///
/// - parameters:
/// - bufferPool: a pool of buffers to be used for IOVector and storage references
/// - msgs: A pre-allocated array of `MMsgHdr` elements
/// - addresses: A pre-allocated array of `sockaddr_storage` elements
/// - controlMessageStorage: Pre-allocated memory for storing cmsghdr data during a vector write operation.
init(bufferPool: Pool<PooledBuffer>, msgBufferPool: Pool<PooledMsgBuffer>, controlMessageStorage: UnsafeControlMessageStorage) {
/// - msgBufferPool: a pool of buffers to be usded for `MMsgHdr`, `sockaddr_storage` and cmsghdr elements
init(bufferPool: Pool<PooledBuffer>, msgBufferPool: Pool<PooledMsgBuffer>) {
self.bufferPool = bufferPool
self.msgBufferPool = msgBufferPool
self.controlMessageStorage = controlMessageStorage
}
/// Mark the flush checkpoint.
@ -610,12 +606,12 @@ final class PendingDatagramWritesManager: PendingWritesManager {
let msgBuffer = self.msgBufferPool.get()
defer { self.msgBufferPool.put(msgBuffer) }
return try msgBuffer.withUnsafePointers { msgs, addresses in
return try msgBuffer.withUnsafePointers { msgs, addresses, controlMessageStorage in
return self.didWrite(try doPendingDatagramWriteVectorOperation(pending: self.state,
bufferPool: self.bufferPool,
msgs: msgs,
addresses: addresses,
controlMessageStorage: self.controlMessageStorage,
controlMessageStorage: controlMessageStorage,
{ try vectorWriteOperation($0) }),
messages: msgs)
}

View File

@ -212,6 +212,7 @@ struct PooledMsgBuffer: PoolElement {
let count: Int
let spaceForMsgHdrs: Int
let spaceForAddresses: Int
let spaceForControlData: Int
init(count: Int) {
var spaceForMsgHdrs = MemoryLayout<MMsgHdr>.stride * count
@ -220,13 +221,17 @@ struct PooledMsgBuffer: PoolElement {
var spaceForAddress = MemoryLayout<sockaddr_storage>.stride * count
spaceForAddress.roundUpToAlignment(for: MemorySentinel.self)
var spaceForControlData = (UnsafeControlMessageStorage.bytesPerMessage * count)
spaceForControlData.roundUpToAlignment(for: cmsghdr.self)
self.count = count
self.spaceForMsgHdrs = spaceForMsgHdrs
self.spaceForAddresses = spaceForAddress
self.spaceForControlData = spaceForControlData
}
var totalByteCount: Int {
self.spaceForMsgHdrs + self.spaceForAddresses + MemoryLayout<MemorySentinel>.size
self.spaceForMsgHdrs + self.spaceForAddresses + self.spaceForControlData + MemoryLayout<MemorySentinel>.size
}
var msgHdrsOffset: Int {
@ -237,8 +242,12 @@ struct PooledMsgBuffer: PoolElement {
self.spaceForMsgHdrs
}
var controlDataOffset: Int {
self.spaceForMsgHdrs + self.spaceForAddresses
}
var memorySentinelOffset: Int {
return self.spaceForMsgHdrs + self.spaceForAddresses
return self.spaceForMsgHdrs + self.spaceForAddresses + self.spaceForControlData
}
}
@ -254,6 +263,7 @@ struct PooledMsgBuffer: PoolElement {
storage.withUnsafeMutablePointers { headPointer, tailPointer in
UnsafeRawPointer(tailPointer + headPointer.pointee.msgHdrsOffset).bindMemory(to: MMsgHdr.self, capacity: count)
UnsafeRawPointer(tailPointer + headPointer.pointee.addressesOffset).bindMemory(to: sockaddr_storage.self, capacity: count)
// space for control message data not needed to be bound
UnsafeRawPointer(tailPointer + headPointer.pointee.memorySentinelOffset).bindMemory(to: MemorySentinel.self, capacity: 1)
}
@ -261,11 +271,12 @@ struct PooledMsgBuffer: PoolElement {
}
func withUnsafeMutableTypedPointers<ReturnType>(
_ body: (UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>, UnsafeMutablePointer<MemorySentinel>) throws -> ReturnType
_ body: (UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>, UnsafeControlMessageStorage, UnsafeMutablePointer<MemorySentinel>) throws -> ReturnType
) rethrows -> ReturnType {
return try self.withUnsafeMutablePointers { headPointer, tailPointer in
let msgHdrsPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.msgHdrsOffset).assumingMemoryBound(to: MMsgHdr.self)
let addressesPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.addressesOffset).assumingMemoryBound(to: sockaddr_storage.self)
let controlDataPointer = UnsafeMutableRawBufferPointer(start: tailPointer + headPointer.pointee.controlDataOffset, count: headPointer.pointee.spaceForControlData)
let sentinelPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.memorySentinelOffset).assumingMemoryBound(to: MemorySentinel.self)
let msgHdrsBufferPointer = UnsafeMutableBufferPointer(
@ -274,13 +285,16 @@ struct PooledMsgBuffer: PoolElement {
let addressesBufferPointer = UnsafeMutableBufferPointer(
start: addressesPointer, count: headPointer.pointee.count
)
return try body(msgHdrsBufferPointer, addressesBufferPointer, sentinelPointer)
let controlMessageStorage = UnsafeControlMessageStorage.makeNotOwning(
bytesPerMessage: UnsafeControlMessageStorage.bytesPerMessage,
buffer: controlDataPointer)
return try body(msgHdrsBufferPointer, addressesBufferPointer, controlMessageStorage, sentinelPointer)
}
}
}
private func validateSentinel() {
self.storage.withUnsafeMutableTypedPointers { _, _, sentinelPointer in
self.storage.withUnsafeMutableTypedPointers { _, _, _, sentinelPointer in
precondition(sentinelPointer.pointee == Self.sentinelValue, "Detected memory handling error!")
}
}
@ -289,7 +303,7 @@ struct PooledMsgBuffer: PoolElement {
init() {
self.storage = .create(count: Socket.writevLimitIOVectors)
self.storage.withUnsafeMutableTypedPointers { _, _, sentinelPointer in
self.storage.withUnsafeMutableTypedPointers { _, _, _, sentinelPointer in
sentinelPointer.pointee = Self.sentinelValue
}
}
@ -299,22 +313,22 @@ struct PooledMsgBuffer: PoolElement {
}
func withUnsafePointers<ReturnValue>(
_ body: (UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>) throws -> ReturnValue
_ body: (UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>, UnsafeControlMessageStorage) throws -> ReturnValue
) rethrows -> ReturnValue {
defer {
self.validateSentinel()
}
return try self.storage.withUnsafeMutableTypedPointers { msgs, addresses, _ in
return try body(msgs, addresses)
return try self.storage.withUnsafeMutableTypedPointers { msgs, addresses, controlMessageStorage, _ in
return try body(msgs, addresses, controlMessageStorage)
}
}
func withUnsafePointersWithStorageManagement<ReturnValue>(
_ body: (UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>, Unmanaged<AnyObject>) throws -> ReturnValue
_ body: (UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>, UnsafeControlMessageStorage, Unmanaged<AnyObject>) throws -> ReturnValue
) rethrows -> ReturnValue {
let storageRef: Unmanaged<AnyObject> = Unmanaged.passUnretained(self.storage)
return try self.storage.withUnsafeMutableTypedPointers { msgs, addresses, _ in
try body(msgs, addresses, storageRef)
return try self.storage.withUnsafeMutableTypedPointers { msgs, addresses, controlMessageStorage, _ in
try body(msgs, addresses, controlMessageStorage, storageRef)
}
}
}

View File

@ -191,7 +191,5 @@ public final class NIORawSocketBootstrap {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIORawSocketBootstrap: Sendable {}
#endif

View File

@ -105,9 +105,6 @@ internal final class SelectableEventLoop: EventLoop {
let bufferPool: Pool<PooledBuffer>
let msgBufferPool: Pool<PooledMsgBuffer>
// Used for UDP control messages.
private(set) var controlMessageStorage: UnsafeControlMessageStorage
// The `_parentGroup` will always be set unless this is a thread takeover or we shut down.
@usableFromInline
internal var _parentGroup: Optional<MultiThreadedEventLoopGroup>
@ -185,7 +182,6 @@ Further information:
self.thread = thread
self.bufferPool = Pool<PooledBuffer>(maxSize: 16)
self.msgBufferPool = Pool<PooledMsgBuffer>(maxSize: 16)
self.controlMessageStorage = UnsafeControlMessageStorage.allocate(msghdrCount: Socket.writevLimitIOVectors)
// We will process 4096 tasks per while loop.
self.tasksCopy.reserveCapacity(4096)
self.canBeShutdownIndividually = canBeShutdownIndividually
@ -202,7 +198,6 @@ Further information:
"illegal internal state on deinit: \(self.internalState)")
assert(self.externalState == .resourcesReclaimed,
"illegal external state on shutdown: \(self.externalState)")
self.controlMessageStorage.deallocate()
}
/// Is this `SelectableEventLoop` still open (ie. not shutting down or shut down)

View File

@ -106,7 +106,9 @@ import NIOCore
guard let fd = result else {
return nil
}
let sock = try Socket(socket: fd)
#if !os(Linux)
if setNonBlocking {
do {

View File

@ -424,8 +424,7 @@ final class DatagramChannel: BaseSocketChannel<Socket> {
}
self.pendingWrites = PendingDatagramWritesManager(bufferPool: eventLoop.bufferPool,
msgBufferPool: eventLoop.msgBufferPool,
controlMessageStorage: eventLoop.controlMessageStorage)
msgBufferPool: eventLoop.msgBufferPool)
try super.init(
socket: socket,
@ -440,8 +439,7 @@ final class DatagramChannel: BaseSocketChannel<Socket> {
self.vectorReadManager = nil
try socket.setNonBlocking()
self.pendingWrites = PendingDatagramWritesManager(bufferPool: eventLoop.bufferPool,
msgBufferPool: eventLoop.msgBufferPool,
controlMessageStorage: eventLoop.controlMessageStorage)
msgBufferPool: eventLoop.msgBufferPool)
try super.init(
socket: socket,
parent: parent,
@ -607,24 +605,22 @@ final class DatagramChannel: BaseSocketChannel<Socket> {
override func readFromSocket() throws -> ReadResult {
if self.vectorReadManager != nil {
return try self.vectorReadFromSocket()
} else if self.reportExplicitCongestionNotifications || self.receivePacketInfo {
let pooledMsgBuffer = self.selectableEventLoop.msgBufferPool.get()
defer { self.selectableEventLoop.msgBufferPool.put(pooledMsgBuffer) }
return try pooledMsgBuffer.withUnsafePointers { _, _, controlMessageStorage in
return try self.singleReadFromSocket(controlBytesBuffer: controlMessageStorage[0])
}
} else {
return try self.singleReadFromSocket()
return try self.singleReadFromSocket(controlBytesBuffer: UnsafeMutableRawBufferPointer(start: nil, count: 0))
}
}
private func singleReadFromSocket() throws -> ReadResult {
private func singleReadFromSocket(controlBytesBuffer: UnsafeMutableRawBufferPointer) throws -> ReadResult {
var rawAddress = sockaddr_storage()
var rawAddressLength = socklen_t(MemoryLayout<sockaddr_storage>.size)
var readResult = ReadResult.none
// These control bytes must not escape the current call stack
let controlBytesBuffer: UnsafeMutableRawBufferPointer
if self.reportExplicitCongestionNotifications || self.receivePacketInfo {
controlBytesBuffer = self.selectableEventLoop.controlMessageStorage[0]
} else {
controlBytesBuffer = UnsafeMutableRawBufferPointer(start: nil, count: 0)
}
for _ in 1...self.maxMessagesPerRead {
guard self.isOpen else {
throw ChannelError.eof
@ -804,16 +800,17 @@ final class DatagramChannel: BaseSocketChannel<Socket> {
override func writeToSocket() throws -> OverallWriteResult {
let result = try self.pendingWrites.triggerAppropriateWriteOperations(
scalarWriteOperation: { (ptr, destinationPtr, destinationSize, metadata) in
// normal write
// Control bytes must not escape current stack.
var controlBytes = UnsafeOutboundControlBytes(
controlBytes: self.selectableEventLoop.controlMessageStorage[0])
controlBytes.appendExplicitCongestionState(metadata: metadata,
protocolFamily: self.localAddress?.protocol)
return try self.socket.sendmsg(pointer: ptr,
destinationPtr: destinationPtr,
destinationSize: destinationSize,
controlBytes: controlBytes.validControlBytes)
let msgBuffer = self.selectableEventLoop.msgBufferPool.get()
defer { self.selectableEventLoop.msgBufferPool.put(msgBuffer) }
return try msgBuffer.withUnsafePointers { _, _, controlMessageStorage in
var controlBytes = UnsafeOutboundControlBytes(controlBytes: controlMessageStorage[0])
controlBytes.appendExplicitCongestionState(metadata: metadata,
protocolFamily: self.localAddress?.protocol)
return try self.socket.sendmsg(pointer: ptr,
destinationPtr: destinationPtr,
destinationSize: destinationSize,
controlBytes: controlBytes.validControlBytes)
}
},
vectorWriteOperation: { msgs in
return try self.socket.sendmmsg(msgs: msgs)
@ -822,7 +819,6 @@ final class DatagramChannel: BaseSocketChannel<Socket> {
return result
}
// MARK: Datagram Channel overrides not required by BaseSocketChannel
override func bind0(to address: SocketAddress, promise: EventLoopPromise<Void>?) {

View File

@ -93,11 +93,11 @@ extension BaseSocketProtocol {
do {
try Posix.fcntl(descriptor: fd, command: F_SETNOSIGPIPE, value: 1)
} catch let error as IOError {
try? Posix.close(descriptor: fd) // don't care about failure here
if error.errnoCode == EINVAL {
// Darwin seems to sometimes do this despite the docs claiming it can't happen
throw NIOFcntlFailedError()
}
try? Posix.close(descriptor: fd) // don't care about failure here
throw error
}
#endif

View File

@ -34,6 +34,14 @@ public enum ALPNResult: Equatable, Sendable {
/// ALPN negotiation either failed, or never took place. The application
/// should fall back to a default protocol choice or close the connection.
case fallback
init(negotiated: String?) {
if let negotiated = negotiated {
self = .negotiated(negotiated)
} else {
self = .fallback
}
}
}
/// A helper `ChannelInboundHandler` that makes it easy to swap channel pipelines
@ -61,8 +69,7 @@ public final class ApplicationProtocolNegotiationHandler: ChannelInboundHandler,
public typealias InboundOut = Any
private let completionHandler: (ALPNResult, Channel) -> EventLoopFuture<Void>
private var waitingForUser: Bool
private var eventBuffer: [NIOAny]
private var stateMachine = ProtocolNegotiationHandlerStateMachine<Void>()
/// Create an `ApplicationProtocolNegotiationHandler` with the given completion
/// callback.
@ -71,8 +78,6 @@ public final class ApplicationProtocolNegotiationHandler: ChannelInboundHandler,
/// negotiation has completed.
public init(alpnCompleteHandler: @escaping (ALPNResult, Channel) -> EventLoopFuture<Void>) {
self.completionHandler = alpnCompleteHandler
self.waitingForUser = false
self.eventBuffer = []
}
/// Create an `ApplicationProtocolNegotiationHandler` with the given completion
@ -87,57 +92,73 @@ public final class ApplicationProtocolNegotiationHandler: ChannelInboundHandler,
}
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
guard let tlsEvent = event as? TLSUserEvent else {
switch self.stateMachine.userInboundEventTriggered(event: event) {
case .fireUserInboundEventTriggered:
context.fireUserInboundEventTriggered(event)
return
}
if case .handshakeCompleted(let p) = tlsEvent {
handshakeCompleted(context: context, negotiatedProtocol: p)
} else {
context.fireUserInboundEventTriggered(event)
case .invokeUserClosure(let result):
self.invokeUserClosure(context: context, result: result)
}
}
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
if waitingForUser {
eventBuffer.append(data)
} else {
switch self.stateMachine.channelRead(data: data) {
case .fireChannelRead:
context.fireChannelRead(data)
case .none:
break
}
}
private func handshakeCompleted(context: ChannelHandlerContext, negotiatedProtocol: String?) {
waitingForUser = true
public func channelInactive(context: ChannelHandlerContext) {
self.stateMachine.channelInactive()
let result: ALPNResult
if let negotiatedProtocol = negotiatedProtocol {
result = .negotiated(negotiatedProtocol)
} else {
result = .fallback
}
context.fireChannelInactive()
}
private func invokeUserClosure(context: ChannelHandlerContext, result: ALPNResult) {
let switchFuture = self.completionHandler(result, context.channel)
switchFuture.whenComplete { (_: Result<Void, Error>) in
switchFuture
.hop(to: context.eventLoop)
.whenComplete { result in
self.userFutureCompleted(context: context, result: result)
}
}
private func userFutureCompleted(context: ChannelHandlerContext, result: Result<Void, Error>) {
switch self.stateMachine.userFutureCompleted(with: result) {
case .fireErrorCaughtAndRemoveHandler(let error):
context.fireErrorCaught(error)
context.pipeline.removeHandler(self, promise: nil)
case .fireErrorCaughtAndStartUnbuffering(let error):
context.fireErrorCaught(error)
self.unbuffer(context: context)
case .startUnbuffering:
self.unbuffer(context: context)
case .removeHandler:
context.pipeline.removeHandler(self, promise: nil)
}
}
private func unbuffer(context: ChannelHandlerContext) {
for datum in eventBuffer {
context.fireChannelRead(datum)
}
let buffer = eventBuffer
eventBuffer = []
waitingForUser = false
if buffer.count > 0 {
context.fireChannelReadComplete()
while true {
switch self.stateMachine.unbuffer() {
case .fireChannelRead(let data):
context.fireChannelRead(data)
case .fireChannelReadCompleteAndRemoveHandler:
context.fireChannelReadComplete()
context.pipeline.removeHandler(self, promise: nil)
return
}
}
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension ApplicationProtocolNegotiationHandler: Sendable {}
#endif

View File

@ -0,0 +1,161 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
@_spi(AsyncChannel) import NIOCore
/// A helper ``ChannelInboundHandler`` that makes it easy to swap channel pipelines
/// based on the result of an ALPN negotiation.
///
/// The standard pattern used by applications that want to use ALPN is to select
/// an application protocol based on the result, optionally falling back to some
/// default protocol. To do this in SwiftNIO requires that the channel pipeline be
/// reconfigured based on the result of the ALPN negotiation. This channel handler
/// encapsulates that logic in a generic form that doesn't depend on the specific
/// TLS implementation in use by using ``TLSUserEvent``
///
/// The user of this channel handler provides a single closure that is called with
/// an ``ALPNResult`` when the ALPN negotiation is complete. Based on that result
/// the user is free to reconfigure the ``ChannelPipeline`` as required, and should
/// return an ``EventLoopFuture`` that will complete when the pipeline is reconfigured.
///
/// Until the ``EventLoopFuture`` completes, this channel handler will buffer inbound
/// data. When the ``EventLoopFuture`` completes, the buffered data will be replayed
/// down the channel. Then, finally, this channel handler will automatically remove
/// itself from the channel pipeline, leaving the pipeline in its final
/// configuration.
///
/// Importantly, this is a typed variant of the ``ApplicationProtocolNegotiationHandler`` and allows the user to
/// specify a type that must be returned from the supplied closure. The result will then be used to succeed the ``NIOTypedApplicationProtocolNegotiationHandler/protocolNegotiationResult``
/// promise. This allows us to construct pipelines that include protocol negotiation handlers and be able to bridge them into ``NIOAsyncChannel``
/// based bootstraps.
@_spi(AsyncChannel)
public final class NIOTypedApplicationProtocolNegotiationHandler<NegotiationResult>: ChannelInboundHandler, RemovableChannelHandler, NIOProtocolNegotiationHandler {
@_spi(AsyncChannel)
public typealias InboundIn = Any
@_spi(AsyncChannel)
public typealias InboundOut = Any
@_spi(AsyncChannel)
public var protocolNegotiationResult: EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>> {
self.negotiatedPromise.futureResult
}
private let negotiatedPromise: EventLoopPromise<NIOProtocolNegotiationResult<NegotiationResult>>
private let completionHandler: (ALPNResult, Channel) -> EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>>
private var stateMachine = ProtocolNegotiationHandlerStateMachine<NIOProtocolNegotiationResult<NegotiationResult>>()
/// Create an `ApplicationProtocolNegotiationHandler` with the given completion
/// callback.
///
/// - Parameter alpnCompleteHandler: The closure that will fire when ALPN
/// negotiation has completed.
@_spi(AsyncChannel)
public init(eventLoop: EventLoop, alpnCompleteHandler: @escaping (ALPNResult, Channel) -> EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>>) {
self.completionHandler = alpnCompleteHandler
self.negotiatedPromise = eventLoop.makePromise(of: NIOProtocolNegotiationResult<NegotiationResult>.self)
}
/// Create an `ApplicationProtocolNegotiationHandler` with the given completion
/// callback.
///
/// - Parameter alpnCompleteHandler: The closure that will fire when ALPN
/// negotiation has completed.
@_spi(AsyncChannel)
public convenience init(eventLoop: EventLoop, alpnCompleteHandler: @escaping (ALPNResult) -> EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>>) {
self.init(eventLoop: eventLoop) { result, _ in
alpnCompleteHandler(result)
}
}
@_spi(AsyncChannel)
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
switch self.stateMachine.userInboundEventTriggered(event: event) {
case .fireUserInboundEventTriggered:
context.fireUserInboundEventTriggered(event)
case .invokeUserClosure(let result):
self.invokeUserClosure(context: context, result: result)
}
}
@_spi(AsyncChannel)
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
switch self.stateMachine.channelRead(data: data) {
case .fireChannelRead:
context.fireChannelRead(data)
case .none:
break
}
}
@_spi(AsyncChannel)
public func channelInactive(context: ChannelHandlerContext) {
self.stateMachine.channelInactive()
self.negotiatedPromise.fail(ChannelError.outputClosed)
context.fireChannelInactive()
}
private func invokeUserClosure(context: ChannelHandlerContext, result: ALPNResult) {
let switchFuture = self.completionHandler(result, context.channel)
switchFuture
.hop(to: context.eventLoop)
.whenComplete { result in
self.userFutureCompleted(context: context, result: result)
}
}
private func userFutureCompleted(context: ChannelHandlerContext, result: Result<NIOProtocolNegotiationResult<NegotiationResult>, Error>) {
switch self.stateMachine.userFutureCompleted(with: result) {
case .fireErrorCaughtAndRemoveHandler(let error):
self.negotiatedPromise.fail(error)
context.fireErrorCaught(error)
context.pipeline.removeHandler(self, promise: nil)
case .fireErrorCaughtAndStartUnbuffering(let error):
self.negotiatedPromise.fail(error)
context.fireErrorCaught(error)
self.unbuffer(context: context)
case .startUnbuffering(let value):
self.negotiatedPromise.succeed(value)
self.unbuffer(context: context)
case .removeHandler(let value):
self.negotiatedPromise.succeed(value)
context.pipeline.removeHandler(self, promise: nil)
}
}
private func unbuffer(context: ChannelHandlerContext) {
while true {
switch self.stateMachine.unbuffer() {
case .fireChannelRead(let data):
context.fireChannelRead(data)
case .fireChannelReadCompleteAndRemoveHandler:
context.fireChannelReadComplete()
context.pipeline.removeHandler(self, promise: nil)
return
}
}
}
}
@available(*, unavailable)
extension NIOTypedApplicationProtocolNegotiationHandler: Sendable {}

View File

@ -0,0 +1,159 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import DequeModule
import NIOCore
struct ProtocolNegotiationHandlerStateMachine<NegotiationResult> {
enum State {
/// The state before we received a TLSUserEvent. We are just forwarding any read at this point.
case initial
/// The state after we received a ``TLSUserEvent`` and are waiting for the future of the user to complete.
case waitingForUser(buffer: Deque<NIOAny>)
/// The state after the users future finished and we are unbuffering all the reads.
case unbuffering(buffer: Deque<NIOAny>)
/// The state once the negotiation is done and we are finished with unbuffering.
case finished
}
private var state = State.initial
@usableFromInline
enum UserInboundEventTriggeredAction {
case fireUserInboundEventTriggered
case invokeUserClosure(ALPNResult)
}
@inlinable
mutating func userInboundEventTriggered(event: Any) -> UserInboundEventTriggeredAction {
if case .handshakeCompleted(let negotiated) = event as? TLSUserEvent {
switch self.state {
case .initial:
self.state = .waitingForUser(buffer: .init())
return .invokeUserClosure(.init(negotiated: negotiated))
case .waitingForUser, .unbuffering:
preconditionFailure("Unexpectedly received two TLSUserEvents")
case .finished:
// This is weird but we can tolerate it and just forward the event
return .fireUserInboundEventTriggered
}
} else {
return .fireUserInboundEventTriggered
}
}
@usableFromInline
enum ChannelReadAction {
case fireChannelRead
}
@inlinable
mutating func channelRead(data: NIOAny) -> ChannelReadAction? {
switch self.state {
case .initial, .finished:
return .fireChannelRead
case .waitingForUser(var buffer):
buffer.append(data)
self.state = .waitingForUser(buffer: buffer)
return .none
case .unbuffering(var buffer):
buffer.append(data)
self.state = .unbuffering(buffer: buffer)
return .none
}
}
@usableFromInline
enum UserFutureCompletedAction {
case fireErrorCaughtAndRemoveHandler(Error)
case fireErrorCaughtAndStartUnbuffering(Error)
case startUnbuffering(NegotiationResult)
case removeHandler(NegotiationResult)
}
@inlinable
mutating func userFutureCompleted(with result: Result<NegotiationResult, Error>) -> UserFutureCompletedAction {
switch self.state {
case .initial, .finished:
preconditionFailure("Invalid state \(self.state)")
case .waitingForUser(let buffer):
switch result {
case .success(let value):
if !buffer.isEmpty {
self.state = .unbuffering(buffer: buffer)
return .startUnbuffering(value)
} else {
self.state = .finished
return .removeHandler(value)
}
case .failure(let error):
if !buffer.isEmpty {
self.state = .unbuffering(buffer: buffer)
return .fireErrorCaughtAndStartUnbuffering(error)
} else {
self.state = .finished
return .fireErrorCaughtAndRemoveHandler(error)
}
}
case .unbuffering:
preconditionFailure("Invalid state \(self.state)")
}
}
@usableFromInline
enum UnbufferAction {
case fireChannelRead(NIOAny)
case fireChannelReadCompleteAndRemoveHandler
}
@inlinable
mutating func unbuffer() -> UnbufferAction {
switch self.state {
case .initial, .waitingForUser, .finished:
preconditionFailure("Invalid state \(self.state)")
case .unbuffering(var buffer):
if let element = buffer.popFirst() {
self.state = .unbuffering(buffer: buffer)
return .fireChannelRead(element)
} else {
self.state = .finished
return .fireChannelReadCompleteAndRemoveHandler
}
}
}
@inlinable
mutating func channelInactive() {
switch self.state {
case .initial, .unbuffering, .waitingForUser:
self.state = .finished
case .finished:
break
}
}
}

View File

@ -427,7 +427,5 @@ public final class SNIHandler: ByteToMessageDecoder {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension SNIHandler: Sendable {}
#endif

View File

@ -14,11 +14,7 @@
import NIOCore
import NIOConcurrencyHelpers
#if compiler(>=5.6)
@preconcurrency import Atomics
#else
import Atomics
#endif
/// `EventCounterHandler` is a `ChannelHandler` that counts and forwards all the events that it sees coming through
/// the `ChannelPipeline`.
@ -28,7 +24,7 @@ import Atomics
///
/// - note: Contrary to most `ChannelHandler`s, all of `EventCounterHandler`'s API is thread-safe meaning that you can
/// query the events received from any thread.
public final class EventCounterHandler {
public final class EventCounterHandler: Sendable {
private let _channelRegisteredCalls = ManagedAtomic<Int>(0)
private let _channelUnregisteredCalls = ManagedAtomic<Int>(0)
private let _channelActiveCalls = ManagedAtomic<Int>(0)
@ -369,11 +365,3 @@ extension EventCounterHandler: ChannelDuplexHandler {
context.triggerUserOutboundEvent(event, promise: promise)
}
}
#if compiler(>=5.6) && canImport(_Concurrency)
// This is a workaround before ManagedAtomic gets Sendable conformance. Once the support
// is ready, we should remove '@preconcurrency import' and declare Sendable directly.
extension EventCounterHandler: Sendable {
}
#endif

View File

@ -99,10 +99,8 @@ public final class NIOWebSocketClientUpgrader: NIOHTTPClientProtocolUpgrader {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIOWebSocketClientUpgrader: Sendable {}
#endif
extension NIOWebSocketClientUpgrader {
/// Generates a random WebSocket Request Key by generating 16 bytes randomly and encoding them as a base64 string as defined in RFC6455 https://tools.ietf.org/html/rfc6455#section-4.1

View File

@ -137,7 +137,5 @@ public final class NIOWebSocketFrameAggregator: ChannelInboundHandler {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension NIOWebSocketFrameAggregator: Sendable {}
#endif

View File

@ -344,10 +344,8 @@ extension WebSocketFrame {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension WebSocketFrame._Storage: Sendable {}
#endif
extension WebSocketFrame._Storage: Equatable {
static func ==(lhs: WebSocketFrame._Storage, rhs: WebSocketFrame._Storage) -> Bool {

View File

@ -270,7 +270,5 @@ public final class WebSocketFrameDecoder: ByteToMessageDecoder {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension WebSocketFrameDecoder: Sendable {}
#endif

View File

@ -115,10 +115,8 @@ public final class WebSocketFrameEncoder: ChannelOutboundHandler {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension WebSocketFrameEncoder: Sendable {}
#endif
extension ByteBuffer {
fileprivate mutating func prependFrameHeaderIfPossible(_ frameHeader: FrameHeader) -> Bool {

View File

@ -43,7 +43,5 @@ public final class WebSocketProtocolErrorHandler: ChannelInboundHandler {
}
}
#if swift(>=5.6)
@available(*, unavailable)
extension WebSocketProtocolErrorHandler: Sendable {}
#endif

View File

@ -1,152 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// LinuxMain.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
#if !compiler(>=5.5)
#if os(Linux) || os(FreeBSD) || os(Android)
@testable import NIOConcurrencyHelpersTests
@testable import NIOCoreTests
@testable import NIODataStructuresTests
@testable import NIOEmbeddedTests
@testable import NIOFoundationCompatTests
@testable import NIOHTTP1Tests
@testable import NIOPosixTests
@testable import NIOTLSTests
@testable import NIOTestUtilsTests
@testable import NIOTests
@testable import NIOWebSocketTests
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
@main
class LinuxMainRunner {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static func main() {
XCTMain([
testCase(AcceptBackoffHandlerTest.allTests),
testCase(AdaptiveRecvByteBufferAllocatorTest.allTests),
testCase(AddressedEnvelopeTests.allTests),
testCase(ApplicationProtocolNegotiationHandlerTests.allTests),
testCase(AsyncSequenceCollectTests.allTests),
testCase(AsyncTestingChannelTests.allTests),
testCase(Base64Test.allTests),
testCase(BaseObjectTest.allTests),
testCase(BlockingIOThreadPoolTest.allTests),
testCase(BootstrapTest.allTests),
testCase(ByteBufferDataProtocolTests.allTests),
testCase(ByteBufferLengthPrefixTests.allTests),
testCase(ByteBufferTest.allTests),
testCase(ByteBufferUUIDTests.allTests),
testCase(ByteBufferUtilsTest.allTests),
testCase(ByteBufferViewDataProtocolTests.allTests),
testCase(ByteToMessageDecoderTest.allTests),
testCase(ByteToMessageDecoderVerifierTest.allTests),
testCase(ChannelNotificationTest.allTests),
testCase(ChannelOptionStorageTest.allTests),
testCase(ChannelPipelineTest.allTests),
testCase(ChannelTests.allTests),
testCase(CircularBufferTests.allTests),
testCase(CodableByteBufferTest.allTests),
testCase(ControlMessageTests.allTests),
testCase(CustomChannelTests.allTests),
testCase(DatagramChannelTests.allTests),
testCase(DispatchQueueWithFutureTest.allTests),
testCase(EchoServerClientTest.allTests),
testCase(EmbeddedChannelTest.allTests),
testCase(EmbeddedEventLoopTest.allTests),
testCase(EventCounterHandlerTest.allTests),
testCase(EventLoopFutureTest.allTests),
testCase(EventLoopTest.allTests),
testCase(FileRegionTest.allTests),
testCase(GetaddrinfoResolverTest.allTests),
testCase(HTTPClientUpgradeTestCase.allTests),
testCase(HTTPDecoderLengthTest.allTests),
testCase(HTTPDecoderTest.allTests),
testCase(HTTPHeaderValidationTests.allTests),
testCase(HTTPHeadersTest.allTests),
testCase(HTTPRequestEncoderTests.allTests),
testCase(HTTPResponseEncoderTests.allTests),
testCase(HTTPResponseStatusTests.allTests),
testCase(HTTPServerClientTest.allTests),
testCase(HTTPServerPipelineHandlerTest.allTests),
testCase(HTTPServerProtocolErrorHandlerTest.allTests),
testCase(HTTPServerUpgradeTestCase.allTests),
testCase(HTTPTest.allTests),
testCase(HTTPTypesTest.allTests),
testCase(HappyEyeballsTest.allTests),
testCase(HeapTests.allTests),
testCase(IOErrorTest.allTests),
testCase(IdleStateHandlerTest.allTests),
testCase(IntegerBitPackingTests.allTests),
testCase(IntegerTypesTest.allTests),
testCase(JSONSerializationByteBufferTest.allTests),
testCase(LinuxTest.allTests),
testCase(MarkedCircularBufferTests.allTests),
testCase(MessageToByteEncoderTest.allTests),
testCase(MessageToByteHandlerTest.allTests),
testCase(MulticastTest.allTests),
testCase(NIOAnyDebugTest.allTests),
testCase(NIOAsyncTestingEventLoopTests.allTests),
testCase(NIOCloseOnErrorHandlerTest.allTests),
testCase(NIOConcurrencyHelpersTests.allTests),
testCase(NIOHTTP1TestServerTest.allTests),
testCase(NIOHTTPClientResponseAggregatorTest.allTests),
testCase(NIOHTTPServerRequestAggregatorTest.allTests),
testCase(NIOLoopBoundTests.allTests),
testCase(NIOSingleStepByteToMessageDecoderTest.allTests),
testCase(NIOTests.allTests),
testCase(NIOThreadPoolTest.allTests),
testCase(NIOWebSocketClientUpgraderTests.allTests),
testCase(NIOWebSocketFrameAggregatorTests.allTests),
testCase(NonBlockingFileIOTest.allTests),
testCase(PendingDatagramWritesManagerTests.allTests),
testCase(PipeChannelTest.allTests),
testCase(PooledRecvBufferAllocatorTests.allTests),
testCase(PriorityQueueTest.allTests),
testCase(RawSocketBootstrapTests.allTests),
testCase(SALChannelTest.allTests),
testCase(SALEventLoopTests.allTests),
testCase(SNIHandlerTest.allTests),
testCase(SelectorTest.allTests),
testCase(SocketAddressTest.allTests),
testCase(SocketChannelTest.allTests),
testCase(SocketOptionProviderTest.allTests),
testCase(StreamChannelTest.allTests),
testCase(SystemTest.allTests),
testCase(ThreadTest.allTests),
testCase(TimeAmountDurationTests.allTests),
testCase(TimeAmountTests.allTests),
testCase(TypeAssistedChannelHandlerTest.allTests),
testCase(UniversalBootstrapSupportTest.allTests),
testCase(UtilitiesTest.allTests),
testCase(WebSocketClientEndToEndTests.allTests),
testCase(WebSocketFrameDecoderTest.allTests),
testCase(WebSocketFrameEncoderTest.allTests),
testCase(WebSocketMaskingKeyTests.allTests),
testCase(WebSocketServerEndToEndTests.allTests),
])
}
}
#endif
#else
#error("on Swift 5.5 and newer, --enable-test-discovery is required")
#endif

View File

@ -1,66 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// NIOConcurrencyHelpersTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension NIOConcurrencyHelpersTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (NIOConcurrencyHelpersTests) -> () throws -> Void)] {
return [
("testLargeContendedAtomicSum", testLargeContendedAtomicSum),
("testCompareAndExchangeBool", testCompareAndExchangeBool),
("testAllOperationsBool", testAllOperationsBool),
("testCompareAndExchangeUInts", testCompareAndExchangeUInts),
("testCompareAndExchangeInts", testCompareAndExchangeInts),
("testAddSub", testAddSub),
("testExchange", testExchange),
("testLoadStore", testLoadStore),
("testLargeContendedNIOAtomicSum", testLargeContendedNIOAtomicSum),
("testCompareAndExchangeBoolNIOAtomic", testCompareAndExchangeBoolNIOAtomic),
("testAllOperationsBoolNIOAtomic", testAllOperationsBoolNIOAtomic),
("testCompareAndExchangeUIntsNIOAtomic", testCompareAndExchangeUIntsNIOAtomic),
("testCompareAndExchangeIntsNIOAtomic", testCompareAndExchangeIntsNIOAtomic),
("testAddSubNIOAtomic", testAddSubNIOAtomic),
("testExchangeNIOAtomic", testExchangeNIOAtomic),
("testLoadStoreNIOAtomic", testLoadStoreNIOAtomic),
("testLockMutualExclusion", testLockMutualExclusion),
("testWithLockMutualExclusion", testWithLockMutualExclusion),
("testConditionLockMutualExclusion", testConditionLockMutualExclusion),
("testConditionLock", testConditionLock),
("testConditionLockWithDifferentConditions", testConditionLockWithDifferentConditions),
("testAtomicBoxDoesNotTriviallyLeak", testAtomicBoxDoesNotTriviallyLeak),
("testAtomicBoxCompareAndExchangeWorksIfEqual", testAtomicBoxCompareAndExchangeWorksIfEqual),
("testAtomicBoxCompareAndExchangeWorksIfNotEqual", testAtomicBoxCompareAndExchangeWorksIfNotEqual),
("testAtomicBoxStoreWorks", testAtomicBoxStoreWorks),
("testAtomicBoxCompareAndExchangeOntoItselfWorks", testAtomicBoxCompareAndExchangeOntoItselfWorks),
("testAtomicLoadMassLoadAndStore", testAtomicLoadMassLoadAndStore),
("testAtomicBoxCompareAndExchangeOntoItself", testAtomicBoxCompareAndExchangeOntoItself),
("testLoadAndExchangeHammering", testLoadAndExchangeHammering),
("testLoadAndStoreHammering", testLoadAndStoreHammering),
("testLoadAndCASHammering", testLoadAndCASHammering),
("testMultipleLoadsRacingWhilstStoresAreGoingOn", testMultipleLoadsRacingWhilstStoresAreGoingOn),
("testNIOLockedValueBox", testNIOLockedValueBox),
]
}
}

View File

@ -1034,6 +1034,23 @@ class NIOConcurrencyHelpersTests: XCTestCase {
XCTAssertEqual(50_000, lv.withLockedValue { $0.count })
}
func testNIOLockedValueBoxHandlesThingsWithTransitiveClassesProperly() {
struct State {
var counts: [Int] = []
}
let lv = NIOLockedValueBox<State>(State())
spawnAndJoinRacingThreads(count: 50) { _ in
for i in 0..<1000 {
lv.withLockedValue { state in
state.counts.append(i)
}
}
}
XCTAssertEqual(50_000, lv.withLockedValue { $0.counts.count })
}
}
func spawnAndJoinRacingThreads(count: Int, _ body: @escaping (Int) -> Void) {

View File

@ -1,40 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// AddressedEnvelopeTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension AddressedEnvelopeTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (AddressedEnvelopeTests) -> () throws -> Void)] {
return [
("testHashable_whenEqual", testHashable_whenEqual),
("testHashable_whenDifferentData", testHashable_whenDifferentData),
("testHashable_whenDifferentAddress", testHashable_whenDifferentAddress),
("testHashable_whenDifferentMetadata", testHashable_whenDifferentMetadata),
("testHashable_whenDifferentData_andDifferentAddress", testHashable_whenDifferentData_andDifferentAddress),
("testHashable_whenDifferentData_andDifferentMetadata", testHashable_whenDifferentData_andDifferentMetadata),
("testHashable_whenDifferentAddress_andDifferentMetadata", testHashable_whenDifferentAddress_andDifferentMetadata),
]
}
}

View File

@ -0,0 +1,527 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Atomics
import NIOConcurrencyHelpers
@_spi(AsyncChannel) @testable import NIOCore
import NIOEmbedded
import XCTest
final class AsyncChannelTests: XCTestCase {
func testAsyncChannelBasicFunctionality() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: String.self, outboundType: Never.self)
}
var iterator = wrapped.inboundStream.makeAsyncIterator()
try await channel.writeInbound("hello")
let firstRead = try await iterator.next()
XCTAssertEqual(firstRead, "hello")
try await channel.writeInbound("world")
let secondRead = try await iterator.next()
XCTAssertEqual(secondRead, "world")
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
}
let thirdRead = try await iterator.next()
XCTAssertNil(thirdRead)
try await channel.close()
}
}
func testAsyncChannelBasicWrites() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: Never.self, outboundType: String.self)
}
try await wrapped.outboundWriter.write("hello")
try await wrapped.outboundWriter.write("world")
let firstRead = try await channel.waitForOutboundWrite(as: String.self)
let secondRead = try await channel.waitForOutboundWrite(as: String.self)
XCTAssertEqual(firstRead, "hello")
XCTAssertEqual(secondRead, "world")
try await channel.close()
}
}
func testDroppingTheWriterClosesTheWriteSideOfTheChannel() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let closeRecorder = CloseRecorder()
try await channel.pipeline.addHandler(closeRecorder)
let inboundReader: NIOAsyncChannelInboundStream<Never>
do {
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(
synchronouslyWrapping: channel,
isOutboundHalfClosureEnabled: true,
inboundType: Never.self,
outboundType: Never.self
)
}
inboundReader = wrapped.inboundStream
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(0, closeRecorder.outboundCloses)
}
}
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(1, closeRecorder.outboundCloses)
}
// Just use this to keep the inbound reader alive.
withExtendedLifetime(inboundReader) {}
channel.close(promise: nil)
}
}
func testDroppingTheWriterDoesntCloseTheWriteSideOfTheChannelIfHalfClosureIsDisabled() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let closeRecorder = CloseRecorder()
try await channel.pipeline.addHandler(closeRecorder)
let inboundReader: NIOAsyncChannelInboundStream<Never>
do {
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, isOutboundHalfClosureEnabled: false, inboundType: Never.self, outboundType: Never.self)
}
inboundReader = wrapped.inboundStream
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(0, closeRecorder.outboundCloses)
}
}
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(0, closeRecorder.outboundCloses)
}
// Just use this to keep the inbound reader alive.
withExtendedLifetime(inboundReader) {}
channel.close(promise: nil)
}
}
func testDroppingTheWriterFirstLeadsToChannelClosureWhenReaderIsAlsoDropped() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let closeRecorder = CloseRecorder()
try await channel.pipeline.addHandler(CloseSuppressor())
try await channel.pipeline.addHandler(closeRecorder)
do {
let inboundReader: NIOAsyncChannelInboundStream<Never>
do {
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(
synchronouslyWrapping: channel,
isOutboundHalfClosureEnabled: true,
inboundType: Never.self,
outboundType: Never.self
)
}
inboundReader = wrapped.inboundStream
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(0, closeRecorder.allCloses)
}
}
// First we see half-closure.
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(1, closeRecorder.allCloses)
}
// Just use this to keep the inbound reader alive.
withExtendedLifetime(inboundReader) {}
}
// Now the inbound reader is dead, we see full closure.
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(2, closeRecorder.allCloses)
}
try await channel.closeIgnoringSuppression()
}
}
func testDroppingEverythingClosesTheChannel() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let closeRecorder = CloseRecorder()
try await channel.pipeline.addHandler(CloseSuppressor())
try await channel.pipeline.addHandler(closeRecorder)
do {
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, isOutboundHalfClosureEnabled: false, inboundType: Never.self, outboundType: Never.self)
}
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(0, closeRecorder.allCloses)
}
// Just use this to keep the wrapper alive until here.
withExtendedLifetime(wrapped) {}
}
// Now that everything is dead, we see full closure.
try await channel.testingEventLoop.executeInContext {
XCTAssertEqual(1, closeRecorder.allCloses)
}
try await channel.closeIgnoringSuppression()
}
}
func testReadsArePropagated() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: String.self, outboundType: Never.self)
}
try await channel.writeInbound("hello")
let propagated = try await channel.readInbound(as: String.self)
XCTAssertEqual(propagated, "hello")
try await channel.close().get()
let reads = try await Array(wrapped.inboundStream)
XCTAssertEqual(reads, ["hello"])
}
}
func testErrorsArePropagatedButAfterReads() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: String.self, outboundType: Never.self)
}
try await channel.writeInbound("hello")
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireErrorCaught(TestError.bang)
}
var iterator = wrapped.inboundStream.makeAsyncIterator()
let first = try await iterator.next()
XCTAssertEqual(first, "hello")
try await XCTAssertThrowsError(await iterator.next()) { error in
XCTAssertEqual(error as? TestError, .bang)
}
}
}
func testChannelBecomingNonWritableDelaysWriters() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: Never.self, outboundType: String.self)
}
try await channel.testingEventLoop.executeInContext {
channel.isWritable = false
channel.pipeline.fireChannelWritabilityChanged()
}
let lock = NIOLockedValueBox(false)
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await wrapped.outboundWriter.write("hello")
lock.withLockedValue {
XCTAssertTrue($0)
}
}
group.addTask {
// 10ms sleep before we wake the thing up
try await Task.sleep(nanoseconds: 10_000_000)
try await channel.testingEventLoop.executeInContext {
channel.isWritable = true
lock.withLockedValue { $0 = true }
channel.pipeline.fireChannelWritabilityChanged()
}
}
}
try await channel.close().get()
}
}
func testBufferDropsReadsIfTheReaderIsGone() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
try await channel.pipeline.addHandler(CloseSuppressor()).get()
do {
// Create the NIOAsyncChannel, then drop it. The handler will still be in the pipeline.
_ = try await channel.testingEventLoop.executeInContext {
_ = try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: Sentinel.self, outboundType: Never.self)
}
}
weak var sentinel: Sentinel?
do {
let strongSentinel: Sentinel? = Sentinel()
sentinel = strongSentinel!
try await XCTAsyncAssertNotNil(await channel.pipeline.handler(type: NIOAsyncChannelInboundStreamChannelHandler<Sentinel, Sentinel>.self).get())
try await channel.writeInbound(strongSentinel!)
_ = try await channel.readInbound(as: Sentinel.self)
}
XCTAssertNil(sentinel)
try await channel.closeIgnoringSuppression()
}
}
func testManagingBackpressure() {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let readCounter = ReadCounter()
try await channel.pipeline.addHandler(readCounter)
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, backpressureStrategy: .init(lowWatermark: 2, highWatermark: 4), inboundType: Void.self, outboundType: Never.self)
}
// Attempt to read. This should succeed an arbitrary number of times.
XCTAssertEqual(readCounter.readCount, 0)
try await channel.testingEventLoop.executeInContext {
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
}
XCTAssertEqual(readCounter.readCount, 3)
// Push 3 elements into the buffer. Reads continue to work.
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelReadComplete()
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
}
XCTAssertEqual(readCounter.readCount, 6)
// Add one more element into the buffer. This should flip our backpressure mode, and the reads should now be delayed.
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelReadComplete()
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
}
XCTAssertEqual(readCounter.readCount, 6)
// More elements don't help.
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelReadComplete()
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
}
XCTAssertEqual(readCounter.readCount, 6)
// Now consume three elements from the pipeline. This should not unbuffer the read, as 3 elements remain.
var reader = wrapped.inboundStream.makeAsyncIterator()
for _ in 0..<3 {
try await XCTAsyncAssertNotNil(await reader.next())
}
await channel.testingEventLoop.run()
XCTAssertEqual(readCounter.readCount, 6)
// Removing the next element should trigger an automatic read.
try await XCTAsyncAssertNotNil(await reader.next())
await channel.testingEventLoop.run()
XCTAssertEqual(readCounter.readCount, 7)
// Reads now work again, even if more data arrives.
try await channel.testingEventLoop.executeInContext {
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelReadComplete()
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
}
XCTAssertEqual(readCounter.readCount, 13)
// The next reads arriving pushes us past the limit again.
// This time we won't read.
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelRead(NIOAny(()))
channel.pipeline.fireChannelReadComplete()
}
XCTAssertEqual(readCounter.readCount, 13)
// This time we'll consume 4 more elements, and we won't find a read at all.
for _ in 0..<4 {
try await XCTAsyncAssertNotNil(await reader.next())
}
await channel.testingEventLoop.run()
XCTAssertEqual(readCounter.readCount, 13)
// But the next reads work fine.
try await channel.testingEventLoop.executeInContext {
channel.pipeline.read()
channel.pipeline.read()
channel.pipeline.read()
}
XCTAssertEqual(readCounter.readCount, 16)
}
}
func testCanWrapAChannelSynchronously() throws {
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
XCTAsyncTest(timeout: 5) {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
try NIOAsyncChannel(synchronouslyWrapping: channel, inboundType: String.self, outboundType: String.self)
}
var iterator = wrapped.inboundStream.makeAsyncIterator()
try await channel.writeInbound("hello")
let firstRead = try await iterator.next()
XCTAssertEqual(firstRead, "hello")
try await wrapped.outboundWriter.write("world")
let write = try await channel.waitForOutboundWrite(as: String.self)
XCTAssertEqual(write, "world")
try await channel.testingEventLoop.executeInContext {
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
}
let secondRead = try await iterator.next()
XCTAssertNil(secondRead)
try await channel.close()
}
}
}
// This is unchecked Sendable since we only call this in the testing eventloop
private final class CloseRecorder: ChannelOutboundHandler, @unchecked Sendable {
typealias OutboundIn = Any
typealias outbound = Any
var outboundCloses = 0
var allCloses = 0
init() {}
func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
self.allCloses += 1
if case .output = mode {
self.outboundCloses += 1
}
context.close(mode: mode, promise: promise)
}
}
private final class CloseSuppressor: ChannelOutboundHandler, RemovableChannelHandler {
typealias OutboundIn = Any
typealias outbound = Any
func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
// We drop the close here.
promise?.fail(TestError.bang)
}
}
extension NIOAsyncTestingChannel {
fileprivate func closeIgnoringSuppression() async throws {
try await self.pipeline.context(handlerType: CloseSuppressor.self).flatMap {
self.pipeline.removeHandler(context: $0)
}.flatMap {
self.close()
}.get()
}
}
private final class ReadCounter: ChannelOutboundHandler, @unchecked Sendable {
typealias OutboundIn = Any
typealias outbound = Any
private let _readCount = ManagedAtomic(0)
var readCount: Int {
self._readCount.load(ordering: .acquiring)
}
func read(context: ChannelHandlerContext) {
self._readCount.wrappingIncrement(ordering: .releasing)
context.read()
}
}
private enum TestError: Error {
case bang
}
extension Array {
fileprivate init<AS: AsyncSequence>(_ sequence: AS) async throws where AS.Element == Self.Element {
self = []
for try await nextElement in sequence {
self.append(nextElement)
}
}
}
private final class Sentinel: Sendable {}

View File

@ -1,34 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// AsyncSequenceTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension AsyncSequenceCollectTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (AsyncSequenceCollectTests) -> () throws -> Void)] {
return [
("testAsyncSequenceCollect", testAsyncSequenceCollect),
]
}
}

View File

@ -130,31 +130,19 @@ final class NIOAsyncWriterTests: XCTestCase {
}
func testWriterDeinitialized_whenStreaming() async throws {
Task { [writer] in
try await writer!.yield("message1")
}
try await Task.sleep(nanoseconds: 1_000_000)
try await writer.yield("message1")
self.writer = nil
XCTAssertEqual(self.delegate.didTerminateCallCount, 1)
}
func testWriterDeinitialized_whenWriterFinished() async throws {
self.sink.setWritability(to: false)
Task { [writer] in
try await writer!.yield("message1")
}
try await Task.sleep(nanoseconds: 1_000_000)
try await writer.yield("message1")
self.writer.finish()
self.writer = nil
XCTAssertEqual(self.delegate.didYieldCallCount, 0)
XCTAssertEqual(self.delegate.didTerminateCallCount, 0)
XCTAssertEqual(self.delegate.didYieldCallCount, 1)
XCTAssertEqual(self.delegate.didTerminateCallCount, 1)
}
func testWriterDeinitialized_whenFinished() async throws {

View File

@ -457,9 +457,40 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {
try await Task.sleep(nanoseconds: 1_000_000)
task.cancel()
let value = try await task.value
let result = await task.result
XCTAssertEqualWithoutAutoclosure(await self.delegate.events.prefix(1).collect(), [.didTerminate])
XCTAssertNil(value)
await XCTAssertThrowsError(try result.get()) { error in
XCTAssertTrue(error is CancellationError)
}
}
@available(*, deprecated, message: "tests the deprecated custom generic failure type")
func testTaskCancel_whenStreaming_andSuspended_withCustomErrorType() async throws {
struct CustomError: Error {}
// We are registering our demand and sleeping a bit to make
// sure our task runs when the demand is registered
let backPressureStrategy = MockNIOElementStreamBackPressureStrategy()
let delegate = MockNIOBackPressuredStreamSourceDelegate()
let new = NIOThrowingAsyncSequenceProducer.makeSequence(
elementType: Int.self,
failureType: CustomError.self,
backPressureStrategy: backPressureStrategy,
delegate: delegate
)
let sequence = new.sequence
let task: Task<Int?, Error> = Task {
let iterator = sequence.makeAsyncIterator()
return try await iterator.next()
}
try await Task.sleep(nanoseconds: 1_000_000)
task.cancel()
let result = await task.result
XCTAssertEqualWithoutAutoclosure(await delegate.events.prefix(1).collect(), [.didTerminate])
try withExtendedLifetime(new.source) {
XCTAssertNil(try result.get())
}
}
func testTaskCancel_whenStreaming_andNotSuspended() async throws {
@ -513,9 +544,39 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {
task.cancel()
let value = try await task.value
let result = await task.result
XCTAssertNil(value)
await XCTAssertThrowsError(try result.get()) { error in
XCTAssertTrue(error is CancellationError, "unexpected error \(error)")
}
}
@available(*, deprecated, message: "tests the deprecated custom generic failure type")
func testTaskCancel_whenStreaming_andTaskIsAlreadyCancelled_withCustomErrorType() async throws {
struct CustomError: Error {}
let backPressureStrategy = MockNIOElementStreamBackPressureStrategy()
let delegate = MockNIOBackPressuredStreamSourceDelegate()
let new = NIOThrowingAsyncSequenceProducer.makeSequence(
elementType: Int.self,
failureType: CustomError.self,
backPressureStrategy: backPressureStrategy,
delegate: delegate
)
let sequence = new.sequence
let task: Task<Int?, Error> = Task {
// We are sleeping here to allow some time for us to cancel the task.
// Once the Task is cancelled we will call `next()`
try? await Task.sleep(nanoseconds: 1_000_000)
let iterator = sequence.makeAsyncIterator()
return try await iterator.next()
}
task.cancel()
let result = await task.result
try withExtendedLifetime(new.source) {
XCTAssertNil(try result.get())
}
}
// MARK: - Next
@ -682,6 +743,36 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {
XCTAssertEqualWithoutAutoclosure(await self.delegate.events.prefix(1).collect(), [.didTerminate])
}
func testIteratorThrows_whenCancelled() async {
_ = self.source.yield(contentsOf: Array(0..<100))
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
var counter = 0
guard let sequence = self.sequence else {
return XCTFail("Expected to have an AsyncSequence")
}
do {
for try await next in sequence {
XCTAssertEqual(next, counter)
counter += 1
}
XCTFail("Expected that this throws")
} catch is CancellationError {
// expected
} catch {
XCTFail("Unexpected error: \(error)")
}
XCTAssertLessThan(counter, 100)
}
group.cancelAll()
}
XCTAssertEqualWithoutAutoclosure(await self.delegate.events.prefix(1).collect(), [.didTerminate])
}
}
// This is needed until async let is supported to be used in autoclosures

View File

@ -1,40 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// BaseObjectsTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension BaseObjectTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (BaseObjectTest) -> () throws -> Void)] {
return [
("testNIOByteBufferConversion", testNIOByteBufferConversion),
("testNIOIODataConversion", testNIOIODataConversion),
("testNIOFileRegionConversion", testNIOFileRegionConversion),
("testBadConversions", testBadConversions),
("testByteBufferFromIOData", testByteBufferFromIOData),
("testFileRegionFromIOData", testFileRegionFromIOData),
("testIODataEquals", testIODataEquals),
]
}
}

View File

@ -1,54 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2021-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// ByteBufferLengthPrefixTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension ByteBufferLengthPrefixTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (ByteBufferLengthPrefixTests) -> () throws -> Void)] {
return [
("testWriteMessageWithLengthOfZero", testWriteMessageWithLengthOfZero),
("testWriteMessageWithLengthOfOne", testWriteMessageWithLengthOfOne),
("testWriteMessageWithMultipleWrites", testWriteMessageWithMultipleWrites),
("testWriteMessageWithMaxLength", testWriteMessageWithMaxLength),
("testWriteTooLongMessage", testWriteTooLongMessage),
("testWriteMessageWithBigEndianInteger", testWriteMessageWithBigEndianInteger),
("testWriteMessageWithLittleEndianInteger", testWriteMessageWithLittleEndianInteger),
("testReadMessageWithLengthOfZero", testReadMessageWithLengthOfZero),
("testReadMessageWithLengthOfOne", testReadMessageWithLengthOfOne),
("testReadMessageWithLengthOfTen", testReadMessageWithLengthOfTen),
("testReadMessageWithMaxLength", testReadMessageWithMaxLength),
("testReadOneByteTooMuch", testReadOneByteTooMuch),
("testReadOneByteTooFew", testReadOneByteTooFew),
("testReadMessageWithBigEndianInteger", testReadMessageWithBigEndianInteger),
("testReadMessageWithLittleEndianInteger", testReadMessageWithLittleEndianInteger),
("testReadMessageWithMaliciousLength", testReadMessageWithMaliciousLength),
("testReadMessageWithNegativeLength", testReadMessageWithNegativeLength),
("testReadSliceWithBigEndianInteger", testReadSliceWithBigEndianInteger),
("testReadSliceWithLittleEndianInteger", testReadSliceWithLittleEndianInteger),
("testGetSliceWithBigEndianInteger", testGetSliceWithBigEndianInteger),
("testGetSliceWithLittleEndianInteger", testGetSliceWithLittleEndianInteger),
]
}
}

View File

@ -1,237 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// ByteBufferTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension ByteBufferTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (ByteBufferTest) -> () throws -> Void)] {
return [
("testAllocateAndCount", testAllocateAndCount),
("testEqualsComparesReadBuffersOnly", testEqualsComparesReadBuffersOnly),
("testHasherUsesReadBuffersOnly", testHasherUsesReadBuffersOnly),
("testSimpleReadTest", testSimpleReadTest),
("testSimpleWrites", testSimpleWrites),
("testMakeSureUniquelyOwnedSliceDoesNotGetReallocatedOnWrite", testMakeSureUniquelyOwnedSliceDoesNotGetReallocatedOnWrite),
("testWriteToUniquelyOwnedSliceWhichTriggersAReallocation", testWriteToUniquelyOwnedSliceWhichTriggersAReallocation),
("testReadWrite", testReadWrite),
("testStaticStringReadTests", testStaticStringReadTests),
("testString", testString),
("testNullTerminatedString", testNullTerminatedString),
("testReadNullTerminatedStringWithoutNullTermination", testReadNullTerminatedStringWithoutNullTermination),
("testGetNullTerminatedStringOutOfRangeTests", testGetNullTerminatedStringOutOfRangeTests),
("testWriteSubstring", testWriteSubstring),
("testSetSubstring", testSetSubstring),
("testSliceEasy", testSliceEasy),
("testWriteStringMovesWriterIndex", testWriteStringMovesWriterIndex),
("testSetExpandsBufferOnUpperBoundsCheckFailure", testSetExpandsBufferOnUpperBoundsCheckFailure),
("testCoWWorks", testCoWWorks),
("testWithMutableReadPointerMovesReaderIndexAndReturnsNumBytesConsumed", testWithMutableReadPointerMovesReaderIndexAndReturnsNumBytesConsumed),
("testWithMutableWritePointerMovesWriterIndexAndReturnsNumBytesWritten", testWithMutableWritePointerMovesWriterIndexAndReturnsNumBytesWritten),
("testWithMutableWritePointerWithMinimumSpecifiedAdjustsCapacity", testWithMutableWritePointerWithMinimumSpecifiedAdjustsCapacity),
("testWithMutableWritePointerWithMinimumSpecifiedWhileAtMaxCapacity", testWithMutableWritePointerWithMinimumSpecifiedWhileAtMaxCapacity),
("testSetGetInt8", testSetGetInt8),
("testSetGetInt16", testSetGetInt16),
("testSetGetInt32", testSetGetInt32),
("testSetGetInt64", testSetGetInt64),
("testSetGetUInt8", testSetGetUInt8),
("testSetGetUInt16", testSetGetUInt16),
("testSetGetUInt32", testSetGetUInt32),
("testSetGetUInt64", testSetGetUInt64),
("testWriteReadInt8", testWriteReadInt8),
("testWriteReadInt16", testWriteReadInt16),
("testWriteReadInt32", testWriteReadInt32),
("testWriteReadInt64", testWriteReadInt64),
("testWriteReadUInt8", testWriteReadUInt8),
("testWriteReadUInt16", testWriteReadUInt16),
("testWriteReadUInt32", testWriteReadUInt32),
("testWriteReadUInt64", testWriteReadUInt64),
("testSlice", testSlice),
("testSliceWithParams", testSliceWithParams),
("testReadSlice", testReadSlice),
("testSliceNoCopy", testSliceNoCopy),
("testSetGetData", testSetGetData),
("testWriteReadData", testWriteReadData),
("testDiscardReadBytes", testDiscardReadBytes),
("testDiscardReadBytesCoW", testDiscardReadBytesCoW),
("testDiscardReadBytesSlice", testDiscardReadBytesSlice),
("testWithDataSlices", testWithDataSlices),
("testEndianness", testEndianness),
("testExpansion", testExpansion),
("testExpansion2", testExpansion2),
("testNotEnoughBytesToReadForIntegers", testNotEnoughBytesToReadForIntegers),
("testNotEnoughBytesToReadForData", testNotEnoughBytesToReadForData),
("testSlicesThatAreOutOfBands", testSlicesThatAreOutOfBands),
("testMutableBytesCoW", testMutableBytesCoW),
("testWritableBytesTriggersCoW", testWritableBytesTriggersCoW),
("testBufferWithZeroBytes", testBufferWithZeroBytes),
("testPastEnd", testPastEnd),
("testReadDataNotEnoughAvailable", testReadDataNotEnoughAvailable),
("testReadSliceNotEnoughAvailable", testReadSliceNotEnoughAvailable),
("testSetBuffer", testSetBuffer),
("testWriteBuffer", testWriteBuffer),
("testMisalignedIntegerRead", testMisalignedIntegerRead),
("testSetAndWriteBytes", testSetAndWriteBytes),
("testCopyBytesWithNegativeLength", testCopyBytesWithNegativeLength),
("testCopyBytesNonReadable", testCopyBytesNonReadable),
("testCopyBytes", testCopyBytes),
("testCopyZeroBytesOutOfBoundsIsOk", testCopyZeroBytesOutOfBoundsIsOk),
("testCopyBytesBeyondWriterIndex", testCopyBytesBeyondWriterIndex),
("testCopyBytesOverSelf", testCopyBytesOverSelf),
("testCopyBytesCoWs", testCopyBytesCoWs),
("testWriteABunchOfCollections", testWriteABunchOfCollections),
("testSetABunchOfCollections", testSetABunchOfCollections),
("testTryStringTooLong", testTryStringTooLong),
("testSetGetBytesAllFine", testSetGetBytesAllFine),
("testGetBytesTooLong", testGetBytesTooLong),
("testReadWriteBytesOkay", testReadWriteBytesOkay),
("testReadTooLong", testReadTooLong),
("testReadWithUnsafeReadableBytesVariantsNothingToRead", testReadWithUnsafeReadableBytesVariantsNothingToRead),
("testReadWithUnsafeReadableBytesVariantsSomethingToRead", testReadWithUnsafeReadableBytesVariantsSomethingToRead),
("testSomePotentialIntegerUnderOrOverflows", testSomePotentialIntegerUnderOrOverflows),
("testWriteForContiguousCollections", testWriteForContiguousCollections),
("testWriteForNonContiguousCollections", testWriteForNonContiguousCollections),
("testReadStringOkay", testReadStringOkay),
("testReadStringTooMuch", testReadStringTooMuch),
("testSetIntegerBeyondCapacity", testSetIntegerBeyondCapacity),
("testGetIntegerBeyondCapacity", testGetIntegerBeyondCapacity),
("testSetStringBeyondCapacity", testSetStringBeyondCapacity),
("testGetStringBeyondCapacity", testGetStringBeyondCapacity),
("testAllocationOfReallyBigByteBuffer", testAllocationOfReallyBigByteBuffer),
("testWritableBytesAccountsForSlicing", testWritableBytesAccountsForSlicing),
("testClearDupesStorageIfTheresTwoBuffersSharingStorage", testClearDupesStorageIfTheresTwoBuffersSharingStorage),
("testClearDoesNotDupeStorageIfTheresOnlyOneBuffer", testClearDoesNotDupeStorageIfTheresOnlyOneBuffer),
("testClearWithBiggerMinimumCapacityDupesStorageIfTheresTwoBuffersSharingStorage", testClearWithBiggerMinimumCapacityDupesStorageIfTheresTwoBuffersSharingStorage),
("testClearWithSmallerMinimumCapacityDupesStorageIfTheresTwoBuffersSharingStorage", testClearWithSmallerMinimumCapacityDupesStorageIfTheresTwoBuffersSharingStorage),
("testClearWithBiggerMinimumCapacityDoesNotDupeStorageIfTheresOnlyOneBuffer", testClearWithBiggerMinimumCapacityDoesNotDupeStorageIfTheresOnlyOneBuffer),
("testClearWithSmallerMinimumCapacityDoesNotDupeStorageIfTheresOnlyOneBuffer", testClearWithSmallerMinimumCapacityDoesNotDupeStorageIfTheresOnlyOneBuffer),
("testClearWithBiggerCapacityDoesReallocateStorageCorrectlyIfTheresOnlyOneBuffer", testClearWithBiggerCapacityDoesReallocateStorageCorrectlyIfTheresOnlyOneBuffer),
("testClearWithSmallerCapacityDoesReallocateStorageCorrectlyIfTheresOnlyOneBuffer", testClearWithSmallerCapacityDoesReallocateStorageCorrectlyIfTheresOnlyOneBuffer),
("testClearDoesAllocateStorageCorrectlyIfTheresTwoBuffersSharingStorage", testClearDoesAllocateStorageCorrectlyIfTheresTwoBuffersSharingStorage),
("testClearResetsTheSliceCapacityIfTheresOnlyOneBuffer", testClearResetsTheSliceCapacityIfTheresOnlyOneBuffer),
("testClearResetsTheSliceCapacityIfTheresTwoSlicesSharingStorage", testClearResetsTheSliceCapacityIfTheresTwoSlicesSharingStorage),
("testWeUseFastWriteForContiguousCollections", testWeUseFastWriteForContiguousCollections),
("testUnderestimatingSequenceWorks", testUnderestimatingSequenceWorks),
("testZeroSizeByteBufferResizes", testZeroSizeByteBufferResizes),
("testSpecifyTypesAndEndiannessForIntegerMethods", testSpecifyTypesAndEndiannessForIntegerMethods),
("testByteBufferFitsInACoupleOfEnums", testByteBufferFitsInACoupleOfEnums),
("testLargeSliceBegin16MBIsOkayAndDoesNotCopy", testLargeSliceBegin16MBIsOkayAndDoesNotCopy),
("testLargeSliceBeginMoreThan16MBIsOkay", testLargeSliceBeginMoreThan16MBIsOkay),
("testSliceOfMassiveBufferWithAdvancedReaderIndexIsOk", testSliceOfMassiveBufferWithAdvancedReaderIndexIsOk),
("testSliceOnSliceAfterHitting16MBMark", testSliceOnSliceAfterHitting16MBMark),
("testDiscardReadBytesOnConsumedBuffer", testDiscardReadBytesOnConsumedBuffer),
("testDumpBytesFormat", testDumpBytesFormat),
("testReadableBytesView", testReadableBytesView),
("testReadableBytesViewNoReadableBytes", testReadableBytesViewNoReadableBytes),
("testBytesView", testBytesView),
("testViewsStartIndexIsStable", testViewsStartIndexIsStable),
("testSlicesOfByteBufferViewsAreByteBufferViews", testSlicesOfByteBufferViewsAreByteBufferViews),
("testReadableBufferViewRangeEqualCapacity", testReadableBufferViewRangeEqualCapacity),
("testBufferViewCoWs", testBufferViewCoWs),
("testBufferViewMutationViaSubscriptIndex", testBufferViewMutationViaSubscriptIndex),
("testBufferViewReplaceBeyondEndOfRange", testBufferViewReplaceBeyondEndOfRange),
("testBufferViewReplaceWithSubrangeOfSelf", testBufferViewReplaceWithSubrangeOfSelf),
("testBufferViewMutationViaSubscriptRange", testBufferViewMutationViaSubscriptRange),
("testBufferViewReplaceSubrangeWithEqualLengthBytes", testBufferViewReplaceSubrangeWithEqualLengthBytes),
("testBufferViewReplaceSubrangeWithFewerBytes", testBufferViewReplaceSubrangeWithFewerBytes),
("testBufferViewReplaceSubrangeWithMoreBytes", testBufferViewReplaceSubrangeWithMoreBytes),
("testBufferViewAppend", testBufferViewAppend),
("testBufferViewAppendContentsOf", testBufferViewAppendContentsOf),
("testBufferViewEmpty", testBufferViewEmpty),
("testBufferViewFirstIndex", testBufferViewFirstIndex),
("testBufferViewLastIndex", testBufferViewLastIndex),
("testBufferViewContains", testBufferViewContains),
("testByteBuffersCanBeInitializedFromByteBufferViews", testByteBuffersCanBeInitializedFromByteBufferViews),
("testReserveCapacityWhenOversize", testReserveCapacityWhenOversize),
("testReserveCapacitySameCapacity", testReserveCapacitySameCapacity),
("testReserveCapacityLargerUniquelyReferencedCallsRealloc", testReserveCapacityLargerUniquelyReferencedCallsRealloc),
("testReserveCapacityLargerMultipleReferenceCallsMalloc", testReserveCapacityLargerMultipleReferenceCallsMalloc),
("testReserveCapacityWithMinimumWritableBytesWhenNotEnoughWritableBytes", testReserveCapacityWithMinimumWritableBytesWhenNotEnoughWritableBytes),
("testReserveCapacityWithMinimumWritableBytesWhenEnoughWritableBytes", testReserveCapacityWithMinimumWritableBytesWhenEnoughWritableBytes),
("testReserveCapacityWithMinimumWritableBytesWhenSameWritableBytes", testReserveCapacityWithMinimumWritableBytesWhenSameWritableBytes),
("testReadWithFunctionsThatReturnNumberOfReadBytesAreDiscardable", testReadWithFunctionsThatReturnNumberOfReadBytesAreDiscardable),
("testWriteAndSetAndGetAndReadEncoding", testWriteAndSetAndGetAndReadEncoding),
("testPossiblyLazilyBridgedString", testPossiblyLazilyBridgedString),
("testWithVeryUnsafeMutableBytesWorksOnEmptyByteBuffer", testWithVeryUnsafeMutableBytesWorksOnEmptyByteBuffer),
("testWithVeryUnsafeMutableBytesYieldsPointerToWholeStorage", testWithVeryUnsafeMutableBytesYieldsPointerToWholeStorage),
("testWithVeryUnsafeMutableBytesYieldsPointerToWholeStorageAndCanBeWritenTo", testWithVeryUnsafeMutableBytesYieldsPointerToWholeStorageAndCanBeWritenTo),
("testWithVeryUnsafeMutableBytesDoesCoW", testWithVeryUnsafeMutableBytesDoesCoW),
("testWithVeryUnsafeMutableBytesDoesCoWonSlices", testWithVeryUnsafeMutableBytesDoesCoWonSlices),
("testGetDispatchDataWorks", testGetDispatchDataWorks),
("testGetDispatchDataReadWrite", testGetDispatchDataReadWrite),
("testVariousContiguousStorageAccessors", testVariousContiguousStorageAccessors),
("testGetBytesThatAreNotReadable", testGetBytesThatAreNotReadable),
("testByteBufferViewAsDataProtocol", testByteBufferViewAsDataProtocol),
("testDataByteTransferStrategyNoCopy", testDataByteTransferStrategyNoCopy),
("testDataByteTransferStrategyCopy", testDataByteTransferStrategyCopy),
("testDataByteTransferStrategyAutomaticMayNotCopy", testDataByteTransferStrategyAutomaticMayNotCopy),
("testDataByteTransferStrategyAutomaticMayCopy", testDataByteTransferStrategyAutomaticMayCopy),
("testViewBytesIsHappyWithNegativeValues", testViewBytesIsHappyWithNegativeValues),
("testByteBufferAllocatorSize1Capacity", testByteBufferAllocatorSize1Capacity),
("testByteBufferModifiedWithoutAllocationLogic", testByteBufferModifiedWithoutAllocationLogic),
("testByteBufferModifyIfUniquelyOwnedMayThrow", testByteBufferModifyIfUniquelyOwnedMayThrow),
("testDeprecatedSetBytes", testDeprecatedSetBytes),
("testWriteRepeatingBytes", testWriteRepeatingBytes),
("testSetRepeatingBytes", testSetRepeatingBytes),
("testSetRepeatingBytes_unqiueReference", testSetRepeatingBytes_unqiueReference),
("testWriteOptionalWorksForNilCase", testWriteOptionalWorksForNilCase),
("testWriteOptionalWorksForNonNilCase", testWriteOptionalWorksForNonNilCase),
("testWriteImmutableOptionalWorksForNilCase", testWriteImmutableOptionalWorksForNilCase),
("testWriteImmutableOptionalWorksForNonNilCase", testWriteImmutableOptionalWorksForNonNilCase),
("testWritingToEmptyDoesNotCauseTrouble", testWritingToEmptyDoesNotCauseTrouble),
("testReadEmptySliceFromEmpty", testReadEmptySliceFromEmpty),
("testConvenienceStringInitWorks", testConvenienceStringInitWorks),
("testConvenienceCreateUInt64", testConvenienceCreateUInt64),
("testConvenienceCreateUInt8", testConvenienceCreateUInt8),
("testConvenienceCreateBuffer", testConvenienceCreateBuffer),
("testConvenienceCreateRepeatingByte", testConvenienceCreateRepeatingByte),
("testConvenienceCreateData", testConvenienceCreateData),
("testConvenienceCreateDispatchData", testConvenienceCreateDispatchData),
("testConvenienceCreateStaticString", testConvenienceCreateStaticString),
("testConvenienceCreateSubstring", testConvenienceCreateSubstring),
("testConvenienceCreateBytes", testConvenienceCreateBytes),
("testAllocatorGivesStableZeroSizedBuffers", testAllocatorGivesStableZeroSizedBuffers),
("testClearOnZeroCapacityActuallyAllocates", testClearOnZeroCapacityActuallyAllocates),
("testCreateBufferFromSequence", testCreateBufferFromSequence),
("testWeDoNotResizeIfWeHaveExactlyTheRightCapacityAvailable", testWeDoNotResizeIfWeHaveExactlyTheRightCapacityAvailable),
("testWithUnsafeMutableReadableBytesNoCoW", testWithUnsafeMutableReadableBytesNoCoW),
("testWithUnsafeMutableReadableBytesCoWOfNonSlice", testWithUnsafeMutableReadableBytesCoWOfNonSlice),
("testWithUnsafeMutableReadableBytesCoWOfSlice", testWithUnsafeMutableReadableBytesCoWOfSlice),
("testWithUnsafeMutableReadableBytesAllThingsNonZero", testWithUnsafeMutableReadableBytesAllThingsNonZero),
("testCreateArrayFromBuffer", testCreateArrayFromBuffer),
("testCreateStringFromBuffer", testCreateStringFromBuffer),
("testCreateDispatchDataFromBuffer", testCreateDispatchDataFromBuffer),
("testCreateBufferFromArray", testCreateBufferFromArray),
("testByteBufferViewEqualityWithRange", testByteBufferViewEqualityWithRange),
("testInvalidBufferEqualityWithDifferentRange", testInvalidBufferEqualityWithDifferentRange),
("testInvalidBufferEqualityWithDifferentContent", testInvalidBufferEqualityWithDifferentContent),
("testHashableConformance", testHashableConformance),
("testInvalidHash", testInvalidHash),
("testValidHashFromSlice", testValidHashFromSlice),
("testWritingMultipleIntegers", testWritingMultipleIntegers),
("testReadAndWriteMultipleIntegers", testReadAndWriteMultipleIntegers),
("testAllByteBufferMultiByteVersions", testAllByteBufferMultiByteVersions),
]
}
}

View File

@ -1,38 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// ChannelOptionStorageTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension ChannelOptionStorageTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (ChannelOptionStorageTest) -> () throws -> Void)] {
return [
("testWeStartWithNoOptions", testWeStartWithNoOptions),
("testSetTwoOptionsOfDifferentType", testSetTwoOptionsOfDifferentType),
("testSetTwoOptionsOfSameType", testSetTwoOptionsOfSameType),
("testSetOneOptionTwice", testSetOneOptionTwice),
("testClearingOptions", testClearingOptions),
]
}
}

View File

@ -1,87 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// CircularBufferTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension CircularBufferTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (CircularBufferTests) -> () throws -> Void)] {
return [
("testTrivial", testTrivial),
("testAddRemoveInALoop", testAddRemoveInALoop),
("testAddAllRemoveAll", testAddAllRemoveAll),
("testRemoveAt", testRemoveAt),
("testRemoveAtLastPosition", testRemoveAtLastPosition),
("testRemoveAtTailIdx0", testRemoveAtTailIdx0),
("testRemoveAtFirstPosition", testRemoveAtFirstPosition),
("testHarderExpansion", testHarderExpansion),
("testCollection", testCollection),
("testReplaceSubrange5ElementsWith1", testReplaceSubrange5ElementsWith1),
("testReplaceSubrangeAllElementsWithFewerElements", testReplaceSubrangeAllElementsWithFewerElements),
("testReplaceSubrangeEmptyRange", testReplaceSubrangeEmptyRange),
("testReplaceSubrangeWithSubrangeLargerThanTargetRange", testReplaceSubrangeWithSubrangeLargerThanTargetRange),
("testReplaceSubrangeSameSize", testReplaceSubrangeSameSize),
("testReplaceSubrangeReplaceBufferWithEmptyArray", testReplaceSubrangeReplaceBufferWithEmptyArray),
("testRangeSubscriptExpanding", testRangeSubscriptExpanding),
("testWeCanDistinguishBetweenEmptyAndFull", testWeCanDistinguishBetweenEmptyAndFull),
("testExpandZeroBasedRingWorks", testExpandZeroBasedRingWorks),
("testExpandNonZeroBasedRingWorks", testExpandNonZeroBasedRingWorks),
("testEmptyingExpandedRingWorks", testEmptyingExpandedRingWorks),
("testChangeElements", testChangeElements),
("testSliceTheRing", testSliceTheRing),
("testCount", testCount),
("testFirst", testFirst),
("testLast", testLast),
("testRemoveLast", testRemoveLast),
("testRemoveLastCountElements", testRemoveLastCountElements),
("testRemoveLastElements", testRemoveLastElements),
("testOperateOnBothSides", testOperateOnBothSides),
("testPrependExpandBuffer", testPrependExpandBuffer),
("testRemoveAllKeepingCapacity", testRemoveAllKeepingCapacity),
("testRemoveAllNotKeepingCapacity", testRemoveAllNotKeepingCapacity),
("testBufferManaged", testBufferManaged),
("testDoesNotExpandCapacityNeedlesslyWhenInserting", testDoesNotExpandCapacityNeedlesslyWhenInserting),
("testDoesNotExpandCapacityNeedlesslyWhenAppending", testDoesNotExpandCapacityNeedlesslyWhenAppending),
("testExpandRemoveAllKeepingAndNotKeepingCapacityAndExpandAgain", testExpandRemoveAllKeepingAndNotKeepingCapacityAndExpandAgain),
("testRemoveAllNilsOutTheContents", testRemoveAllNilsOutTheContents),
("testIntIndexing", testIntIndexing),
("testIndexDistance", testIndexDistance),
("testIndexAdvancing", testIndexAdvancing),
("testPopFirst", testPopFirst),
("testSlicing", testSlicing),
("testRemoveInMiddle", testRemoveInMiddle),
("testLotsOfPrepending", testLotsOfPrepending),
("testLotsOfInsertAtStart", testLotsOfInsertAtStart),
("testLotsOfInsertAtEnd", testLotsOfInsertAtEnd),
("testPopLast", testPopLast),
("testModify", testModify),
("testEquality", testEquality),
("testHash", testHash),
("testArrayLiteralInit", testArrayLiteralInit),
("testFirstWorks", testFirstWorks),
("testReserveCapacityActuallyDoesSomething", testReserveCapacityActuallyDoesSomething),
("testCopyContents", testCopyContents),
]
}
}

View File

@ -1,34 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// CustomChannelTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension CustomChannelTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (CustomChannelTests) -> () throws -> Void)] {
return [
("testWritingIntToSpecialChannel", testWritingIntToSpecialChannel),
]
}
}

View File

@ -1,35 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// DispatchQueue+WithFutureTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension DispatchQueueWithFutureTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (DispatchQueueWithFutureTest) -> () throws -> Void)] {
return [
("testDispatchQueueAsyncWithFuture", testDispatchQueueAsyncWithFuture),
("testDispatchQueueAsyncWithFutureThrows", testDispatchQueueAsyncWithFutureThrows),
]
}
}

View File

@ -1,35 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// IOErrorTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension IOErrorTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (IOErrorTest) -> () throws -> Void)] {
return [
("testMemoryLayoutBelowThreshold", testMemoryLayoutBelowThreshold),
("testDeprecatedAPIStillFunctional", testDeprecatedAPIStillFunctional),
]
}
}

View File

@ -1,41 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// IntegerTypesTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension IntegerTypesTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (IntegerTypesTest) -> () throws -> Void)] {
return [
("testNextPowerOfOfTwoZero", testNextPowerOfOfTwoZero),
("testNextPowerOfTwoOfOne", testNextPowerOfTwoOfOne),
("testNextPowerOfTwoOfTwo", testNextPowerOfTwoOfTwo),
("testNextPowerOfTwoOfThree", testNextPowerOfTwoOfThree),
("testNextPowerOfTwoOfFour", testNextPowerOfTwoOfFour),
("testNextPowerOfTwoOfFive", testNextPowerOfTwoOfFive),
("testDescriptionUInt24", testDescriptionUInt24),
("testDescriptionUInt56", testDescriptionUInt56),
]
}
}

View File

@ -1,35 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// LinuxTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension LinuxTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (LinuxTest) -> () throws -> Void)] {
return [
("testCoreCountQuota", testCoreCountQuota),
("testCoreCountCpuset", testCoreCountCpuset),
]
}
}

View File

@ -1,44 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// MarkedCircularBufferTests+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension MarkedCircularBufferTests {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (MarkedCircularBufferTests) -> () throws -> Void)] {
return [
("testEmptyMark", testEmptyMark),
("testSimpleMark", testSimpleMark),
("testPassingTheMark", testPassingTheMark),
("testMovingTheMark", testMovingTheMark),
("testIndices", testIndices),
("testFirst", testFirst),
("testCount", testCount),
("testSubscript", testSubscript),
("testRangeSubscript", testRangeSubscript),
("testIsEmpty", testIsEmpty),
("testPopFirst", testPopFirst),
]
}
}

View File

@ -1,34 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// NIOAnyDebugTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension NIOAnyDebugTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (NIOAnyDebugTest) -> () throws -> Void)] {
return [
("testCustomStringConvertible", testCustomStringConvertible),
]
}
}

View File

@ -1,34 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// NIOCloseOnErrorHandlerTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension NIOCloseOnErrorHandlerTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (NIOCloseOnErrorHandlerTest) -> () throws -> Void)] {
return [
("testChannelCloseOnError", testChannelCloseOnError),
]
}
}

View File

@ -1,38 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// RecvByteBufAllocatorTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension AdaptiveRecvByteBufferAllocatorTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (AdaptiveRecvByteBufferAllocatorTest) -> () throws -> Void)] {
return [
("testAdaptive", testAdaptive),
("testFixed", testFixed),
("testMaxAllocSizeIsIntMax", testMaxAllocSizeIsIntMax),
("testAdaptiveRoundsValues", testAdaptiveRoundsValues),
("testSettingMinimumAboveMaxAllowed", testSettingMinimumAboveMaxAllowed),
]
}
}

View File

@ -1,43 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// SingleStepByteToMessageDecoderTest+XCTest.swift
//
import XCTest
///
/// NOTE: This file was generated by generate_linux_tests.rb
///
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
///
extension NIOSingleStepByteToMessageDecoderTest {
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings")
static var allTests : [(String, (NIOSingleStepByteToMessageDecoderTest) -> () throws -> Void)] {
return [
("testDecoder", testDecoder),
("testMemoryIsReclaimedIfMostIsConsumed", testMemoryIsReclaimedIfMostIsConsumed),
("testMemoryIsReclaimedIfLotsIsAvailable", testMemoryIsReclaimedIfLotsIsAvailable),
("testLeftOversMakeDecodeLastCalled", testLeftOversMakeDecodeLastCalled),
("testStructsWorkAsOSBTMDecoders", testStructsWorkAsOSBTMDecoders),
("testDecodeLastIsInvokedOnceEvenIfNothingEverArrivedOnChannelClosed", testDecodeLastIsInvokedOnceEvenIfNothingEverArrivedOnChannelClosed),
("testPayloadTooLarge", testPayloadTooLarge),
("testPayloadTooLargeButHandlerOk", testPayloadTooLargeButHandlerOk),
("testReentrancy", testReentrancy),
("testWeDoNotCallShouldReclaimMemoryAsLongAsFramesAreProduced", testWeDoNotCallShouldReclaimMemoryAsLongAsFramesAreProduced),
]
}
}

Some files were not shown because too many files have changed in this diff Show More