Copilot commented on code in PR #3978:
URL: https://github.com/apache/thrift/pull/3978#discussion_r4136065524


##########
lib/netstd/Thrift/TBaseClient.cs:
##########
@@ -42,13 +47,235 @@ protected TBaseClient(TProtocol inputProtocol, TProtocol 
outputProtocol)
             _outputProtocol = outputProtocol ?? throw new 
ArgumentNullException(nameof(outputProtocol));
         }
 
-        public TProtocol InputProtocol => _inputProtocol;
+        /// <summary>
+        /// Initializes a client with shared protocols and optional per-call 
transport support.
+        /// </summary>
+        /// <param name="inputProtocol">The shared input protocol.</param>
+        /// <param name="outputProtocol">The shared output protocol.</param>
+        /// <param name="transport">
+        /// The shared transport. When it implements <see 
cref="ITPerCallTransportProvider"/>,
+        /// each client call uses protocols created for a new per-call 
transport.
+        /// </param>
+        /// <param name="inputProtocolFactory">The factory used to create 
per-call input protocols.</param>
+        /// <param name="outputProtocolFactory">The factory used to create 
per-call output protocols.</param>
+        protected TBaseClient(
+            TProtocol inputProtocol,
+            TProtocol outputProtocol,
+            TTransport transport,
+            TProtocolFactory inputProtocolFactory,
+            TProtocolFactory outputProtocolFactory)
+            : this(inputProtocol, outputProtocol)
+        {
+            _perCallTransportProvider = transport as 
ITPerCallTransportProvider;
+            if (_perCallTransportProvider != null)
+            {
+                _inputProtocolFactory = inputProtocolFactory ?? throw new 
ArgumentNullException(nameof(inputProtocolFactory));
+                _outputProtocolFactory = outputProtocolFactory ?? throw new 
ArgumentNullException(nameof(outputProtocolFactory));
+            }
+        }
+
+        /// <summary>
+        /// Initializes a client with protocols created from the supplied 
transport and protocol factories.
+        /// </summary>
+        /// <param name="transport">
+        /// The shared transport. When it implements <see 
cref="ITPerCallTransportProvider"/>,
+        /// each client call uses protocols created for a new per-call 
transport.
+        /// </param>
+        /// <param name="inputProtocolFactory">The factory used to create 
input protocols.</param>
+        /// <param name="outputProtocolFactory">The factory used to create 
output protocols.</param>
+        /// <exception cref="ArgumentNullException">
+        /// Thrown when <paramref name="transport"/>, <paramref 
name="inputProtocolFactory"/>,
+        /// or <paramref name="outputProtocolFactory"/> is <see 
langword="null"/>.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown when a protocol factory returns <see langword="null"/>.
+        /// </exception>
+        protected TBaseClient(
+            TTransport transport,
+            TProtocolFactory inputProtocolFactory,
+            TProtocolFactory outputProtocolFactory)
+        {
+            if (transport == null) throw new 
ArgumentNullException(nameof(transport));
+            if (inputProtocolFactory == null) throw new 
ArgumentNullException(nameof(inputProtocolFactory));
+            if (outputProtocolFactory == null) throw new 
ArgumentNullException(nameof(outputProtocolFactory));
+
+            var protocols = CreateProtocols(transport, inputProtocolFactory, 
outputProtocolFactory);
+            _inputProtocol = protocols.InputProtocol;
+            _outputProtocol = protocols.OutputProtocol;
+            _perCallTransportProvider = transport as 
ITPerCallTransportProvider;
+            if (_perCallTransportProvider != null)
+            {
+                _inputProtocolFactory = inputProtocolFactory;
+                _outputProtocolFactory = outputProtocolFactory;
+            }
+        }
+
+        private static ProtocolPair CreateProtocols(
+            TTransport transport,
+            TProtocolFactory inputProtocolFactory,
+            TProtocolFactory outputProtocolFactory)
+        {
+            TProtocol inputProtocol = null;
+            TProtocol outputProtocol = null;
+            try
+            {
+                inputProtocol = inputProtocolFactory.GetProtocol(transport)
+                    ?? throw new InvalidOperationException("The input protocol 
factory returned null.");
+                outputProtocol = outputProtocolFactory.GetProtocol(transport)
+                    ?? throw new InvalidOperationException("The output 
protocol factory returned null.");
+                return new ProtocolPair(inputProtocol, outputProtocol);
+            }
+            catch
+            {
+                outputProtocol?.Dispose();
+                if (inputProtocol != null && !ReferenceEquals(inputProtocol, 
outputProtocol))
+                {
+                    inputProtocol.Dispose();
+                }
+                if (inputProtocol == null && outputProtocol == null)
+                {
+                    transport.Dispose();
+                }
+                throw;
+            }
+        }
 
-        public TProtocol OutputProtocol => _outputProtocol;
+        private sealed class ProtocolPair
+        {
+            public ProtocolPair(TProtocol inputProtocol, TProtocol 
outputProtocol)
+            {
+                InputProtocol = inputProtocol;
+                OutputProtocol = outputProtocol;
+            }
+
+            public TProtocol InputProtocol { get; }
+            public TProtocol OutputProtocol { get; }
+        }
 
+        /// <summary>
+        /// Gets the input protocol for the current call, or the shared input 
protocol when no
+        /// per-call transport is active.
+        /// </summary>
+        public TProtocol InputProtocol => 
_perCallProtocols.Value?.InputProtocol ?? _inputProtocol;
+
+        /// <summary>
+        /// Gets the output protocol for the current call, or the shared 
output protocol when no
+        /// per-call transport is active.
+        /// </summary>
+        public TProtocol OutputProtocol => 
_perCallProtocols.Value?.OutputProtocol ?? _outputProtocol;
+
+        /// <summary>
+        /// Gets the next client sequence identifier.
+        /// </summary>
         public int SeqId
         {
-            get { return ++_seqId; }
+            get { return Interlocked.Increment(ref _seqId); }
+        }
+
+        /// <summary>
+        /// Executes an operation using protocols scoped to a new per-call 
transport when the
+        /// shared transport supports per-call transports.
+        /// </summary>
+        /// <param name="operation">The operation to execute.</param>
+        /// <param name="cancellationToken">The token used to cancel per-call 
transport creation.</param>
+        /// <returns>A task that represents the asynchronous 
operation.</returns>
+        protected async Task ExecutePerCallAsync(Func<Task> operation, 
CancellationToken cancellationToken)
+        {
+            await ExecutePerCallCoreAsync(async () =>
+            {
+                await operation();
+                return true;
+            }, cancellationToken);
+        }
+
+        /// <summary>
+        /// Executes an operation using protocols scoped to a new per-call 
transport when the
+        /// shared transport supports per-call transports.
+        /// </summary>
+        /// <typeparam name="TResult">The type of result returned by the 
operation.</typeparam>
+        /// <param name="operation">The operation to execute.</param>
+        /// <param name="cancellationToken">The token used to cancel per-call 
transport creation.</param>
+        /// <returns>A task that contains the operation result.</returns>
+        protected Task<TResult> ExecutePerCallAsync<TResult>(
+            Func<Task<TResult>> operation,
+            CancellationToken cancellationToken)
+        {
+            return ExecutePerCallCoreAsync(operation, cancellationToken);
+        }
+
+        private async Task<TResult> ExecutePerCallCoreAsync<TResult>(
+            Func<Task<TResult>> operation,
+            CancellationToken cancellationToken)
+        {
+            if (_perCallTransportProvider == null)
+                return await operation();
+
+            var transport = await 
_perCallTransportProvider.CreatePerCallTransportAsync(cancellationToken);

Review Comment:
   Validate the provider result before invoking either protocol factory. 
`ITPerCallTransportProvider` is a public extension point and can return `null`; 
in that case the factory failure path reaches `transport.Dispose()` and throws 
a secondary `NullReferenceException`, masking the actual contract violation. 
This should fail deterministically, just as null protocol-factory results do 
below.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to