code.vegaprotocol.io/vega@v0.79.0/datanode/candlesv2/candle_updates_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package candlesv2_test 17 18 import ( 19 "context" 20 "fmt" 21 "sync" 22 "testing" 23 "time" 24 25 "code.vegaprotocol.io/vega/datanode/candlesv2" 26 "code.vegaprotocol.io/vega/datanode/config/encoding" 27 "code.vegaprotocol.io/vega/datanode/entities" 28 "code.vegaprotocol.io/vega/logging" 29 30 "github.com/shopspring/decimal" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 type nonReturningCandleSource struct{} 36 37 func (t *nonReturningCandleSource) GetCandleDataForTimeSpan(ctx context.Context, candleID string, from *time.Time, to *time.Time, 38 p entities.CursorPagination, 39 ) ([]entities.Candle, entities.PageInfo, error) { 40 for { 41 time.Sleep(1 * time.Second) 42 } 43 } 44 45 type errorsAlwaysCandleSource struct{} 46 47 func (t *errorsAlwaysCandleSource) GetCandleDataForTimeSpan(ctx context.Context, candleID string, from *time.Time, to *time.Time, 48 p entities.CursorPagination, 49 ) ([]entities.Candle, entities.PageInfo, error) { 50 return nil, entities.PageInfo{}, fmt.Errorf("always errors") 51 } 52 53 type testCandleSource struct { 54 candles chan []entities.Candle 55 errorCh chan error 56 } 57 58 func (t *testCandleSource) GetCandleDataForTimeSpan(ctx context.Context, candleID string, from *time.Time, to *time.Time, 59 p entities.CursorPagination, 60 ) ([]entities.Candle, entities.PageInfo, error) { 61 pageInfo := entities.PageInfo{} 62 select { 63 case c := <-t.candles: 64 return c, pageInfo, nil 65 case err := <-t.errorCh: 66 return nil, entities.PageInfo{}, err 67 default: 68 return nil, pageInfo, nil 69 } 70 } 71 72 func TestSubscribeAndUnsubscribeCloseChannelPanic(t *testing.T) { 73 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle, 3), errorCh: make(chan error)} 74 // ensure the sub channels are buffered 75 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 76 testCandleSource, newTestCandleConfig(5).CandleUpdates) 77 startTime := time.Now() 78 79 updated := startTime 80 firstCandle := createCandle(startTime, updated, 1, 1, 1, 1, 10, 200) 81 lastCandle := firstCandle // just for the sake of types 82 fCh := make(chan struct{}) 83 wg := sync.WaitGroup{} 84 wg.Add(1) 85 go func() { 86 defer wg.Done() 87 testCandleSource.candles <- []entities.Candle{firstCandle} 88 close(fCh) 89 // keep updating the most recent candle 90 for i := 0; i < 3; i++ { 91 updated = updated.Add(time.Second * time.Duration(i)) 92 lastCandle = createCandle(startTime, updated, 1, 1, 1, 1, 10, 200) 93 testCandleSource.candles <- []entities.Candle{lastCandle} 94 } 95 }() 96 <-fCh 97 // ensure the first candle is sent 98 sub1Id, out1, _ := updates.Subscribe() 99 sub2Id, out2, _ := updates.Subscribe() 100 101 candle1 := <-out1 102 assert.Equal(t, firstCandle, candle1) 103 104 candle2 := <-out2 105 assert.Equal(t, firstCandle, candle2) 106 107 // unsubscribe the first subscriber 108 updates.Unsubscribe(sub1Id) 109 // now wait for the updates: 110 wg.Wait() 111 sub3Id, out3, _ := updates.Subscribe() 112 candle3 := <-out3 113 require.Equal(t, lastCandle, candle3) 114 // this should unsubscribe sub2 already 115 testCandleSource.errorCh <- fmt.Errorf("transient error") 116 117 // this sub should get instantly unsubscribed. 118 errSub, eOut, _ := updates.Subscribe() 119 require.NotNil(t, eOut) 120 // reading from the channel should indicate it was closed already due to the error 121 // once the channel is closed, the subscriber effectively has to have been removed. 122 _, closed := <-eOut 123 require.True(t, closed) 124 // we can still safely call unsubscribe, though: 125 updates.Unsubscribe(errSub) 126 updates.Unsubscribe(sub2Id) 127 updates.Unsubscribe(sub3Id) 128 } 129 130 func TestSubscribeAndUnsubscribeWhenCandleSourceErrorsAlways(t *testing.T) { 131 errorsAlwaysCandleSource := &errorsAlwaysCandleSource{} 132 133 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 134 errorsAlwaysCandleSource, newTestCandleConfig(0).CandleUpdates) 135 136 sub1Id, _, err1 := updates.Subscribe() 137 sub2Id, _, err2 := updates.Subscribe() 138 require.NoError(t, err1) 139 require.NoError(t, err2) 140 141 updates.Unsubscribe(sub1Id) 142 updates.Unsubscribe(sub2Id) 143 } 144 145 func TestUnsubscribeAfterTransientFailure(t *testing.T) { 146 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle), errorCh: make(chan error)} 147 148 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 149 testCandleSource, newTestCandleConfig(0).CandleUpdates) 150 startTime := time.Now() 151 152 sub1Id, out1, _ := updates.Subscribe() 153 sub2Id, out2, _ := updates.Subscribe() 154 155 firstCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 200) 156 testCandleSource.candles <- []entities.Candle{firstCandle} 157 158 candle1 := <-out1 159 assert.Equal(t, firstCandle, candle1) 160 161 candle2 := <-out2 162 assert.Equal(t, firstCandle, candle2) 163 164 testCandleSource.errorCh <- fmt.Errorf("transient error") 165 166 updates.Unsubscribe(sub1Id) 167 updates.Unsubscribe(sub2Id) 168 } 169 170 func TestSubscribeAfterTransientFailure(t *testing.T) { 171 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle), errorCh: make(chan error)} 172 173 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 174 testCandleSource, newTestCandleConfig(0).CandleUpdates) 175 startTime := time.Now() 176 177 _, out1, _ := updates.Subscribe() 178 _, out2, _ := updates.Subscribe() 179 180 firstCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100) 181 testCandleSource.candles <- []entities.Candle{firstCandle} 182 183 candle1 := <-out1 184 assert.Equal(t, firstCandle, candle1) 185 186 candle2 := <-out2 187 assert.Equal(t, firstCandle, candle2) 188 189 testCandleSource.errorCh <- fmt.Errorf("transient error") 190 191 _, out3, _ := updates.Subscribe() 192 193 candle3 := <-out3 194 assert.Equal(t, firstCandle, candle3) 195 196 secondCandle := createCandle(startTime.Add(1*time.Minute), startTime.Add(1*time.Minute), 2, 2, 2, 2, 20, 100) 197 testCandleSource.candles <- []entities.Candle{secondCandle} 198 199 candle1 = <-out1 200 assert.Equal(t, secondCandle, candle1) 201 202 candle2 = <-out2 203 assert.Equal(t, secondCandle, candle2) 204 205 candle3 = <-out3 206 assert.Equal(t, secondCandle, candle3) 207 } 208 209 func TestSubscribe(t *testing.T) { 210 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)} 211 212 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 213 testCandleSource, newTestCandleConfig(0).CandleUpdates) 214 startTime := time.Now() 215 216 _, out1, _ := updates.Subscribe() 217 _, out2, _ := updates.Subscribe() 218 219 expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100) 220 testCandleSource.candles <- []entities.Candle{expectedCandle} 221 222 candle1 := <-out1 223 assert.Equal(t, expectedCandle, candle1) 224 225 candle2 := <-out2 226 assert.Equal(t, expectedCandle, candle2) 227 228 expectedCandle = createCandle(startTime.Add(1*time.Minute), startTime.Add(1*time.Minute), 2, 2, 2, 2, 20, 100) 229 testCandleSource.candles <- []entities.Candle{expectedCandle} 230 231 candle1 = <-out1 232 assert.Equal(t, expectedCandle, candle1) 233 234 candle2 = <-out2 235 assert.Equal(t, expectedCandle, candle2) 236 } 237 238 func TestUnsubscribe(t *testing.T) { 239 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)} 240 241 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 242 testCandleSource, newTestCandleConfig(0).CandleUpdates) 243 startTime := time.Now() 244 245 id, out1, _ := updates.Subscribe() 246 247 expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100) 248 testCandleSource.candles <- []entities.Candle{expectedCandle} 249 250 candle1 := <-out1 251 assert.Equal(t, expectedCandle, candle1) 252 253 updates.Unsubscribe(id) 254 255 _, ok := <-out1 256 assert.False(t, ok, "candle should be closed") 257 } 258 259 func TestNewSubscriberAlwaysGetsLastCandle(t *testing.T) { 260 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)} 261 262 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 263 testCandleSource, newTestCandleConfig(0).CandleUpdates) 264 startTime := time.Now() 265 266 _, out1, _ := updates.Subscribe() 267 268 expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100) 269 testCandleSource.candles <- []entities.Candle{expectedCandle} 270 271 candle1 := <-out1 272 assert.Equal(t, expectedCandle, candle1) 273 274 _, out2, _ := updates.Subscribe() 275 candle2 := <-out2 276 assert.Equal(t, expectedCandle, candle2) 277 } 278 279 func TestSubscribeWithNonZeroSubscribeBuffer(t *testing.T) { 280 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)} 281 282 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 283 testCandleSource, newTestCandleConfig(100).CandleUpdates) 284 startTime := time.Now() 285 286 _, out1, _ := updates.Subscribe() 287 _, out2, _ := updates.Subscribe() 288 289 expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100) 290 testCandleSource.candles <- []entities.Candle{expectedCandle} 291 292 candle1 := <-out1 293 assert.Equal(t, expectedCandle, candle1) 294 295 candle2 := <-out2 296 assert.Equal(t, expectedCandle, candle2) 297 298 expectedCandle = createCandle(startTime.Add(1*time.Minute), startTime.Add(1*time.Minute), 2, 2, 2, 2, 20, 100) 299 testCandleSource.candles <- []entities.Candle{expectedCandle} 300 301 candle1 = <-out1 302 assert.Equal(t, expectedCandle, candle1) 303 304 candle2 = <-out2 305 assert.Equal(t, expectedCandle, candle2) 306 } 307 308 func TestUnsubscribeWithNonZeroSubscribeBuffer(t *testing.T) { 309 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle)} 310 311 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 312 testCandleSource, newTestCandleConfig(100).CandleUpdates) 313 startTime := time.Now() 314 315 id, out1, _ := updates.Subscribe() 316 317 expectedCandle := createCandle(startTime, startTime, 1, 1, 1, 1, 10, 100) 318 testCandleSource.candles <- []entities.Candle{expectedCandle} 319 320 candle1 := <-out1 321 assert.Equal(t, expectedCandle, candle1) 322 323 updates.Unsubscribe(id) 324 325 _, ok := <-out1 326 assert.False(t, ok, "candle should be closed") 327 } 328 329 func TestSubscribeAndUnSubscribeWithNonReturningSource(t *testing.T) { 330 testCandleSource := &nonReturningCandleSource{} 331 332 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 333 testCandleSource, newTestCandleConfig(100).CandleUpdates) 334 335 subID1, _, _ := updates.Subscribe() 336 subID2, _, _ := updates.Subscribe() 337 338 updates.Unsubscribe(subID1) 339 updates.Unsubscribe(subID2) 340 } 341 342 func TestMultipleSlowConsumers(t *testing.T) { 343 nSends := 100 344 testCandleSource := &testCandleSource{candles: make(chan []entities.Candle, nSends), errorCh: make(chan error)} 345 // ensure the sub channels are buffered 346 updates := candlesv2.NewCandleUpdates(context.Background(), logging.NewTestLogger(), "testCandles", 347 testCandleSource, newTestCandleConfig(5).CandleUpdates) 348 startTime := time.Now() 349 350 updated := startTime 351 firstCandle := createCandle(startTime, updated, 1, 1, 1, 1, 10, 200) 352 lastCandle := firstCandle // just for the sake of types 353 354 wg := sync.WaitGroup{} 355 wg.Add(1) 356 go func() { 357 defer wg.Done() 358 testCandleSource.candles <- []entities.Candle{firstCandle} 359 360 // keep updating the most recent candle 361 for i := 0; i < nSends; i++ { 362 updated = updated.Add(time.Second * time.Duration(i)) 363 lastCandle = createCandle(startTime, updated, 1, 1, 1, 1, 10, 200) 364 testCandleSource.candles <- []entities.Candle{lastCandle, lastCandle, lastCandle} 365 } 366 }() 367 // ensure the first candle is sent 368 _, _, _ = updates.Subscribe() 369 _, _, _ = updates.Subscribe() 370 wg.Wait() 371 } 372 373 func newTestCandleConfig(subscribeBufferSize int) candlesv2.Config { 374 conf := candlesv2.NewDefaultConfig() 375 conf.CandleUpdates = candlesv2.CandleUpdatesConfig{ 376 CandleUpdatesStreamBufferSize: 1, 377 CandleUpdatesStreamInterval: encoding.Duration{Duration: 1 * time.Microsecond}, 378 CandlesFetchTimeout: encoding.Duration{Duration: 2 * time.Minute}, 379 CandleUpdatesStreamSubscriptionMsgBufferSize: subscribeBufferSize, 380 } 381 382 return conf 383 } 384 385 func createCandle(periodStart time.Time, lastUpdate time.Time, open int, close int, high int, low int, volume, notional int) entities.Candle { 386 return entities.Candle{ 387 PeriodStart: periodStart, 388 LastUpdateInPeriod: lastUpdate, 389 Open: decimal.NewFromInt(int64(open)), 390 Close: decimal.NewFromInt(int64(close)), 391 High: decimal.NewFromInt(int64(high)), 392 Low: decimal.NewFromInt(int64(low)), 393 Volume: uint64(volume), 394 Notional: uint64(notional), 395 } 396 }