File tree Expand file tree Collapse file tree
solutions/46_non_blocking_channel_operations
templates/46_non_blocking_channel_operations Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -241,6 +241,12 @@ concepts:
241241 test_regex : " .*"
242242 hints :
243243 - Use `for i := range ch {...}` syntax to range over a channels.
244+ - slug : 46_non_blocking_channel_operations
245+ title : Non-blocking channel operations
246+ test_regex : " .*"
247+ hints :
248+ - Use `default` case in select to handle non-blocking channel operations.
249+ - Safely increment `dropped` using mutex `Lock()` and `Unlock()`.
244250
245251projects :
246252- slug : 101_text_analyzer
Original file line number Diff line number Diff line change 1+ package non_blocking_channel_operations
2+
3+ import (
4+ "sync"
5+ "time"
6+ )
7+
8+ // Assume case where you have high throughput system
9+ // where you have to drop logs
10+ // if they cannot be processed in time
11+ // in order to keep system throughput high.
12+
13+ type Logger struct {
14+ ch chan string
15+ wg sync.WaitGroup
16+ processed int
17+ dropped int
18+
19+ mu sync.Mutex
20+ }
21+
22+ func NewLogger (bufferSize int ) * Logger {
23+ l := & Logger {
24+ ch : make (chan string , bufferSize ),
25+ }
26+ l .wg .Add (1 )
27+ go l .run ()
28+ return l
29+ }
30+
31+ var processingDelay = 200 * time .Microsecond
32+
33+ // slow log processor
34+ func (l * Logger ) run () {
35+ defer l .wg .Done ()
36+ for msg := range l .ch {
37+ _ = msg
38+ time .Sleep (processingDelay )
39+
40+ l .mu .Lock ()
41+ l .processed ++
42+ l .mu .Unlock ()
43+ }
44+ }
45+
46+ func (l * Logger ) Log (msg string ) {
47+ select {
48+ case l .ch <- msg : // queued
49+ default :
50+ l .mu .Lock ()
51+ l .dropped ++
52+ l .mu .Unlock ()
53+ }
54+ }
55+
56+ // graceful shutdown
57+ func (l * Logger ) Close () {
58+ close (l .ch )
59+ l .wg .Wait ()
60+ }
61+
62+ func (l * Logger ) Stats () (processed , dropped int ) {
63+ l .mu .Lock ()
64+ defer l .mu .Unlock ()
65+ return l .processed , l .dropped
66+ }
Original file line number Diff line number Diff line change 1+ package non_blocking_channel_operations
2+
3+ import (
4+ "sync"
5+ "time"
6+ )
7+
8+ // Assume case where you have high throughput system
9+ // where you have to drop logs
10+ // if they cannot be processed in time,
11+ // in order to keep system throughput high.
12+
13+ type Logger struct {
14+ ch chan string
15+ wg sync.WaitGroup
16+ processed int
17+ dropped int
18+
19+ mu sync.Mutex
20+ }
21+
22+ func NewLogger (bufferSize int ) * Logger {
23+ l := & Logger {
24+ ch : make (chan string , bufferSize ),
25+ }
26+ l .wg .Add (1 )
27+ go l .run ()
28+ return l
29+ }
30+
31+ var processingDelay = 200 * time .Microsecond
32+
33+ // slow log processor
34+ func (l * Logger ) run () {
35+ defer l .wg .Done ()
36+ for msg := range l .ch {
37+ _ = msg
38+ time .Sleep (processingDelay )
39+
40+ l .mu .Lock ()
41+ l .processed ++
42+ l .mu .Unlock ()
43+ }
44+ }
45+
46+ // TODO: Implement logger function in a way
47+ // 1. incoming messages are queued to the channel
48+ // 2. if queue is full, increment dropped
49+ func (l * Logger ) Log (msg string ) {
50+
51+ }
52+
53+ // graceful shutdown
54+ func (l * Logger ) Close () {
55+ close (l .ch )
56+ l .wg .Wait ()
57+ }
58+
59+ func (l * Logger ) Stats () (processed , dropped int ) {
60+ l .mu .Lock ()
61+ defer l .mu .Unlock ()
62+ return l .processed , l .dropped
63+ }
Original file line number Diff line number Diff line change 1+ package non_blocking_channel_operations
2+
3+ import (
4+ "testing"
5+ "time"
6+ )
7+
8+ func TestNonBlockingChannelOperations (t * testing.T ) {
9+
10+ t .Run ("Logs are processed without dropping" , func (t * testing.T ) {
11+
12+ processingDelay = 20 * time .Microsecond
13+
14+ logger := NewLogger (10 )
15+
16+ for range 10 {
17+ logger .Log ("msg" )
18+ }
19+ logger .Close ()
20+
21+ processed , dropped := logger .Stats ()
22+
23+ if dropped != 0 {
24+ t .Fatalf ("Expected no dropped logs, got %d" , dropped )
25+ }
26+ if processed != 10 {
27+ t .Fatalf ("Expected 10 processed logs, got %d" , processed )
28+ }
29+ })
30+
31+ t .Run ("Logs are not dropped" , func (t * testing.T ) {
32+ logger := NewLogger (5 )
33+
34+ for range 10 {
35+ logger .Log ("msg" )
36+ }
37+
38+ logger .Close ()
39+
40+ processed , dropped := logger .Stats ()
41+
42+ if dropped != 5 {
43+ t .Fatalf ("Expected no dropped logs, got %d" , dropped )
44+ }
45+ if processed != 5 {
46+ t .Fatalf ("Expected 10 processed logs, got %d" , processed )
47+ }
48+ })
49+ }
You can’t perform that action at this time.
0 commit comments