@@ -8,17 +8,74 @@ import (
8
8
"os"
9
9
"path"
10
10
"reflect"
11
+ "sync"
11
12
"time"
12
13
13
14
"github.com/pkg/errors"
14
15
log "github.com/sirupsen/logrus"
15
16
16
17
"github.com/c9s/bbgo/pkg/types"
18
+ "github.com/c9s/bbgo/pkg/util"
19
+ "github.com/c9s/bbgo/pkg/util/backoff"
17
20
)
18
21
19
- type DataFetcher func () (interface {}, error )
22
+ const memCacheExpiry = 5 * time .Minute
23
+ const fileCacheExpiry = 24 * time .Hour
24
+
25
+ var globalMarketMemCache * marketMemCache = newMarketMemCache ()
26
+
27
+ type marketMemCache struct {
28
+ sync.Mutex
29
+ markets map [string ]marketMapWithTime
30
+ }
31
+
32
+ type marketMapWithTime struct {
33
+ updatedAt time.Time
34
+ markets types.MarketMap
35
+ }
36
+
37
+ func newMarketMemCache () * marketMemCache {
38
+ cache := & marketMemCache {
39
+ markets : make (map [string ]marketMapWithTime ),
40
+ }
41
+ return cache
42
+ }
43
+
44
+ func (c * marketMemCache ) IsOutdated (exName string ) bool {
45
+ c .Lock ()
46
+ defer c .Unlock ()
47
+
48
+ data , ok := c .markets [exName ]
49
+ return ! ok || time .Since (data .updatedAt ) > memCacheExpiry
50
+ }
51
+
52
+ func (c * marketMemCache ) Set (exName string , markets types.MarketMap ) {
53
+ c .Lock ()
54
+ defer c .Unlock ()
55
+
56
+ c .markets [exName ] = marketMapWithTime {
57
+ updatedAt : time .Now (),
58
+ markets : markets ,
59
+ }
60
+ }
61
+
62
+ func (c * marketMemCache ) Get (exName string ) (types.MarketMap , bool ) {
63
+ c .Lock ()
64
+ defer c .Unlock ()
65
+
66
+ markets , ok := c .markets [exName ]
67
+ if ! ok {
68
+ return nil , false
69
+ }
20
70
21
- const cacheExpiry = 24 * time .Hour
71
+ copied := types.MarketMap {}
72
+ for key , val := range markets .markets {
73
+ copied [key ] = val
74
+ }
75
+ return copied , true
76
+ }
77
+
78
+ type DataFetcher func () (interface {}, error )
22
79
23
80
// WithCache let you use the cache with the given cache key, variable reference and your data fetcher,
24
81
// The key must be an unique ID.
@@ -29,7 +86,7 @@ func WithCache(key string, obj interface{}, fetcher DataFetcher) error {
29
86
cacheFile := path .Join (cacheDir , key + ".json" )
30
87
31
88
stat , err := os .Stat (cacheFile )
32
- if os .IsNotExist (err ) || (stat != nil && time .Since (stat .ModTime ()) > cacheExpiry ) {
89
+ if os .IsNotExist (err ) || (stat != nil && time .Since (stat .ModTime ()) > fileCacheExpiry ) {
33
90
log .Debugf ("cache %s not found or cache expired, executing fetcher callback to get the data" , cacheFile )
34
91
35
92
data , err := fetcher ()
@@ -70,6 +127,42 @@ func WithCache(key string, obj interface{}, fetcher DataFetcher) error {
70
127
}
71
128
72
129
func LoadExchangeMarketsWithCache (ctx context.Context , ex types.Exchange ) (markets types.MarketMap , err error ) {
130
+ inMem , ok := util .GetEnvVarBool ("USE_MARKETS_CACHE_IN_MEMORY" )
131
+ if ok && inMem {
132
+ return loadMarketsFromMem (ctx , ex )
133
+ }
134
+
135
+ // fallback to use files as cache
136
+ return loadMarketsFromFile (ctx , ex )
137
+ }
138
+
139
+ // loadMarketsFromMem is useful for one process to run multiple bbgos in different go routines.
140
+ func loadMarketsFromMem (ctx context.Context , ex types.Exchange ) (markets types.MarketMap , _ error ) {
141
+ exName := ex .Name ().String ()
142
+ if globalMarketMemCache .IsOutdated (exName ) {
143
+ op := func () error {
144
+ rst , err2 := ex .QueryMarkets (ctx )
145
+ if err2 != nil {
146
+ return err2
147
+ }
148
+
149
+ markets = rst
150
+ globalMarketMemCache .Set (exName , rst )
151
+ return nil
152
+ }
153
+
154
+ if err := backoff .RetryGeneral (ctx , op ); err != nil {
155
+ return nil , err
156
+ }
157
+
158
+ return markets , nil
159
+ }
160
+
161
+ rst , _ := globalMarketMemCache .Get (exName )
162
+ return rst , nil
163
+ }
164
+
165
+ func loadMarketsFromFile (ctx context.Context , ex types.Exchange ) (markets types.MarketMap , err error ) {
73
166
key := fmt .Sprintf ("%s-markets" , ex .Name ())
74
167
if futureExchange , implemented := ex .(types.FuturesExchange ); implemented {
75
168
settings := futureExchange .GetFuturesSettings ()
@@ -82,4 +175,4 @@ func LoadExchangeMarketsWithCache(ctx context.Context, ex types.Exchange) (marke
82
175
return ex .QueryMarkets (ctx )
83
176
})
84
177
return markets , err
85
- }
178
+ }
0 commit comments