Skip to content

Commit 413190a

Browse files
committed
Fix local in-memory cache
1 parent 898dcc3 commit 413190a

File tree

8 files changed

+72
-42
lines changed

8 files changed

+72
-42
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
## [0.1.4] - 2020-10-30
10+
911
### Added
1012

1113
- New adapters:
1214
- `linkpool` to get ICE futures quotes
1315
- `onchain` to get ICE futures quotes
1416

17+
### Fixed
18+
19+
- Local in-memory cache got broken in the last release, and was reinitialized on every request.
20+
- `amberdata` adapter wasn't responding for some assets because they don't make cross-rates by default. Cross-rates are now included which enables this adapter to support markets like `BNB/USD`, not just the actual `BNB/USDT`.
21+
1522
## [0.1.3] - 2020-10-29
1623

1724
### Added

bootstrap/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const { Requester, logger } = require('@chainlink/external-adapter')
22
const { types } = require('util')
3-
const { withCache, envOptions } = require('./lib/cache')
3+
const { withCache, defaultOptions, redactOptions } = require('./lib/cache')
44
const util = require('./lib/util')
55
const server = require('./lib/server')
66
const gcp = require('./lib/gcp')
@@ -89,7 +89,7 @@ const expose = (execute, checkHealth) => {
8989
}
9090

9191
// Log cache default options once
92-
const cacheOptions = envOptions()
93-
if (cacheOptions.enabled) logger.info('Cache enabled: ', cacheOptions)
92+
const cacheOptions = defaultOptions()
93+
if (cacheOptions.enabled) logger.info('Cache enabled: ', redactOptions(cacheOptions))
9494

9595
module.exports = { expose, util }

bootstrap/lib/cache/index.js

+30-21
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ const DEFAULT_RC_INTERVAL_COEFFICIENT = 2
1414
const DEFAULT_RC_ENTROPY_MAX = 0
1515

1616
const env = process.env
17-
const envOptions = () => ({
17+
const defaultOptions = () => ({
1818
enabled: parseBool(env.CACHE_ENABLED),
19-
type: env.CACHE_TYPE || DEFAULT_CACHE_TYPE,
20-
local: local.envOptions(),
21-
redis: redis.envOptions(),
19+
cacheOptions: defaultCacheOptions(),
20+
cacheBuilder: defaultCacheBuilder(),
2221
key: {
2322
group: env.CACHE_KEY_GROUP || DEFAULT_CACHE_KEY_GROUP,
2423
ignored: [
@@ -38,31 +37,40 @@ const envOptions = () => ({
3837
entropyMax: Number(env.REQUEST_COALESCING_ENTROPY_MAX) || DEFAULT_RC_ENTROPY_MAX,
3938
},
4039
})
41-
42-
const envOptionsSafe = () => {
43-
const opts = envOptions()
44-
const redis = opts.redis
45-
if (redis.password) redis.password = redis.password.replace(/.+/g, '*****')
46-
if (redis.url) redis.url = redis.url.replace(/:\/\/.+@/g, '://*****@')
47-
return opts
40+
const defaultCacheOptions = () => {
41+
const type = env.CACHE_TYPE || DEFAULT_CACHE_TYPE
42+
const options = type === 'redis' ? redis.defaultOptions() : local.defaultOptions()
43+
return { ...options, type }
4844
}
49-
50-
const getCacheImpl = (options) => {
51-
switch (options.type) {
52-
case 'redis':
53-
return redis.RedisCache.build(options.redis)
54-
default:
55-
return new local.LocalLRUCache(options.local)
45+
// TODO: Revisit this after we stop to reinitialize middleware on every request
46+
// We store the local LRU cache instance, so it's not reinitialized on every request
47+
let localLRUCache
48+
const defaultCacheBuilder = () => {
49+
return (options) => {
50+
switch (options.type) {
51+
case 'redis':
52+
return redis.RedisCache.build(options)
53+
default:
54+
return localLRUCache || (localLRUCache = new local.LocalLRUCache(options))
55+
}
5656
}
5757
}
58+
// Options without sensitive data
59+
const redactOptions = (options) => ({
60+
...options,
61+
cacheOptions:
62+
options.cacheOptions.type === 'redis'
63+
? redis.redactOptions(options.cacheOptions)
64+
: local.redactOptions(options.cacheOptions),
65+
})
5866

5967
const withCache = async (execute, options) => {
6068
// If no options read the env with sensible defaults
61-
if (!options) options = envOptions()
69+
if (!options) options = defaultOptions()
6270
// If disabled noop
6371
if (!options.enabled) return (data) => execute(data)
6472

65-
const cache = await getCacheImpl(options)
73+
const cache = await options.cacheBuilder(options.cacheOptions)
6674

6775
// Algorithm we use to derive entry key
6876
const hashOptions = {
@@ -165,5 +173,6 @@ const withCache = async (execute, options) => {
165173

166174
module.exports = {
167175
withCache,
168-
envOptions: envOptionsSafe,
176+
defaultOptions,
177+
redactOptions,
169178
}

bootstrap/lib/cache/local.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ const DEFAULT_CACHE_MAX_AGE = 1000 * 30 // Maximum age in ms
77
const DEFAULT_CACHE_UPDATE_AGE_ON_GET = false
88

99
const env = process.env
10-
const envOptions = () => ({
10+
const defaultOptions = () => ({
1111
max: Number(env.CACHE_MAX_ITEMS) || DEFAULT_CACHE_MAX_ITEMS,
1212
maxAge: Number(env.CACHE_MAX_AGE) || DEFAULT_CACHE_MAX_AGE,
1313
updateAgeOnGet: parseBool(env.CACHE_UPDATE_AGE_ON_GET) || DEFAULT_CACHE_UPDATE_AGE_ON_GET,
1414
})
15+
// Options without sensitive data
16+
const redactOptions = (opts) => opts
1517

1618
class LocalLRUCache {
1719
constructor(options) {
@@ -38,5 +40,6 @@ class LocalLRUCache {
3840

3941
module.exports = {
4042
LocalLRUCache,
41-
envOptions,
43+
defaultOptions,
44+
redactOptions,
4245
}

bootstrap/lib/cache/redis.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const DEFAULT_CACHE_REDIS_TIMEOUT = 500 // Timeout in ms
1414
const DEFAULT_CACHE_MAX_AGE = 1000 * 30 // Maximum age in ms
1515

1616
const env = process.env
17-
const envOptions = () => ({
17+
const defaultOptions = () => ({
1818
host: env.CACHE_REDIS_HOST || DEFAULT_CACHE_REDIS_HOST,
1919
port: env.CACHE_REDIS_PORT || DEFAULT_CACHE_REDIS_PORT,
2020
path: env.CACHE_REDIS_PATH || DEFAULT_CACHE_REDIS_PATH,
@@ -23,6 +23,12 @@ const envOptions = () => ({
2323
maxAge: Number(env.CACHE_MAX_AGE) || DEFAULT_CACHE_MAX_AGE,
2424
timeout: Number(env.CACHE_REDIS_TIMEOUT) || DEFAULT_CACHE_REDIS_TIMEOUT,
2525
})
26+
// Options without sensitive data
27+
const redactOptions = (opts) => {
28+
if (opts.password) opts.password = opts.password.replace(/.+/g, '*****')
29+
if (opts.url) redis.url = opts.url.replace(/:\/\/.+@/g, '://*****@')
30+
return opts
31+
}
2632

2733
const retryStrategy = (options) => {
2834
logger.warn('Redis retry strategy activated.', options)
@@ -98,5 +104,6 @@ class RedisCache {
98104

99105
module.exports = {
100106
RedisCache,
101-
envOptions,
107+
defaultOptions,
108+
redactOptions,
102109
}

bootstrap/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@chainlink/ea-bootstrap",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"description": "Bootstrap an external adapter with this package",
55
"main": "index.js",
66
"license": "MIT",

bootstrap/test/cache.test.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { expect } = require('chai')
22
const { useFakeTimers } = require('sinon')
3-
const { withCache, envOptions } = require('../lib/cache')
3+
const { withCache, defaultOptions } = require('../lib/cache')
4+
const { LocalLRUCache } = require('../lib/cache/local')
45

56
const callAndExpect = async (fn, n, result) => {
67
while (n--) {
@@ -11,6 +12,8 @@ const callAndExpect = async (fn, n, result) => {
1112

1213
// Helper test function: a stateful counter
1314
const counterFrom = (i = 0) => () => ({ statusCode: 200, data: i++ })
15+
// Build new cache every time
16+
const cacheBuilder = (options) => new LocalLRUCache(options)
1417

1518
describe('cache', () => {
1619
context('options defaults', () => {
@@ -20,7 +23,7 @@ describe('cache', () => {
2023
})
2124

2225
it(`configures env options with cache enabled: false`, () => {
23-
const options = envOptions()
26+
const options = defaultOptions()
2427
expect(options).to.have.property('enabled', false)
2528
})
2629
})
@@ -31,13 +34,13 @@ describe('cache', () => {
3134
})
3235

3336
it(`configures env options with cache enabled: true`, () => {
34-
const options = envOptions()
37+
const options = defaultOptions()
3538
expect(options).to.have.property('enabled', true)
3639
})
3740

3841
it(`configures env options with default maxAge: 1000 * 30`, () => {
39-
const options = envOptions()
40-
expect(options.local).to.have.property('maxAge', 1000 * 30)
42+
const options = defaultOptions()
43+
expect(options.cacheOptions).to.have.property('maxAge', 1000 * 30)
4144
})
4245
})
4346
})
@@ -57,9 +60,11 @@ describe('cache', () => {
5760
})
5861

5962
context('enabled', () => {
63+
let options
6064
let clock
6165
beforeEach(() => {
6266
process.env.CACHE_ENABLED = 'true'
67+
options = { ...defaultOptions(), cacheBuilder }
6368
clock = useFakeTimers()
6469
})
6570

@@ -68,12 +73,12 @@ describe('cache', () => {
6873
})
6974

7075
it(`caches fn result`, async () => {
71-
const counter = await withCache(counterFrom(0))
76+
const counter = await withCache(counterFrom(0), options)
7277
await callAndExpect(counter, 3, 0)
7378
})
7479

7580
it(`caches fn result - while entry still young (under 30s default)`, async () => {
76-
const counter = await withCache(counterFrom(0))
81+
const counter = await withCache(counterFrom(0), options)
7782
await callAndExpect(counter, 3, 0)
7883
await callAndExpect(counter, 3, 0)
7984

@@ -87,7 +92,7 @@ describe('cache', () => {
8792
})
8893

8994
it(`invalidates cache - after default configured maxAge of 30s`, async () => {
90-
const counter = await withCache(counterFrom(0))
95+
const counter = await withCache(counterFrom(0), options)
9196
await callAndExpect(counter, 3, 0)
9297

9398
clock.tick(1000 * 25)
@@ -102,8 +107,7 @@ describe('cache', () => {
102107
})
103108

104109
it(`invalidates cache - after configured maxAge of 10s`, async () => {
105-
const options = envOptions()
106-
options.local.maxAge = 1000 * 10
110+
options.cacheOptions.maxAge = 1000 * 10
107111

108112
const counter = await withCache(counterFrom(0), options)
109113
await callAndExpect(counter, 3, 0)

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@chainlink/external-adapters-js",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
44
"license": "MIT",
55
"private": true,
66
"workspaces": [
@@ -85,8 +85,8 @@
8585
"test:example-start-server": "node ./helpers/server.js"
8686
},
8787
"dependencies": {
88-
"@chainlink/ea-bootstrap": "^0.1.0",
89-
"@chainlink/external-adapter": "^0.2.4",
88+
"@chainlink/ea-bootstrap": "^0.1.3",
89+
"@chainlink/external-adapter": "^0.2.6",
9090
"@zeit/ncc": "^0.22.1",
9191
"boxhock_google-finance-data": "^0.1.0",
9292
"decimal.js": "^10.2.0",

0 commit comments

Comments
 (0)