mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-19 15:22:15 +08:00
359abe68be
## Proposed changes Fixes #4729 on master378 (1.5.378.x), the branch the issue was reported against. The master counterpart is #4736. If a server is stopped while requests are queued, a request could be completed twice. The second completion landed on a `ServiceResponsePooledValueTaskSource` that had already gone back to the process-wide pool and been rented by another request. That request, possibly on another server in the same process, then received a foreign response, which showed up as `InvalidCastException` or `BadSecurityChecksFailed`. ### Changes - **Leased completion (root defense, same as #4736):** each rental of the pooled source gets a lease number. `EndpointIncomingRequest` captures it at construction and completes through `TrySetResult(lease, response)`, a single CAS on the pending lease. Only the first completion of the current rental is accepted, and a late completion can never reach the next owner. - **`ServerBase.RequestQueue`:** - `Dispose` drains the queue with `TryDequeue` instead of completing a `ToList()` snapshot. Each request is now removed by exactly one party, either a worker or `Dispose`. This replaces the `m_queue.Clear()` that only compiled under `NETSTANDARD2_1_OR_GREATER`. - The stop flag is now an `int`. `Dispose` sets it with `Interlocked.Exchange`, which is a full fence, and is now idempotent. - `ScheduleIncomingRequest` re-checks the stop flag after enqueuing and drains the queue instead of letting `ObjectDisposedException` from `Release()` escape. Previously that exception made `ProcessAsync` complete the already-queued request a second time. - A worker that dequeues a request after stop completes it with `BadServerHalted` instead of running it. Workers also treat a disposed semaphore as shutdown. - Completions from the queue go through a helper that catches and logs exceptions. ## Tests - `RequestQueueShutdownTests.DisposeCompletesQueuedRequestsOnceAsync`: fails on the current master378 (a request is completed twice) and passes with the fix. - `RequestQueueShutdownTests.ScheduleAfterDisposeCompletesOnce`. - `RequestQueueShutdownTests.ConcurrentScheduleAndDisposeCompletesEachRequestOnceAsync`: a stress guard. It did not reproduce the old bug locally. - `ServiceResponsePooledValueTaskSourceTests`: a duplicate completion is ignored, and a stale completion after the source is recycled does not complete the next rental. - Local net10.0 runs: `Opc.Ua.Core.Tests` Stack.* 1496 passed, 0 failed; `Opc.Ua.Server.Tests` 513 passed, 0 failed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>