Skip to content

Add solution for Challenge 20 by hvijaycse#1468

Merged
RezaSi merged 1 commit intoRezaSi:mainfrom
hvijaycse:challenge-20-hvijaycse
Mar 16, 2026
Merged

Add solution for Challenge 20 by hvijaycse#1468
RezaSi merged 1 commit intoRezaSi:mainfrom
hvijaycse:challenge-20-hvijaycse

Conversation

@hvijaycse
Copy link
Contributor

Challenge 20 Solution

Submitted by: @hvijaycse
Challenge: Challenge 20

Description

This PR contains my solution for Challenge 20.

Changes

  • Added solution file to challenge-20/submissions/hvijaycse/solution-template.go

Testing

  • Solution passes all test cases
  • Code follows Go best practices

Thank you for reviewing my submission! 🚀

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

Walkthrough

Introduces a complete Go circuit breaker implementation with state machine transitions (Closed, Open, HalfOpen), thread-safe metrics tracking, configurable trip logic, and operation execution with context support in a single new file.

Changes

Cohort / File(s) Summary
Circuit Breaker Implementation
challenge-20/submissions/hvijaycse/solution-template.go
Adds full circuit breaker pattern with public API (CircuitBreaker interface, State enum, Metrics/Config structs, NewCircuitBreaker constructor), internal state machine (circuitBreakerImpl with canExecute, executeOperationWithContext, setState, recordSuccess/Failure, shouldTrip), error variables (ErrCircuitBreakerOpen, ErrTooManyRequests), and example usage demonstrating configuration and operation calls.

Sequence Diagram

sequenceDiagram
    actor Client
    participant CB as CircuitBreaker
    participant Op as Operation
    
    Client->>CB: Call(ctx, operation)
    CB->>CB: canExecute()
    alt Circuit Closed or Ready
        CB->>Op: executeOperationWithContext()
        Op-->>CB: result/error
        alt Success
            CB->>CB: recordSuccess()
            CB->>CB: setState (HalfOpen→Closed)
        else Failure
            CB->>CB: recordFailure()
            CB->>CB: shouldTrip()
            alt Trip Triggered
                CB->>CB: setState (any→Open)
            else HalfOpen Limit Exceeded
                CB->>CB: setState (HalfOpen→Open)
            end
        end
    else Circuit Open (Timeout Not Reached)
        CB-->>Client: ErrCircuitBreakerOpen
    else Circuit Open (Ready to HalfOpen)
        CB->>CB: setState (Open→HalfOpen)
        CB->>Op: executeOperationWithContext()
    else HalfOpen (At MaxRequests)
        CB-->>Client: ErrTooManyRequests
    end
    CB-->>Client: result/error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A circuit breaker stands so tall,
With states that dance—Closed, Open, Half!
When failures pile up, don't let them fall,
Trip the logic, make it laugh.
Thread-safe hops through context's call,
Metrics tracked—the circuit's math! 📊

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a solution for Challenge 20 by the contributor hvijaycse. It directly relates to the changeset which contains a circuit breaker solution implementation.
Description check ✅ Passed The description is clearly related to the changeset, explaining that it contains the Challenge 20 solution submission with appropriate details about changes, testing, and acknowledgment.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
challenge-20/submissions/hvijaycse/solution-template.go (5)

47-47: Interval field is declared but never used.

The Interval field (described as "Statistical window for closed state") is defined in Config but the implementation never uses it to reset metrics periodically. If this is intentional for a simplified implementation, consider removing it or adding a comment. Otherwise, you may need to implement interval-based metric resets.


211-216: Minor: increment halfOpenRequests only when allowing the request.

Currently, halfOpenRequests is incremented before the limit check, so rejected requests also inflate the counter. While functionally correct (the counter resets on state transitions), incrementing only upon successful admission is cleaner.

♻️ Suggested refactor
 	case StateHalfOpen:
-		cb.halfOpenRequests++
 		if cb.halfOpenRequests > cb.config.MaxRequests {
 			return ErrTooManyRequests
 		}
+		cb.halfOpenRequests++
 		return nil

Note: With this change, the condition should use >= instead of >:

 	case StateHalfOpen:
-		if cb.halfOpenRequests > cb.config.MaxRequests {
+		if cb.halfOpenRequests >= cb.config.MaxRequests {
 			return ErrTooManyRequests
 		}
 		cb.halfOpenRequests++
 		return nil

237-238: Remove commented-out code.

These lines appear to be leftover from development and should be removed.

🧹 Proposed cleanup
 	if cb.state == StateHalfOpen {
 		cb.setState(StateClosed)
-		// cb.state = StateClosed
-		// cb.lastStateChange = time.Now()
 	}

259-260: Remove commented-out code.

These leftover comments should be removed. Note that line 259 has an incorrect comment (StateClosed instead of StateOpen).

🧹 Proposed cleanup
 	if cb.state == StateHalfOpen {
 		cb.setState(StateOpen)
-		// cb.state = StateClosed
-		// cb.lastStateChange = time.Now()
 	} else if cb.state == StateClosed && cb.shouldTrip() {
 		cb.setState(StateOpen)
-		// cb.state = StateOpen
-		// cb.lastStateChange = time.Now()
-
 	}

Also applies to: 263-264


274-276: Remove commented-out code.

The commented-out lock code should be removed. Note that it had a bug (RLock paired with Unlock instead of RUnlock), so it's good this was commented out.

🧹 Proposed cleanup
 func (cb *circuitBreakerImpl) shouldTrip() bool {
 	// Use the ReadyToTrip function from config with current metrics
-	// cb.mutex.RLock()
-	// defer cb.mutex.Unlock()
 	return cb.config.ReadyToTrip(cb.metrics)
 }
 func (cb *circuitBreakerImpl) isReady() bool {
 	// Check if enough time has passed since last state change (Timeout duration)
-	// cb.mutex.RLock()
-	// defer cb.mutex.Unlock()
 	return time.Since(cb.lastStateChange) > cb.config.Timeout
 }

Also applies to: 285-287


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7ac764 and ebdac4b.

📒 Files selected for processing (1)
  • challenge-20/submissions/hvijaycse/solution-template.go

Comment on lines +108 to +123
// function to execute the operation while also keeping a check on context
func executeOperationWithContext(ctx context.Context, operation func() (interface{}, error)) (interface{}, error) {
resultChan := make(chan result)

go func() {
data, err := operation()
resultChan <- result{res: data, err: err}
}()

select {
case <-ctx.Done():
return nil, ctx.Err()
case out := <-resultChan:
return out.res, out.err
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Goroutine leak when context is cancelled.

If ctx.Done() fires before the operation completes, this function returns immediately, but the spawned goroutine continues running. When the operation eventually finishes, the goroutine blocks forever trying to send on the unbuffered resultChan since no receiver exists.

🛠️ Proposed fix using a buffered channel
 func executeOperationWithContext(ctx context.Context, operation func() (interface{}, error)) (interface{}, error) {
-	resultChan := make(chan result)
+	resultChan := make(chan result, 1)
 
 	go func() {
 		data, err := operation()
 		resultChan <- result{res: data, err: err}
 	}()
 
 	select {
 	case <-ctx.Done():
 		return nil, ctx.Err()
 	case out := <-resultChan:
 		return out.res, out.err
 	}
 }

A buffered channel of size 1 allows the goroutine to complete its send even if no receiver is waiting, preventing the leak. Note that the operation itself will still run to completion—this fix only prevents the goroutine from blocking indefinitely.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// function to execute the operation while also keeping a check on context
func executeOperationWithContext(ctx context.Context, operation func() (interface{}, error)) (interface{}, error) {
resultChan := make(chan result)
go func() {
data, err := operation()
resultChan <- result{res: data, err: err}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case out := <-resultChan:
return out.res, out.err
}
}
// function to execute the operation while also keeping a check on context
func executeOperationWithContext(ctx context.Context, operation func() (interface{}, error)) (interface{}, error) {
resultChan := make(chan result, 1)
go func() {
data, err := operation()
resultChan <- result{res: data, err: err}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case out := <-resultChan:
return out.res, out.err
}
}

@RezaSi RezaSi merged commit b971a35 into RezaSi:main Mar 16, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants