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,