Compare commits
34 Commits
Author | SHA1 | Date |
---|---|---|
|
8bdc286847 | |
![]() |
48e2fa746e | |
![]() |
46c0538253 | |
![]() |
6213ba7a06 | |
![]() |
d5519dba0b | |
![]() |
b22575ac96 | |
![]() |
5768317b70 | |
![]() |
27620851c4 | |
![]() |
47b6289d93 | |
![]() |
a22a35a0ff | |
![]() |
7a0ec436a1 | |
![]() |
2d8e6ca36f | |
![]() |
7f4d10bd94 | |
![]() |
546eaa261e | |
![]() |
014812aab6 | |
![]() |
d1690f8541 | |
![]() |
5f8b0647e4 | |
![]() |
77d35d2f43 | |
![]() |
e3497ffdc5 | |
![]() |
fd35cd9e52 | |
![]() |
d836d6bef5 | |
![]() |
f7c4655298 | |
![]() |
003fbadf51 | |
![]() |
ad859ae82e | |
![]() |
6720612111 | |
![]() |
a2fd8ad077 | |
![]() |
b4ebd5a64a | |
![]() |
20cc037d23 | |
![]() |
e0cc6dd6ff | |
![]() |
a7c36a7654 | |
![]() |
e7e83d6aa4 | |
![]() |
75cea45e61 | |
![]() |
4f7b78202d | |
![]() |
8423040a9b |
|
@ -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 project’s 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. -->
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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``
|
||||
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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`.
|
||||
///
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -257,10 +257,8 @@ public struct NIOAny {
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension NIOAny: Sendable {}
|
||||
#endif
|
||||
|
||||
extension NIOAny: CustomStringConvertible {
|
||||
public var description: String {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -72,7 +72,5 @@ public final class HTTPServerProtocolErrorHandler: ChannelDuplexHandler, Removab
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension HTTPServerProtocolErrorHandler: Sendable {}
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
@ -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.
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,7 +191,5 @@ public final class NIORawSocketBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension NIORawSocketBootstrap: Sendable {}
|
||||
#endif
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>?) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -427,7 +427,5 @@ public final class SNIHandler: ByteToMessageDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension SNIHandler: Sendable {}
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -137,7 +137,5 @@ public final class NIOWebSocketFrameAggregator: ChannelInboundHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension NIOWebSocketFrameAggregator: Sendable {}
|
||||
#endif
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -270,7 +270,5 @@ public final class WebSocketFrameDecoder: ByteToMessageDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension WebSocketFrameDecoder: Sendable {}
|
||||
#endif
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -43,7 +43,5 @@ public final class WebSocketProtocolErrorHandler: ChannelInboundHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.6)
|
||||
@available(*, unavailable)
|
||||
extension WebSocketProtocolErrorHandler: Sendable {}
|
||||
#endif
|
||||
|
|
|
@ -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
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue