private async Task CloseAsyncPrivate()

in src/Microsoft.Azure.Relay/WebSockets/NetStandard20/ManagedWebSocket.cs [995:1063]


        private async Task CloseAsyncPrivate(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
        {
            // Send the close message.  Skip sending a close frame if we're currently in a CloseSent state,
            // for example having just done a CloseOutputAsync.
            if (!_sentCloseFrame)
            {
                await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false);
            }

            // We should now either be in a CloseSent case (because we just sent one), or in a CloseReceived state, in case
            // there was a concurrent receive that ended up handling an immediate close frame response from the server.
            // Of course it could also be Aborted if something happened concurrently to cause things to blow up.
            Debug.Assert(
                State == WebSocketState.CloseSent ||
                State == WebSocketState.CloseReceived ||
                State == WebSocketState.Aborted,
                $"Unexpected state {State}.");

            // Wait until we've received a close response
            byte[] closeBuffer = ArrayPool<byte>.Shared.Rent(MaxMessageHeaderLength + MaxControlPayloadLength);
            try
            {
                while (!_receivedCloseFrame)
                {
                    Debug.Assert(!Monitor.IsEntered(StateUpdateLock), $"{nameof(StateUpdateLock)} must never be held when acquiring {nameof(ReceiveAsyncLock)}");
                    Task<WebSocketReceiveResult> receiveTask;
                    lock (ReceiveAsyncLock)
                    {
                        // Now that we're holding the ReceiveAsyncLock, double-check that we've not yet received the close frame.
                        // It could have been received between our check above and now due to a concurrent receive completing.
                        if (_receivedCloseFrame)
                        {
                            break;
                        }

                        // We've not yet processed a received close frame, which means we need to wait for a received close to complete.
                        // There may already be one in flight, in which case we want to just wait for that one rather than kicking off
                        // another (we don't support concurrent receive operations).  We need to kick off a new receive if either we've
                        // never issued a receive or if the last issued receive completed for reasons other than a close frame.  There is
                        // a race condition here, e.g. if there's a in-flight receive that completes after we check, but that's fine: worst
                        // case is we then await it, find that it's not what we need, and try again.
                        receiveTask = _lastReceiveAsync;
                        if (receiveTask == null ||
                            (receiveTask.Status == TaskStatus.RanToCompletion && receiveTask.Result.MessageType != WebSocketMessageType.Close))
                        {
                            _lastReceiveAsync = receiveTask = ReceiveAsyncPrivate(new ArraySegment<byte>(closeBuffer), cancellationToken);
                        }
                    }

                    // Wait for whatever receive task we have.  We'll then loop around again to re-check our state.
                    Debug.Assert(receiveTask != null);
                    await receiveTask.ConfigureAwait(false);
                }
            }
            finally
            {
                ArrayPool<byte>.Shared.Return(closeBuffer);
            }

            // We're closed.  Close the connection and update the status.
            lock (StateUpdateLock)
            {
                DisposeCore();
                if (_state < WebSocketState.Closed)
                {
                    _state = WebSocketState.Closed;
                }
            }
        }