private async Task PrepareOnTheRestOfTheNodes(PrepareRequest request, IInternalSession session) { Host host; HostDistance distance; var lbp = session.Cluster.Configuration.Policies.LoadBalancingPolicy; var tasks = new List <Task>(); var triedHosts = new Dictionary <IPEndPoint, Exception>(); while ((host = GetNextHost(lbp, out distance)) != null) { var connection = await RequestHandler .GetConnectionFromHostAsync(host, distance, session, triedHosts).ConfigureAwait(false); if (connection == null) { continue; } // For each valid connection, send a the request in parallel tasks.Add(connection.Send(request)); } try { await Task.WhenAll(tasks).ConfigureAwait(false); } catch { // Don't consider individual failures } }
/// <summary> /// Executes the prepare request on the first host selected by the load balancing policy. /// When <see cref="QueryOptions.IsPrepareOnAllHosts"/> is enabled, it prepares on the rest of the hosts in /// parallel. /// </summary> internal static async Task <PreparedStatement> Prepare(IInternalSession session, Serializer serializer, PrepareRequest request) { var cluster = session.InternalCluster; var lbp = cluster.Configuration.Policies.LoadBalancingPolicy; var handler = new PrepareHandler(serializer, lbp.NewQueryPlan(session.Keyspace, null).GetEnumerator()); var ps = await handler.Prepare(request, session, null).ConfigureAwait(false); var psAdded = cluster.PreparedQueries.GetOrAdd(ps.Id, ps); if (ps != psAdded) { Logger.Warning("Re-preparing already prepared query is generally an anti-pattern and will likely " + "affect performance. Consider preparing the statement only once. Query='{0}'", ps.Cql); ps = psAdded; } var prepareOnAllHosts = cluster.Configuration.QueryOptions.IsPrepareOnAllHosts(); if (!prepareOnAllHosts) { return(ps); } await handler.PrepareOnTheRestOfTheNodes(request, session).ConfigureAwait(false); return(ps); }
private async Task <PreparedStatement> GetPreparedStatement( Response response, PrepareRequest request, string keyspace, ICluster cluster) { if (response == null) { throw new DriverInternalError("Response can not be null"); } var resultResponse = response as ResultResponse; if (resultResponse == null) { throw new DriverInternalError("Excepted ResultResponse, obtained " + response.GetType().FullName); } var output = resultResponse.Output; if (!(output is OutputPrepared)) { throw new DriverInternalError("Expected prepared response, obtained " + output.GetType().FullName); } var prepared = (OutputPrepared)output; var ps = new PreparedStatement(prepared.Metadata, prepared.QueryId, prepared.ResultMetadataId, request.Query, keyspace, _serializer) { IncomingPayload = resultResponse.CustomPayload }; await FillRoutingInfo(ps, cluster).ConfigureAwait(false); return(ps); }
private async Task <PreparedStatement> Prepare(PrepareRequest request, IInternalSession session, Dictionary <IPEndPoint, Exception> triedHosts) { if (triedHosts == null) { triedHosts = new Dictionary <IPEndPoint, Exception>(); } // It may throw a NoHostAvailableException which we should yield to the caller var connection = await GetNextConnection(session, triedHosts) .ConfigureAwait(false); Response response; try { response = await connection.Send(request).ConfigureAwait(false); } catch (Exception ex) when(CanBeRetried(ex)) { triedHosts[connection.Address] = ex; return(await Prepare(request, session, triedHosts).ConfigureAwait(false)); } return(await GetPreparedStatement(response, request, connection.Keyspace, session.Cluster) .ConfigureAwait(false)); }
internal static async Task PrepareAllQueries(Host host, ICollection <PreparedStatement> preparedQueries, IEnumerable <IInternalSession> sessions) { if (preparedQueries.Count == 0) { return; } // Get the first connection for that host, in any of the existings connection pool var connection = sessions.SelectMany(s => s.GetExistingPool(host.Address)?.ConnectionsSnapshot) .FirstOrDefault(); if (connection == null) { Logger.Info($"Could not re-prepare queries on {host.Address} as there wasn't an open connection to" + " the node"); return; } Logger.Info($"Re-preparing {preparedQueries.Count} queries on {host.Address}"); var tasks = new List <Task>(preparedQueries.Count); using (var semaphore = new SemaphoreSlim(64, 64)) { foreach (var query in preparedQueries.Select(ps => ps.Cql)) { var request = new PrepareRequest(query); await semaphore.WaitAsync().ConfigureAwait(false); async Task SendSingle() { try { await connection.Send(request).ConfigureAwait(false); } finally { // ReSharper disable once AccessToDisposedClosure // There is no risk of being disposed as the list of tasks is awaited upon below semaphore.Release(); } } tasks.Add(Task.Run(SendSingle)); } try { await Task.WhenAll(tasks).ConfigureAwait(false); } catch (Exception ex) { Logger.Error($"There was an error when re-preparing queries on {host.Address}", ex); } } }
public async Task <PreparedStatement> Prepare( PrepareRequest request, IInternalSession session, IEnumerator <Host> queryPlan) { var prepareResult = await SendRequestToOneNode(session, queryPlan, request).ConfigureAwait(false); if (session.Cluster.Configuration.QueryOptions.IsPrepareOnAllHosts()) { await _reprepareHandler.ReprepareOnAllNodesWithExistingConnections(session, request, prepareResult).ConfigureAwait(false); } return(prepareResult.PreparedStatement); }
/// <inheritdoc /> public async Task ReprepareOnAllNodesWithExistingConnections( IInternalSession session, PrepareRequest request, PrepareResult prepareResult) { var pools = session.GetPools(); var hosts = session.InternalCluster.AllHosts(); var poolsByHosts = pools.Join( hosts, po => po.Key, h => h.Address, (pair, host) => new { host, pair.Value }).ToDictionary(k => k.host, k => k.Value); if (poolsByHosts.Count == 0) { PrepareHandler.Logger.Warning("Could not prepare query on all hosts because there are no connection pools."); return; } using (var semaphore = new SemaphoreSlim(64, 64)) { var tasks = new List <Task>(poolsByHosts.Count); foreach (var poolKvp in poolsByHosts) { if (poolKvp.Key.Address.Equals(prepareResult.HostAddress)) { continue; } if (prepareResult.TriedHosts.ContainsKey(poolKvp.Key.Address)) { PrepareHandler.Logger.Warning( $"An error occured while attempting to prepare query on {{0}}:{Environment.NewLine}{{1}}", poolKvp.Key.Address, prepareResult.TriedHosts[poolKvp.Key.Address]); continue; } await semaphore.WaitAsync().ConfigureAwait(false); tasks.Add(ReprepareOnSingleNodeAsync(poolKvp, prepareResult.PreparedStatement, request, semaphore, false)); } await Task.WhenAll(tasks).ConfigureAwait(false); } }
/// <summary> /// Sends a prepare request before retrying the statement /// </summary> private void PrepareAndRetry(PreparedQueryNotFoundException ex, Host host) { BoundStatement boundStatement = null; if (_parent.Statement is BoundStatement statement1) { boundStatement = statement1; } else if (_parent.Statement is BatchStatement batch) { bool SearchBoundStatement(Statement s) => s is BoundStatement statement && statement.PreparedStatement.Id.SequenceEqual(ex.UnknownId); boundStatement = (BoundStatement)batch.Queries.FirstOrDefault(SearchBoundStatement); } if (boundStatement == null) { throw new DriverInternalError("Expected Bound or batch statement"); } var preparedKeyspace = boundStatement.PreparedStatement.Keyspace; var request = new PrepareRequest(_parent.Serializer, boundStatement.PreparedStatement.Cql, preparedKeyspace, null); if (!_parent.Serializer.ProtocolVersion.SupportsKeyspaceInRequest() && preparedKeyspace != null && _session.Keyspace != preparedKeyspace) { Logger.Warning(string.Format("The statement was prepared using another keyspace, changing the keyspace temporarily to" + " {0} and back to {1}. Use keyspace and table identifiers in your queries and avoid switching keyspaces.", preparedKeyspace, _session.Keyspace)); _connection .SetKeyspace(preparedKeyspace) .ContinueSync(_ => { Send(request, host, NewReprepareResponseHandler(ex)); return(true); }); return; } Send(request, host, NewReprepareResponseHandler(ex)); }
/// <summary> /// Sends a prepare request before retrying the statement /// </summary> private void PrepareAndRetry(byte[] id) { Logger.Info(String.Format("Query {0} is not prepared on {1}, preparing before retrying executing.", BitConverter.ToString(id), _connection.Address)); BoundStatement boundStatement = null; if (_parent.Statement is BoundStatement) { boundStatement = (BoundStatement)_parent.Statement; } else if (_parent.Statement is BatchStatement) { var batch = (BatchStatement)_parent.Statement; bool SearchBoundStatement(Statement s) => s is BoundStatement && ((BoundStatement)s).PreparedStatement.Id.SequenceEqual(id); boundStatement = (BoundStatement)batch.Queries.FirstOrDefault(SearchBoundStatement); } if (boundStatement == null) { throw new DriverInternalError("Expected Bound or batch statement"); } var request = new PrepareRequest(boundStatement.PreparedStatement.Cql); if (boundStatement.PreparedStatement.Keyspace != null && _session.Keyspace != boundStatement.PreparedStatement.Keyspace) { Logger.Warning(String.Format("The statement was prepared using another keyspace, changing the keyspace temporarily to" + " {0} and back to {1}. Use keyspace and table identifiers in your queries and avoid switching keyspaces.", boundStatement.PreparedStatement.Keyspace, _session.Keyspace)); _connection .SetKeyspace(boundStatement.PreparedStatement.Keyspace) .ContinueSync(_ => { Send(request, ReprepareResponseHandler); return(true); }); return; } Send(request, ReprepareResponseHandler); }
private async Task <PrepareResult> SendRequestToOneNode(IInternalSession session, IEnumerator <Host> queryPlan, PrepareRequest request) { var triedHosts = new Dictionary <IPEndPoint, Exception>(); while (true) { // It may throw a NoHostAvailableException which we should yield to the caller var hostConnectionTuple = await GetNextConnection(session, queryPlan, triedHosts).ConfigureAwait(false); var connection = hostConnectionTuple.Item2; var host = hostConnectionTuple.Item1; try { var result = await connection.Send(request).ConfigureAwait(false); return(new PrepareResult { PreparedStatement = await GetPreparedStatement(result, request, request.Keyspace ?? connection.Keyspace, session.Cluster).ConfigureAwait(false), TriedHosts = triedHosts, HostAddress = host.Address }); } catch (Exception ex) when(PrepareHandler.CanBeRetried(ex)) { triedHosts[host.Address] = ex; } } }