/// <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;
}
}
}