joao-r-reis commented on code in PR #1943:
URL: 
https://github.com/apache/cassandra-gocql-driver/pull/1943#discussion_r3623804803


##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch
+)
+
+type ImmutableQuery interface {
+       Statement() string
+       Values() []interface{}
+}
+
+type immutableQuery struct {
+       query *Query
+}
+
+func (q *immutableQuery) Statement() string {
+       return q.query.stmt
+}
+
+func (q *immutableQuery) Values() []interface{} {
+       return q.query.values
+}
+
+type ImmutableBatch interface {
+       Type() BatchType
+       Entries() []BatchEntry
+       Cons() Consistency
+}
+
+type immutableBatch struct {
+       batch *Batch
+}
+
+func (b *immutableBatch) Type() BatchType {
+       return b.batch.Type
+}
+
+func (b *immutableBatch) Entries() []BatchEntry {
+       return b.batch.Entries
+}
+
+func (b *immutableBatch) Cons() Consistency {
+       return b.batch.Cons
+}
+
+type QueryAttempt struct {
+       // The op type, whether query or batch.
+       Type OpType
+       // Only one of Query or Batch will be set, depending on the op type.
+       Query ImmutableQuery
+       Batch ImmutableBatch
+       // The host that will receive the query.
+       Host *HostInfo
+       // The local address of the connection used to execute the query.
+       LocalAddr net.Addr
+       // The remote address of the connection used to execute the query.
+       RemoteAddr net.Addr
+       // The number of this query attempt. 0 for the initial attempt, 1 for 
the first retry, etc. Note that there may be multiple serial Attempts to 
different hosts within a single execution, whether main execution or 
speculative.
+       Attempts int
+       // The index of the speculative execution attempt this attempt is 
associated with, starting with index 1. -1 indicates this is the "main" 
execution.

Review Comment:
   Why -1 and not 0?



##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch
+)
+
+type ImmutableQuery interface {
+       Statement() string
+       Values() []interface{}
+}
+
+type immutableQuery struct {
+       query *Query
+}
+
+func (q *immutableQuery) Statement() string {
+       return q.query.stmt
+}
+
+func (q *immutableQuery) Values() []interface{} {
+       return q.query.values
+}
+
+type ImmutableBatch interface {
+       Type() BatchType
+       Entries() []BatchEntry
+       Cons() Consistency
+}
+
+type immutableBatch struct {
+       batch *Batch
+}
+
+func (b *immutableBatch) Type() BatchType {
+       return b.batch.Type
+}
+
+func (b *immutableBatch) Entries() []BatchEntry {
+       return b.batch.Entries
+}
+
+func (b *immutableBatch) Cons() Consistency {
+       return b.batch.Cons
+}
+
+type QueryAttempt struct {
+       // The op type, whether query or batch.
+       Type OpType
+       // Only one of Query or Batch will be set, depending on the op type.
+       Query ImmutableQuery
+       Batch ImmutableBatch
+       // The host that will receive the query.
+       Host *HostInfo
+       // The local address of the connection used to execute the query.
+       LocalAddr net.Addr
+       // The remote address of the connection used to execute the query.
+       RemoteAddr net.Addr
+       // The number of this query attempt. 0 for the initial attempt, 1 for 
the first retry, etc. Note that there may be multiple serial Attempts to 
different hosts within a single execution, whether main execution or 
speculative.
+       Attempts int
+       // The index of the speculative execution attempt this attempt is 
associated with, starting with index 1. -1 indicates this is the "main" 
execution.
+       SpeculativeExecutionCount int
+}
+
+// QueryAttemptHandler is a function that attempts query execution. The 
interceptor must call this once if it does not return an error.
+type QueryAttemptHandler = func(context.Context) (*Iter, error)
+
+// ExecAttemptInterceptor is the interface implemented by interceptors / 
middleware.
+//
+// Interceptors are well-suited to logic that is not specific to a single 
query or batch, such as flow control.
+type ExecAttemptInterceptor interface {
+       // Intercept is invoked once immediately before a query or batch 
execution attempt, including retry attempts and
+       // speculative execution attempts.
+       //
+       // The interceptor is responsible for calling the `handler` function 
and returning the handler result. If the
+       // interceptor wants to bypass the handler and skip query execution, it 
should return an error. Failure to
+       // return either the handler result or an error will panic.
+       //
+       // Note that there is no affordance to mutate the query or batch at 
this stage -- the handler already encapsulates the original query/batch and 
cannot be modified.

Review Comment:
   This comment is not 100% accurate but it will have to be modified regardless 
once my feedback on this topic is addressed



##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch
+)
+
+type ImmutableQuery interface {
+       Statement() string
+       Values() []interface{}
+}
+
+type immutableQuery struct {
+       query *Query
+}
+
+func (q *immutableQuery) Statement() string {
+       return q.query.stmt
+}
+
+func (q *immutableQuery) Values() []interface{} {
+       return q.query.values
+}
+
+type ImmutableBatch interface {
+       Type() BatchType
+       Entries() []BatchEntry
+       Cons() Consistency
+}
+
+type immutableBatch struct {
+       batch *Batch
+}
+
+func (b *immutableBatch) Type() BatchType {
+       return b.batch.Type
+}
+
+func (b *immutableBatch) Entries() []BatchEntry {
+       return b.batch.Entries
+}
+
+func (b *immutableBatch) Cons() Consistency {
+       return b.batch.Cons
+}
+
+type QueryAttempt struct {
+       // The op type, whether query or batch.
+       Type OpType
+       // Only one of Query or Batch will be set, depending on the op type.
+       Query ImmutableQuery
+       Batch ImmutableBatch
+       // The host that will receive the query.
+       Host *HostInfo
+       // The local address of the connection used to execute the query.
+       LocalAddr net.Addr
+       // The remote address of the connection used to execute the query.
+       RemoteAddr net.Addr
+       // The number of this query attempt. 0 for the initial attempt, 1 for 
the first retry, etc. Note that there may be multiple serial Attempts to 
different hosts within a single execution, whether main execution or 
speculative.
+       Attempts int
+       // The index of the speculative execution attempt this attempt is 
associated with, starting with index 1. -1 indicates this is the "main" 
execution.
+       SpeculativeExecutionCount int
+}
+
+// QueryAttemptHandler is a function that attempts query execution. The 
interceptor must call this once if it does not return an error.
+type QueryAttemptHandler = func(context.Context) (*Iter, error)
+
+// ExecAttemptInterceptor is the interface implemented by interceptors / 
middleware.
+//
+// Interceptors are well-suited to logic that is not specific to a single 
query or batch, such as flow control.
+type ExecAttemptInterceptor interface {

Review Comment:
   We could probably get away with naming this `RequestInterceptor` no? And 
renaming the related types as well



##########
example_interceptor_test.go:
##########
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Content before git sha 34fdeebefcbf183ed7f916f931aa0586fdaa1b40
+ * Copyright (c) 2016, The Gocql authors,
+ * provided under the BSD-3-Clause License.
+ * See the NOTICE file distributed with this work for additional information.
+ */
+
+package gocql_test
+
+import (
+       "context"
+       "log"
+       "time"
+
+       gocql "github.com/apache/cassandra-gocql-driver/v2"
+)
+
+type MyQueryAttemptInterceptor struct {
+       injectFault bool
+}
+
+func (q MyQueryAttemptInterceptor) Intercept(
+       ctx context.Context,
+       attempt gocql.QueryAttempt,
+       handler gocql.QueryAttemptHandler,
+) (*gocql.Iter, error) {
+       switch q := attempt.Statement.Statement().(type) {
+       case *gocql.Query:
+               // Inspect query
+               log.Println(q.Statement())
+       case *gocql.Batch:
+               // Inspect batch
+
+               log.Println(q.Entries[0].Stmt)
+       }

Review Comment:
   I think it's fine if the original Batch and Query objects are exposed here, 
the observers already do this and they have a comment on the field to let users 
know they should only use these as read only (`// Batch object associated with 
this request. Should be used as read only.`). It's not ideal but it's the best 
we can do until we have query and batch types that are **actually** immutable 
without creating an overcomplicated API.
   
   In regards to allowing interceptors to do things like setting the trace I 
think that as long as we have the `QueryAttempt` struct we would have a way to 
add this type of functionality in the future without having to add more ways to 
mutate Query/Batch (e.g. add a field to the struct that interceptor 
implementations are expected to set).



##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch
+)
+
+type ImmutableQuery interface {
+       Statement() string
+       Values() []interface{}
+}
+
+type immutableQuery struct {
+       query *Query
+}
+
+func (q *immutableQuery) Statement() string {
+       return q.query.stmt
+}
+
+func (q *immutableQuery) Values() []interface{} {
+       return q.query.values
+}
+
+type ImmutableBatch interface {
+       Type() BatchType
+       Entries() []BatchEntry
+       Cons() Consistency
+}
+
+type immutableBatch struct {
+       batch *Batch
+}
+
+func (b *immutableBatch) Type() BatchType {
+       return b.batch.Type
+}
+
+func (b *immutableBatch) Entries() []BatchEntry {
+       return b.batch.Entries
+}
+
+func (b *immutableBatch) Cons() Consistency {
+       return b.batch.Cons
+}
+
+type QueryAttempt struct {
+       // The op type, whether query or batch.
+       Type OpType
+       // Only one of Query or Batch will be set, depending on the op type.
+       Query ImmutableQuery
+       Batch ImmutableBatch
+       // The host that will receive the query.
+       Host *HostInfo
+       // The local address of the connection used to execute the query.
+       LocalAddr net.Addr
+       // The remote address of the connection used to execute the query.
+       RemoteAddr net.Addr
+       // The number of this query attempt. 0 for the initial attempt, 1 for 
the first retry, etc. Note that there may be multiple serial Attempts to 
different hosts within a single execution, whether main execution or 
speculative.
+       Attempts int
+       // The index of the speculative execution attempt this attempt is 
associated with, starting with index 1. -1 indicates this is the "main" 
execution.
+       SpeculativeExecutionCount int
+}
+
+// QueryAttemptHandler is a function that attempts query execution. The 
interceptor must call this once if it does not return an error.
+type QueryAttemptHandler = func(context.Context) (*Iter, error)
+
+// ExecAttemptInterceptor is the interface implemented by interceptors / 
middleware.
+//
+// Interceptors are well-suited to logic that is not specific to a single 
query or batch, such as flow control.
+type ExecAttemptInterceptor interface {
+       // Intercept is invoked once immediately before a query or batch 
execution attempt, including retry attempts and
+       // speculative execution attempts.
+       //
+       // The interceptor is responsible for calling the `handler` function 
and returning the handler result. If the
+       // interceptor wants to bypass the handler and skip query execution, it 
should return an error. Failure to
+       // return either the handler result or an error will panic.
+       //
+       // Note that there is no affordance to mutate the query or batch at 
this stage -- the handler already encapsulates the original query/batch and 
cannot be modified.
+       Intercept(ctx context.Context, attempt QueryAttempt, handler 
QueryAttemptHandler) (*Iter, error)
+}
+
+// Use an ExecAttemptInterceptorChain to apply multiple Interceptors at 
Intercept time.
+type ExecAttemptInterceptorChain struct {

Review Comment:
   If we want to follow the 
https://github.com/apache/cassandra-gocql-driver/pull/1929 precedent of adding 
multiplexer types for this type of feature then we should probably use similar 
names. In this case it would probably be `ExecAttemptInterceptorMux`



##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch

Review Comment:
   We already have a `Statement` interface that is implemented by both `Query` 
and `Batch`, maybe we should rename `OpType` to `StatementType`



##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch
+)
+
+type ImmutableQuery interface {
+       Statement() string
+       Values() []interface{}
+}
+
+type immutableQuery struct {
+       query *Query
+}
+
+func (q *immutableQuery) Statement() string {
+       return q.query.stmt
+}
+
+func (q *immutableQuery) Values() []interface{} {
+       return q.query.values
+}
+
+type ImmutableBatch interface {
+       Type() BatchType
+       Entries() []BatchEntry
+       Cons() Consistency
+}
+
+type immutableBatch struct {
+       batch *Batch
+}
+
+func (b *immutableBatch) Type() BatchType {
+       return b.batch.Type
+}
+
+func (b *immutableBatch) Entries() []BatchEntry {
+       return b.batch.Entries
+}
+
+func (b *immutableBatch) Cons() Consistency {
+       return b.batch.Cons
+}
+
+type QueryAttempt struct {
+       // The op type, whether query or batch.
+       Type OpType
+       // Only one of Query or Batch will be set, depending on the op type.
+       Query ImmutableQuery
+       Batch ImmutableBatch
+       // The host that will receive the query.
+       Host *HostInfo
+       // The local address of the connection used to execute the query.
+       LocalAddr net.Addr
+       // The remote address of the connection used to execute the query.
+       RemoteAddr net.Addr
+       // The number of this query attempt. 0 for the initial attempt, 1 for 
the first retry, etc. Note that there may be multiple serial Attempts to 
different hosts within a single execution, whether main execution or 
speculative.
+       Attempts int
+       // The index of the speculative execution attempt this attempt is 
associated with, starting with index 1. -1 indicates this is the "main" 
execution.
+       SpeculativeExecutionCount int
+}
+
+// QueryAttemptHandler is a function that attempts query execution. The 
interceptor must call this once if it does not return an error.
+type QueryAttemptHandler = func(context.Context) (*Iter, error)
+
+// ExecAttemptInterceptor is the interface implemented by interceptors / 
middleware.
+//
+// Interceptors are well-suited to logic that is not specific to a single 
query or batch, such as flow control.
+type ExecAttemptInterceptor interface {
+       // Intercept is invoked once immediately before a query or batch 
execution attempt, including retry attempts and
+       // speculative execution attempts.
+       //
+       // The interceptor is responsible for calling the `handler` function 
and returning the handler result. If the
+       // interceptor wants to bypass the handler and skip query execution, it 
should return an error. Failure to
+       // return either the handler result or an error will panic.
+       //
+       // Note that there is no affordance to mutate the query or batch at 
this stage -- the handler already encapsulates the original query/batch and 
cannot be modified.
+       Intercept(ctx context.Context, attempt QueryAttempt, handler 
QueryAttemptHandler) (*Iter, error)

Review Comment:
   I'm not sure I like having the interceptor being responsible for calling the 
driver code that executes the request. I know this is following the 
"middleware" way of doing things but I'd prefer having two methods on this 
interface like `BeforeAttempt` and `AfterAttempt` so the driver is in full 
control of the request execution and just "checks in" with the interceptor at 
the relevant points in time.



##########
session.go:
##########
@@ -968,26 +969,49 @@ type hostMetrics struct {
        TotalLatency int64
 }
 
+// Query attempts could be started and finished out of order when using 
speculative execution. Therefore,
+// we issue attempt indexes at query start time (so that Interceptors can use 
them) then record those attempts
+// at completion time (so that metrics can correctly express average latency).
 type queryMetrics struct {
-       totalAttempts int64
-       totalLatency  int64
+       l                 sync.RWMutex

Review Comment:
   Do we need this mutex? Can't we keep using the atomic operations?



##########
session.go:
##########
@@ -968,26 +969,49 @@ type hostMetrics struct {
        TotalLatency int64
 }
 
+// Query attempts could be started and finished out of order when using 
speculative execution. Therefore,
+// we issue attempt indexes at query start time (so that Interceptors can use 
them) then record those attempts
+// at completion time (so that metrics can correctly express average latency).
 type queryMetrics struct {
-       totalAttempts int64
-       totalLatency  int64
+       l                 sync.RWMutex
+       attemptsStarted   int
+       attemptsCompleted int
+       totalLatency      int64
 }
 
-func (qm *queryMetrics) attempt(addLatency time.Duration) int {
-       atomic.AddInt64(&qm.totalLatency, addLatency.Nanoseconds())
-       return int(atomic.AddInt64(&qm.totalAttempts, 1) - 1)
+func (qm *queryMetrics) getNextAttempt() int {
+       qm.l.Lock()
+       defer qm.l.Unlock()
+       attempt := qm.attemptsStarted
+       qm.attemptsStarted++
+       return attempt
 }
 
+func (qm *queryMetrics) recordAttempt(attempt int, addLatency time.Duration, s 
*Session) {
+       qm.l.Lock()
+       defer qm.l.Unlock()
+       qm.totalLatency += addLatency.Nanoseconds()
+       qm.attemptsCompleted++
+       if attempt > qm.attemptsCompleted && s != nil {
+               s.logger.Debug("attempt number is greater than total attempts 
completed, this should not happen", NewLogFieldInt("attempt", attempt), 
NewLogFieldInt("attemptsCompleted", qm.attemptsCompleted))

Review Comment:
   If multiple speculative executions are triggered before responses come back 
won't this happen? I think that's expected no?



##########
session.go:
##########
@@ -968,26 +969,49 @@ type hostMetrics struct {
        TotalLatency int64
 }
 
+// Query attempts could be started and finished out of order when using 
speculative execution. Therefore,
+// we issue attempt indexes at query start time (so that Interceptors can use 
them) then record those attempts
+// at completion time (so that metrics can correctly express average latency).
 type queryMetrics struct {
-       totalAttempts int64
-       totalLatency  int64
+       l                 sync.RWMutex
+       attemptsStarted   int
+       attemptsCompleted int
+       totalLatency      int64
 }
 
-func (qm *queryMetrics) attempt(addLatency time.Duration) int {
-       atomic.AddInt64(&qm.totalLatency, addLatency.Nanoseconds())
-       return int(atomic.AddInt64(&qm.totalAttempts, 1) - 1)
+func (qm *queryMetrics) getNextAttempt() int {
+       qm.l.Lock()
+       defer qm.l.Unlock()
+       attempt := qm.attemptsStarted
+       qm.attemptsStarted++
+       return attempt
 }
 
+func (qm *queryMetrics) recordAttempt(attempt int, addLatency time.Duration, s 
*Session) {
+       qm.l.Lock()
+       defer qm.l.Unlock()
+       qm.totalLatency += addLatency.Nanoseconds()
+       qm.attemptsCompleted++
+       if attempt > qm.attemptsCompleted && s != nil {
+               s.logger.Debug("attempt number is greater than total attempts 
completed, this should not happen", NewLogFieldInt("attempt", attempt), 
NewLogFieldInt("attemptsCompleted", qm.attemptsCompleted))
+       }
+}
+
+// Returns total number of attempts started.
 func (qm *queryMetrics) attempts() int {
-       return int(atomic.LoadInt64(&qm.totalAttempts))
+       qm.l.RLock()
+       defer qm.l.RUnlock()
+       return qm.attemptsStarted

Review Comment:
   I know you mention that changing this from "attemptsCompleted" to 
"attemptsStarted" was a deliberate choice but I would prefer not changing the 
behavior of this for existing users with custom retry policies. We can discuss 
this on a new issue/PR dedicated to this topic if needed.



##########
query_executor.go:
##########
@@ -69,16 +71,151 @@ type internalRequest interface {
 }
 
 type queryExecutor struct {
-       pool   *policyConnPool
-       policy HostSelectionPolicy
+       pool        *policyConnPool
+       policy      HostSelectionPolicy
+       interceptor ExecAttemptInterceptor
 }
 
-func (q *queryExecutor) attemptQuery(ctx context.Context, qry internalRequest, 
conn *Conn) *Iter {
+type OpType int
+
+const (
+       OpQuery OpType = iota
+       OpBatch
+)
+
+type ImmutableQuery interface {

Review Comment:
   Even with this API you can still modify the slices that are being returned 
so I don't think there's a way to prevent the interceptors from modifying the 
query/batch completely unless we deep clone these but this would have to be 
opt-in so that applications that don't need to modify them don't have to suffer 
the performance penalty that comes with copying these slices on every request. 
And to make this opt-in the API would have to be much more complicated and I 
don't think it's worth going there.
   
   The observers already expose `Query` and `Batch` directly so I think we 
should do the same here with a comment mentioning that they should be treated 
as read only.
   
   In the future the goal is to have proper immutable Query and Batch types and 
this issue will go away then.



##########
session.go:
##########
@@ -968,26 +969,49 @@ type hostMetrics struct {
        TotalLatency int64
 }
 
+// Query attempts could be started and finished out of order when using 
speculative execution. Therefore,
+// we issue attempt indexes at query start time (so that Interceptors can use 
them) then record those attempts
+// at completion time (so that metrics can correctly express average latency).
 type queryMetrics struct {
-       totalAttempts int64
-       totalLatency  int64
+       l                 sync.RWMutex
+       attemptsStarted   int
+       attemptsCompleted int
+       totalLatency      int64
 }
 
-func (qm *queryMetrics) attempt(addLatency time.Duration) int {
-       atomic.AddInt64(&qm.totalLatency, addLatency.Nanoseconds())
-       return int(atomic.AddInt64(&qm.totalAttempts, 1) - 1)
+func (qm *queryMetrics) getNextAttempt() int {
+       qm.l.Lock()
+       defer qm.l.Unlock()
+       attempt := qm.attemptsStarted
+       qm.attemptsStarted++
+       return attempt
 }
 
+func (qm *queryMetrics) recordAttempt(attempt int, addLatency time.Duration, s 
*Session) {
+       qm.l.Lock()
+       defer qm.l.Unlock()
+       qm.totalLatency += addLatency.Nanoseconds()
+       qm.attemptsCompleted++
+       if attempt > qm.attemptsCompleted && s != nil {
+               s.logger.Debug("attempt number is greater than total attempts 
completed, this should not happen", NewLogFieldInt("attempt", attempt), 
NewLogFieldInt("attemptsCompleted", qm.attemptsCompleted))

Review Comment:
   If you remove this log statement then the session argument can also be 
removed



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to