Skip to content

Commit dc1fedd

Browse files
committed
receive: Return most common err instead on writeErr cause
Signed-off-by: Saswata Mukherjee <saswataminsta@yahoo.com>
1 parent d90e7d3 commit dc1fedd

File tree

2 files changed

+59
-6
lines changed

2 files changed

+59
-6
lines changed

pkg/receive/handler.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,8 +1305,8 @@ func (es *writeErrors) ErrOrNil() error {
13051305
}
13061306

13071307
// Cause returns the primary cause for a write failure.
1308-
// If multiple errors have occurred, Cause will prefer
1309-
// recoverable over non-recoverable errors.
1308+
// If multiple errors have occurred, Cause will return the most
1309+
// frequently occurring error type.
13101310
func (es *writeErrors) Cause() error {
13111311
if len(es.errs) == 0 {
13121312
return nil
@@ -1336,10 +1336,10 @@ func (es *writeErrors) Cause() error {
13361336
}
13371337
}
13381338

1339-
for _, exp := range expErrs {
1340-
if exp.count > 0 {
1341-
return exp.err
1342-
}
1339+
// Sort by count descending and return the most frequent error.
1340+
sort.Sort(sort.Reverse(expErrs))
1341+
if expErrs[0].count > 0 {
1342+
return expErrs[0].err
13431343
}
13441344

13451345
return unknownErr

pkg/receive/handler_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,3 +2001,56 @@ func TestHandlerFlippingHashrings(t *testing.T) {
20012001
cancel()
20022002
wg.Wait()
20032003
}
2004+
2005+
// TestReplicationErrorsConflictScenario tests what happens in RF=3, Quorum=2
2006+
// when 1 replica is unavailable and 2 replicas return conflict errors.
2007+
func TestReplicationErrorsConflictScenario(t *testing.T) {
2008+
t.Parallel()
2009+
2010+
// Simulate RF=3, Quorum=2 scenario:
2011+
// - 1 replica returns unavailable
2012+
// - 2 replicas return conflict
2013+
errs := &replicationErrors{
2014+
threshold: 2,
2015+
}
2016+
2017+
// Add 1 unavailable error
2018+
errs.Add(errUnavailable)
2019+
// Add 2 conflict errors
2020+
errs.Add(storage.ErrOutOfOrderSample)
2021+
errs.Add(storage.ErrDuplicateSampleForTimestamp)
2022+
2023+
// Call Cause() to see what error is returned
2024+
cause := errs.Cause()
2025+
2026+
// With threshold=2:
2027+
// - conflict count = 2 (>= threshold)
2028+
// - unavailable count = 1 (< threshold)
2029+
// Since conflict count meets the threshold, errConflict is returned
2030+
// This then gets mapped to HTTP 409 Conflict in the handler
2031+
2032+
require.Equal(t, errConflict, cause, "Expected errConflict since 2 conflicts meet the quorum threshold of 2")
2033+
}
2034+
2035+
func TestWriteErrorsConflictScenario(t *testing.T) {
2036+
t.Parallel()
2037+
2038+
errs := &writeErrors{}
2039+
2040+
// Add 1 unavailable error
2041+
errs.Add(errUnavailable)
2042+
// Add 2 conflict errors
2043+
errs.Add(storage.ErrOutOfOrderSample)
2044+
errs.Add(storage.ErrDuplicateSampleForTimestamp)
2045+
2046+
// Call Cause() to see what error is returned
2047+
cause := errs.Cause()
2048+
2049+
// writeErrors.Cause() now returns the most frequent error:
2050+
// - conflict count = 2
2051+
// - unavailable count = 1
2052+
// Since conflict is most frequent, errConflict is returned
2053+
// This maps to HTTP 409 Conflict
2054+
2055+
require.Equal(t, errConflict, cause, "Expected errConflict since it's the most frequent error (2 vs 1)")
2056+
}

0 commit comments

Comments
 (0)