/// <summary> /// Submits a frame, and waits until response is received /// </summary> /// <param name="frame"> The frame to send. </param> /// <param name="logger">logger to write progress to</param> /// <param name="load"> the load indication of the request. Used for balancing queries over nodes and connections </param> /// <param name="isConnecting">indicates if this request is send as part of connection setup protocol</param> /// <returns> </returns> internal async Task<Frame> SendRequestAsync(Frame frame, Logger logger, int load = 1, bool isConnecting = false) { try { //make sure we're already connected if (!isConnecting) await OpenAsync(logger).ConfigureAwait(false); //make sure we are connected if (!IsConnected) throw new IOException("Not connected"); //count the operation Interlocked.Increment(ref _activeRequests); //increase the load UpdateLoad(load, logger); logger.LogVerbose("Waiting for connection lock on {0}...", this); //wait until allowed to submit a frame await _frameSubmitLock.WaitAsync().ConfigureAwait(false); //get a task that gets completed when a response is received var waitTask = new TaskCompletionSource<Frame>(); //get a stream id, and store wait task under that id sbyte id; lock (_availableQueryIds) { id = _availableQueryIds.Dequeue(); _openRequests.Add(id, waitTask); } try { //send frame frame.Stream = id; //serialize frame outside lock Stream frameBytes = frame.GetFrameBytes(_allowCompression && !isConnecting, _cluster.Config.CompressionTreshold); await _writeLock.WaitAsync().ConfigureAwait(false); try { //final check to make sure we're connected if (_connectionState != 1) throw new IOException("Not connected"); logger.LogVerbose("Sending {0} Frame with Id {1}, to {2}", frame.OpCode, id, this); await frameBytes.CopyToAsync(_writeStream).ConfigureAwait(false); } finally { _writeLock.Release(); frameBytes.Dispose(); } //wait until response is received Frame response = await waitTask.Task.ConfigureAwait(false); logger.LogVerbose("{0} response for frame with Id {1} received from {2}", response.OpCode, id, Address); //throw error if result is an error var error = response as ErrorFrame; if (error != null) { throw error.Exception; } //return response return response; } finally { //return request slot to the pool lock (_availableQueryIds) { _openRequests.Remove(id); _availableQueryIds.Enqueue(id); } //allow another frame to be send _frameSubmitLock.Release(); //reduce load, we are done Interlocked.Decrement(ref _activeRequests); UpdateLoad(-load, logger); } } catch (ProtocolException pex) { switch (pex.Code) { case ErrorCode.IsBootstrapping: case ErrorCode.Overloaded: using (logger.ThreadBinding()) { //IO or node status related error, dispose this connection Dispose(true, pex); throw; } default: //some other Cql error (syntax ok?), simply rethrow throw; } } catch (Exception ex) { using (logger.ThreadBinding()) { //connection collapsed, dispose this connection Dispose(true, ex); throw; } } }
/// <summary> /// Submits a frame, and waits until response is received (complex version) /// </summary> /// <param name="frame"> The frame to send. </param> /// <param name="logger"> logger to write progress to </param> /// <param name="load"> the load indication of the request. Used for balancing queries over nodes and connections </param> /// <param name="token"> The token. </param> /// <returns></returns> private async Task<Frame> SendRequestAsyncComplex(Frame frame, Logger logger, int load, CancellationToken token) { //make sure connection is open await OpenAsync(logger).AutoConfigureAwait(); //send request var requestTask = SendRequestAsyncInternal(frame, logger, load, token); //take fast path if we can skip cancellation support if(!token.CanBeCanceled) return await requestTask.AutoConfigureAwait(); //setup task that completes when token is set to cancelled var cancelTask = new TaskCompletionSource<bool>(); using (token.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), cancelTask)) { //wait for either sendTask or cancellation task to complete if (requestTask != await Task.WhenAny(requestTask, cancelTask.Task).AutoConfigureAwait()) { //ignore/log any exception of the handled task // ReSharper disable once UnusedVariable var logError = requestTask.ContinueWith((sendTask, log) => { if (sendTask.Exception == null) return; var logger1 = (Logger)log; logger1.LogWarning( "Cancelled query threw exception: {0}", sendTask.Exception.InnerException); }, logger, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); //get this request cancelled throw new OperationCanceledException(token); } } return await requestTask.AutoConfigureAwait(); }
/// <summary> /// Sends the request async internal. Cancellation supported until request is send, after which answer must be handled /// to avoid connection corruption. /// </summary> /// <param name="frame"> The frame. </param> /// <param name="logger"> The logger. </param> /// <param name="load"> The load. </param> /// <param name="token"> The token. </param> /// <returns> </returns> /// <exception cref="System.IO.IOException">Not connected</exception> private async Task<Frame> SendRequestAsyncInternal(Frame frame, Logger logger, int load, CancellationToken token) { try { //make sure we aren't disposed if(_connectionState == ConnectionState.Closed) throw new ObjectDisposedException(ToString()); //count the operation Interlocked.Increment(ref _activeRequests); if (_connectionState == ConnectionState.Connected) { //increase the load UpdateLoad(load, logger); //wait until frame id is available to submit a frame logger.LogVerbose("Waiting for connection lock on {0}...", this); if (Scheduler.RunningSynchronously) _frameSubmitLock.Wait(token); else await _frameSubmitLock.WaitAsync(token).AutoConfigureAwait(); } //get a task that gets completed when a response is received var waitTask = new TaskCompletionSource<Frame>(); //get a stream id, and store wait task under that id short id; lock (_availableQueryIds) { id = _availableQueryIds.Count > 0 ? _availableQueryIds.Dequeue() : _usedQueryIds++; _openRequests.Add(id, waitTask); } try { //send frame frame.Stream = id; //set protocol version in use frame.ProtocolVersion = Node.ProtocolVersion; //serialize frame outside lock PoolMemoryStream frameBytes = frame.GetFrameBytes(_allowCompression && (_connectionState!=ConnectionState.Connecting), _config.CompressionTreshold); //wait to get access to stream if (Scheduler.RunningSynchronously) _writeLock.Wait(token); else await _writeLock.WaitAsync(token).AutoConfigureAwait(); try { //make very sure we aren't disposed if (_connectionState == ConnectionState.Closed) throw new ObjectDisposedException(ToString()); logger.LogVerbose("Sending {0} Frame with Id {1} over {2}", frame.OpCode, id, this); //write frame to stream, don't use cancelToken to prevent half-written frames if (Scheduler.RunningSynchronously) frameBytes.CopyTo(_writeStream); else await frameBytes.CopyToAsync(_writeStream).AutoConfigureAwait(); //unblock readloop to read result _readLock.Release(); } finally { _writeLock.Release(); frameBytes.Dispose(); } //wait until response is received Frame response = await waitTask.Task.AutoConfigureAwait(); logger.LogVerbose("Received {0} Frame with Id {1} on {2}", response.OpCode, id, this); //read frame content await response.ReadFrameContentAsync().AutoConfigureAwait(); //throw error if result is an error var error = response as ErrorFrame; if (error != null) { //dispose error frame error.Dispose(); //throw exception throw error.Exception; } //check for keyspace change var keyspaceChange = response as ResultFrame; if (keyspaceChange != null && keyspaceChange.CqlResultType == CqlResultType.SetKeyspace) { logger.LogVerbose("{0} changed KeySpace to \"{1}\"", this, keyspaceChange.Keyspace); CurrentKeySpace = keyspaceChange.Keyspace; } //dispose frame, when cancellation requested if (token.IsCancellationRequested) { response.Dispose(); throw new OperationCanceledException(token); } //return response return response; } finally { //return request slot to the pool lock (_availableQueryIds) { _openRequests.Remove(id); _availableQueryIds.Enqueue(id); } if (_connectionState == ConnectionState.Connected) { //allow another frame to be send _frameSubmitLock.Release(); //reduce load, we are done UpdateLoad(-load, logger); } //decrease the amount of operations Interlocked.Decrement(ref _activeRequests); } } catch (OperationCanceledException) { throw; } catch (ProtocolException pex) { switch (pex.Code) { case ErrorCode.IsBootstrapping: case ErrorCode.Overloaded: using (logger.ThreadBinding()) { //IO or node status related error, dispose this connection Close(true); throw; } default: //some other Cql error (syntax ok?), simply rethrow throw; } } catch (ObjectDisposedException odex) { throw new IOException("Connection closed while processing request", odex); } catch (Exception) { using (logger.ThreadBinding()) { //connection collapsed, dispose this connection Close(true); throw; } } }
/// <summary> /// Submits a frame, and waits until response is received /// </summary> /// <param name="frame"> The frame to send. </param> /// <param name="logger"> logger to write progress to </param> /// <param name="load"> the load indication of the request. Used for balancing queries over nodes and connections </param> /// <param name="token"> The token. </param> /// <returns> </returns> public Task<Frame> SendRequestAsync(Frame frame, Logger logger, int load, CancellationToken token) { if (_connectionState == ConnectionState.Closed) throw new ObjectDisposedException(ToString()); if(_connectionState == ConnectionState.Connected && !token.CanBeCanceled) return SendRequestAsyncInternal(frame, logger, load, token); return SendRequestAsyncComplex(frame, logger, load, token); }