Skip to content

Commit 9e0b26f

Browse files
Merge pull request #233 from Daniel1984/feature/rate-limit
ability to instantly subscripbe to 20 channels and rate limit afterwa…
2 parents b4f52b0 + 22a1b9b commit 9e0b26f

File tree

4 files changed

+73
-29
lines changed

4 files changed

+73
-29
lines changed

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
3.0.5
2+
- Features
3+
- rate limit to avoid 429 HTTP status codes when subscribing too often
4+
- Fixes
5+
- auth channel payload event name to avoid invalid channel exception
6+
17
3.0.4
28
- Adds new rest v2 functions
39
- tickers/hist

examples/ws/public-feed-ingest/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,32 @@ func main() {
3232

3333
for _, pair := range pairs {
3434
tradePld := event.Subscribe{
35+
Event: "subscribe",
3536
Channel: "trades",
3637
Symbol: "t" + pair,
3738
}
3839

3940
tickPld := event.Subscribe{
41+
Event: "subscribe",
4042
Channel: "ticker",
4143
Symbol: "t" + pair,
4244
}
4345

4446
candlesPld := event.Subscribe{
47+
Event: "subscribe",
4548
Channel: "candles",
4649
Key: "trade:1m:t" + pair,
4750
}
4851

4952
rawBookPld := event.Subscribe{
53+
Event: "subscribe",
5054
Channel: "book",
5155
Precision: "R0",
5256
Symbol: "t" + pair,
5357
}
5458

5559
bookPld := event.Subscribe{
60+
Event: "subscribe",
5661
Channel: "book",
5762
Precision: "P0",
5863
Frequency: "F0",
@@ -67,16 +72,19 @@ func main() {
6772
}
6873

6974
derivStatusPld := event.Subscribe{
75+
Event: "subscribe",
7076
Channel: "status",
7177
Key: "deriv:tBTCF0:USTF0",
7278
}
7379

7480
liqStatusPld := event.Subscribe{
81+
Event: "subscribe",
7582
Channel: "status",
7683
Key: "liq:global",
7784
}
7885

7986
fundingPairTrade := event.Subscribe{
87+
Event: "subscribe",
8088
Channel: "trades",
8189
Symbol: "fUSD",
8290
}

pkg/mux/client/client.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ func (c *Client) Private(key, sec, url string, dms int) (*Client, error) {
8888
// Subscribe takes subscription payload as per docs and subscribes client to it.
8989
// We keep track of subscriptions so that when client failes, we can resubscribe.
9090
func (c *Client) Subscribe(sub event.Subscribe) error {
91-
sub.Event = "subscribe"
9291
if err := c.Send(sub); err != nil {
9392
return err
9493
}

pkg/mux/mux.go

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log"
77
"sync"
8+
"time"
89

910
"github.com/bitfinexcom/bitfinex-api-go/pkg/models/event"
1011
"github.com/bitfinexcom/bitfinex-api-go/pkg/mux/client"
@@ -16,25 +17,32 @@ import (
1617
// to all incomming client messages and reconnect client with all its subscriptions
1718
// in case of a failure
1819
type Mux struct {
19-
cid int
20-
dms int
21-
publicChan chan msg.Msg
22-
publicClients map[int]*client.Client
23-
privateChan chan msg.Msg
24-
closeChan chan bool
25-
privateClient *client.Client
26-
mtx *sync.RWMutex
27-
Err error
28-
transform bool
29-
apikey string
30-
apisec string
31-
subInfo map[int64]event.Info
32-
authenticated bool
33-
publicURL string
34-
authURL string
35-
online bool
20+
cid int
21+
dms int
22+
publicChan chan msg.Msg
23+
publicClients map[int]*client.Client
24+
privateChan chan msg.Msg
25+
closeChan chan bool
26+
privateClient *client.Client
27+
mtx *sync.RWMutex
28+
Err error
29+
transform bool
30+
apikey string
31+
apisec string
32+
subInfo map[int64]event.Info
33+
authenticated bool
34+
publicURL string
35+
authURL string
36+
online bool
37+
rateLimitQueueSize int
3638
}
3739

40+
// api rate limit is 20 calls per minute. 1x3s, 20x1min
41+
const (
42+
rateLimitDuration = 3 * time.Second
43+
maxRateLimitQueueSize = 20
44+
)
45+
3846
// New returns pointer to instance of mux
3947
func New() *Mux {
4048
return &Mux{
@@ -68,7 +76,7 @@ func (m *Mux) WithDeadManSwitch() *Mux {
6876
return m
6977
}
7078

71-
// WithAPISEC accepts and persists api sec
79+
// WithAPISEC accepts and persists api secret
7280
func (m *Mux) WithAPISEC(sec string) *Mux {
7381
m.apisec = sec
7482
return m
@@ -95,17 +103,23 @@ func (m *Mux) Close() bool {
95103
return true
96104
}
97105

98-
// Subscribe - given the details in form of event.Subscribe,
99-
// subscribes client to public channels
106+
// Subscribe - given the details in form of event.Subscribe, subscribes client to public
107+
// channels. If rate limit is reached, calls itself recursively after 1s with same params
100108
func (m *Mux) Subscribe(sub event.Subscribe) *Mux {
101109
if m.Err != nil {
102110
return m
103111
}
104112

105-
m.mtx.Lock()
106-
defer m.mtx.Unlock()
113+
// if limit is reached, wait 1 second and recuresively
114+
// call Subscribe again with same subscription details
115+
if m.rateLimitQueueSize == maxRateLimitQueueSize {
116+
time.Sleep(1 * time.Second)
117+
return m.Subscribe(sub)
118+
}
107119

108-
if subscribed := m.publicClients[m.cid].SubAdded(sub); subscribed {
120+
m.mtx.RLock()
121+
defer m.mtx.RUnlock()
122+
if m.publicClients[m.cid].SubAdded(sub) {
109123
return m
110124
}
111125

@@ -117,6 +131,8 @@ func (m *Mux) Subscribe(sub event.Subscribe) *Mux {
117131
log.Printf("subs limit is reached on cid: %d, spawning new conn\n", m.cid)
118132
m.addPublicClient()
119133
}
134+
135+
m.rateLimitQueueSize++
120136
return m
121137
}
122138

@@ -126,7 +142,9 @@ func (m *Mux) Start() *Mux {
126142
m.addPrivateClient()
127143
}
128144

129-
return m.addPublicClient()
145+
m.watchRateLimit()
146+
m.addPublicClient()
147+
return m
130148
}
131149

132150
// Listen accepts a callback func that will get called each time mux
@@ -138,12 +156,11 @@ func (m *Mux) Listen(cb func(interface{}, error)) error {
138156
}
139157

140158
m.online = true
141-
142159
for {
143160
select {
144161
case ms, ok := <-m.publicChan:
145162
if !ok {
146-
return errors.New("channel has closed unexpectedly")
163+
return errors.New("public channel has closed unexpectedly")
147164
}
148165
if ms.Err != nil {
149166
cb(nil, fmt.Errorf("conn:%d has failed | err:%s | reconnecting", ms.CID, ms.Err))
@@ -179,7 +196,7 @@ func (m *Mux) Listen(cb func(interface{}, error)) error {
179196
cb(nil, fmt.Errorf("unrecognized msg signature: %s", ms.Data))
180197
case ms, ok := <-m.privateChan:
181198
if !ok {
182-
return errors.New("channel has closed unexpectedly")
199+
return errors.New("private channel has closed unexpectedly")
183200
}
184201
if ms.Err != nil {
185202
cb(nil, fmt.Errorf("err: %s | reconnecting", ms.Err))
@@ -286,7 +303,7 @@ func (m *Mux) addPublicClient() *Mux {
286303
c, err := client.
287304
New().
288305
WithID(m.cid).
289-
WithSubsLimit(20).
306+
WithSubsLimit(30).
290307
Public(m.publicURL)
291308
if err != nil {
292309
m.Err = err
@@ -311,3 +328,17 @@ func (m *Mux) addPrivateClient() *Mux {
311328
go c.Read(m.privateChan)
312329
return m
313330
}
331+
332+
// watchRateLimit will run once every rateLimitDuration
333+
// and free up the queue
334+
func (m *Mux) watchRateLimit() {
335+
go func() {
336+
for {
337+
if m.rateLimitQueueSize > 0 {
338+
m.rateLimitQueueSize--
339+
}
340+
341+
time.Sleep(rateLimitDuration)
342+
}
343+
}()
344+
}

0 commit comments

Comments
 (0)