diff --git a/CMakeLists.txt b/CMakeLists.txt
index 610d0f5e07..91bd7bc0bd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -317,7 +317,7 @@ else()
set(QUIC_LOGGING_TYPE "lttng")
message(STATUS "Choosing lttng as default logging type for platform")
endif()
-
+
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|aarch64)$")
set(QUIC_LINUX_XDP_ENABLED OFF CACHE BOOL "XDP not supported on ARM architectures" FORCE)
endif()
@@ -714,7 +714,7 @@ else() #!WIN32
if (QUIC_SANITIZER_ACTIVE)
message(STATUS "Configuring sanitizers: ASAN:${QUIC_ENABLE_ASAN} LSAN:${QUIC_ENABLE_LSAN} UBSAN:${QUIC_ENABLE_UBSAN} EXTRA:${QUIC_ENABLE_EXTRA_SANITIZERS}")
# Append common flags for all sanitizer options
- list(APPEND QUIC_COMMON_FLAGS -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls)
+ list(APPEND QUIC_COMMON_FLAGS -O0 -fno-omit-frame-pointer -fno-optimize-sibling-calls)
# Clang/LLVM doesn't support this flag, but GCC does.
check_c_compiler_flag(-fno-var-tracking-assignments HAS_NO_VAR_TRACKING)
@@ -775,6 +775,10 @@ if (QUIC_USE_SYSTEM_LIBCRYPTO)
list(APPEND QUIC_COMMON_DEFINES CXPLAT_SYSTEM_CRYPTO)
endif()
+if (QUIC_LINUX_XDP_ENABLED)
+ list(APPEND QUIC_COMMON_DEFINES CXPLAT_LINUX_XDP_ENABLED)
+endif()
+
if (QUIC_LINUX_IOURING_ENABLED)
list(APPEND QUIC_COMMON_DEFINES CXPLAT_USE_IO_URING)
endif()
diff --git a/docs/API.md b/docs/API.md
index 0fb1e38db9..b9e2bdd193 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -90,7 +90,7 @@ Please note, there is no explicit start/stop API for this library. Each API func
Generally, each app only needs a single registration. The registration represents the execution context where all logic for the app's connections run. The library will create a number of worker threads for each registration, shared for all the connections. This execution context is not shared between different registrations.
-A registration is created by calling [RegistrationOpen](api/RegistrationOpen.md) and deleted by calling [RegistrationClose](api/RegistrationClose.md).
+A registration is created by calling [RegistrationOpen](api/RegistrationOpen.md) and deleted by calling [RegistrationClose](api/RegistrationClose.md) or (Preview) [RegistrationClose2](api/RegistrationClose2.md).
## Configuration
diff --git a/docs/Diagnostics.md b/docs/Diagnostics.md
index d1061e21f8..13ed2ebf15 100644
--- a/docs/Diagnostics.md
+++ b/docs/Diagnostics.md
@@ -291,6 +291,7 @@ QUIC_PERF_COUNTER_PATH_FAILURE | Total path challenges that fail ever
QUIC_PERF_COUNTER_SEND_STATELESS_RESET | Total stateless reset packets sent ever
QUIC_PERF_COUNTER_SEND_STATELESS_RETRY | Total stateless retry packets sent ever
QUIC_PERF_COUNTER_CONN_LOAD_REJECT | Total connections rejected due to worker load.
+QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH | Current listeners queued for processing.
## Windows Performance Monitor
diff --git a/docs/Settings.md b/docs/Settings.md
index 4b3d2a66c2..44b984a93a 100644
--- a/docs/Settings.md
+++ b/docs/Settings.md
@@ -171,6 +171,7 @@ These parameters are accessed by calling [GetParam](./api/GetParam.md) or [SetPa
| `QUIC_PARAM_LISTENER_STATS`
1 | QUIC_LISTENER_STATISTICS | Get-only | Get statistics specific to this Listener instance. |
| `QUIC_PARAM_LISTENER_CIBIR_ID`
2 | uint8_t[] | Both | The CIBIR well-known idenfitier. |
| `QUIC_PARAM_DOS_MODE_EVENTS`
2 | BOOLEAN | Both | The Listener opted in for DoS Mode event. |
+| `QUIC_PARAM_LISTENER_PARTITION_INDEX`
(preview) | uint16_t | Both | The partition to use for listener callback events and incoming connections. |
## Connection Parameters
@@ -204,7 +205,8 @@ These parameters are accessed by calling [GetParam](./api/GetParam.md) or [SetPa
| `QUIC_PARAM_CONN_STATISTICS_V2_PLAT`
23 | QUIC_STATISTICS_V2 | Get-only | Connection-level statistics with platform-specific time format, version 2. |
| `QUIC_PARAM_CONN_ORIG_DEST_CID`
24 | uint8_t[] | Get-only | The original destination connection ID used by the client to connect to the server. |
| `QUIC_PARAM_CONN_SEND_DSCP`
25 | uint8_t | Both | The DiffServ Code Point put in the DiffServ field (formerly TypeOfService/TrafficClass) on packets sent from this connection. |
-| `QUIC_PARAM_CONN_NETWORK_STATISTICS`
20 | QUIC_NETWORK_STATISTICS | Get-only | Returns Connection level network statistics |
+| `QUIC_PARAM_CONN_NETWORK_STATISTICS`
32 | QUIC_NETWORK_STATISTICS | Get-only | Returns Connection level network statistics |
+| `QUIC_PARAM_CONN_CLOSE_ASYNC`
26 | uint8_t (BOOLEAN) | Both | The desired connection close behavior. Defaults to false (synchronous). |
### QUIC_PARAM_CONN_STATISTICS_V2
diff --git a/docs/api/QUIC_API_TABLE.md b/docs/api/QUIC_API_TABLE.md
index 9513e84b7f..d66f3426fe 100644
--- a/docs/api/QUIC_API_TABLE.md
+++ b/docs/api/QUIC_API_TABLE.md
@@ -80,6 +80,10 @@ See [RegistrationOpen](RegistrationOpen.md)
See [RegistrationClose](RegistrationClose.md)
+`RegistrationClose2`
+
+See (Preview) [RegistrationClose2](RegistrationClose2.md)
+
`RegistrationShutdown`
See [RegistrationShutdown](RegistrationShutdown.md)
diff --git a/docs/api/RegistrationClose.md b/docs/api/RegistrationClose.md
index 26c828a4b2..785cae5744 100644
--- a/docs/api/RegistrationClose.md
+++ b/docs/api/RegistrationClose.md
@@ -23,8 +23,9 @@ A registration handle from a previous call to [RegistrationOpen](RegistrationOpe
# Remarks
-The application **must** close/delete all child configurations and connection objects before closing the registration. This call **will block** on those outstanding objects being cleaned up. Do no call it on any MsQuic event callback, or it will deadlock.
+The application **must** close/delete all child configurations and connection objects before closing the registration. This call **will block** on those outstanding objects being cleaned up. Do not call it on any MsQuic event callback or a thread that would otherwise be running an external execution context, or it will deadlock.
# See Also
[RegistrationOpen](RegistrationOpen.md)
+(Preview) [RegistrationClose2](RegistrationClose2.md)
diff --git a/docs/api/RegistrationClose2.md b/docs/api/RegistrationClose2.md
new file mode 100644
index 0000000000..6db903782b
--- /dev/null
+++ b/docs/api/RegistrationClose2.md
@@ -0,0 +1,54 @@
+RegistrationClose2 function
+======
+
+> **Preview**
+> This routine is in preview and is subject to breaking changes.
+
+Closes an existing registration asynchronously.
+
+# Syntax
+
+```C
+typedef
+_Function_class_(QUIC_REGISTRATION_CLOSE_CALLBACK)
+void
+(QUIC_API QUIC_REGISTRATION_CLOSE_CALLBACK)(
+ _In_opt_ void* Context
+ );
+typedef QUIC_REGISTRATION_CLOSE_CALLBACK *QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER;
+
+typedef
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+(QUIC_API * QUIC_REGISTRATION_CLOSE2_FN)(
+ _In_ _Pre_defensive_ __drv_freesMem(Mem)
+ HQUIC Registration,
+ _In_ _Pre_defensive_ QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context
+ );
+```
+
+# Parameters
+
+`Registration`
+
+A registration handle from a previous call to [RegistrationOpen](RegistrationOpen.md).
+
+`Handler`
+
+A registration close completion handler. It will be invoked exactly once upon completion of the registration close request.
+
+`Context`
+
+The context to provide to the close completion handler.
+
+# Remarks
+
+> **Preview**
+> This routine is in preview and is subject to breaking changes.
+
+The application should close/delete all child configurations and connection objects before closing the registration. This request **will not complete** until those outstanding objects are cleaned up.
+
+# See Also
+
+[RegistrationOpen](RegistrationOpen.md)
diff --git a/docs/api/RegistrationOpen.md b/docs/api/RegistrationOpen.md
index 8b7f1b72c8..8b5fb2a650 100644
--- a/docs/api/RegistrationOpen.md
+++ b/docs/api/RegistrationOpen.md
@@ -39,3 +39,4 @@ A caveat to this independence is that until a packet or connection can be determ
# See Also
[RegistrationClose](RegistrationClose.md)
+(Preview) [RegistrationClose2](RegistrationClose2.md)
diff --git a/docs/api/RegistrationShutdown.md b/docs/api/RegistrationShutdown.md
index 66abc9f5f7..097aa33a8a 100644
--- a/docs/api/RegistrationShutdown.md
+++ b/docs/api/RegistrationShutdown.md
@@ -44,3 +44,4 @@ The 62-bit error code to indicate to the peer as the reason for the shutdown.
[RegistrationOpen](RegistrationOpen.md)
[RegistrationClose](RegistrationClose.md)
[ConnectionShutdown](ConnectionShutdown.md)
+(Preview) [RegistrationClose2](RegistrationClose2.md)
diff --git a/src/core/api.c b/src/core/api.c
index 56561c668a..ce9f2ab5fa 100644
--- a/src/core/api.c
+++ b/src/core/api.c
@@ -33,31 +33,12 @@
_IRQL_requires_max_(DISPATCH_LEVEL)
QUIC_STATUS
QUIC_API
-MsQuicConnectionOpen(
- _In_ _Pre_defensive_ HQUIC RegistrationHandle,
- _In_ _Pre_defensive_ QUIC_CONNECTION_CALLBACK_HANDLER Handler,
- _In_opt_ void* Context,
- _Outptr_ _At_(*NewConnection, __drv_allocatesMem(Mem)) _Pre_defensive_
- HQUIC *NewConnection
- )
-{
- return
- MsQuicConnectionOpenInPartition(
- RegistrationHandle,
- QuicLibraryGetCurrentPartition()->Index,
- Handler,
- Context,
- NewConnection);
-}
-
-_IRQL_requires_max_(DISPATCH_LEVEL)
-QUIC_STATUS
-QUIC_API
-MsQuicConnectionOpenInPartition(
+QuicConnectionOpenInPartition(
_In_ _Pre_defensive_ HQUIC RegistrationHandle,
_In_ uint16_t PartitionIndex,
_In_ _Pre_defensive_ QUIC_CONNECTION_CALLBACK_HANDLER Handler,
_In_opt_ void* Context,
+ _In_ BOOLEAN Partitioned,
_Outptr_ _At_(*NewConnection, __drv_allocatesMem(Mem)) _Pre_defensive_
HQUIC *NewConnection
)
@@ -98,6 +79,14 @@ MsQuicConnectionOpenInPartition(
goto Error;
}
+ //
+ // Hard partitioning is only supported on a subset of platforms.
+ //
+#if defined(__linux__) && !defined(CXPLAT_USE_IO_URING) && !defined(CXPLAT_LINUX_XDP_ENABLED)
+ Connection->State.Partitioned = Partitioned;
+#else
+ UNREFERENCED_PARAMETER(Partitioned);
+#endif
Connection->ClientCallbackHandler = Handler;
Connection->ClientContext = Context;
@@ -114,6 +103,49 @@ MsQuicConnectionOpenInPartition(
return Status;
}
+_IRQL_requires_max_(DISPATCH_LEVEL)
+QUIC_STATUS
+QUIC_API
+MsQuicConnectionOpen(
+ _In_ _Pre_defensive_ HQUIC RegistrationHandle,
+ _In_ _Pre_defensive_ QUIC_CONNECTION_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context,
+ _Outptr_ _At_(*NewConnection, __drv_allocatesMem(Mem)) _Pre_defensive_
+ HQUIC *NewConnection
+ )
+{
+ return
+ QuicConnectionOpenInPartition(
+ RegistrationHandle,
+ QuicLibraryGetCurrentPartition()->Index,
+ Handler,
+ Context,
+ FALSE,
+ NewConnection);
+}
+
+_IRQL_requires_max_(DISPATCH_LEVEL)
+QUIC_STATUS
+QUIC_API
+MsQuicConnectionOpenInPartition(
+ _In_ _Pre_defensive_ HQUIC RegistrationHandle,
+ _In_ uint16_t PartitionIndex,
+ _In_ _Pre_defensive_ QUIC_CONNECTION_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context,
+ _Outptr_ _At_(*NewConnection, __drv_allocatesMem(Mem)) _Pre_defensive_
+ HQUIC *NewConnection
+ )
+{
+ return
+ QuicConnectionOpenInPartition(
+ RegistrationHandle,
+ PartitionIndex,
+ Handler,
+ Context,
+ TRUE,
+ NewConnection);
+}
+
#pragma warning(push)
#pragma warning(disable:6014) // SAL doesn't understand the free happens on the worker
_IRQL_requires_max_(PASSIVE_LEVEL)
@@ -125,6 +157,7 @@ MsQuicConnectionClose(
)
{
QUIC_CONNECTION* Connection;
+ BOOLEAN WaitForCompletion = TRUE;
CXPLAT_PASSIVE_CODE();
@@ -157,7 +190,7 @@ MsQuicConnectionClose(
CXPLAT_TEL_ASSERT(!Connection->State.HandleClosed);
- if (MsQuicLib.CustomExecutions || IsWorkerThread) {
+ if (IsWorkerThread) {
//
// Execute this blocking API call inline if called on the worker thread.
//
@@ -172,40 +205,49 @@ MsQuicConnectionClose(
} else {
- CXPLAT_EVENT CompletionEvent;
- QUIC_OPERATION Oper = { 0 };
- QUIC_API_CONTEXT ApiCtx;
+ CXPLAT_EVENT CompletionEvent = {0};
- Oper.Type = QUIC_OPER_TYPE_API_CALL;
- Oper.FreeAfterProcess = FALSE;
- Oper.API_CALL.Context = &ApiCtx;
+ Connection->CloseOper.Type = QUIC_OPER_TYPE_API_CALL;
+ Connection->CloseOper.FreeAfterProcess = FALSE;
+ Connection->CloseOper.API_CALL.Context = &Connection->CloseApiContext;
- ApiCtx.Type = QUIC_API_TYPE_CONN_CLOSE;
- CxPlatEventInitialize(&CompletionEvent, TRUE, FALSE);
- ApiCtx.Completed = &CompletionEvent;
- ApiCtx.Status = NULL;
+ Connection->CloseApiContext.Type = QUIC_API_TYPE_CONN_CLOSE;
+ Connection->CloseApiContext.Status = NULL;
+
+ if (Connection->State.CloseAsync) {
+ Connection->CloseApiContext.Completed = NULL;
+ WaitForCompletion = FALSE;
+ } else {
+ CxPlatEventInitialize(&CompletionEvent, TRUE, FALSE);
+ Connection->CloseApiContext.Completed = &CompletionEvent;
+ }
//
// Queue the operation and wait for it to be processed.
//
- QuicConnQueueOper(Connection, &Oper);
- QuicTraceEvent(
- ApiWaitOperation,
- "[ api] Waiting on operation");
- CxPlatEventWaitForever(CompletionEvent);
- CxPlatEventUninitialize(CompletionEvent);
+ QuicConnQueueOper(Connection, &Connection->CloseOper);
+
+ if (WaitForCompletion) {
+ QuicTraceEvent(
+ ApiWaitOperation,
+ "[ api] Waiting on operation");
+ CxPlatEventWaitForever(CompletionEvent);
+ CxPlatEventUninitialize(CompletionEvent);
+ }
}
//
// Connection can only be released by the application after the released
// flag was set, in response to the CONN_CLOSE operation was processed.
//
- CXPLAT_TEL_ASSERT(Connection->State.HandleClosed);
+ if (WaitForCompletion) {
+ CXPLAT_TEL_ASSERT(Connection->State.HandleClosed);
- //
- // Release the reference to the Connection.
- //
- QuicConnRelease(Connection, QUIC_CONN_REF_HANDLE_OWNER);
+ //
+ // Release the reference to the Connection.
+ //
+ QuicConnRelease(Connection, QUIC_CONN_REF_HANDLE_OWNER);
+ }
Error:
diff --git a/src/core/api.h b/src/core/api.h
index ff94dd2cba..a02b4614bb 100644
--- a/src/core/api.h
+++ b/src/core/api.h
@@ -55,6 +55,16 @@ MsQuicRegistrationClose(
HQUIC Handle
);
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QUIC_API
+MsQuicRegistrationClose2(
+ _In_ _Pre_defensive_ __drv_freesMem(Mem)
+ HQUIC Handle,
+ _In_ _Pre_defensive_ QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context
+ );
+
_IRQL_requires_max_(DISPATCH_LEVEL)
void
QUIC_API
diff --git a/src/core/binding.c b/src/core/binding.c
index 443a6520d6..b404220702 100644
--- a/src/core/binding.c
+++ b/src/core/binding.c
@@ -59,6 +59,10 @@ QuicBindingInitialize(
Binding->Exclusive = !(UdpConfig->Flags & CXPLAT_SOCKET_FLAG_SHARE);
Binding->ServerOwned = !!(UdpConfig->Flags & CXPLAT_SOCKET_SERVER_OWNED);
Binding->Connected = UdpConfig->RemoteAddress == NULL ? FALSE : TRUE;
+ Binding->Partitioned = !!(UdpConfig->Flags & CXPLAT_SOCKET_FLAG_PARTITIONED);
+ if (Binding->Partitioned) {
+ Binding->PartitionIndex = UdpConfig->PartitionIndex;
+ }
Binding->StatelessOperCount = 0;
CxPlatDispatchRwLockInitialize(&Binding->RwLock);
CxPlatDispatchLockInitialize(&Binding->StatelessOperLock);
@@ -446,7 +450,7 @@ QuicBindingGetListener(
FailedAddrMatch = FALSE;
if (QuicListenerMatchesAlpn(ExistingListener, Info)) {
- if (CxPlatRefIncrementNonZero(&ExistingListener->RefCount, 1)) {
+ if (CxPlatRefIncrementNonZero(&ExistingListener->StartRefCount, 1)) {
Listener = ExistingListener;
}
goto Done;
@@ -550,7 +554,7 @@ QuicBindingAcceptConnection(
Error:
- QuicListenerRelease(Listener, TRUE);
+ QuicListenerStartRelease(Listener, TRUE);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
@@ -1840,7 +1844,7 @@ QuicBindingHandleDosModeStateChange(
ListenerLink = ListenerLink->Flink) {
QUIC_LISTENER* Listener = CXPLAT_CONTAINING_RECORD(ListenerLink, QUIC_LISTENER, Link);
- QuicListenerHandleDosModeStateChange(Listener, DosModeEnabled);
+ QuicListenerHandleDosModeStateChange(Listener, DosModeEnabled, FALSE);
}
CxPlatDispatchRwLockReleaseShared(&Binding->RwLock, PrevIrql);
}
diff --git a/src/core/binding.h b/src/core/binding.h
index 5c4265cdb8..075b3c29fd 100644
--- a/src/core/binding.h
+++ b/src/core/binding.h
@@ -210,6 +210,14 @@ typedef struct QUIC_BINDING {
//
BOOLEAN Connected : 1;
+ //
+ // Indicates that the binding is constrained to a single partition. If
+ // partitioned, the binding must indicate all events within the partition,
+ // use the event queue belonging to the partition for IO, and perform its
+ // own processing within the partition's execution context.
+ //
+ BOOLEAN Partitioned : 1;
+
//
// Number of (connection and listener) references to the binding.
//
@@ -227,6 +235,11 @@ typedef struct QUIC_BINDING {
QUIC_COMPARTMENT_ID CompartmentId;
#endif
+ //
+ // The partition index, if partitioned.
+ //
+ uint16_t PartitionIndex;
+
//
// The datapath binding.
//
diff --git a/src/core/connection.c b/src/core/connection.c
index 8648dfe4c0..8e37776520 100644
--- a/src/core/connection.c
+++ b/src/core/connection.c
@@ -1870,7 +1870,7 @@ QuicConnStart(
UdpConfig.LocalAddress = Connection->State.LocalAddressSet ? &Path->Route.LocalAddress : NULL;
UdpConfig.RemoteAddress = &Path->Route.RemoteAddress;
UdpConfig.Flags = CXPLAT_SOCKET_FLAG_NONE;
- UdpConfig.InterfaceIndex = Connection->State.LocalInterfaceSet ? (uint32_t)Path->Route.LocalAddress.Ipv6.sin6_scope_id : 0, // NOLINT(google-readability-casting)
+ UdpConfig.InterfaceIndex = Connection->State.LocalInterfaceSet ? (uint32_t)Path->Route.LocalAddress.Ipv6.sin6_scope_id : 0; // NOLINT(google-readability-casting)
UdpConfig.PartitionIndex = QuicPartitionIdGetIndex(Connection->PartitionID);
#ifdef QUIC_COMPARTMENT_ID
UdpConfig.CompartmentId = Configuration->CompartmentId;
@@ -1888,6 +1888,9 @@ QuicConnStart(
if (Connection->Settings.QTIPEnabled) {
UdpConfig.Flags |= CXPLAT_SOCKET_FLAG_QTIP;
}
+ if (Connection->State.Partitioned) {
+ UdpConfig.Flags |= CXPLAT_SOCKET_FLAG_PARTITIONED;
+ }
//
// Get the binding for the current local & remote addresses.
@@ -5594,7 +5597,8 @@ QuicConnRecvDatagramBatch(
RecvState->ResetIdleTimeout |= Packet->CompletelyValid;
if (Connection->Registration != NULL && !Connection->Registration->NoPartitioning &&
- Path->IsActive && !Path->PartitionUpdated && Packet->CompletelyValid &&
+ !Path->Binding->Partitioned && !Connection->State.Partitioned && Path->IsActive &&
+ !Path->PartitionUpdated && Packet->CompletelyValid &&
(Packets[i]->PartitionIndex % MsQuicLib.PartitionCount) != RecvState->PartitionIndex) {
RecvState->PartitionIndex = Packets[i]->PartitionIndex % MsQuicLib.PartitionCount;
RecvState->UpdatePartitionId = TRUE;
@@ -6720,6 +6724,28 @@ QuicConnParamSet(
break;
}
+ case QUIC_PARAM_CONN_CLOSE_ASYNC:
+ if (BufferLength != sizeof(BOOLEAN)) {
+ Status = QUIC_STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ if (Connection->State.HandleClosed) {
+ Status = QUIC_STATUS_INVALID_STATE;
+ break;
+ }
+
+ Connection->State.CloseAsync = *(BOOLEAN*)Buffer;
+ Status = QUIC_STATUS_SUCCESS;
+
+ QuicTraceLogConnVerbose(
+ CloseAsyncUpdated,
+ Connection,
+ "Updated CloseAsync to %hhu",
+ Connection->State.CloseAsync);
+
+ break;
+
//
// Private
//
@@ -7390,6 +7416,25 @@ QuicConnParamGet(
QuicConnGetNetworkStatistics(Connection, BufferLength, (QUIC_NETWORK_STATISTICS *)Buffer);
break;
+ case QUIC_PARAM_CONN_CLOSE_ASYNC:
+
+ if (*BufferLength < sizeof(BOOLEAN)) {
+ *BufferLength = sizeof(BOOLEAN);
+ Status = QUIC_STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+
+ if (Buffer == NULL) {
+ Status = QUIC_STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ *BufferLength = sizeof(BOOLEAN);
+ *(BOOLEAN*)Buffer = Connection->State.CloseAsync;
+
+ Status = QUIC_STATUS_SUCCESS;
+ break;
+
default:
Status = QUIC_STATUS_INVALID_PARAMETER;
break;
@@ -7575,6 +7620,14 @@ QuicConnProcessApiOperation(
case QUIC_API_TYPE_CONN_CLOSE:
QuicConnCloseHandle(Connection);
+ if (Connection->State.CloseAsync) {
+ CXPLAT_TEL_ASSERT(Connection->State.HandleClosed);
+ //
+ // Release the external reference to the connection for async
+ // completion.
+ //
+ QuicConnRelease(Connection, QUIC_CONN_REF_HANDLE_OWNER);
+ }
break;
case QUIC_API_TYPE_CONN_SHUTDOWN:
diff --git a/src/core/connection.h b/src/core/connection.h
index e13c74ffc6..f8ef10bf4f 100644
--- a/src/core/connection.h
+++ b/src/core/connection.h
@@ -39,6 +39,8 @@ typedef union QUIC_CONNECTION_STATE {
BOOLEAN ShutdownComplete : 1; // Shutdown callback delivered for handle.
BOOLEAN HandleClosed : 1; // Handle closed by application layer.
BOOLEAN Freed : 1; // Freed. Used for Debugging.
+ BOOLEAN Partitioned : 1; // The connection cannot move across partitions.
+ BOOLEAN CloseAsync : 1; // The connection will close without waiting for callbacks.
//
// Indicates whether packet number encryption is enabled or not for the
@@ -558,6 +560,8 @@ typedef struct QUIC_CONNECTION {
QUIC_OPERATION BackUpOper;
QUIC_API_CONTEXT BackupApiContext;
uint16_t BackUpOperUsed;
+ QUIC_OPERATION CloseOper;
+ QUIC_API_CONTEXT CloseApiContext;
//
// The status code used for indicating transport closed notifications.
diff --git a/src/core/library.c b/src/core/library.c
index c0fc05aade..7fc0ff6aab 100644
--- a/src/core/library.c
+++ b/src/core/library.c
@@ -30,6 +30,8 @@ QuicLibraryEvaluateSendRetryState(
void
);
+CXPLAT_THREAD_CALLBACK(RegistrationCleanupWorker, Context);
+
CXPLAT_DATAPATH_FEATURES
QuicLibraryGetDatapathFeatures(
void
@@ -516,11 +518,28 @@ MsQuicLibraryInitialize(
"[ lib] Failed to open global settings, 0x%x",
Status);
// Non-fatal, as the process may not have access
- Status = QUIC_STATUS_SUCCESS;
}
MsQuicLibraryReadSettings(NULL); // NULL means don't update registrations.
+ CxPlatLockInitialize(&MsQuicLib.RegistrationCloseCleanupLock);
+ CxPlatEventInitialize(&MsQuicLib.RegistrationCloseCleanupEvent, FALSE, FALSE);
+ MsQuicLib.RegistrationCloseCleanupShutdown = FALSE;
+ CxPlatListInitializeHead(&MsQuicLib.RegistrationCloseCleanupList);
+
+ CXPLAT_THREAD_CONFIG ThreadConfig = {
+ 0,
+ 0,
+ "RegistrationCleanupWorker",
+ RegistrationCleanupWorker,
+ NULL,
+ };
+
+ Status = CxPlatThreadCreate(&ThreadConfig, &MsQuicLib.RegistrationCloseCleanupWorker);
+ if (QUIC_FAILED(Status)) {
+ goto Error;
+ }
+
uint32_t CompatibilityListByteLength = 0;
QuicVersionNegotiationExtGenerateCompatibleVersionsList(
QUIC_VERSION_LATEST,
@@ -579,6 +598,15 @@ MsQuicLibraryInitialize(
Error:
if (QUIC_FAILED(Status)) {
+ if (MsQuicLib.RegistrationCloseCleanupWorker) {
+ MsQuicLib.RegistrationCloseCleanupShutdown = TRUE;
+ CxPlatEventSet(MsQuicLib.RegistrationCloseCleanupEvent);
+ CxPlatThreadWait(&MsQuicLib.RegistrationCloseCleanupWorker);
+ CxPlatThreadDelete(&MsQuicLib.RegistrationCloseCleanupWorker);
+ MsQuicLib.RegistrationCloseCleanupWorker = 0;
+ }
+ CxPlatEventUninitialize(MsQuicLib.RegistrationCloseCleanupEvent);
+ CxPlatLockUninitialize(&MsQuicLib.RegistrationCloseCleanupLock);
if (MsQuicLib.Storage != NULL) {
CxPlatStorageClose(MsQuicLib.Storage);
MsQuicLib.Storage = NULL;
@@ -598,7 +626,7 @@ MsQuicLibraryInitialize(
_IRQL_requires_max_(PASSIVE_LEVEL)
void
-MsQuicLibraryUninitialize(
+MsQuicLibraryLazyUninitialize(
void
)
{
@@ -649,11 +677,6 @@ MsQuicLibraryUninitialize(
MsQuicLib.Datapath = NULL;
}
- if (MsQuicLib.Storage != NULL) {
- CxPlatStorageClose(MsQuicLib.Storage);
- MsQuicLib.Storage = NULL;
- }
-
#if DEBUG
//
// If you hit this assert, MsQuic API is trying to be unloaded without
@@ -685,6 +708,26 @@ MsQuicLibraryUninitialize(
MsQuicLibraryFreePartitions();
+ MsQuicLib.LazyInitComplete = FALSE;
+}
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+MsQuicLibraryUninitialize(
+ void
+ )
+{
+ QuicTraceEvent(
+ LibraryUninitialized,
+ "[ lib] Uninitialized");
+
+ MsQuicLibraryLazyUninitialize();
+
+ if (MsQuicLib.Storage != NULL) {
+ CxPlatStorageClose(MsQuicLib.Storage);
+ MsQuicLib.Storage = NULL;
+ }
+
QuicSettingsCleanup(&MsQuicLib.Settings);
CXPLAT_FREE(MsQuicLib.DefaultCompatibilityList, QUIC_POOL_DEFAULT_COMPAT_VER_LIST);
@@ -692,17 +735,20 @@ MsQuicLibraryUninitialize(
CxPlatDispatchRwLockUninitialize(&MsQuicLib.StatelessRetry.Lock);
+ MsQuicLib.RegistrationCloseCleanupShutdown = TRUE;
+ CxPlatEventSet(MsQuicLib.RegistrationCloseCleanupEvent);
+ CxPlatThreadWait(&MsQuicLib.RegistrationCloseCleanupWorker);
+ CxPlatThreadDelete(&MsQuicLib.RegistrationCloseCleanupWorker);
+ MsQuicLib.RegistrationCloseCleanupWorker = 0;
+
+ CxPlatEventUninitialize(MsQuicLib.RegistrationCloseCleanupEvent);
+ CxPlatLockUninitialize(&MsQuicLib.RegistrationCloseCleanupLock);
+
if (MsQuicLib.ExecutionConfig != NULL) {
CXPLAT_FREE(MsQuicLib.ExecutionConfig, QUIC_POOL_EXECUTION_CONFIG);
MsQuicLib.ExecutionConfig = NULL;
}
- MsQuicLib.LazyInitComplete = FALSE;
-
- QuicTraceEvent(
- LibraryUninitialized,
- "[ lib] Uninitialized");
-
#ifndef _KERNEL_MODE
CxPlatWorkerPoolDelete(MsQuicLib.WorkerPool);
MsQuicLib.WorkerPool = NULL;
@@ -710,6 +756,33 @@ MsQuicLibraryUninitialize(
CxPlatUninitialize();
}
+CXPLAT_THREAD_CALLBACK(RegistrationCleanupWorker, Context)
+{
+ UNREFERENCED_PARAMETER(Context);
+
+ while (!MsQuicLib.RegistrationCloseCleanupShutdown) {
+ CxPlatEventWaitForever(MsQuicLib.RegistrationCloseCleanupEvent);
+
+ CxPlatLockAcquire(&MsQuicLib.RegistrationCloseCleanupLock);
+ while (!CxPlatListIsEmpty(&MsQuicLib.RegistrationCloseCleanupList)) {
+ CXPLAT_LIST_ENTRY* Entry =
+ CxPlatListRemoveHead(&MsQuicLib.RegistrationCloseCleanupList);
+ QUIC_REGISTRATION* Registration =
+ CXPLAT_CONTAINING_RECORD(Entry, QUIC_REGISTRATION, CloseCleanupEntry);
+ CxPlatLockRelease(&MsQuicLib.RegistrationCloseCleanupLock);
+
+ CxPlatThreadWait(&Registration->CloseThread);
+ CxPlatThreadDelete(&Registration->CloseThread);
+ CXPLAT_FREE(Registration, QUIC_POOL_REGISTRATION);
+
+ CxPlatLockAcquire(&MsQuicLib.RegistrationCloseCleanupLock);
+ }
+ CxPlatLockRelease(&MsQuicLib.RegistrationCloseCleanupLock);
+ }
+
+ CXPLAT_THREAD_RETURN(QUIC_STATUS_SUCCESS);
+}
+
_IRQL_requires_max_(PASSIVE_LEVEL)
QUIC_STATUS
MsQuicAddRef(
@@ -2032,6 +2105,8 @@ MsQuicOpenVersion(
Api->ExecutionPoll = MsQuicExecutionPoll;
#endif
+ Api->RegistrationClose2 = MsQuicRegistrationClose2;
+
Api->ConnectionPoolCreate = MsQuicConnectionPoolCreate;
*QuicApi = Api;
@@ -2145,6 +2220,7 @@ QuicLibraryGetBinding(
UdpConfig->LocalAddress == NULL || QuicAddrGetPort(UdpConfig->LocalAddress) == 0;
const BOOLEAN ShareBinding = !!(UdpConfig->Flags & CXPLAT_SOCKET_FLAG_SHARE);
const BOOLEAN ServerOwned = !!(UdpConfig->Flags & CXPLAT_SOCKET_SERVER_OWNED);
+ const BOOLEAN Partitioned = !!(UdpConfig->Flags & CXPLAT_SOCKET_FLAG_PARTITIONED);
#ifdef QUIC_SHARED_EPHEMERAL_WORKAROUND
//
@@ -2185,7 +2261,9 @@ QuicLibraryGetBinding(
UdpConfig->RemoteAddress);
if (Binding != NULL) {
if (!ShareBinding || Binding->Exclusive ||
- (ServerOwned != Binding->ServerOwned)) {
+ (ServerOwned != Binding->ServerOwned) ||
+ (Partitioned != Binding->Partitioned) ||
+ (Partitioned && UdpConfig->PartitionIndex != Binding->PartitionIndex)) {
//
// The binding does already exist, but cannot be shared with the
// requested configuration.
@@ -2690,8 +2768,10 @@ MsQuicExecutionDelete(
UNREFERENCED_PARAMETER(Count);
UNREFERENCED_PARAMETER(Executions);
+
CxPlatWorkerPoolDelete(MsQuicLib.WorkerPool);
MsQuicLib.WorkerPool = NULL;
+ MsQuicLib.CustomExecutions = FALSE;
QuicTraceEvent(
ApiExit,
diff --git a/src/core/library.h b/src/core/library.h
index 59eef6d607..dd4c3f71fb 100644
--- a/src/core/library.h
+++ b/src/core/library.h
@@ -197,6 +197,31 @@ typedef struct QUIC_LIBRARY {
//
QUIC_REGISTRATION* StatelessRegistration;
+ //
+ // Protects all registration close completion fields.
+ CXPLAT_LOCK RegistrationCloseCleanupLock;
+
+ //
+ // Event set when the registration worker needs to wake.
+ //
+ CXPLAT_EVENT RegistrationCloseCleanupEvent;
+
+ //
+ // Set to true to shut down the worker thread.
+ //
+ BOOLEAN RegistrationCloseCleanupShutdown;
+
+ //
+ // A dedicated worker thread to clean up async registration close.
+ //
+ CXPLAT_THREAD RegistrationCloseCleanupWorker;
+
+ //
+ // List of registrations needing asynchronous close completion indications.
+ //
+ CXPLAT_LIST_ENTRY RegistrationCloseCleanupList;
+
+
//
// Per-partition storage. Count of `PartitionCount`.
//
@@ -446,6 +471,12 @@ QuicLibraryLazyInitialize(
BOOLEAN AcquireLock
);
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+MsQuicLibraryLazyUninitialize(
+ void
+ );
+
_IRQL_requires_max_(PASSIVE_LEVEL)
QUIC_STATUS
QuicLibrarySetGlobalParam(
diff --git a/src/core/listener.c b/src/core/listener.c
index 7e08d4d449..1f75b62b4d 100644
--- a/src/core/listener.c
+++ b/src/core/listener.c
@@ -20,6 +20,19 @@ QuicListenerStopAsync(
_In_ QUIC_LISTENER* Listener
);
+BOOLEAN
+QuicListenerIsOnWorker(
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ if (Listener->Partitioned) {
+ return QuicWorkerPoolIsInPartition(
+ Listener->Registration->WorkerPool, Listener->PartitionIndex);
+ }
+
+ return TRUE;
+}
+
_IRQL_requires_max_(PASSIVE_LEVEL)
QUIC_STATUS
QUIC_API
@@ -70,6 +83,7 @@ MsQuicListenerOpen(
Listener->Stopped = TRUE;
Listener->DosModeEventsEnabled = FALSE;
CxPlatEventInitialize(&Listener->StopEvent, TRUE, TRUE);
+ CxPlatRefInitialize(&Listener->RefCount);
#ifdef QUIC_SILO
Listener->Silo = QuicSiloGetCurrentServerSilo();
@@ -142,6 +156,7 @@ QuicListenerFree(
CxPlatDispatchLockRelease(&Listener->Registration->ConnectionLock);
CxPlatRefUninitialize(&Listener->RefCount);
+ CxPlatRefUninitialize(&Listener->StartRefCount);
CxPlatEventUninitialize(Listener->StopEvent);
CXPLAT_DBG_ASSERT(Listener->AlpnList == NULL);
CXPLAT_FREE(Listener, QUIC_POOL_LISTENER);
@@ -173,14 +188,11 @@ MsQuicListenerClose(
QUIC_LIB_VERIFY(!Listener->AppClosed);
Listener->AppClosed = TRUE;
- if (Listener->StopCompleteThreadID == CxPlatCurThreadID()) {
- //
- // We're currently in the stop complete event, so we can't free the
- // listener until that callback unwinds.
- //
- Listener->NeedsCleanup = TRUE;
-
- } else {
+ //
+ // If we're currently in the stop complete event, there's no need to
+ // implicitly perform the stop.
+ //
+ if (Listener->StopCompleteThreadID != CxPlatCurThreadID()) {
//
// Make sure the listener has unregistered from the binding, all other
// references have been released, and the stop complete event has been
@@ -188,10 +200,10 @@ MsQuicListenerClose(
//
QuicListenerStopAsync(Listener);
CxPlatEventWaitForever(Listener->StopEvent);
-
- QuicListenerFree(Listener);
}
+ QuicListenerRelease(Listener);
+
QuicTraceEvent(
ApiExit,
"[ api] Exit");
@@ -317,6 +329,10 @@ MsQuicListenerStart(
#ifdef QUIC_OWNING_PROCESS
UdpConfig.OwningProcess = NULL; // Owning process not supported for listeners.
#endif
+ if (Listener->Partitioned) {
+ UdpConfig.Flags |= CXPLAT_SOCKET_FLAG_PARTITIONED;
+ UdpConfig.PartitionIndex = Listener->PartitionIndex;
+ }
// for RAW datapath
UdpConfig.CibirIdLength = Listener->CibirId[0];
@@ -354,7 +370,7 @@ MsQuicListenerStart(
Listener->Stopped = FALSE;
CxPlatEventReset(Listener->StopEvent);
- CxPlatRefInitialize(&Listener->RefCount);
+ CxPlatRefInitialize(&Listener->StartRefCount);
Status = QuicBindingRegisterListener(Listener->Binding, Listener);
if (QUIC_FAILED(Status)) {
@@ -364,7 +380,7 @@ MsQuicListenerStart(
Listener,
Status,
"Register with binding");
- QuicListenerRelease(Listener, FALSE);
+ QuicListenerStartRelease(Listener, FALSE);
goto Error;
}
@@ -416,6 +432,7 @@ QuicListenerIndicateEvent(
{
CXPLAT_PASSIVE_CODE();
CXPLAT_FRE_ASSERT(Listener->ClientCallbackHandler);
+ CXPLAT_DBG_ASSERT(!Listener->Partitioned || QuicListenerIsOnWorker(Listener));
return
Listener->ClientCallbackHandler(
(HQUIC)Listener,
@@ -431,6 +448,7 @@ QuicListenerIndicateDispatchEvent(
)
{
CXPLAT_DBG_ASSERT(Event->Type == QUIC_LISTENER_EVENT_DOS_MODE_CHANGED);
+ CXPLAT_DBG_ASSERT(!Listener->Partitioned || QuicListenerIsOnWorker(Listener));
CXPLAT_FRE_ASSERT(Listener->ClientCallbackHandler);
return
Listener->ClientCallbackHandler(
@@ -441,63 +459,125 @@ QuicListenerIndicateDispatchEvent(
_IRQL_requires_max_(PASSIVE_LEVEL)
void
-QuicListenerStopComplete(
+QuicListenerEndStopComplete(
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ Listener->Stopped = TRUE;
+ CxPlatEventSet(Listener->StopEvent);
+}
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicListenerIndicateStopComplete(
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ QUIC_LISTENER_EVENT Event;
+ Event.Type = QUIC_LISTENER_EVENT_STOP_COMPLETE;
+ Event.STOP_COMPLETE.AppCloseInProgress = Listener->AppClosed;
+
+ //
+ // Take an internal cleanup reference to prevent an inline ListenerClose
+ // freeing the listener from under us.
+ //
+ QuicListenerReference(Listener);
+
+ QuicListenerAttachSilo(Listener);
+
+ QuicTraceLogVerbose(
+ ListenerIndicateStopComplete,
+ "[list][%p] Indicating STOP_COMPLETE",
+ Listener);
+
+ Listener->StopCompleteThreadID = CxPlatCurThreadID();
+ (void)QuicListenerIndicateEvent(Listener, &Event);
+ Listener->StopCompleteThreadID = 0;
+
+ QuicListenerDetachSilo();
+
+ QuicListenerRelease(Listener);
+}
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicListenerBeginStopComplete(
_In_ QUIC_LISTENER* Listener,
_In_ BOOLEAN IndicateEvent
)
{
+ BOOLEAN EndStopComplete = TRUE;
+
QuicTraceEvent(
ListenerStopped,
"[list][%p] Stopped",
Listener);
+ //
+ // Ensure the listener is not freed while processing this function.
+ //
+ QuicListenerReference(Listener);
+
if (Listener->AlpnList != NULL) {
CXPLAT_FREE(Listener->AlpnList, QUIC_POOL_ALPN);
Listener->AlpnList = NULL;
}
if (IndicateEvent) {
- QUIC_LISTENER_EVENT Event;
- Event.Type = QUIC_LISTENER_EVENT_STOP_COMPLETE;
- Event.STOP_COMPLETE.AppCloseInProgress = Listener->AppClosed;
-
- QuicListenerAttachSilo(Listener);
-
- QuicTraceLogVerbose(
- ListenerIndicateStopComplete,
- "[list][%p] Indicating STOP_COMPLETE",
- Listener);
-
- Listener->StopCompleteThreadID = CxPlatCurThreadID();
- (void)QuicListenerIndicateEvent(Listener, &Event);
- Listener->StopCompleteThreadID = 0;
+ if (Listener->Partitioned) {
+ EndStopComplete = FALSE;
+ Listener->NeedsStopCompleteEvent = TRUE;
+ QuicWorkerQueueListener(Listener->Worker, Listener);
+ } else {
+ QuicListenerIndicateStopComplete(Listener);
+ }
+ }
- QuicListenerDetachSilo();
+ if (EndStopComplete) {
+ QuicListenerEndStopComplete(Listener);
}
- const BOOLEAN CleanupOnExit = Listener->NeedsCleanup;
+ QuicListenerRelease(Listener);
+}
- //
- // If !Listener->NeedsCleanup, then another thread is waiting on this event
- // and may immediately free the listener after setting the stop event.
- //
- Listener->Stopped = TRUE;
- CxPlatEventSet(Listener->StopEvent);
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicListenerStartReference(
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ CxPlatRefIncrement(&Listener->StartRefCount);
+}
- if (CleanupOnExit) {
- QuicListenerFree(Listener);
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicListenerStartRelease(
+ _In_ QUIC_LISTENER* Listener,
+ _In_ BOOLEAN IndicateEvent
+ )
+{
+ if (CxPlatRefDecrement(&Listener->StartRefCount)) {
+ QuicListenerBeginStopComplete(Listener, IndicateEvent);
}
}
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicListenerReference(
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ CxPlatRefIncrement(&Listener->RefCount);
+}
+
_IRQL_requires_max_(PASSIVE_LEVEL)
void
QuicListenerRelease(
- _In_ QUIC_LISTENER* Listener,
- _In_ BOOLEAN IndicateEvent
+ _In_ QUIC_LISTENER* Listener
)
{
if (CxPlatRefDecrement(&Listener->RefCount)) {
- QuicListenerStopComplete(Listener, IndicateEvent);
+ QuicListenerFree(Listener);
}
}
@@ -512,7 +592,7 @@ QuicListenerStopAsync(
QuicLibraryReleaseBinding(Listener->Binding);
Listener->Binding = NULL;
- QuicListenerRelease(Listener, TRUE);
+ QuicListenerStartRelease(Listener, TRUE);
}
}
@@ -653,6 +733,17 @@ QuicListenerClaimConnection(
Connection->State.ListenerAccepted = TRUE;
Connection->State.ExternalOwner = TRUE;
+ if (Listener->Partitioned) {
+ Connection->State.Partitioned = TRUE;
+ //
+ // The connection should not have already migrated partitions within a
+ // partitioned listener above a a partitioned binding. The current
+ // thread should also be within the partition by the same virtue, and is
+ // asserted in QuicListenerIndicateEvent.
+ //
+ CXPLAT_DBG_ASSERT(Connection->Partition->Index == Listener->PartitionIndex);
+ }
+
QUIC_LISTENER_EVENT Event;
Event.Type = QUIC_LISTENER_EVENT_NEW_CONNECTION;
Event.NEW_CONNECTION.Info = Info;
@@ -797,12 +888,40 @@ QuicListenerParamSet(
if (BufferLength == sizeof(BOOLEAN)) {
Listener->DosModeEventsEnabled = *(BOOLEAN*)Buffer;
if (MsQuicLib.SendRetryEnabled && Listener->DosModeEventsEnabled) {
- QuicListenerHandleDosModeStateChange(Listener, MsQuicLib.SendRetryEnabled);
+ QuicListenerHandleDosModeStateChange(Listener, MsQuicLib.SendRetryEnabled, FALSE);
}
return QUIC_STATUS_SUCCESS;
}
}
+ if (Param == QUIC_PARAM_LISTENER_PARTITION_INDEX) {
+ uint16_t PartitionIndex;
+ if (BufferLength != sizeof(uint16_t)) {
+ return QUIC_STATUS_INVALID_PARAMETER;
+ }
+ PartitionIndex = *(uint16_t*)Buffer;
+ if (PartitionIndex >= MsQuicLib.PartitionCount ||
+ Listener->Registration->NoPartitioning ||
+ Listener->Partitioned ||
+ !Listener->Stopped) {
+ return QUIC_STATUS_INVALID_PARAMETER;
+ }
+#if defined(__linux__) && !defined(CXPLAT_USE_IO_URING) && !defined(CXPLAT_LINUX_XDP_ENABLED)
+ Listener->PartitionIndex = PartitionIndex;
+ Listener->Partitioned = TRUE;
+ QuicWorkerAssignListener(
+ &Listener->Registration->WorkerPool->Workers[PartitionIndex], Listener);
+ QuicTraceLogVerbose(
+ ListenerPartitionIndexSet,
+ "[list][%p] PartitionIndex set (index %hu)",
+ Listener,
+ Listener->PartitionIndex);
+ return QUIC_STATUS_SUCCESS;
+#else
+ return QUIC_STATUS_NOT_SUPPORTED;
+#endif
+ }
+
return QUIC_STATUS_INVALID_PARAMETER;
}
@@ -905,6 +1024,26 @@ QuicListenerParamGet(
Status = QUIC_STATUS_SUCCESS;
break;
+ case QUIC_PARAM_LISTENER_PARTITION_INDEX:
+
+ if (*BufferLength < sizeof(Listener->PartitionIndex)) {
+ *BufferLength = sizeof(Listener->PartitionIndex);
+ return QUIC_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ if (Buffer == NULL) {
+ return QUIC_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!Listener->Partitioned) {
+ return QUIC_STATUS_INVALID_STATE;
+ }
+
+ *BufferLength = sizeof(Listener->PartitionIndex);
+ *(uint16_t*)Buffer = Listener->PartitionIndex;
+ Status = QUIC_STATUS_SUCCESS;
+ break;
+
default:
Status = QUIC_STATUS_INVALID_PARAMETER;
break;
@@ -918,18 +1057,59 @@ _IRQL_requires_max_(DISPATCH_LEVEL)
void
QuicListenerHandleDosModeStateChange(
_In_ QUIC_LISTENER* Listener,
- _In_ BOOLEAN DosModeEnabled
+ _In_ BOOLEAN DosModeEnabled,
+ _In_ BOOLEAN OnWorker
)
{
if (Listener->DosModeEventsEnabled) {
- QUIC_LISTENER_EVENT Event;
- Event.Type = QUIC_LISTENER_EVENT_DOS_MODE_CHANGED;
- Event.DOS_MODE_CHANGED.DosModeEnabled = DosModeEnabled;
+ if (!Listener->Partitioned || OnWorker) {
+ QUIC_LISTENER_EVENT Event;
+ Event.Type = QUIC_LISTENER_EVENT_DOS_MODE_CHANGED;
+ Event.DOS_MODE_CHANGED.DosModeEnabled = DosModeEnabled;
+
+ QuicListenerAttachSilo(Listener);
+
+ (void)QuicListenerIndicateDispatchEvent(Listener, &Event);
+
+ QuicListenerDetachSilo();
+ } else {
+ //
+ // Best effort mode synchronization: the non-partitioned case is
+ // also racy.
+ //
+ Listener->DosModeEnabled = DosModeEnabled;
+ if (!InterlockedFetchAndSetBoolean(&Listener->NeedsDosModeModeEvent)) {
+ QuicListenerStartReference(Listener);
+ QuicWorkerQueueListener(Listener->Worker, Listener);
+ }
+ }
+ }
+}
- QuicListenerAttachSilo(Listener);
+_IRQL_requires_max_(PASSIVE_LEVEL)
+BOOLEAN
+QuicListenerDrainOperations(
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ CXPLAT_PASSIVE_CODE();
- (void)QuicListenerIndicateDispatchEvent(Listener, &Event);
+ if (Listener->NeedsDosModeModeEvent) {
+ BOOLEAN DosModeEnabled;
+ CXPLAT_FRE_ASSERT(InterlockedFetchAndClearBoolean(&Listener->NeedsDosModeModeEvent));
+ DosModeEnabled = Listener->DosModeEnabled;
+ QuicListenerHandleDosModeStateChange(Listener, DosModeEnabled, TRUE);
+ QuicListenerStartRelease(Listener, TRUE);
+ }
- QuicListenerDetachSilo();
+ if (Listener->NeedsStopCompleteEvent) {
+ Listener->NeedsStopCompleteEvent = FALSE;
+ //
+ // This must be the final event indication.
+ //
+ QuicListenerIndicateStopComplete(Listener);
+ QuicListenerEndStopComplete(Listener);
}
+
+ return FALSE;
}
diff --git a/src/core/listener.h b/src/core/listener.h
index 67d40a8e14..e6f963a882 100644
--- a/src/core/listener.h
+++ b/src/core/listener.h
@@ -32,15 +32,32 @@ typedef struct QUIC_LISTENER {
BOOLEAN Stopped;
//
- // Indicates the listener was closed by the app in the stop complete event.
+ // Indicates the listener needs its stop complete event.
//
- BOOLEAN NeedsCleanup;
+ BOOLEAN NeedsStopCompleteEvent;
//
// Indicates the listener opted in for DoS Mode event.
//
BOOLEAN DosModeEventsEnabled;
+ //
+ // Indicates a DoS Mode event indication is required; cannot share a
+ // bitfield due to atomic update.
+ //
+ BOOLEAN NeedsDosModeModeEvent;
+
+ //
+ // Tracks the most recent DoS Mode event state. Uses best-effort
+ // synchronization.
+ //
+ BOOLEAN DosModeEnabled;
+
+ //
+ // Indicates the listener is constrained to a specific partition.
+ //
+ BOOLEAN Partitioned : 1;
+
//
// The thread ID that the listener is actively indicating a stop compelete
// callback on.
@@ -62,6 +79,23 @@ typedef struct QUIC_LISTENER {
//
CXPLAT_LIST_ENTRY RegistrationLink;
+ //
+ // The listener worker.
+ //
+ QUIC_WORKER* Worker;
+
+ //
+ // Link into the worker.
+ //
+ CXPLAT_LIST_ENTRY WorkerLink;
+
+ //
+ // Indicates whether a worker is currently processing a listener.
+ // N.B. Multi-threaded access, synchronized by worker's listener lock.
+ //
+ BOOLEAN WorkerProcessing : 1;
+ BOOLEAN HasQueuedWork : 1;
+
#ifdef QUIC_SILO
//
// The silo.
@@ -70,7 +104,12 @@ typedef struct QUIC_LISTENER {
#endif
//
- // Active reference count on the listener.
+ // Active reference count on the listener preventing a stop completion.
+ //
+ CXPLAT_REF_COUNT StartRefCount;
+
+ //
+ // Internal reference count on the listener preventing cleanup.
//
CXPLAT_REF_COUNT RefCount;
@@ -114,6 +153,12 @@ typedef struct QUIC_LISTENER {
// the ID in the CID and the rest payload of the identifier.
//
uint8_t CibirId[2 + QUIC_MAX_CIBIR_LENGTH];
+
+ //
+ // An optional app-configured partition index for the listener and its
+ // connections.
+ //
+ uint16_t PartitionIndex;
} QUIC_LISTENER;
#ifdef QUIC_SILO
@@ -143,16 +188,34 @@ QuicListenerTraceRundown(
_In_ QUIC_LISTENER* Listener
);
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicListenerStartReference(
+ _In_ QUIC_LISTENER* Listener
+ );
+
//
// Releases an active reference on the listener.
//
_IRQL_requires_max_(PASSIVE_LEVEL)
void
-QuicListenerRelease(
+QuicListenerStartRelease(
_In_ QUIC_LISTENER* Listener,
_In_ BOOLEAN IndicateEvent
);
+ _IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicListenerReference(
+ _In_ QUIC_LISTENER* Listener
+ );
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicListenerRelease(
+ _In_ QUIC_LISTENER* Listener
+ );
+
//
// Returns TRUE if the two listeners have an overlapping ALPN.
//
@@ -218,5 +281,17 @@ _IRQL_requires_max_(DISPATCH_LEVEL)
void
QuicListenerHandleDosModeStateChange(
_In_ QUIC_LISTENER* Listener,
- _In_ BOOLEAN DosModeEnabled
+ _In_ BOOLEAN DosModeEnabled,
+ _In_ BOOLEAN OnWorker
+ );
+
+//
+// Allows the listener to drain some operations that it currently has
+// queued up. Returns TRUE if there are still work to do after the function
+// returns.
+//
+_IRQL_requires_max_(PASSIVE_LEVEL)
+BOOLEAN
+QuicListenerDrainOperations(
+ _In_ QUIC_LISTENER* Listener
);
diff --git a/src/core/registration.c b/src/core/registration.c
index b48fcd5351..2036e5c126 100644
--- a/src/core/registration.c
+++ b/src/core/registration.c
@@ -24,6 +24,89 @@
#include "registration.c.clog.h"
#endif
+void
+QuicRegistrationClose(
+ _Inout_ QUIC_REGISTRATION* Registration
+ )
+{
+ QuicTraceEvent(
+ RegistrationCleanup,
+ "[ reg][%p] Cleaning up",
+ Registration);
+
+ if (Registration->CloseCompleteHandler == NULL) {
+ CxPlatEventSet(Registration->CloseEvent);
+ CxPlatThreadWait(&Registration->CloseThread);
+ CxPlatThreadDelete(&Registration->CloseThread);
+ }
+
+ if (Registration->ExecProfile != QUIC_EXECUTION_PROFILE_TYPE_INTERNAL) {
+ CxPlatLockAcquire(&MsQuicLib.Lock);
+ CxPlatListEntryRemove(&Registration->Link);
+ CxPlatLockRelease(&MsQuicLib.Lock);
+ }
+
+ CxPlatRundownReleaseAndWait(&Registration->Rundown);
+
+ QuicWorkerPoolUninitialize(Registration->WorkerPool);
+ CxPlatRundownUninitialize(&Registration->Rundown);
+ CxPlatDispatchLockUninitialize(&Registration->ConnectionLock);
+ CxPlatLockUninitialize(&Registration->ConfigLock);
+ CxPlatEventUninitialize(Registration->CloseEvent);
+
+ if (Registration->ExecProfile != QUIC_EXECUTION_PROFILE_TYPE_INTERNAL) {
+ CxPlatLockAcquire(&MsQuicLib.Lock);
+ if (MsQuicLib.CustomExecutions && CxPlatListIsEmpty(&MsQuicLib.Registrations)) {
+ //
+ // To allow all references to the execution context to be released,
+ // clean up implicitly allocated internal resources when the final
+ // registration is closed.
+ //
+ // Alternatively, MsQuic could expose an explicit ExecutionShutdown
+ // API that completes asynchronously and requires the exeuction
+ // contexts to continue executing for the duration. This is slightly
+ // awkward to impose upon apps, though, because these resources were
+ // implicitly created.
+ //
+ MsQuicLibraryLazyUninitialize();
+
+ }
+ CxPlatLockRelease(&MsQuicLib.Lock);
+ }
+}
+
+CXPLAT_THREAD_CALLBACK(RegistrationCloseWorker, Context)
+{
+ QUIC_REGISTRATION* Registration = Context;
+
+ CxPlatEventWaitForever(Registration->CloseEvent);
+
+ if (Registration->CloseCompleteHandler != NULL) {
+ BOOLEAN WakeCleanupWorker;
+
+ QuicRegistrationClose(Registration);
+
+ Registration->CloseCompleteHandler(Registration->CloseCompleteContext);
+
+ //
+ // Hand the registration off to the global cleanup worker so MsQuic can
+ // wait for this thread to exit before allowing the library to
+ // uninitialize and possibly unload code.
+ //
+ CxPlatLockAcquire(&MsQuicLib.RegistrationCloseCleanupLock);
+ WakeCleanupWorker = CxPlatListIsEmpty(&MsQuicLib.RegistrationCloseCleanupList);
+ CxPlatListInsertTail(
+ &MsQuicLib.RegistrationCloseCleanupList, &Registration->CloseCleanupEntry);
+ CxPlatLockRelease(&MsQuicLib.RegistrationCloseCleanupLock);
+
+ if (WakeCleanupWorker) {
+ CxPlatEventSet(MsQuicLib.RegistrationCloseCleanupEvent);
+ }
+ }
+
+ CXPLAT_THREAD_RETURN(QUIC_STATUS_SUCCESS);
+}
+
_IRQL_requires_max_(PASSIVE_LEVEL)
QUIC_STATUS
QUIC_API
@@ -84,6 +167,7 @@ MsQuicRegistrationOpen(
CxPlatListInitializeHead(&Registration->Connections);
CxPlatListInitializeHead(&Registration->Listeners);
CxPlatRundownInitialize(&Registration->Rundown);
+ CxPlatEventInitialize(&Registration->CloseEvent, TRUE, FALSE);
Registration->AppNameLength = (uint8_t)(AppNameLength + 1);
if (AppNameLength != 0) {
CxPlatCopyMemory(Registration->AppName, Config->AppName, AppNameLength + 1);
@@ -96,6 +180,25 @@ MsQuicRegistrationOpen(
goto Error;
}
+ CXPLAT_THREAD_CONFIG ThreadConfig = {
+ 0,
+ 0,
+ "RegistrationCloseWorker",
+ RegistrationCloseWorker,
+ Registration,
+ };
+
+ //
+ // Create a dedicated thread to implement asynchronous registration close,
+ // which may block for an arbitrary amount of time. Ideally the registration
+ // close operation can be refactored to avoid blocking waits, i.e., be made
+ // intrinsically asynchronous.
+ //
+ Status = CxPlatThreadCreate(&ThreadConfig, &Registration->CloseThread);
+ if (QUIC_FAILED(Status)) {
+ goto Error;
+ }
+
QuicTraceEvent(
RegistrationCreatedV2,
"[ reg][%p] Created, AppName=%s, ExecProfile=%u",
@@ -129,6 +232,8 @@ MsQuicRegistrationOpen(
Error:
if (Registration != NULL) {
+ CXPLAT_DBG_ASSERT(!Registration->CloseThread);
+ CxPlatEventUninitialize(Registration->CloseEvent);
CxPlatRundownUninitialize(&Registration->Rundown);
CxPlatDispatchLockUninitialize(&Registration->ConnectionLock);
CxPlatLockUninitialize(&Registration->ConfigLock);
@@ -161,25 +266,38 @@ MsQuicRegistrationClose(
#pragma prefast(suppress: __WARNING_25024, "Pointer cast already validated.")
QUIC_REGISTRATION* Registration = (QUIC_REGISTRATION*)Handle;
- QuicTraceEvent(
- RegistrationCleanup,
- "[ reg][%p] Cleaning up",
- Registration);
+ QuicRegistrationClose(Registration);
+ CXPLAT_FREE(Registration, QUIC_POOL_REGISTRATION);
- if (Registration->ExecProfile != QUIC_EXECUTION_PROFILE_TYPE_INTERNAL) {
- CxPlatLockAcquire(&MsQuicLib.Lock);
- CxPlatListEntryRemove(&Registration->Link);
- CxPlatLockRelease(&MsQuicLib.Lock);
- }
+ QuicTraceEvent(
+ ApiExit,
+ "[ api] Exit");
+ }
+}
- CxPlatRundownReleaseAndWait(&Registration->Rundown);
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QUIC_API
+MsQuicRegistrationClose2(
+ _In_ _Pre_defensive_ __drv_freesMem(Mem)
+ HQUIC Handle,
+ _In_ _Pre_defensive_ QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context
+ )
+{
+ if (Handle != NULL && Handle->Type == QUIC_HANDLE_TYPE_REGISTRATION) {
+ QuicTraceEvent(
+ ApiEnter,
+ "[ api] Enter %u (%p).",
+ QUIC_TRACE_API_REGISTRATION_CLOSE2,
+ Handle);
- QuicWorkerPoolUninitialize(Registration->WorkerPool);
- CxPlatRundownUninitialize(&Registration->Rundown);
- CxPlatDispatchLockUninitialize(&Registration->ConnectionLock);
- CxPlatLockUninitialize(&Registration->ConfigLock);
+#pragma prefast(suppress: __WARNING_25024, "Pointer cast already validated.")
+ QUIC_REGISTRATION* Registration = (QUIC_REGISTRATION*)Handle;
- CXPLAT_FREE(Registration, QUIC_POOL_REGISTRATION);
+ Registration->CloseCompleteHandler = Handler;
+ Registration->CloseCompleteContext = Context;
+ CxPlatEventSet(Registration->CloseEvent);
QuicTraceEvent(
ApiExit,
diff --git a/src/core/registration.h b/src/core/registration.h
index 5e68448af9..7ec803817a 100644
--- a/src/core/registration.h
+++ b/src/core/registration.h
@@ -105,6 +105,31 @@ typedef struct QUIC_REGISTRATION {
//
uint64_t ShutdownErrorCode;
+ //
+ // Close request event, to support async close.
+ //
+ CXPLAT_EVENT CloseEvent;
+
+ //
+ // Close thread, to support async close.
+ //
+ CXPLAT_THREAD CloseThread;
+
+ //
+ // Entry in the registration close cleanup list, to support async close.
+ //
+ CXPLAT_LIST_ENTRY CloseCleanupEntry;
+
+ //
+ // The app's close complete handler, if async close is used.
+ //
+ QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER CloseCompleteHandler;
+
+ //
+ // The app's close complete handler context, if async close is used.
+ //
+ void* CloseCompleteContext;
+
//
// Name of the application layer.
//
diff --git a/src/core/worker.c b/src/core/worker.c
index 6cf03175eb..fc5a056cbc 100644
--- a/src/core/worker.c
+++ b/src/core/worker.c
@@ -83,6 +83,7 @@ QuicWorkerInitialize(
CxPlatEventInitialize(&Worker->Ready, FALSE, FALSE);
CxPlatListInitializeHead(&Worker->Connections);
Worker->PriorityConnectionsTail = &Worker->Connections.Flink;
+ CxPlatListInitializeHead(&Worker->Listeners);
CxPlatListInitializeHead(&Worker->Operations);
QUIC_STATUS Status = QuicTimerWheelInitialize(&Worker->TimerWheel);
@@ -175,12 +176,8 @@ QuicWorkerUninitialize(
//
Worker->Enabled = FALSE;
if (Worker->ExecutionContext.Context) {
- if (MsQuicLib.CustomExecutions) {
- QuicWorkerLoopCleanup(Worker);
- } else {
- QuicWorkerThreadWake(Worker);
- CxPlatEventWaitForever(Worker->Done);
- }
+ QuicWorkerThreadWake(Worker);
+ CxPlatEventWaitForever(Worker->Done);
}
CxPlatEventUninitialize(Worker->Done);
@@ -198,6 +195,7 @@ QuicWorkerUninitialize(
CXPLAT_TEL_ASSERT(CxPlatListIsEmpty(&Worker->Connections));
Worker->PriorityConnectionsTail = NULL;
CXPLAT_TEL_ASSERT(CxPlatListIsEmpty(&Worker->Operations));
+ CXPLAT_TEL_ASSERT(CxPlatListIsEmpty(&Worker->Listeners));
CxPlatDispatchLockUninitialize(&Worker->Lock);
QuicTimerWheelUninitialize(&Worker->TimerWheel);
@@ -225,6 +223,22 @@ QuicWorkerAssignConnection(
Worker);
}
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicWorkerAssignListener(
+ _In_ QUIC_WORKER* Worker,
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ CXPLAT_DBG_ASSERT(Listener->Worker != Worker);
+ Listener->Worker = Worker;
+ QuicTraceEvent(
+ ConnAssignWorker,
+ "[list][%p] Assigned worker: %p",
+ Listener,
+ Worker);
+}
+
BOOLEAN
QuicWorkerIsIdle(
_In_ const QUIC_WORKER* Worker
@@ -232,6 +246,7 @@ QuicWorkerIsIdle(
{
return
CxPlatListIsEmpty(&Worker->Connections) &&
+ CxPlatListIsEmpty(&Worker->Listeners) &&
CxPlatListIsEmpty(&Worker->Operations);
}
@@ -353,6 +368,43 @@ QuicWorkerMoveConnection(
}
}
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicWorkerQueueListener(
+ _In_ QUIC_WORKER* Worker,
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ CXPLAT_DBG_ASSERT(Listener->Worker != NULL);
+ BOOLEAN ListenerQueued = FALSE;
+ BOOLEAN WakeWorkerThread = FALSE;
+
+ CxPlatDispatchLockAcquire(&Worker->Lock);
+
+ if (!Listener->WorkerProcessing && !Listener->HasQueuedWork) {
+ WakeWorkerThread = QuicWorkerIsIdle(Worker);
+ QuicTraceEvent(
+ ConnScheduleState,
+ "[list][%p] Scheduling: %u",
+ Listener,
+ QUIC_SCHEDULE_QUEUED);
+ QuicListenerReference(Listener);
+ CxPlatListInsertTail(&Worker->Listeners, &Listener->WorkerLink);
+ ListenerQueued = TRUE;
+ }
+
+ Listener->HasQueuedWork = TRUE;
+
+ CxPlatDispatchLockRelease(&Worker->Lock);
+
+ if (ListenerQueued) {
+ if (WakeWorkerThread) {
+ QuicWorkerThreadWake(Worker);
+ }
+ QuicPerfCounterIncrement(Worker->Partition, QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH);
+ }
+}
+
_IRQL_requires_max_(DISPATCH_LEVEL)
void
QuicWorkerQueueOperation(
@@ -449,6 +501,33 @@ QuicWorkerGetNextConnection(
return Connection;
}
+_IRQL_requires_max_(PASSIVE_LEVEL)
+QUIC_LISTENER*
+QuicWorkerGetNextListener(
+ _In_ QUIC_WORKER* Worker
+ )
+{
+ QUIC_LISTENER* Listener = NULL;
+
+ if (Worker->Enabled &&
+ !CxPlatListIsEmptyNoFence(&Worker->Listeners)) {
+ CxPlatDispatchLockAcquire(&Worker->Lock);
+ if (!CxPlatListIsEmpty(&Worker->Listeners)) {
+ Listener =
+ CXPLAT_CONTAINING_RECORD(
+ CxPlatListRemoveHead(&Worker->Listeners), QUIC_LISTENER, WorkerLink);
+ CXPLAT_DBG_ASSERT(!Listener->WorkerProcessing);
+ CXPLAT_DBG_ASSERT(Listener->HasQueuedWork);
+ Listener->HasQueuedWork = FALSE;
+ Listener->WorkerProcessing = TRUE;
+ QuicPerfCounterDecrement(Worker->Partition, QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH);
+ }
+ CxPlatDispatchLockRelease(&Worker->Lock);
+ }
+
+ return Listener;
+}
+
_IRQL_requires_max_(PASSIVE_LEVEL)
QUIC_OPERATION*
QuicWorkerGetNextOperation(
@@ -638,6 +717,58 @@ QuicWorkerProcessConnection(
}
}
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicWorkerProcessListener(
+ _In_ QUIC_WORKER* Worker,
+ _In_ QUIC_LISTENER* Listener
+ )
+{
+ QuicTraceEvent(
+ ConnScheduleState,
+ "[list][%p] Scheduling: %u",
+ Listener,
+ QUIC_SCHEDULE_PROCESSING);
+
+ //
+ // Process some operations.
+ //
+ BOOLEAN StillHasWorkToDo = QuicListenerDrainOperations(Listener);
+
+ //
+ // Determine whether the listener needs to be requeued.
+ //
+ CxPlatDispatchLockAcquire(&Worker->Lock);
+ Listener->WorkerProcessing = FALSE;
+ Listener->HasQueuedWork |= StillHasWorkToDo;
+
+ BOOLEAN DoneWithListener = TRUE;
+ if (Listener->HasQueuedWork) {
+ CxPlatListInsertTail(&Worker->Listeners, &Listener->WorkerLink);
+ QuicTraceEvent(
+ ConnScheduleState,
+ "[list][%p] Scheduling: %u",
+ Listener,
+ QUIC_SCHEDULE_QUEUED);
+ DoneWithListener = FALSE;
+ } else {
+ QuicTraceEvent(
+ ConnScheduleState,
+ "[list][%p] Scheduling: %u",
+ Listener,
+ QUIC_SCHEDULE_IDLE);
+ }
+ CxPlatDispatchLockRelease(&Worker->Lock);
+
+ if (DoneWithListener) {
+ //
+ // This worker is no longer managing the listener, so we can
+ // release its reference.
+ //
+ QuicListenerRelease(Listener);
+ }
+}
+
_IRQL_requires_max_(PASSIVE_LEVEL)
void
QuicWorkerLoopCleanup(
@@ -675,6 +806,16 @@ QuicWorkerLoopCleanup(
}
QuicPerfCounterAdd(Worker->Partition, QUIC_PERF_COUNTER_CONN_QUEUE_DEPTH, Dequeue);
+ Dequeue = 0;
+ while (!CxPlatListIsEmpty(&Worker->Listeners)) {
+ QUIC_LISTENER* Listener =
+ CXPLAT_CONTAINING_RECORD(
+ CxPlatListRemoveHead(&Worker->Listeners), QUIC_LISTENER, WorkerLink);
+ QuicListenerRelease(Listener);
+ --Dequeue;
+ }
+ QuicPerfCounterAdd(Worker->Partition, QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH, Dequeue);
+
Dequeue = 0;
while (!CxPlatListIsEmpty(&Worker->Operations)) {
QUIC_OPERATION* Operation =
@@ -742,6 +883,13 @@ QuicWorkerLoop(
State->NoWorkCount = 0;
}
+ QUIC_LISTENER* Listener = QuicWorkerGetNextListener(Worker);
+ if (Listener != NULL) {
+ QuicWorkerProcessListener(Worker, Listener);
+ Worker->ExecutionContext.Ready = TRUE;
+ State->NoWorkCount = 0;
+ }
+
QUIC_OPERATION* Operation = QuicWorkerGetNextOperation(Worker);
if (Operation != NULL) {
QuicBindingProcessStatelessOperation(
@@ -947,4 +1095,29 @@ QuicWorkerPoolGetLeastLoadedWorker(
WorkerPool->LastWorker = MinQueueDelayWorker;
return MinQueueDelayWorker;
-}
\ No newline at end of file
+}
+
+BOOLEAN
+QuicWorkerPoolIsInPartition(
+ _In_ QUIC_WORKER_POOL* WorkerPool,
+ _In_ uint16_t PartitionIndex
+ )
+{
+ QUIC_WORKER* Worker = &WorkerPool->Workers[PartitionIndex];
+ CXPLAT_DBG_ASSERT(PartitionIndex < WorkerPool->WorkerCount);
+
+ if (Worker->IsExternal) {
+ return CxPlatWorkerIsThisThread(&Worker->ExecutionContext);
+ }
+
+ //
+ // For "internal" workers that spawn their own threads, we lack a
+ // function to resolve the effective worker of the current thread.
+ // Since this function is only used for assertions, simply ignore this
+ // case for now rather than maintaining unambiguous state.
+ //
+#ifndef DEBUG
+ CXPLAT_FRE_ASSERTMSG(FALSE, "QuicWorkerPoolIsInPartition may return false positives.");
+#endif
+ return TRUE;
+}
diff --git a/src/core/worker.h b/src/core/worker.h
index 4e8e4f39b4..69203b5285 100644
--- a/src/core/worker.h
+++ b/src/core/worker.h
@@ -37,7 +37,7 @@ typedef struct QUIC_CACHEALIGN QUIC_WORKER {
BOOLEAN Enabled;
//
- // TRUE if the worker is currently processing connections.
+ // TRUE if the worker is currently processing work.
//
BOOLEAN IsActive;
@@ -57,12 +57,12 @@ typedef struct QUIC_CACHEALIGN QUIC_WORKER {
CXPLAT_EVENT Ready;
//
- // A thread for draining operations from queued connections.
+ // A thread for draining operations.
//
CXPLAT_THREAD Thread;
//
- // Serializes access to the connection and operation lists.
+ // Serializes access to the connection, listener, and operation lists.
//
CXPLAT_DISPATCH_LOCK Lock;
@@ -72,6 +72,11 @@ typedef struct QUIC_CACHEALIGN QUIC_WORKER {
CXPLAT_LIST_ENTRY Connections;
CXPLAT_LIST_ENTRY** PriorityConnectionsTail;
+ //
+ // Queue of listeners with operations to be processed.
+ //
+ CXPLAT_LIST_ENTRY Listeners;
+
//
// Queue of stateless operations to be processed.
//
@@ -188,6 +193,20 @@ QuicWorkerQueuePriorityConnection(
_In_ QUIC_CONNECTION* Connection
);
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicWorkerAssignListener(
+ _In_ QUIC_WORKER* Worker,
+ _In_ QUIC_LISTENER* Listener
+ );
+
+_IRQL_requires_max_(DISPATCH_LEVEL)
+void
+QuicWorkerQueueListener(
+ _In_ QUIC_WORKER* Worker,
+ _In_ QUIC_LISTENER* Listener
+ );
+
//
// Queues the operation onto the worker, and kicks the worker thread if
// necessary.
@@ -198,3 +217,9 @@ QuicWorkerQueueOperation(
_In_ QUIC_WORKER* Worker,
_In_ QUIC_OPERATION* Operation
);
+
+BOOLEAN
+QuicWorkerPoolIsInPartition(
+ _In_ QUIC_WORKER_POOL* WorkerPool,
+ _In_ uint16_t PartitionIndex
+ );
diff --git a/src/cs/lib/msquic_generated.cs b/src/cs/lib/msquic_generated.cs
index b268a4af91..73f7059ff6 100644
--- a/src/cs/lib/msquic_generated.cs
+++ b/src/cs/lib/msquic_generated.cs
@@ -993,6 +993,7 @@ internal enum QUIC_PERFORMANCE_COUNTERS
SEND_STATELESS_RESET,
SEND_STATELESS_RETRY,
CONN_LOAD_REJECT,
+ LISTEN_QUEUE_DEPTH,
MAX,
}
@@ -3555,6 +3556,9 @@ internal unsafe partial struct QUIC_API_TABLE
[NativeTypeName("QUIC_EXECUTION_POLL_FN")]
internal delegate* unmanaged[Cdecl] ExecutionPoll;
+
+ [NativeTypeName("QUIC_REGISTRATION_CLOSE2_FN")]
+ internal delegate* unmanaged[Cdecl], void*, void> RegistrationClose2;
}
internal static unsafe partial class MsQuic
@@ -3677,6 +3681,9 @@ internal static unsafe partial class MsQuic
[NativeTypeName("#define QUIC_PARAM_LISTENER_CIBIR_ID 0x04000002")]
internal const uint QUIC_PARAM_LISTENER_CIBIR_ID = 0x04000002;
+ [NativeTypeName("#define QUIC_PARAM_LISTENER_PARTITION_INDEX 0x04000005")]
+ internal const uint QUIC_PARAM_LISTENER_PARTITION_INDEX = 0x04000005;
+
[NativeTypeName("#define QUIC_PARAM_DOS_MODE_EVENTS 0x04000004")]
internal const uint QUIC_PARAM_DOS_MODE_EVENTS = 0x04000004;
@@ -3761,6 +3768,9 @@ internal static unsafe partial class MsQuic
[NativeTypeName("#define QUIC_PARAM_CONN_NETWORK_STATISTICS 0x05000020")]
internal const uint QUIC_PARAM_CONN_NETWORK_STATISTICS = 0x05000020;
+ [NativeTypeName("#define QUIC_PARAM_CONN_CLOSE_ASYNC 0x0500001A")]
+ internal const uint QUIC_PARAM_CONN_CLOSE_ASYNC = 0x0500001A;
+
[NativeTypeName("#define QUIC_PARAM_TLS_HANDSHAKE_INFO 0x06000000")]
internal const uint QUIC_PARAM_TLS_HANDSHAKE_INFO = 0x06000000;
diff --git a/src/generated/linux/api.c.clog.h b/src/generated/linux/api.c.clog.h
index 2ad2211191..b8fbb2f626 100644
--- a/src/generated/linux/api.c.clog.h
+++ b/src/generated/linux/api.c.clog.h
@@ -63,8 +63,8 @@ tracepoint(CLOG_API_C, ApiExitStatus , arg2);\
// Decoder Ring for ApiWaitOperation
// [ api] Waiting on operation
// QuicTraceEvent(
- ApiWaitOperation,
- "[ api] Waiting on operation");
+ ApiWaitOperation,
+ "[ api] Waiting on operation");
----------------------------------------------------------*/
#ifndef _clog_2_ARGS_TRACE_ApiWaitOperation
#define _clog_2_ARGS_TRACE_ApiWaitOperation(uniqueId, encoded_arg_string)\
diff --git a/src/generated/linux/api.c.clog.h.lttng.h b/src/generated/linux/api.c.clog.h.lttng.h
index 3ad21b6e3d..7215d2d7c1 100644
--- a/src/generated/linux/api.c.clog.h.lttng.h
+++ b/src/generated/linux/api.c.clog.h.lttng.h
@@ -47,8 +47,8 @@ TRACEPOINT_EVENT(CLOG_API_C, ApiExitStatus,
// Decoder Ring for ApiWaitOperation
// [ api] Waiting on operation
// QuicTraceEvent(
- ApiWaitOperation,
- "[ api] Waiting on operation");
+ ApiWaitOperation,
+ "[ api] Waiting on operation");
----------------------------------------------------------*/
TRACEPOINT_EVENT(CLOG_API_C, ApiWaitOperation,
TP_ARGS(
diff --git a/src/generated/linux/connection.c.clog.h b/src/generated/linux/connection.c.clog.h
index 263a0932ec..2e20d57b45 100644
--- a/src/generated/linux/connection.c.clog.h
+++ b/src/generated/linux/connection.c.clog.h
@@ -1474,6 +1474,26 @@ tracepoint(CLOG_CONNECTION_C, Disable1RttEncrytionUpdated , arg1, arg3);\
+/*----------------------------------------------------------
+// Decoder Ring for CloseAsyncUpdated
+// [conn][%p] Updated CloseAsync to %hhu
+// QuicTraceLogConnVerbose(
+ CloseAsyncUpdated,
+ Connection,
+ "Updated CloseAsync to %hhu",
+ Connection->State.CloseAsync);
+// arg1 = arg1 = Connection = arg1
+// arg3 = arg3 = Connection->State.CloseAsync = arg3
+----------------------------------------------------------*/
+#ifndef _clog_4_ARGS_TRACE_CloseAsyncUpdated
+#define _clog_4_ARGS_TRACE_CloseAsyncUpdated(uniqueId, arg1, encoded_arg_string, arg3)\
+tracepoint(CLOG_CONNECTION_C, CloseAsyncUpdated , arg1, arg3);\
+
+#endif
+
+
+
+
/*----------------------------------------------------------
// Decoder Ring for ForceKeyUpdate
// [conn][%p] Forcing key update
diff --git a/src/generated/linux/connection.c.clog.h.lttng.h b/src/generated/linux/connection.c.clog.h.lttng.h
index 311b5633e2..7b1c6afd56 100644
--- a/src/generated/linux/connection.c.clog.h.lttng.h
+++ b/src/generated/linux/connection.c.clog.h.lttng.h
@@ -1630,6 +1630,29 @@ TRACEPOINT_EVENT(CLOG_CONNECTION_C, Disable1RttEncrytionUpdated,
+/*----------------------------------------------------------
+// Decoder Ring for CloseAsyncUpdated
+// [conn][%p] Updated CloseAsync to %hhu
+// QuicTraceLogConnVerbose(
+ CloseAsyncUpdated,
+ Connection,
+ "Updated CloseAsync to %hhu",
+ Connection->State.CloseAsync);
+// arg1 = arg1 = Connection = arg1
+// arg3 = arg3 = Connection->State.CloseAsync = arg3
+----------------------------------------------------------*/
+TRACEPOINT_EVENT(CLOG_CONNECTION_C, CloseAsyncUpdated,
+ TP_ARGS(
+ const void *, arg1,
+ unsigned char, arg3),
+ TP_FIELDS(
+ ctf_integer_hex(uint64_t, arg1, (uint64_t)arg1)
+ ctf_integer(unsigned char, arg3, arg3)
+ )
+)
+
+
+
/*----------------------------------------------------------
// Decoder Ring for ForceKeyUpdate
// [conn][%p] Forcing key update
diff --git a/src/generated/linux/listener.c.clog.h b/src/generated/linux/listener.c.clog.h
index 5d041a8728..91a9bfc714 100644
--- a/src/generated/linux/listener.c.clog.h
+++ b/src/generated/linux/listener.c.clog.h
@@ -33,9 +33,9 @@ extern "C" {
// Decoder Ring for ListenerIndicateStopComplete
// [list][%p] Indicating STOP_COMPLETE
// QuicTraceLogVerbose(
- ListenerIndicateStopComplete,
- "[list][%p] Indicating STOP_COMPLETE",
- Listener);
+ ListenerIndicateStopComplete,
+ "[list][%p] Indicating STOP_COMPLETE",
+ Listener);
// arg2 = arg2 = Listener = arg2
----------------------------------------------------------*/
#ifndef _clog_3_ARGS_TRACE_ListenerIndicateStopComplete
@@ -89,6 +89,26 @@ tracepoint(CLOG_LISTENER_C, ListenerCibirIdSet , arg2, arg3, arg4);\
+/*----------------------------------------------------------
+// Decoder Ring for ListenerPartitionIndexSet
+// [list][%p] PartitionIndex set (index %hu)
+// QuicTraceLogVerbose(
+ ListenerPartitionIndexSet,
+ "[list][%p] PartitionIndex set (index %hu)",
+ Listener,
+ Listener->PartitionIndex);
+// arg2 = arg2 = Listener = arg2
+// arg3 = arg3 = Listener->PartitionIndex = arg3
+----------------------------------------------------------*/
+#ifndef _clog_4_ARGS_TRACE_ListenerPartitionIndexSet
+#define _clog_4_ARGS_TRACE_ListenerPartitionIndexSet(uniqueId, encoded_arg_string, arg2, arg3)\
+tracepoint(CLOG_LISTENER_C, ListenerPartitionIndexSet , arg2, arg3);\
+
+#endif
+
+
+
+
/*----------------------------------------------------------
// Decoder Ring for CibirIdSet
// [conn][%p] CIBIR ID set (len %hhu, offset %hhu)
diff --git a/src/generated/linux/listener.c.clog.h.lttng.h b/src/generated/linux/listener.c.clog.h.lttng.h
index ce7e4b605e..619128e7f2 100644
--- a/src/generated/linux/listener.c.clog.h.lttng.h
+++ b/src/generated/linux/listener.c.clog.h.lttng.h
@@ -5,9 +5,9 @@
// Decoder Ring for ListenerIndicateStopComplete
// [list][%p] Indicating STOP_COMPLETE
// QuicTraceLogVerbose(
- ListenerIndicateStopComplete,
- "[list][%p] Indicating STOP_COMPLETE",
- Listener);
+ ListenerIndicateStopComplete,
+ "[list][%p] Indicating STOP_COMPLETE",
+ Listener);
// arg2 = arg2 = Listener = arg2
----------------------------------------------------------*/
TRACEPOINT_EVENT(CLOG_LISTENER_C, ListenerIndicateStopComplete,
@@ -70,6 +70,29 @@ TRACEPOINT_EVENT(CLOG_LISTENER_C, ListenerCibirIdSet,
+/*----------------------------------------------------------
+// Decoder Ring for ListenerPartitionIndexSet
+// [list][%p] PartitionIndex set (index %hu)
+// QuicTraceLogVerbose(
+ ListenerPartitionIndexSet,
+ "[list][%p] PartitionIndex set (index %hu)",
+ Listener,
+ Listener->PartitionIndex);
+// arg2 = arg2 = Listener = arg2
+// arg3 = arg3 = Listener->PartitionIndex = arg3
+----------------------------------------------------------*/
+TRACEPOINT_EVENT(CLOG_LISTENER_C, ListenerPartitionIndexSet,
+ TP_ARGS(
+ const void *, arg2,
+ unsigned short, arg3),
+ TP_FIELDS(
+ ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2)
+ ctf_integer(unsigned short, arg3, arg3)
+ )
+)
+
+
+
/*----------------------------------------------------------
// Decoder Ring for CibirIdSet
// [conn][%p] CIBIR ID set (len %hhu, offset %hhu)
diff --git a/src/generated/linux/registration.c.clog.h b/src/generated/linux/registration.c.clog.h
index 9278a26a05..850052870c 100644
--- a/src/generated/linux/registration.c.clog.h
+++ b/src/generated/linux/registration.c.clog.h
@@ -43,6 +43,24 @@ tracepoint(CLOG_REGISTRATION_C, RegistrationVerifierEnabled , arg2);\
+/*----------------------------------------------------------
+// Decoder Ring for RegistrationCleanup
+// [ reg][%p] Cleaning up
+// QuicTraceEvent(
+ RegistrationCleanup,
+ "[ reg][%p] Cleaning up",
+ Registration);
+// arg2 = arg2 = Registration = arg2
+----------------------------------------------------------*/
+#ifndef _clog_3_ARGS_TRACE_RegistrationCleanup
+#define _clog_3_ARGS_TRACE_RegistrationCleanup(uniqueId, encoded_arg_string, arg2)\
+tracepoint(CLOG_REGISTRATION_C, RegistrationCleanup , arg2);\
+
+#endif
+
+
+
+
/*----------------------------------------------------------
// Decoder Ring for ApiEnter
// [ api] Enter %u (%p).
@@ -123,24 +141,6 @@ tracepoint(CLOG_REGISTRATION_C, ApiExitStatus , arg2);\
-/*----------------------------------------------------------
-// Decoder Ring for RegistrationCleanup
-// [ reg][%p] Cleaning up
-// QuicTraceEvent(
- RegistrationCleanup,
- "[ reg][%p] Cleaning up",
- Registration);
-// arg2 = arg2 = Registration = arg2
-----------------------------------------------------------*/
-#ifndef _clog_3_ARGS_TRACE_RegistrationCleanup
-#define _clog_3_ARGS_TRACE_RegistrationCleanup(uniqueId, encoded_arg_string, arg2)\
-tracepoint(CLOG_REGISTRATION_C, RegistrationCleanup , arg2);\
-
-#endif
-
-
-
-
/*----------------------------------------------------------
// Decoder Ring for ApiExit
// [ api] Exit
diff --git a/src/generated/linux/registration.c.clog.h.lttng.h b/src/generated/linux/registration.c.clog.h.lttng.h
index 34713d4c36..ae60fc8f5b 100644
--- a/src/generated/linux/registration.c.clog.h.lttng.h
+++ b/src/generated/linux/registration.c.clog.h.lttng.h
@@ -20,6 +20,25 @@ TRACEPOINT_EVENT(CLOG_REGISTRATION_C, RegistrationVerifierEnabled,
+/*----------------------------------------------------------
+// Decoder Ring for RegistrationCleanup
+// [ reg][%p] Cleaning up
+// QuicTraceEvent(
+ RegistrationCleanup,
+ "[ reg][%p] Cleaning up",
+ Registration);
+// arg2 = arg2 = Registration = arg2
+----------------------------------------------------------*/
+TRACEPOINT_EVENT(CLOG_REGISTRATION_C, RegistrationCleanup,
+ TP_ARGS(
+ const void *, arg2),
+ TP_FIELDS(
+ ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2)
+ )
+)
+
+
+
/*----------------------------------------------------------
// Decoder Ring for ApiEnter
// [ api] Enter %u (%p).
@@ -112,25 +131,6 @@ TRACEPOINT_EVENT(CLOG_REGISTRATION_C, ApiExitStatus,
-/*----------------------------------------------------------
-// Decoder Ring for RegistrationCleanup
-// [ reg][%p] Cleaning up
-// QuicTraceEvent(
- RegistrationCleanup,
- "[ reg][%p] Cleaning up",
- Registration);
-// arg2 = arg2 = Registration = arg2
-----------------------------------------------------------*/
-TRACEPOINT_EVENT(CLOG_REGISTRATION_C, RegistrationCleanup,
- TP_ARGS(
- const void *, arg2),
- TP_FIELDS(
- ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2)
- )
-)
-
-
-
/*----------------------------------------------------------
// Decoder Ring for ApiExit
// [ api] Exit
diff --git a/src/inc/msquic.h b/src/inc/msquic.h
index a029f7bf1f..a60a58892c 100644
--- a/src/inc/msquic.h
+++ b/src/inc/msquic.h
@@ -702,6 +702,7 @@ typedef enum QUIC_PERFORMANCE_COUNTERS {
QUIC_PERF_COUNTER_SEND_STATELESS_RESET, // Total stateless reset packets sent ever.
QUIC_PERF_COUNTER_SEND_STATELESS_RETRY, // Total stateless retry packets sent ever.
QUIC_PERF_COUNTER_CONN_LOAD_REJECT, // Total connections rejected due to worker load.
+ QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH, // Current listeners queued for processing.
QUIC_PERF_COUNTER_MAX,
} QUIC_PERFORMANCE_COUNTERS;
@@ -1000,6 +1001,7 @@ typedef struct QUIC_SCHANNEL_CREDENTIAL_ATTRIBUTE_W {
#define QUIC_PARAM_LISTENER_STATS 0x04000001 // QUIC_LISTENER_STATISTICS
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
#define QUIC_PARAM_LISTENER_CIBIR_ID 0x04000002 // uint8_t[] {offset, id[]}
+#define QUIC_PARAM_LISTENER_PARTITION_INDEX 0x04000005 // uint16_t
#endif
#define QUIC_PARAM_DOS_MODE_EVENTS 0x04000004 // BOOLEAN
@@ -1038,6 +1040,7 @@ typedef struct QUIC_SCHANNEL_CREDENTIAL_ATTRIBUTE_W {
#define QUIC_PARAM_CONN_SEND_DSCP 0x05000019 // uint8_t
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
#define QUIC_PARAM_CONN_NETWORK_STATISTICS 0x05000020 // struct QUIC_NETWORK_STATISTICS
+#define QUIC_PARAM_CONN_CLOSE_ASYNC 0x0500001A // uint8_t
#endif
//
@@ -1129,6 +1132,34 @@ void
HQUIC Registration
);
+#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
+
+typedef
+_Function_class_(QUIC_REGISTRATION_CLOSE_CALLBACK)
+void
+(QUIC_API QUIC_REGISTRATION_CLOSE_CALLBACK)(
+ _In_opt_ void* Context
+ );
+
+typedef QUIC_REGISTRATION_CLOSE_CALLBACK *QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER;
+
+//
+// Closes the registration. This function synchronizes the cleanup of all child
+// objects. The callback handler is invoked once all those child objects have
+// been closed by the application.
+//
+typedef
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+(QUIC_API * QUIC_REGISTRATION_CLOSE2_FN)(
+ _In_ _Pre_defensive_ __drv_freesMem(Mem)
+ HQUIC Registration,
+ _In_ _Pre_defensive_ QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context
+ );
+
+#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
+
//
// Calls shutdown for all connections in this registration. Don't call on a
// MsQuic callback thread or it might deadlock.
@@ -1845,10 +1876,12 @@ typedef struct QUIC_API_TABLE {
QUIC_CONN_POOL_CREATE_FN ConnectionPoolCreate; // Available from v2.5
#ifndef _KERNEL_MODE
+#define QUIC_API_EXECUTION_CONTEXT
QUIC_EXECUTION_CREATE_FN ExecutionCreate; // Available from v2.5
QUIC_EXECUTION_DELETE_FN ExecutionDelete; // Available from v2.5
QUIC_EXECUTION_POLL_FN ExecutionPoll; // Available from v2.5
#endif // _KERNEL_MODE
+ QUIC_REGISTRATION_CLOSE2_FN RegistrationClose2; // Available from v2.6
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
} QUIC_API_TABLE;
diff --git a/src/inc/msquic.hpp b/src/inc/msquic.hpp
index f2ff819ae4..b3bce6b5a5 100644
--- a/src/inc/msquic.hpp
+++ b/src/inc/msquic.hpp
@@ -229,6 +229,7 @@ class CxPlatThread {
}
void Wait() noexcept {
if (Initialized) {
+ WaitOnDelete = false;
CxPlatThreadWait(&Thread);
}
}
@@ -513,6 +514,14 @@ struct MsQuicExecution {
delete [] Configs;
}
}
+ ~MsQuicExecution() noexcept {
+ if (Executions != nullptr) {
+ MsQuic->ExecutionDelete(Count, Executions);
+ delete[] Executions;
+ Executions = nullptr;
+ Count = 0;
+ }
+ }
MsQuicExecution(const MsQuicExecution&) = delete;
MsQuicExecution& operator=(const MsQuicExecution&) = delete;
MsQuicExecution(MsQuicExecution&&) = delete;
@@ -589,6 +598,17 @@ struct MsQuicRegistration {
) noexcept {
MsQuic->RegistrationShutdown(Handle, Flags, ErrorCode);
}
+#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
+ void CloseAsync(
+ _In_ QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER Handler,
+ _In_opt_ void* Context
+ ) noexcept {
+ if (Handle != nullptr) {
+ MsQuic->RegistrationClose2(Handle, Handler, Context);
+ Handle = nullptr;
+ }
+ }
+#endif
};
class MsQuicAlpn {
@@ -991,9 +1011,7 @@ struct MsQuicListener {
}
~MsQuicListener() noexcept {
- if (Handle) {
- MsQuic->ListenerClose(Handle);
- }
+ Close();
}
QUIC_STATUS
@@ -1004,6 +1022,19 @@ struct MsQuicListener {
return MsQuic->ListenerStart(Handle, Alpns, Alpns.Length(), Address);
}
+ void
+ Stop() noexcept {
+ MsQuic->ListenerStop(Handle);
+ }
+ void
+ Close()
+ {
+ if (Handle) {
+ MsQuic->ListenerClose(Handle);
+ Handle = nullptr;
+ }
+ }
+
QUIC_STATUS
SetParam(
_In_ uint32_t Param,
@@ -1046,6 +1077,17 @@ struct MsQuicListener {
Length,
Value);
}
+
+ QUIC_STATUS
+ SetPartitionId(
+ _In_ const uint16_t Value) noexcept {
+ return
+ MsQuic->SetParam(
+ Handle,
+ QUIC_PARAM_LISTENER_PARTITION_INDEX,
+ sizeof(Value),
+ &Value);
+ }
#endif
QUIC_STATUS
@@ -1107,11 +1149,13 @@ struct MsQuicConnection {
QUIC_UINT62 AppShutdownErrorCode {0};
bool HandshakeComplete {false};
bool HandshakeResumed {false};
+ bool CloseAsync {false};
uint32_t ResumptionTicketLength {0};
uint8_t* ResumptionTicket {nullptr};
#ifdef CX_PLATFORM_TYPE
CxPlatEvent HandshakeCompleteEvent;
CxPlatEvent ResumptionTicketReceivedEvent;
+ CxPlatEvent ShutdownCompleteEvent {true};
#endif // CX_PLATFORM_TYPE
MsQuicConnection(
@@ -1119,7 +1163,8 @@ struct MsQuicConnection {
_In_ MsQuicCleanUpMode CleanUpMode = CleanUpManual,
_In_ MsQuicConnectionCallback* Callback = NoOpCallback,
_In_ void* Context = nullptr
- ) noexcept : CleanUpMode(CleanUpMode), Callback(Callback), Context(Context) {
+ ) noexcept : CleanUpMode(CleanUpMode), Callback(Callback), Context(Context)
+ {
if (!Registration.IsValid()) {
InitStatus = Registration.GetInitStatus();
return;
@@ -1171,6 +1216,11 @@ struct MsQuicConnection {
~MsQuicConnection() noexcept {
Close();
+#ifdef CX_PLATFORM_TYPE
+ if (CloseAsync) {
+ ShutdownCompleteEvent.WaitForever();
+ }
+#endif // CX_PLATFORM_TYPE
delete[] ResumptionTicket;
}
@@ -1443,6 +1493,9 @@ struct MsQuicConnection {
_Inout_ QUIC_CONNECTION_EVENT* Event
) noexcept {
CXPLAT_DBG_ASSERT(pThis);
+ auto DeleteOnExit =
+ Event->Type == QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE &&
+ pThis->CleanUpMode == CleanUpAutoDelete;
if (Event->Type == QUIC_CONNECTION_EVENT_CONNECTED) {
pThis->HandshakeComplete = true;
pThis->HandshakeResumed = Event->CONNECTED.SessionResumed;
@@ -1474,10 +1527,10 @@ struct MsQuicConnection {
#endif // CX_PLATFORM_TYPE
}
}
- auto DeleteOnExit =
- Event->Type == QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE &&
- pThis->CleanUpMode == CleanUpAutoDelete;
auto Status = pThis->Callback(pThis, pThis->Context, Event);
+ if (Event->Type == QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE) {
+ pThis->ShutdownCompleteEvent.Set();
+ }
if (DeleteOnExit) {
delete pThis;
}
diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h
index 3d84b90d89..f4732d145b 100644
--- a/src/inc/quic_datapath.h
+++ b/src/inc/quic_datapath.h
@@ -441,12 +441,13 @@ typedef enum CXPLAT_DATAPATH_FEATURES {
DEFINE_ENUM_FLAG_OPERATORS(CXPLAT_DATAPATH_FEATURES)
typedef enum CXPLAT_SOCKET_FLAGS {
- CXPLAT_SOCKET_FLAG_NONE = 0x00000000,
- CXPLAT_SOCKET_FLAG_PCP = 0x00000001, // Socket is used for internal PCP support
- CXPLAT_SOCKET_FLAG_SHARE = 0x00000002, // Forces sharing of the address and port
- CXPLAT_SOCKET_SERVER_OWNED = 0x00000004, // Indicates socket is a listener socket
- CXPLAT_SOCKET_FLAG_XDP = 0x00000008, // Socket will use XDP
- CXPLAT_SOCKET_FLAG_QTIP = 0x00000010, // Socket will use QTIP
+ CXPLAT_SOCKET_FLAG_NONE = 0x00000000,
+ CXPLAT_SOCKET_FLAG_PCP = 0x00000001, // Socket is used for internal PCP support
+ CXPLAT_SOCKET_FLAG_SHARE = 0x00000002, // Forces sharing of the address and port
+ CXPLAT_SOCKET_SERVER_OWNED = 0x00000004, // Indicates socket is a listener socket
+ CXPLAT_SOCKET_FLAG_XDP = 0x00000008, // Socket will use XDP
+ CXPLAT_SOCKET_FLAG_QTIP = 0x00000010, // Socket will use QTIP
+ CXPLAT_SOCKET_FLAG_PARTITIONED = 0x00000020, // Socket is partitioned
} CXPLAT_SOCKET_FLAGS;
DEFINE_ENUM_FLAG_OPERATORS(CXPLAT_SOCKET_FLAGS)
@@ -587,7 +588,7 @@ typedef struct CXPLAT_UDP_CONFIG {
const QUIC_ADDR* RemoteAddress; // optional
CXPLAT_SOCKET_FLAGS Flags;
uint32_t InterfaceIndex; // 0 means any/all
- uint16_t PartitionIndex; // Client-only
+ uint16_t PartitionIndex; // optional
void* CallbackContext; // optional
#ifdef QUIC_COMPARTMENT_ID
QUIC_COMPARTMENT_ID CompartmentId; // optional
diff --git a/src/inc/quic_platform.h b/src/inc/quic_platform.h
index 0788a3dbd6..80e66b495a 100644
--- a/src/inc/quic_platform.h
+++ b/src/inc/quic_platform.h
@@ -569,13 +569,6 @@ BOOLEAN
_Inout_ CXPLAT_EXECUTION_STATE* State
);
-typedef
-_IRQL_requires_max_(PASSIVE_LEVEL)
-BOOLEAN
-(*CXPLAT_EXECUTION_WAKE_FN)(
- _Inout_ CXPLAT_EXECUTION_CONTEXT* Context
- );
-
typedef struct CXPLAT_EXECUTION_CONTEXT {
CXPLAT_SLIST_ENTRY Entry;
@@ -589,12 +582,22 @@ typedef struct CXPLAT_EXECUTION_CONTEXT {
#ifdef _KERNEL_MODE // Not supported on kernel mode
#define CxPlatWakeExecutionContext(Context) CXPLAT_FRE_ASSERT(FALSE)
+#if DEBUG
+#define CxPlatWorkerIsThisThread(Context) TRUE
#else
+#define CxPlatWorkerIsThisThread(Context) CXPLAT_FRE_ASSERT(FALSE)
+#endif
+#else // _KERNEL_MODE
void
CxPlatWakeExecutionContext(
_In_ CXPLAT_EXECUTION_CONTEXT* Context
);
-#endif
+
+BOOLEAN
+CxPlatWorkerIsThisThread(
+ _In_ CXPLAT_EXECUTION_CONTEXT* Context
+ );
+#endif // _KERNEL_MODE
//
// Test Interface for loading a self-signed certificate.
diff --git a/src/inc/quic_trace.h b/src/inc/quic_trace.h
index 79f6c6de46..a5c0f6b321 100644
--- a/src/inc/quic_trace.h
+++ b/src/inc/quic_trace.h
@@ -108,6 +108,7 @@ typedef enum QUIC_TRACE_API_TYPE {
QUIC_TRACE_API_EXECUTION_CREATE,
QUIC_TRACE_API_EXECUTION_DELETE,
QUIC_TRACE_API_EXECUTION_POLL,
+ QUIC_TRACE_API_REGISTRATION_CLOSE2,
QUIC_TRACE_API_COUNT // Must be last
} QUIC_TRACE_API_TYPE;
diff --git a/src/manifest/clog.sidecar b/src/manifest/clog.sidecar
index 72edc7d48e..be1b48a519 100644
--- a/src/manifest/clog.sidecar
+++ b/src/manifest/clog.sidecar
@@ -735,6 +735,22 @@
],
"macroName": "QuicTraceLogConnVerbose"
},
+ "CloseAsyncUpdated": {
+ "ModuleProperites": {},
+ "TraceString": "[conn][%p] Updated CloseAsync to %hhu",
+ "UniqueId": "CloseAsyncUpdated",
+ "splitArgs": [
+ {
+ "DefinationEncoding": "p",
+ "MacroVariableName": "arg1"
+ },
+ {
+ "DefinationEncoding": "hhu",
+ "MacroVariableName": "arg3"
+ }
+ ],
+ "macroName": "QuicTraceLogConnVerbose"
+ },
"CloseComplete": {
"ModuleProperites": {},
"TraceString": "[conn][%p] Connection close complete",
@@ -6666,6 +6682,22 @@
],
"macroName": "QuicTraceLogVerbose"
},
+ "ListenerPartitionIndexSet": {
+ "ModuleProperites": {},
+ "TraceString": "[list][%p] PartitionIndex set (index %hu)",
+ "UniqueId": "ListenerPartitionIndexSet",
+ "splitArgs": [
+ {
+ "DefinationEncoding": "p",
+ "MacroVariableName": "arg2"
+ },
+ {
+ "DefinationEncoding": "hu",
+ "MacroVariableName": "arg3"
+ }
+ ],
+ "macroName": "QuicTraceLogVerbose"
+ },
"ListenerRundown": {
"ModuleProperites": {},
"TraceString": "[list][%p] Rundown, Registration=%p",
@@ -13364,6 +13396,11 @@
"TraceID": "ClientVersionNegotiationCompatibleVersionUpgrade",
"EncodingString": "[conn][%p] Compatible version upgrade! Old: 0x%x, New: 0x%x"
},
+ {
+ "UniquenessHash": "a07c3ee9-1b99-2c5f-bad8-bd515d90038d",
+ "TraceID": "CloseAsyncUpdated",
+ "EncodingString": "[conn][%p] Updated CloseAsync to %hhu"
+ },
{
"UniquenessHash": "e6969fd7-4bfa-f16b-fd2d-f366f94dce7c",
"TraceID": "CloseComplete",
@@ -15104,6 +15141,11 @@
"TraceID": "ListenerIndicateStopComplete",
"EncodingString": "[list][%p] Indicating STOP_COMPLETE"
},
+ {
+ "UniquenessHash": "81f937b4-2c2a-b818-6b6f-ad498e684df0",
+ "TraceID": "ListenerPartitionIndexSet",
+ "EncodingString": "[list][%p] PartitionIndex set (index %hu)"
+ },
{
"UniquenessHash": "516f74f8-8802-7cdb-d034-3fa2e41d42c0",
"TraceID": "ListenerRundown",
diff --git a/src/platform/datapath_epoll.c b/src/platform/datapath_epoll.c
index 5aad34db0c..6799308188 100644
--- a/src/platform/datapath_epoll.c
+++ b/src/platform/datapath_epoll.c
@@ -1064,8 +1064,9 @@ SocketCreateUdp(
)
{
QUIC_STATUS Status = QUIC_STATUS_SUCCESS;
- const BOOLEAN IsServerSocket = Config->RemoteAddress == NULL;
- const BOOLEAN NumPerProcessorSockets = IsServerSocket && Datapath->PartitionCount > 1;
+ const BOOLEAN IsPartitioned =
+ Config->Flags & CXPLAT_SOCKET_FLAG_PARTITIONED || Config->RemoteAddress != NULL;
+ const BOOLEAN NumPerProcessorSockets = !IsPartitioned && Datapath->PartitionCount > 1;
const uint16_t SocketCount = NumPerProcessorSockets ? (uint16_t)CxPlatProcCount() : 1;
CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP);
@@ -1122,14 +1123,14 @@ SocketCreateUdp(
CxPlatSocketContextInitialize(
&Binding->SocketContexts[i],
Config,
- Config->RemoteAddress ? Config->PartitionIndex : (i % Datapath->PartitionCount),
+ IsPartitioned ? Config->PartitionIndex : (i % Datapath->PartitionCount),
Binding->Type);
if (QUIC_FAILED(Status)) {
goto Exit;
}
}
- if (IsServerSocket) {
+ if (!IsPartitioned) {
//
// The return value is being ignored here, as if a system does not support
// bpf we still want the server to work. If this happens, the sockets will
diff --git a/src/platform/platform_posix.c b/src/platform/platform_posix.c
index 6dd192e222..f7091964ca 100644
--- a/src/platform/platform_posix.c
+++ b/src/platform/platform_posix.c
@@ -317,7 +317,7 @@ CxPlatRefIncrement(
_Inout_ CXPLAT_REF_COUNT* RefCount
)
{
- if (__atomic_add_fetch(RefCount, 1, __ATOMIC_SEQ_CST)) {
+ if (__atomic_add_fetch(RefCount, 1, __ATOMIC_SEQ_CST) > 1) {
return;
}
diff --git a/src/platform/platform_worker.c b/src/platform/platform_worker.c
index 30f3b7e9ad..e6517010b9 100644
--- a/src/platform/platform_worker.c
+++ b/src/platform/platform_worker.c
@@ -513,6 +513,15 @@ CxPlatWakeExecutionContext(
}
}
+BOOLEAN
+CxPlatWorkerIsThisThread(
+ _In_ CXPLAT_EXECUTION_CONTEXT* Context
+ )
+{
+ CXPLAT_WORKER* Worker = (CXPLAT_WORKER*)Context->CxPlatContext;
+ return Worker->State.ThreadID == CxPlatCurThreadID();
+}
+
void
CxPlatUpdateExecutionContexts(
_In_ CXPLAT_WORKER* Worker
@@ -596,8 +605,8 @@ CxPlatWorkerPoolWorkerPoll(
)
{
CXPLAT_WORKER* Worker = (CXPLAT_WORKER*)Execution;
- Worker->State.ThreadID = CxPlatCurThreadID();
Worker->State.TimeNow = CxPlatTimeUs64();
+ Worker->State.ThreadID = CxPlatCurThreadID();
CxPlatRunExecutionContexts(Worker);
if (Worker->State.WaitTime && InterlockedFetchAndClearBoolean(&Worker->Running)) {
@@ -605,8 +614,6 @@ CxPlatWorkerPoolWorkerPoll(
CxPlatRunExecutionContexts(Worker); // Run once more to handle race conditions
}
- Worker->State.ThreadID = UINT32_MAX;
-
return Worker->State.WaitTime;
}
diff --git a/src/plugins/dbg/quictypes.h b/src/plugins/dbg/quictypes.h
index 0a8b506037..6d653b2278 100644
--- a/src/plugins/dbg/quictypes.h
+++ b/src/plugins/dbg/quictypes.h
@@ -96,6 +96,7 @@ typedef union QUIC_CONNECTION_STATE {
BOOLEAN ShutdownComplete : 1; // Shutdown callback delivered for handle.
BOOLEAN HandleClosed : 1; // Handle closed by application layer.
BOOLEAN Freed : 1; // Freed. Used for Debugging.
+ BOOLEAN Partitioned : 1; // The connection cannot move across partitions.
//
// Indicates whether packet number encryption is enabled or not for the
diff --git a/src/rs/ffi/linux_bindings.rs b/src/rs/ffi/linux_bindings.rs
index ad16854d0d..93157fdee3 100644
--- a/src/rs/ffi/linux_bindings.rs
+++ b/src/rs/ffi/linux_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.72.0 */
+/* automatically generated by rust-bindgen 0.72.1 */
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -182,6 +182,7 @@ pub const QUIC_PARAM_CONFIGURATION_SCHANNEL_CREDENTIAL_ATTRIBUTE_W: u32 = 503316
pub const QUIC_PARAM_LISTENER_LOCAL_ADDRESS: u32 = 67108864;
pub const QUIC_PARAM_LISTENER_STATS: u32 = 67108865;
pub const QUIC_PARAM_LISTENER_CIBIR_ID: u32 = 67108866;
+pub const QUIC_PARAM_LISTENER_PARTITION_INDEX: u32 = 67108869;
pub const QUIC_PARAM_DOS_MODE_EVENTS: u32 = 67108868;
pub const QUIC_PARAM_CONN_QUIC_VERSION: u32 = 83886080;
pub const QUIC_PARAM_CONN_LOCAL_ADDRESS: u32 = 83886081;
@@ -209,6 +210,7 @@ pub const QUIC_PARAM_CONN_STATISTICS_V2_PLAT: u32 = 83886103;
pub const QUIC_PARAM_CONN_ORIG_DEST_CID: u32 = 83886104;
pub const QUIC_PARAM_CONN_SEND_DSCP: u32 = 83886105;
pub const QUIC_PARAM_CONN_NETWORK_STATISTICS: u32 = 83886112;
+pub const QUIC_PARAM_CONN_CLOSE_ASYNC: u32 = 83886106;
pub const QUIC_PARAM_TLS_HANDSHAKE_INFO: u32 = 100663296;
pub const QUIC_PARAM_TLS_NEGOTIATED_ALPN: u32 = 100663297;
pub const QUIC_PARAM_STREAM_ID: u32 = 134217728;
@@ -1679,7 +1681,9 @@ pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_SEND_STATELESS_RETRY:
QUIC_PERFORMANCE_COUNTERS = 30;
pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_CONN_LOAD_REJECT: QUIC_PERFORMANCE_COUNTERS =
31;
-pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_MAX: QUIC_PERFORMANCE_COUNTERS = 32;
+pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH:
+ QUIC_PERFORMANCE_COUNTERS = 32;
+pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_MAX: QUIC_PERFORMANCE_COUNTERS = 33;
pub type QUIC_PERFORMANCE_COUNTERS = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -4966,6 +4970,16 @@ pub type QUIC_REGISTRATION_OPEN_FN = ::std::option::Option<
>;
pub type QUIC_REGISTRATION_CLOSE_FN =
::std::option::Option;
+pub type QUIC_REGISTRATION_CLOSE_CALLBACK =
+ ::std::option::Option;
+pub type QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER = QUIC_REGISTRATION_CLOSE_CALLBACK;
+pub type QUIC_REGISTRATION_CLOSE2_FN = ::std::option::Option<
+ unsafe extern "C" fn(
+ Registration: HQUIC,
+ Handler: QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER,
+ Context: *mut ::std::os::raw::c_void,
+ ),
+>;
pub type QUIC_REGISTRATION_SHUTDOWN_FN = ::std::option::Option<
unsafe extern "C" fn(
Registration: HQUIC,
@@ -6640,10 +6654,11 @@ pub struct QUIC_API_TABLE {
pub ExecutionCreate: QUIC_EXECUTION_CREATE_FN,
pub ExecutionDelete: QUIC_EXECUTION_DELETE_FN,
pub ExecutionPoll: QUIC_EXECUTION_POLL_FN,
+ pub RegistrationClose2: QUIC_REGISTRATION_CLOSE2_FN,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
- ["Size of QUIC_API_TABLE"][::std::mem::size_of::() - 296usize];
+ ["Size of QUIC_API_TABLE"][::std::mem::size_of::() - 304usize];
["Alignment of QUIC_API_TABLE"][::std::mem::align_of::() - 8usize];
["Offset of field: QUIC_API_TABLE::SetContext"]
[::std::mem::offset_of!(QUIC_API_TABLE, SetContext) - 0usize];
@@ -6723,6 +6738,8 @@ const _: () = {
[::std::mem::offset_of!(QUIC_API_TABLE, ExecutionDelete) - 280usize];
["Offset of field: QUIC_API_TABLE::ExecutionPoll"]
[::std::mem::offset_of!(QUIC_API_TABLE, ExecutionPoll) - 288usize];
+ ["Offset of field: QUIC_API_TABLE::RegistrationClose2"]
+ [::std::mem::offset_of!(QUIC_API_TABLE, RegistrationClose2) - 296usize];
};
pub const QUIC_STATUS_SUCCESS: QUIC_STATUS = 0;
pub const QUIC_STATUS_PENDING: QUIC_STATUS = 4294967294;
diff --git a/src/rs/ffi/win_bindings.rs b/src/rs/ffi/win_bindings.rs
index 7971d61e38..3097ac9c9e 100644
--- a/src/rs/ffi/win_bindings.rs
+++ b/src/rs/ffi/win_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.72.0 */
+/* automatically generated by rust-bindgen 0.72.1 */
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -176,6 +176,7 @@ pub const QUIC_PARAM_CONFIGURATION_SCHANNEL_CREDENTIAL_ATTRIBUTE_W: u32 = 503316
pub const QUIC_PARAM_LISTENER_LOCAL_ADDRESS: u32 = 67108864;
pub const QUIC_PARAM_LISTENER_STATS: u32 = 67108865;
pub const QUIC_PARAM_LISTENER_CIBIR_ID: u32 = 67108866;
+pub const QUIC_PARAM_LISTENER_PARTITION_INDEX: u32 = 67108869;
pub const QUIC_PARAM_DOS_MODE_EVENTS: u32 = 67108868;
pub const QUIC_PARAM_CONN_QUIC_VERSION: u32 = 83886080;
pub const QUIC_PARAM_CONN_LOCAL_ADDRESS: u32 = 83886081;
@@ -203,6 +204,7 @@ pub const QUIC_PARAM_CONN_STATISTICS_V2_PLAT: u32 = 83886103;
pub const QUIC_PARAM_CONN_ORIG_DEST_CID: u32 = 83886104;
pub const QUIC_PARAM_CONN_SEND_DSCP: u32 = 83886105;
pub const QUIC_PARAM_CONN_NETWORK_STATISTICS: u32 = 83886112;
+pub const QUIC_PARAM_CONN_CLOSE_ASYNC: u32 = 83886106;
pub const QUIC_PARAM_TLS_HANDSHAKE_INFO: u32 = 100663296;
pub const QUIC_PARAM_TLS_NEGOTIATED_ALPN: u32 = 100663297;
pub const QUIC_PARAM_TLS_SCHANNEL_CONTEXT_ATTRIBUTE_W: u32 = 117440512;
@@ -1673,7 +1675,9 @@ pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_SEND_STATELESS_RETRY:
QUIC_PERFORMANCE_COUNTERS = 30;
pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_CONN_LOAD_REJECT: QUIC_PERFORMANCE_COUNTERS =
31;
-pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_MAX: QUIC_PERFORMANCE_COUNTERS = 32;
+pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH:
+ QUIC_PERFORMANCE_COUNTERS = 32;
+pub const QUIC_PERFORMANCE_COUNTERS_QUIC_PERF_COUNTER_MAX: QUIC_PERFORMANCE_COUNTERS = 33;
pub type QUIC_PERFORMANCE_COUNTERS = ::std::os::raw::c_int;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -4997,6 +5001,15 @@ pub type QUIC_REGISTRATION_OPEN_FN = ::std::option::Option<
>;
pub type QUIC_REGISTRATION_CLOSE_FN =
::std::option::Option;
+pub type QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER =
+ ::std::option::Option;
+pub type QUIC_REGISTRATION_CLOSE2_FN = ::std::option::Option<
+ unsafe extern "C" fn(
+ Registration: HQUIC,
+ Handler: QUIC_REGISTRATION_CLOSE_CALLBACK_HANDLER,
+ Context: *mut ::std::os::raw::c_void,
+ ),
+>;
pub type QUIC_REGISTRATION_SHUTDOWN_FN = ::std::option::Option<
unsafe extern "C" fn(
Registration: HQUIC,
@@ -6661,10 +6674,11 @@ pub struct QUIC_API_TABLE {
pub ExecutionCreate: QUIC_EXECUTION_CREATE_FN,
pub ExecutionDelete: QUIC_EXECUTION_DELETE_FN,
pub ExecutionPoll: QUIC_EXECUTION_POLL_FN,
+ pub RegistrationClose2: QUIC_REGISTRATION_CLOSE2_FN,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
- ["Size of QUIC_API_TABLE"][::std::mem::size_of::() - 296usize];
+ ["Size of QUIC_API_TABLE"][::std::mem::size_of::() - 304usize];
["Alignment of QUIC_API_TABLE"][::std::mem::align_of::() - 8usize];
["Offset of field: QUIC_API_TABLE::SetContext"]
[::std::mem::offset_of!(QUIC_API_TABLE, SetContext) - 0usize];
@@ -6744,6 +6758,8 @@ const _: () = {
[::std::mem::offset_of!(QUIC_API_TABLE, ExecutionDelete) - 280usize];
["Offset of field: QUIC_API_TABLE::ExecutionPoll"]
[::std::mem::offset_of!(QUIC_API_TABLE, ExecutionPoll) - 288usize];
+ ["Offset of field: QUIC_API_TABLE::RegistrationClose2"]
+ [::std::mem::offset_of!(QUIC_API_TABLE, RegistrationClose2) - 296usize];
};
pub const QUIC_STATUS_SUCCESS: QUIC_STATUS = 0;
pub const QUIC_STATUS_PENDING: QUIC_STATUS = 459749;
diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h
index d64ac185fd..4d11cb9a89 100644
--- a/src/test/MsQuicTests.h
+++ b/src/test/MsQuicTests.h
@@ -62,6 +62,8 @@ void QuicTestVersionSettings();
void QuicTestValidateParamApi();
void QuicTestCredentialLoad(const QUIC_CREDENTIAL_CONFIG* Config);
void QuicTestValidateConnectionPoolCreate();
+void QuicTestValidateExecutionContext();
+void QuicTestValidatePartition();
void QuicTestRetryConfigSetting();
//
@@ -1407,4 +1409,10 @@ struct QUIC_RUN_CONNECTION_POOL_CREATE_PARAMS {
#define IOCTL_QUIC_RUN_RETRY_CONFIG_SETTING \
QUIC_CTL_CODE(134, METHOD_BUFFERED, FILE_WRITE_DATA)
-#define QUIC_MAX_IOCTL_FUNC_CODE 134
+#define IOCTL_QUIC_RUN_VALIDATE_EXECUTION_CONTEXT \
+ QUIC_CTL_CODE(135, METHOD_BUFFERED, FILE_WRITE_DATA)
+
+#define IOCTL_QUIC_RUN_VALIDATE_PARTITION \
+ QUIC_CTL_CODE(136, METHOD_BUFFERED, FILE_WRITE_DATA)
+
+#define QUIC_MAX_IOCTL_FUNC_CODE 136
diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp
index 78afeb29a3..f03e70d1c7 100644
--- a/src/test/bin/quic_gtest.cpp
+++ b/src/test/bin/quic_gtest.cpp
@@ -367,7 +367,24 @@ TEST(ParameterValidation, ValidateConnectionPoolCreate) {
QuicTestValidateConnectionPoolCreate();
}
}
-#endif
+
+TEST(ParameterValidation, ValidateExecutionContext) {
+ TestLogger Logger("QuicTestValidateExecutionContext");
+ if (TestingKernelMode) {
+ ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_VALIDATE_EXECUTION_CONTEXT));
+ } else {
+ QuicTestValidateExecutionContext();
+ }
+}
+TEST(ParameterValidation, ValidatePartition) {
+ TestLogger Logger("QuicTestValidatePartition");
+ if (TestingKernelMode) {
+ ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_VALIDATE_PARTITION));
+ } else {
+ QuicTestValidatePartition();
+ }
+}
+#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
TEST(OwnershipValidation, RegistrationShutdownBeforeConnOpen) {
TestLogger Logger("RegistrationShutdownBeforeConnOpen");
diff --git a/src/test/bin/winkernel/control.cpp b/src/test/bin/winkernel/control.cpp
index 20988b74a1..be7ec4e68f 100644
--- a/src/test/bin/winkernel/control.cpp
+++ b/src/test/bin/winkernel/control.cpp
@@ -533,6 +533,8 @@ size_t QUIC_IOCTL_BUFFER_SIZES[] =
sizeof(QUIC_RUN_CONNECTION_POOL_CREATE_PARAMS),
0,
0,
+ 0,
+ 0,
};
CXPLAT_STATIC_ASSERT(
@@ -1532,6 +1534,15 @@ QuicTestCtlEvtIoDeviceControl(
case IOCTL_QUIC_RUN_VALIDATE_CONNECTION_POOL_CREATE:
QuicTestCtlRun(QuicTestValidateConnectionPoolCreate());
break;
+
+ case IOCTL_QUIC_RUN_VALIDATE_EXECUTION_CONTEXT:
+ QuicTestCtlRun(QuicTestValidateExecutionContext());
+ break;
+
+ case IOCTL_QUIC_RUN_VALIDATE_PARTITION:
+ QuicTestCtlRun(QuicTestValidatePartition());
+ break;
+
#endif
case IOCTL_QUIC_RUN_TEST_KEY_UPDATE_DURING_HANDSHAKE:
diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp
index f5d8cdba8b..04513d6b58 100644
--- a/src/test/lib/ApiTest.cpp
+++ b/src/test/lib/ApiTest.cpp
@@ -49,6 +49,20 @@ void QuicTestValidateApi()
QUIC_STATUS_INVALID_PARAMETER);
}
+struct RegistrationCloseContext {
+ CxPlatEvent Event;
+};
+
+_Function_class_(QUIC_REGISTRATION_CLOSE_CALLBACK)
+void
+QUIC_API RegistrationCloseCallback(
+ _In_opt_ void* Context
+ )
+{
+ RegistrationCloseContext* CloseContext = (RegistrationCloseContext*)Context;
+ CloseContext->Event.Set();
+}
+
void QuicTestValidateRegistration()
{
TEST_QUIC_STATUS(
@@ -56,6 +70,17 @@ void QuicTestValidateRegistration()
MsQuic->RegistrationOpen(nullptr, nullptr));
MsQuic->RegistrationClose(nullptr);
+
+ {
+ MsQuicRegistration Registration;
+ TEST_TRUE(Registration.IsValid());
+
+#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
+ RegistrationCloseContext CloseContext;
+ Registration.CloseAsync(RegistrationCloseCallback, &CloseContext);
+ TEST_TRUE(CloseContext.Event.WaitTimeout(TestWaitTimeout));
+#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
+ }
}
void QuicTestValidateConfiguration()
@@ -2702,17 +2727,6 @@ void QuicTestGlobalParam()
{
TestScopeLogger LogScope1("SetParam");
uint8_t StatelessResetkey[QUIC_STATELESS_RESET_KEY_LENGTH - 1];
- CxPlatRandom(sizeof(StatelessResetkey), StatelessResetkey);
- {
- TestScopeLogger LogScope2("StatelessResetkey fail with invalid state");
- TEST_QUIC_STATUS(
- QUIC_STATUS_INVALID_STATE,
- MsQuic->SetParam(
- nullptr,
- QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY,
- sizeof(StatelessResetkey),
- StatelessResetkey));
- }
{
TestScopeLogger LogScope2("StatelessResetkey fail with invalid parameter");
MsQuicRegistration Registration;
@@ -4951,6 +4965,45 @@ void QuicTest_QUIC_PARAM_CONN_NETWORK_STATISTICS(MsQuicRegistration& Registratio
UNREFERENCED_PARAMETER(Registration);
}
+void QuicTest_QUIC_PARAM_CONN_CLOSE_ASYNC(MsQuicRegistration& Registration)
+{
+#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
+ TestScopeLogger LogScope0("QUIC_PARAM_CONN_CLOSE_ASYNC");
+ {
+ TestScopeLogger LogScope1("GetParam default");
+ MsQuicConnection Connection(Registration);
+ TEST_QUIC_SUCCEEDED(Connection.GetInitStatus());
+ BOOLEAN Flag = FALSE;
+ SimpleGetParamTest(Connection.Handle, QUIC_PARAM_CONN_CLOSE_ASYNC, sizeof(BOOLEAN), &Flag);
+ }
+ {
+ TestScopeLogger LogScope1("SetParam/GetParam");
+ MsQuicConnection Connection(Registration);
+ TEST_QUIC_SUCCEEDED(Connection.GetInitStatus());
+ uint8_t CloseAsync = true;
+ uint8_t GetValue = 0;
+ TEST_QUIC_STATUS(
+ QUIC_STATUS_SUCCESS,
+ Connection.SetParam(
+ QUIC_PARAM_CONN_CLOSE_ASYNC,
+ sizeof(CloseAsync),
+ &CloseAsync));
+ Connection.CloseAsync = TRUE;
+ uint32_t BufferSize = sizeof(GetValue);
+ TEST_QUIC_STATUS(
+ QUIC_STATUS_SUCCESS,
+ Connection.GetParam(
+ QUIC_PARAM_CONN_CLOSE_ASYNC,
+ &BufferSize,
+ &GetValue));
+ TEST_EQUAL(BufferSize, sizeof(GetValue));
+ TEST_EQUAL(GetValue, CloseAsync);
+ }
+#else
+ UNREFERENCED_PARAMETER(Registration);
+#endif
+}
+
void QuicTestConnectionParam()
{
MsQuicAlpn Alpn("MsQuicTest");
@@ -4986,6 +5039,7 @@ void QuicTestConnectionParam()
QuicTest_QUIC_PARAM_CONN_ORIG_DEST_CID(Registration, ClientConfiguration);
QuicTest_QUIC_PARAM_CONN_SEND_DSCP(Registration);
QuicTest_QUIC_PARAM_CONN_NETWORK_STATISTICS(Registration);
+ QuicTest_QUIC_PARAM_CONN_CLOSE_ASYNC(Registration);
}
//
@@ -6636,6 +6690,511 @@ QuicTestValidateConnectionPoolCreate()
ConnectionPool));
}
}
+
+#ifdef QUIC_API_EXECUTION_CONTEXT
+
+struct TestEventQ {
+ QUIC_EVENTQ QuicEventQ;
+ BOOLEAN Initialized;
+ TestEventQ() noexcept : Initialized {FALSE} { }
+ TestEventQ(const TestEventQ&) = delete;
+ TestEventQ& operator=(const TestEventQ&) = delete;
+ TestEventQ(TestEventQ&&) = delete;
+ TestEventQ& operator=(TestEventQ&&) = delete;
+ ~TestEventQ() {
+ if (Initialized) {
+ CxPlatEventQCleanup(&QuicEventQ);
+ Initialized = FALSE;
+ }
+ }
+};
+
+void
+QuicTestProcessEventQ(
+ QUIC_EVENTQ* EventQ,
+ uint32_t WaitTime
+ )
+{
+ CXPLAT_CQE Cqes[16];
+ uint32_t CqeCount =
+ CxPlatEventQDequeue(
+ EventQ,
+ Cqes,
+ ARRAYSIZE(Cqes),
+ WaitTime);
+ uint32_t CurrentCqeCount = CqeCount;
+ CXPLAT_CQE* CurrentCqe = Cqes;
+
+ while (CurrentCqeCount > 0) {
+ CXPLAT_SQE* Sqe = CxPlatCqeGetSqe(CurrentCqe);
+#ifdef CXPLAT_USE_EVENT_BATCH_COMPLETION
+ Sqe->Completion(&CurrentCqe, &CurrentCqeCount);
+#else
+ Sqe->Completion(CurrentCqe);
+ CurrentCqe++;
+ CurrentCqeCount--;
+#endif
+ }
+ CxPlatEventQReturn(EventQ, CqeCount);
+}
+
+void
+QuicTestValidateExecutionContext(const uint32_t EcCount)
+{
+ const uint32_t PollCount = 10;
+ UniquePtrArray Ecs(new (std::nothrow) TestEventQ[EcCount]);
+ UniquePtrArray EventQs(new (std::nothrow) QUIC_EVENTQ*[EcCount]);
+
+ TEST_NOT_EQUAL(nullptr, Ecs);
+ TEST_NOT_EQUAL(nullptr, EventQs);
+
+ for (uint32_t i = 0; i < EcCount; i++) {
+ auto &Ec = Ecs[i];
+ TEST_TRUE(CxPlatEventQInitialize(&Ec.QuicEventQ));
+ EventQs[i] = &Ec.QuicEventQ;
+ }
+
+ //
+ // Verify an EC can be created and deleted without any other actions.
+ //
+ {
+ MsQuicExecution Execution(EventQs.get(), EcCount, QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE);
+ TEST_TRUE(Execution.IsValid());
+ }
+
+ //
+ // Verify an EC can be polled.
+ //
+ {
+ MsQuicExecution Execution(EventQs.get(), EcCount, QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE);
+ TEST_TRUE(Execution.IsValid());
+
+ for (uint32_t i = 0; i < PollCount; i++) {
+ for (uint32_t j = 0; j < EcCount; j++) {
+ MsQuic->ExecutionPoll(Execution[j]);
+ QuicTestProcessEventQ(EventQs[j], 0);
+ }
+ }
+ }
+
+ //
+ // Verify EC interaction with registrations: registrations can be opened and
+ // closed while running in EC mode.
+ //
+ {
+ MsQuicExecution Execution(EventQs.get(), EcCount, QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE);
+ TEST_TRUE(Execution.IsValid());
+
+ for (uint32_t i = 0; i < PollCount; i++) {
+ for (uint32_t j = 0; j < EcCount; j++) {
+ MsQuic->ExecutionPoll(Execution[j]);
+ QuicTestProcessEventQ(EventQs[j], 0);
+ }
+ }
+
+ {
+ MsQuicRegistration Registration;
+ TEST_TRUE(Registration.IsValid());
+
+ for (uint32_t i = 0; i < PollCount; i++) {
+ for (uint32_t j = 0; j < EcCount; j++) {
+ MsQuic->ExecutionPoll(Execution[j]);
+ QuicTestProcessEventQ(EventQs[j], 0);
+ }
+ }
+
+ RegistrationCloseContext CloseContext;
+ Registration.CloseAsync(RegistrationCloseCallback, &CloseContext);
+
+ //
+ // The EC is required to continue polling MsQuic and the event queue
+ // while the registration is being closed.
+ //
+ TEST_QUIC_SUCCEEDED(
+ TryUntil(1, TestWaitTimeout, [&](){
+ for (uint32_t i = 0; i < EcCount; i++) {
+ MsQuic->ExecutionPoll(Execution[i]);
+ QuicTestProcessEventQ(EventQs[i], 0);
+ }
+ if (CloseContext.Event.WaitTimeout(0)) {
+ return QUIC_STATUS_SUCCESS;
+ }
+ return QUIC_STATUS_CONTINUE;
+ })
+ );
+ }
+
+ //
+ // The EC can be polled even after all registrations are torn down.
+ //
+ for (uint32_t i = 0; i < PollCount; i++) {
+ for (uint32_t j = 0; j < EcCount; j++) {
+ MsQuic->ExecutionPoll(Execution[j]);
+ QuicTestProcessEventQ(EventQs[j], 0);
+ }
+ }
+ }
+}
+
+void
+QuicTestValidateExecutionContext()
+{
+ QuicTestValidateExecutionContext(1);
+ QuicTestValidateExecutionContext(CXPLAT_MAX(CxPlatProcCount() / 2, 1));
+ QuicTestValidateExecutionContext(CxPlatProcCount());
+}
+
+#else // QUIC_API_EXECUTION_CONTEXT
+void QuicTestValidateExecutionContext() {}
+#endif // QUIC_API_EXECUTION_CONTEXT
+
+#if defined(__linux__) && !defined(CXPLAT_USE_IO_URING) && !defined(CXPLAT_LINUX_XDP_ENABLED)
+
+uint32_t
+TestCurThreadID()
+{
+ return (uint32_t)gettid();
+}
+
+struct TestPartitionCallbackContext {
+ MsQuicConfiguration* ServerConfiguration;
+ MsQuicConnection** Server;
+ uint32_t ExpectedThreadId;
+ uint32_t ActualThreadId;
+ bool CloseInStop;
+};
+
+void
+TestPartitionVerifyCallback(TestPartitionCallbackContext* Context)
+{
+ if (TestCurThreadID() != Context->ExpectedThreadId) {
+ Context->ActualThreadId = TestCurThreadID();
+ }
+}
+
+void
+TestPartitionVerifyCallbackContext(TestPartitionCallbackContext* Context)
+{
+ TEST_EQUAL(Context->ExpectedThreadId, Context->ActualThreadId);
+}
+
+QUIC_STATUS
+TestPartitionListenerCallback(
+ _In_ MsQuicListener* Listener,
+ _In_opt_ void* ListenerContext,
+ _Inout_ QUIC_LISTENER_EVENT* Event)
+{
+ TestPartitionCallbackContext* Context = (TestPartitionCallbackContext*)ListenerContext;
+
+ TestPartitionVerifyCallback(Context);
+
+ if (Event->Type == QUIC_LISTENER_EVENT_NEW_CONNECTION) {
+ *Context->Server = new(std::nothrow) MsQuicConnection(
+ Event->NEW_CONNECTION.Connection,
+ CleanUpManual,
+ [](MsQuicConnection*, void* ConnContext, QUIC_CONNECTION_EVENT*) {
+ TestPartitionCallbackContext* Context = (TestPartitionCallbackContext*)ConnContext;
+ TestPartitionVerifyCallback(Context);
+ return QUIC_STATUS_SUCCESS;
+ },
+ Context);
+ (*Context->Server)->SetConfiguration(*Context->ServerConfiguration);
+ } else if (Event->Type == QUIC_LISTENER_EVENT_STOP_COMPLETE) {
+ if (Context->CloseInStop) {
+ Listener->Close();
+ }
+ }
+
+ return QUIC_STATUS_SUCCESS;
+}
+
+void
+QuicTestInvokeEc(QUIC_EVENTQ* EventQ, QUIC_EXECUTION* QuicEc, TestPartitionCallbackContext*)
+{
+ MsQuic->ExecutionPoll(QuicEc);
+ QuicTestProcessEventQ(EventQ, 0);
+}
+
+void
+QuicTestValidatePartitionInline(const uint32_t EcCount)
+{
+ const uint16_t PartitionIndex = (uint16_t)(1 % EcCount);
+ UniquePtrArray Ecs(new (std::nothrow) TestEventQ[EcCount]);
+ UniquePtrArray EventQs(new (std::nothrow) QUIC_EVENTQ*[EcCount]);
+ TEST_NOT_EQUAL(nullptr, Ecs);
+ TEST_NOT_EQUAL(nullptr, EventQs);
+ MsQuicAlpn Alpn("MsQuicTest");
+
+ //
+ // This test verifies MsQuic APIs can be used within the same threads that
+ // are responsible for processing the execution contexts; every API must not
+ // block on the execution context itself, else a deadlock will occur.
+ //
+
+ for (uint32_t i = 0; i < EcCount; i++) {
+ auto &Ec = Ecs[i];
+ TEST_TRUE(CxPlatEventQInitialize(&Ec.QuicEventQ));
+ EventQs[i] = &Ec.QuicEventQ;
+ }
+
+ MsQuicExecution Execution(EventQs.get(), EcCount, QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE);
+ TEST_TRUE(Execution.IsValid());
+
+ MsQuicRegistration Registration;
+ TEST_TRUE(Registration.IsValid());
+
+ UniquePtr Client;
+ UniquePtr Server;
+ TestPartitionCallbackContext CallbackContext{};
+ MsQuicListener Listener(
+ Registration,
+ CleanUpManual,
+ TestPartitionListenerCallback,
+ &CallbackContext);
+ TEST_QUIC_SUCCEEDED(Listener.GetInitStatus());
+ TEST_QUIC_SUCCEEDED(
+ Listener.SetParam(
+ QUIC_PARAM_LISTENER_PARTITION_INDEX, sizeof(PartitionIndex), &PartitionIndex));
+
+ {
+ MsQuicConfiguration ServerConfiguration(Registration, Alpn, ServerSelfSignedCredConfig);
+ TEST_TRUE(ServerConfiguration.IsValid());
+
+ CallbackContext.ActualThreadId = CallbackContext.ExpectedThreadId = TestCurThreadID();
+ CallbackContext.ServerConfiguration = &ServerConfiguration;
+ CallbackContext.Server = (MsQuicConnection**)&Server;
+ CallbackContext.CloseInStop = TRUE;
+
+ TEST_QUIC_SUCCEEDED(Listener.Start(Alpn));
+
+ QuicAddr ServerLocalAddr;
+ TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr));
+
+ MsQuicCredentialConfig ClientCredConfig;
+ MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCredConfig);
+ TEST_QUIC_SUCCEEDED(ClientConfiguration.GetInitStatus());
+
+ Client = UniquePtr(
+ new MsQuicConnection(Registration, PartitionIndex, CleanUpManual,
+ [](MsQuicConnection*, void* ConnContext, QUIC_CONNECTION_EVENT*) {
+ TestPartitionCallbackContext* Context = (TestPartitionCallbackContext*)ConnContext;
+ TestPartitionVerifyCallback(Context);
+ return QUIC_STATUS_SUCCESS;
+ },
+ &CallbackContext));
+ TEST_QUIC_SUCCEEDED(Client->GetInitStatus());
+ TEST_QUIC_SUCCEEDED(Client->Start(
+ ClientConfiguration, ServerLocalAddr.GetFamily(),
+ QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort()));
+
+ //
+ // Allow this thread to poll only the specified partition; the rest of the
+ // partitions will be idle.
+ //
+ TEST_QUIC_SUCCEEDED(
+ TryUntil(1, TestWaitTimeout, [&](){
+ QuicTestInvokeEc(
+ EventQs[PartitionIndex], Execution[PartitionIndex], &CallbackContext);
+ if (Client->HandshakeComplete && Server) {
+ return QUIC_STATUS_SUCCESS;
+ }
+ return QUIC_STATUS_CONTINUE;
+ })
+ );
+ }
+
+ //
+ // Initiate asynchronous teardown of each of the connections; each will receive
+ // a completion event on their callbacks.
+ //
+ Server->Shutdown(QUIC_TEST_NO_ERROR);
+ Client->Shutdown(QUIC_TEST_NO_ERROR);
+ TEST_QUIC_SUCCEEDED(
+ TryUntil(1, TestWaitTimeout, [&](){
+ QuicTestInvokeEc(EventQs[PartitionIndex], Execution[PartitionIndex], &CallbackContext);
+ if (Server->ShutdownCompleteEvent.WaitTimeout(0) &&
+ Client->ShutdownCompleteEvent.WaitTimeout(0)) {
+ return QUIC_STATUS_SUCCESS;
+ }
+ return QUIC_STATUS_CONTINUE;
+ })
+ );
+
+ //
+ // Initiate asynchronous teardown of the rest of the objects.
+ // The registration close will not complete until each of these objects is
+ // cleaned up, so we do not need to wait for each of these explicitly.
+ //
+ BOOLEAN CloseAsync = TRUE;
+ TEST_QUIC_SUCCEEDED(Server->SetParam(QUIC_PARAM_CONN_CLOSE_ASYNC, sizeof(CloseAsync), &CloseAsync));
+ Server->CloseAsync = TRUE;
+ Server->Close();
+ TEST_QUIC_SUCCEEDED(Client->SetParam(QUIC_PARAM_CONN_CLOSE_ASYNC, sizeof(CloseAsync), &CloseAsync));
+ Client->CloseAsync = TRUE;
+ Client->Close();
+ Listener.Stop();
+
+ RegistrationCloseContext CloseContext;
+ Registration.CloseAsync(RegistrationCloseCallback, &CloseContext);
+ TEST_QUIC_SUCCEEDED(
+ TryUntil(1, TestWaitTimeout, [&](){
+ //
+ // To clean up, we do need to poll all the contexts.
+ //
+ for (uint32_t i = 0; i < EcCount; i++) {
+ QuicTestInvokeEc(EventQs[i], Execution[i], &CallbackContext);
+ }
+ if (CloseContext.Event.WaitTimeout(0)) {
+ return QUIC_STATUS_SUCCESS;
+ }
+ return QUIC_STATUS_CONTINUE;
+ })
+ );
+
+ TestPartitionVerifyCallbackContext(&CallbackContext);
+}
+
+struct QuicTestPartitionWorkerContext {
+ volatile BOOLEAN Stop;
+ TestEventQ EventQ;
+ CxPlatThread Thread;
+ QUIC_EXECUTION* QuicEc;
+ CXPLAT_THREAD_ID ThreadId;
+ CxPlatEvent Ready;
+ TestPartitionCallbackContext* CallbackContext;
+};
+
+CXPLAT_THREAD_CALLBACK(QuicTestPartitionWorker, Context)
+{
+ QuicTestPartitionWorkerContext* Worker = (QuicTestPartitionWorkerContext*)Context;
+
+ Worker->ThreadId = TestCurThreadID();
+ Worker->Ready.Set();
+
+ while (!Worker->Stop) {
+ QuicTestInvokeEc(&Worker->EventQ.QuicEventQ, Worker->QuicEc, Worker->CallbackContext);
+ }
+
+ CXPLAT_THREAD_RETURN(QUIC_STATUS_SUCCESS);
+}
+
+void
+QuicTestValidatePartitionWorker(const uint32_t EcCount)
+{
+ const uint16_t PartitionIndex = (uint16_t)(1 % EcCount);
+ UniquePtrArray Ecs(new (std::nothrow) QuicTestPartitionWorkerContext[EcCount]{});
+ UniquePtrArray EventQs(new (std::nothrow) QUIC_EVENTQ*[EcCount]);
+ TEST_NOT_EQUAL(nullptr, Ecs);
+ TEST_NOT_EQUAL(nullptr, EventQs);
+ TestPartitionCallbackContext CallbackContext{};
+ MsQuicAlpn Alpn("MsQuicTest");
+
+ for (uint32_t i = 0; i < EcCount; i++) {
+ auto &Ec = Ecs[i];
+ TEST_TRUE(CxPlatEventQInitialize(&Ec.EventQ.QuicEventQ));
+ Ec.CallbackContext = &CallbackContext;
+ EventQs[i] = &Ec.EventQ.QuicEventQ;
+ }
+
+ MsQuicExecution Execution(EventQs.get(), EcCount, QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE);
+ TEST_TRUE(Execution.IsValid());
+
+ for (uint32_t i = 0; i < EcCount; i++) {
+ auto &Ec = Ecs[i];
+ Ec.QuicEc = Execution[i];
+ CXPLAT_THREAD_CONFIG Config{};
+ Config.Name = "QuicTestPartitionWorker";
+ Config.Callback = QuicTestPartitionWorker;
+ Config.Context = &Ec;
+ TEST_QUIC_SUCCEEDED(Ec.Thread.Create(&Config));
+ TEST_TRUE(Ec.Ready.WaitTimeout(TestWaitTimeout));
+ }
+
+ {
+ MsQuicRegistration Registration;
+ TEST_TRUE(Registration.IsValid());
+
+ MsQuicListener Listener(
+ Registration,
+ CleanUpManual,
+ TestPartitionListenerCallback,
+ &CallbackContext);
+ TEST_QUIC_SUCCEEDED(Listener.GetInitStatus());
+ TEST_QUIC_SUCCEEDED(
+ Listener.SetParam(
+ QUIC_PARAM_LISTENER_PARTITION_INDEX, sizeof(PartitionIndex), &PartitionIndex));
+
+ MsQuicConfiguration ServerConfiguration(Registration, Alpn, ServerSelfSignedCredConfig);
+ TEST_TRUE(ServerConfiguration.IsValid());
+ UniquePtr Server;
+
+ CallbackContext.ActualThreadId = CallbackContext.ExpectedThreadId = Ecs[PartitionIndex].ThreadId;
+ CallbackContext.ServerConfiguration = &ServerConfiguration;
+ CallbackContext.Server = (MsQuicConnection**)&Server;
+
+ TEST_QUIC_SUCCEEDED(Listener.Start(Alpn));
+
+ QuicAddr ServerLocalAddr;
+ TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr));
+
+ MsQuicCredentialConfig ClientCredConfig;
+ MsQuicConfiguration ClientConfiguration(Registration, Alpn, ClientCredConfig);
+ TEST_QUIC_SUCCEEDED(ClientConfiguration.GetInitStatus());
+
+ MsQuicConnection Client(
+ Registration, PartitionIndex, CleanUpManual,
+ [](MsQuicConnection*, void* ConnContext, QUIC_CONNECTION_EVENT*) {
+ TestPartitionCallbackContext* Context = (TestPartitionCallbackContext*)ConnContext;
+ TestPartitionVerifyCallback(Context);
+ return QUIC_STATUS_SUCCESS;
+ },
+ &CallbackContext);
+ TEST_QUIC_SUCCEEDED(Client.GetInitStatus());
+ TEST_QUIC_SUCCEEDED(Client.Start(
+ ClientConfiguration, ServerLocalAddr.GetFamily(),
+ QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort()));
+
+ //
+ // Wait until the connections are established.
+ //
+ TEST_QUIC_SUCCEEDED(
+ TryUntil(1, TestWaitTimeout, [&](){
+ if (Client.HandshakeComplete && Server) {
+ return QUIC_STATUS_SUCCESS;
+ }
+ return QUIC_STATUS_CONTINUE;
+ })
+ );
+ }
+
+ for (uint32_t i = 0; i < EcCount; i++) {
+ auto &Ec = Ecs[i];
+ Ec.Stop = TRUE;
+ Ec.Thread.Wait();
+ }
+
+ TestPartitionVerifyCallbackContext(&CallbackContext);
+}
+
+void
+QuicTestValidatePartition(const uint32_t EcCount)
+{
+ QuicTestValidatePartitionInline(EcCount);
+ QuicTestValidatePartitionWorker(EcCount);
+}
+
+void
+QuicTestValidatePartition()
+{
+ QuicTestValidatePartition(1);
+ QuicTestValidatePartition(CXPLAT_MAX(CxPlatProcCount() / 2, 1));
+ QuicTestValidatePartition(CxPlatProcCount());
+}
+
+#else // defined(__linux__) && !defined(QUIC_LINUX_IOURING_ENABLED) && !defined(CXPLAT_LINUX_XDP_ENABLED)
+void QuicTestValidatePartition() {}
+#endif // defined(__linux__) && !defined(QUIC_LINUX_IOURING_ENABLED) && !defined(CXPLAT_LINUX_XDP_ENABLED)
+
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
void
diff --git a/src/tools/etw/trace.c b/src/tools/etw/trace.c
index f16f2e4f55..6e84b7dd11 100644
--- a/src/tools/etw/trace.c
+++ b/src/tools/etw/trace.c
@@ -311,6 +311,9 @@ QuicTraceGlobalEvent(
case QUIC_PERF_COUNTER_CONN_LOAD_REJECT:
printf(" Total connections rejected due to worker load: ");
break;
+ case QUIC_PERF_COUNTER_LISTEN_QUEUE_DEPTH:
+ printf(" Current listeners queued for processing: ");
+ break;
default:
printf(" Unknown: ");
break;
diff --git a/src/tools/spin/spinquic.cpp b/src/tools/spin/spinquic.cpp
index 44bc154eab..bc33a678d9 100644
--- a/src/tools/spin/spinquic.cpp
+++ b/src/tools/spin/spinquic.cpp
@@ -824,6 +824,9 @@ void SpinQuicSetRandomConnectionParam(HQUIC Connection, uint16_t ThreadID)
break; // Get Only
case QUIC_PARAM_CONN_STATISTICS_V2_PLAT: // QUIC_STATISTICS_V2
break; // Get Only
+ case QUIC_PARAM_CONN_CLOSE_ASYNC: // uint8_t (BOOLEAN)
+ // Do not set: this test does not implement async close waiting.
+ break;
default:
break;
}
@@ -860,8 +863,8 @@ const uint32_t ParamCounts[] = {
QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH + 1,
0,
QUIC_PARAM_CONFIGURATION_SCHANNEL_CREDENTIAL_ATTRIBUTE_W + 1,
- QUIC_PARAM_LISTENER_CIBIR_ID + 1,
- QUIC_PARAM_CONN_STATISTICS_V2_PLAT + 1,
+ QUIC_PARAM_LISTENER_PARTITION_INDEX + 1,
+ QUIC_PARAM_CONN_CLOSE_ASYNC + 1,
QUIC_PARAM_TLS_NEGOTIATED_ALPN + 1,
#ifdef WIN32 // Schannel specific TLS parameters
QUIC_PARAM_TLS_SCHANNEL_SECURITY_CONTEXT_TOKEN + 1,