Skip to content

Commit c429a35

Browse files
authored
fix: incorrect subscribe result for 'FlatMap' and 'SwitchIfEmpty' (#33)
1 parent 468ccce commit c429a35

File tree

6 files changed

+105
-29
lines changed

6 files changed

+105
-29
lines changed

mono/flatmap.go

+18-7
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,41 @@ const (
1515
statComplete = 2
1616
)
1717

18+
type flatMapStat int32
19+
20+
const (
21+
_ flatMapStat = iota
22+
flatMapStatInnerReady
23+
flatMapStatInnerComplete
24+
flatMapStatError
25+
flatMapStatComplete
26+
)
27+
1828
type innerFlatMapSubscriber struct {
1929
parent *flatMapSubscriber
2030
}
2131

2232
func (in *innerFlatMapSubscriber) OnError(err error) {
23-
if atomic.CompareAndSwapInt32(&in.parent.stat, 0, statError) {
33+
if atomic.CompareAndSwapInt32(&in.parent.stat, int32(flatMapStatInnerReady), int32(flatMapStatError)) {
2434
in.parent.actual.OnError(err)
2535
}
2636
}
2737

2838
func (in *innerFlatMapSubscriber) OnNext(v Any) {
29-
if atomic.LoadInt32(&in.parent.stat) != 0 {
39+
if atomic.LoadInt32(&in.parent.stat) != int32(flatMapStatInnerReady) {
40+
hooks.Global().OnNextDrop(v)
3041
return
3142
}
3243
in.parent.actual.OnNext(v)
3344
in.OnComplete()
3445
}
3546

36-
func (in *innerFlatMapSubscriber) OnSubscribe(ctx context.Context, s reactor.Subscription) {
47+
func (in *innerFlatMapSubscriber) OnSubscribe(_ context.Context, s reactor.Subscription) {
3748
s.Request(reactor.RequestInfinite)
3849
}
3950

4051
func (in *innerFlatMapSubscriber) OnComplete() {
41-
if atomic.CompareAndSwapInt32(&in.parent.stat, 0, statComplete) {
52+
if atomic.CompareAndSwapInt32(&in.parent.stat, int32(flatMapStatInnerReady), int32(flatMapStatInnerComplete)) {
4253
in.parent.actual.OnComplete()
4354
}
4455
}
@@ -58,21 +69,21 @@ func (p *flatMapSubscriber) Cancel() {
5869
}
5970

6071
func (p *flatMapSubscriber) OnComplete() {
61-
if atomic.LoadInt32(&p.stat) == statComplete {
72+
if atomic.CompareAndSwapInt32(&p.stat, 0, int32(flatMapStatComplete)) {
6273
p.actual.OnComplete()
6374
}
6475
}
6576

6677
func (p *flatMapSubscriber) OnError(err error) {
67-
if !atomic.CompareAndSwapInt32(&p.stat, 0, statError) {
78+
if !atomic.CompareAndSwapInt32(&p.stat, 0, int32(flatMapStatError)) {
6879
hooks.Global().OnErrorDrop(err)
6980
return
7081
}
7182
p.actual.OnError(err)
7283
}
7384

7485
func (p *flatMapSubscriber) OnNext(v Any) {
75-
if atomic.LoadInt32(&p.stat) != 0 {
86+
if !atomic.CompareAndSwapInt32(&p.stat, 0, int32(flatMapStatInnerReady)) {
7687
hooks.Global().OnNextDrop(v)
7788
return
7889
}

mono/flatmap_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package mono_test
33
import (
44
"context"
55
"errors"
6+
"sync/atomic"
67
"testing"
78
"time"
89

910
"github.com/golang/mock/gomock"
1011
"github.com/jjeffcaii/reactor-go"
1112
"github.com/jjeffcaii/reactor-go/mono"
13+
"github.com/jjeffcaii/reactor-go/scheduler"
1214
"github.com/stretchr/testify/assert"
1315
)
1416

@@ -70,3 +72,64 @@ func TestFlatMap_MultipleEmit(t *testing.T) {
7072
assert.NoError(t, err)
7173
assert.Equal(t, 1, res)
7274
}
75+
76+
func TestFlatMapSubscriber_OnComplete(t *testing.T) {
77+
completes := new(int32)
78+
res, err := mono.Just(1).
79+
FlatMap(func(any reactor.Any) mono.Mono {
80+
return mono.Create(func(ctx context.Context, s mono.Sink) {
81+
s.Success(2)
82+
}).SubscribeOn(scheduler.Parallel())
83+
}).
84+
DoOnComplete(func() {
85+
atomic.AddInt32(completes, 1)
86+
}).
87+
Block(context.Background())
88+
assert.NoError(t, err)
89+
assert.Equal(t, 2, res, "bad result")
90+
assert.Equal(t, int32(1), atomic.LoadInt32(completes), "completes should be 1")
91+
92+
ctrl := gomock.NewController(t)
93+
defer ctrl.Finish()
94+
s := mono.NewMockSubscriber(ctrl)
95+
s.EXPECT().OnComplete().Times(1)
96+
s.EXPECT().OnNext(gomock.Eq(2)).Times(1)
97+
s.EXPECT().OnError(gomock.Any()).Times(0)
98+
s.EXPECT().OnSubscribe(gomock.Any(), gomock.Any()).Do(mono.MockRequestInfinite).Times(1)
99+
100+
mono.Just(1).
101+
FlatMap(func(any reactor.Any) mono.Mono {
102+
return mono.Just(2)
103+
}).
104+
SubscribeWith(context.Background(), s)
105+
}
106+
107+
func TestFlatMap_EmptySource(t *testing.T) {
108+
ctrl := gomock.NewController(t)
109+
defer ctrl.Finish()
110+
111+
s := mono.NewMockSubscriber(ctrl)
112+
s.EXPECT().OnComplete().Times(1)
113+
s.EXPECT().OnNext(gomock.Any()).Times(0)
114+
s.EXPECT().OnError(gomock.Any()).Times(0)
115+
s.EXPECT().OnSubscribe(gomock.Any(), gomock.Any()).Do(mono.MockRequestInfinite).Times(1)
116+
117+
mono.Empty().
118+
FlatMap(func(any reactor.Any) mono.Mono {
119+
return mono.Just(1)
120+
}).
121+
SubscribeWith(context.Background(), s)
122+
123+
s = mono.NewMockSubscriber(ctrl)
124+
s.EXPECT().OnComplete().Times(1)
125+
s.EXPECT().OnNext(gomock.Any()).Times(0)
126+
s.EXPECT().OnError(gomock.Any()).Times(0)
127+
s.EXPECT().OnSubscribe(gomock.Any(), gomock.Any()).Do(mono.MockRequestInfinite).Times(1)
128+
129+
mono.Just(1).
130+
FlatMap(func(value reactor.Any) mono.Mono {
131+
assert.Equal(t, 1, value)
132+
return mono.Empty()
133+
}).
134+
SubscribeWith(context.Background(), s)
135+
}

mono/just.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type justSubscriptionPool struct {
1818

1919
func (j *justSubscriptionPool) get() *justSubscription {
2020
if exist, _ := j.inner.Get().(*justSubscription); exist != nil {
21-
exist.n = 0
21+
exist.stat = 0
2222
return exist
2323
}
2424
return &justSubscription{}
@@ -30,7 +30,7 @@ func (j *justSubscriptionPool) put(s *justSubscription) {
3030
}
3131
s.actual = nil
3232
s.parent = nil
33-
atomic.StoreInt32(&s.n, math.MinInt32)
33+
atomic.StoreInt32(&s.stat, math.MinInt32)
3434
j.inner.Put(s)
3535
}
3636

@@ -47,21 +47,23 @@ func newMonoJust(v Any) *monoJust {
4747
type justSubscription struct {
4848
actual reactor.Subscriber
4949
parent *monoJust
50-
n int32
50+
stat int32
5151
}
5252

5353
func (j *justSubscription) Request(n int) {
54+
defer globalJustSubscriptionPool.put(j)
55+
5456
if n < 1 {
57+
j.actual.OnError(errors.Errorf("positive request amount required but it was %d", n))
5558
return
5659
}
5760

58-
if !atomic.CompareAndSwapInt32(&j.n, 0, statComplete) {
61+
if !atomic.CompareAndSwapInt32(&j.stat, 0, statComplete) {
5962
return
6063
}
6164

6265
defer func() {
6366
actual := j.actual
64-
globalJustSubscriptionPool.put(j)
6567

6668
rec := recover()
6769
if rec == nil {
@@ -81,7 +83,7 @@ func (j *justSubscription) Request(n int) {
8183
}
8284

8385
func (j *justSubscription) Cancel() {
84-
if atomic.CompareAndSwapInt32(&j.n, 0, statCancel) {
86+
if atomic.CompareAndSwapInt32(&j.stat, 0, statCancel) {
8587
j.actual.OnError(reactor.ErrSubscribeCancelled)
8688
}
8789
}

mono/just_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ func TestMonoJust_Request(t *testing.T) {
6767
sub := NewMockSubscriber(ctrl)
6868

6969
onSubscribe := func(ctx context.Context, su reactor.Subscription) {
70-
su.Request(0)
7170
su.Request(1)
7271
su.Request(1)
7372
}

mono/mono.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type (
1414
Disposable = reactor.Disposable
1515
)
1616

17-
type FlatMapper func(reactor.Any) Mono
17+
type FlatMapper func(value reactor.Any) Mono
1818
type Combinator func(values ...*reactor.Item) (reactor.Any, error)
1919

2020
// Mono is a Reactive Streams Publisher with basic rx operators that completes successfully by emitting an element, or with an error.

scheduler/elastic_test.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,19 @@ func TestNewElastic(t *testing.T) {
4040
}
4141

4242
func TestElasticBounded(t *testing.T) {
43-
const total = 1000
44-
var wg sync.WaitGroup
45-
wg.Add(total)
46-
start := time.Now()
47-
worker := scheduler.ElasticBounded().Worker()
48-
for range [total]struct{}{} {
49-
err := worker.Do(func() {
50-
time.Sleep(10 * time.Millisecond)
51-
wg.Done()
52-
})
53-
assert.NoError(t, err)
54-
}
55-
wg.Wait()
56-
assert.Less(t, int64(time.Since(start)), int64(20*time.Millisecond), "bad result")
43+
assert.NotPanics(t, func() {
44+
const total = 1000
45+
var wg sync.WaitGroup
46+
wg.Add(total)
47+
worker := scheduler.ElasticBounded().Worker()
48+
for range [total]struct{}{} {
49+
err := worker.Do(func() {
50+
time.Sleep(10 * time.Millisecond)
51+
wg.Done()
52+
})
53+
assert.NoError(t, err)
54+
}
55+
wg.Wait()
56+
})
57+
5758
}

0 commit comments

Comments
 (0)