Skip to content

Commit

Permalink
Merge pull request #128 from smartcontractkit/release/v0.1.4
Browse files Browse the repository at this point in the history
Fix local in-memory cache
  • Loading branch information
krebernisak authored Nov 1, 2020
2 parents 898dcc3 + 413190a commit 56f0b08
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 42 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

## [0.1.4] - 2020-10-30

### Added

- New adapters:
- `linkpool` to get ICE futures quotes
- `onchain` to get ICE futures quotes

### Fixed

- Local in-memory cache got broken in the last release, and was reinitialized on every request.
- `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`.

## [0.1.3] - 2020-10-29

### Added
Expand Down
6 changes: 3 additions & 3 deletions bootstrap/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { Requester, logger } = require('@chainlink/external-adapter')
const { types } = require('util')
const { withCache, envOptions } = require('./lib/cache')
const { withCache, defaultOptions, redactOptions } = require('./lib/cache')
const util = require('./lib/util')
const server = require('./lib/server')
const gcp = require('./lib/gcp')
Expand Down Expand Up @@ -89,7 +89,7 @@ const expose = (execute, checkHealth) => {
}

// Log cache default options once
const cacheOptions = envOptions()
if (cacheOptions.enabled) logger.info('Cache enabled: ', cacheOptions)
const cacheOptions = defaultOptions()
if (cacheOptions.enabled) logger.info('Cache enabled: ', redactOptions(cacheOptions))

module.exports = { expose, util }
51 changes: 30 additions & 21 deletions bootstrap/lib/cache/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ const DEFAULT_RC_INTERVAL_COEFFICIENT = 2
const DEFAULT_RC_ENTROPY_MAX = 0

const env = process.env
const envOptions = () => ({
const defaultOptions = () => ({
enabled: parseBool(env.CACHE_ENABLED),
type: env.CACHE_TYPE || DEFAULT_CACHE_TYPE,
local: local.envOptions(),
redis: redis.envOptions(),
cacheOptions: defaultCacheOptions(),
cacheBuilder: defaultCacheBuilder(),
key: {
group: env.CACHE_KEY_GROUP || DEFAULT_CACHE_KEY_GROUP,
ignored: [
Expand All @@ -38,31 +37,40 @@ const envOptions = () => ({
entropyMax: Number(env.REQUEST_COALESCING_ENTROPY_MAX) || DEFAULT_RC_ENTROPY_MAX,
},
})

const envOptionsSafe = () => {
const opts = envOptions()
const redis = opts.redis
if (redis.password) redis.password = redis.password.replace(/.+/g, '*****')
if (redis.url) redis.url = redis.url.replace(/:\/\/.+@/g, '://*****@')
return opts
const defaultCacheOptions = () => {
const type = env.CACHE_TYPE || DEFAULT_CACHE_TYPE
const options = type === 'redis' ? redis.defaultOptions() : local.defaultOptions()
return { ...options, type }
}

const getCacheImpl = (options) => {
switch (options.type) {
case 'redis':
return redis.RedisCache.build(options.redis)
default:
return new local.LocalLRUCache(options.local)
// TODO: Revisit this after we stop to reinitialize middleware on every request
// We store the local LRU cache instance, so it's not reinitialized on every request
let localLRUCache
const defaultCacheBuilder = () => {
return (options) => {
switch (options.type) {
case 'redis':
return redis.RedisCache.build(options)
default:
return localLRUCache || (localLRUCache = new local.LocalLRUCache(options))
}
}
}
// Options without sensitive data
const redactOptions = (options) => ({
...options,
cacheOptions:
options.cacheOptions.type === 'redis'
? redis.redactOptions(options.cacheOptions)
: local.redactOptions(options.cacheOptions),
})

const withCache = async (execute, options) => {
// If no options read the env with sensible defaults
if (!options) options = envOptions()
if (!options) options = defaultOptions()
// If disabled noop
if (!options.enabled) return (data) => execute(data)

const cache = await getCacheImpl(options)
const cache = await options.cacheBuilder(options.cacheOptions)

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

module.exports = {
withCache,
envOptions: envOptionsSafe,
defaultOptions,
redactOptions,
}
7 changes: 5 additions & 2 deletions bootstrap/lib/cache/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ const DEFAULT_CACHE_MAX_AGE = 1000 * 30 // Maximum age in ms
const DEFAULT_CACHE_UPDATE_AGE_ON_GET = false

const env = process.env
const envOptions = () => ({
const defaultOptions = () => ({
max: Number(env.CACHE_MAX_ITEMS) || DEFAULT_CACHE_MAX_ITEMS,
maxAge: Number(env.CACHE_MAX_AGE) || DEFAULT_CACHE_MAX_AGE,
updateAgeOnGet: parseBool(env.CACHE_UPDATE_AGE_ON_GET) || DEFAULT_CACHE_UPDATE_AGE_ON_GET,
})
// Options without sensitive data
const redactOptions = (opts) => opts

class LocalLRUCache {
constructor(options) {
Expand All @@ -38,5 +40,6 @@ class LocalLRUCache {

module.exports = {
LocalLRUCache,
envOptions,
defaultOptions,
redactOptions,
}
11 changes: 9 additions & 2 deletions bootstrap/lib/cache/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DEFAULT_CACHE_REDIS_TIMEOUT = 500 // Timeout in ms
const DEFAULT_CACHE_MAX_AGE = 1000 * 30 // Maximum age in ms

const env = process.env
const envOptions = () => ({
const defaultOptions = () => ({
host: env.CACHE_REDIS_HOST || DEFAULT_CACHE_REDIS_HOST,
port: env.CACHE_REDIS_PORT || DEFAULT_CACHE_REDIS_PORT,
path: env.CACHE_REDIS_PATH || DEFAULT_CACHE_REDIS_PATH,
Expand All @@ -23,6 +23,12 @@ const envOptions = () => ({
maxAge: Number(env.CACHE_MAX_AGE) || DEFAULT_CACHE_MAX_AGE,
timeout: Number(env.CACHE_REDIS_TIMEOUT) || DEFAULT_CACHE_REDIS_TIMEOUT,
})
// Options without sensitive data
const redactOptions = (opts) => {
if (opts.password) opts.password = opts.password.replace(/.+/g, '*****')
if (opts.url) redis.url = opts.url.replace(/:\/\/.+@/g, '://*****@')
return opts
}

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

module.exports = {
RedisCache,
envOptions,
defaultOptions,
redactOptions,
}
2 changes: 1 addition & 1 deletion bootstrap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/ea-bootstrap",
"version": "0.1.2",
"version": "0.1.3",
"description": "Bootstrap an external adapter with this package",
"main": "index.js",
"license": "MIT",
Expand Down
24 changes: 14 additions & 10 deletions bootstrap/test/cache.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { expect } = require('chai')
const { useFakeTimers } = require('sinon')
const { withCache, envOptions } = require('../lib/cache')
const { withCache, defaultOptions } = require('../lib/cache')
const { LocalLRUCache } = require('../lib/cache/local')

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

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

describe('cache', () => {
context('options defaults', () => {
Expand All @@ -20,7 +23,7 @@ describe('cache', () => {
})

it(`configures env options with cache enabled: false`, () => {
const options = envOptions()
const options = defaultOptions()
expect(options).to.have.property('enabled', false)
})
})
Expand All @@ -31,13 +34,13 @@ describe('cache', () => {
})

it(`configures env options with cache enabled: true`, () => {
const options = envOptions()
const options = defaultOptions()
expect(options).to.have.property('enabled', true)
})

it(`configures env options with default maxAge: 1000 * 30`, () => {
const options = envOptions()
expect(options.local).to.have.property('maxAge', 1000 * 30)
const options = defaultOptions()
expect(options.cacheOptions).to.have.property('maxAge', 1000 * 30)
})
})
})
Expand All @@ -57,9 +60,11 @@ describe('cache', () => {
})

context('enabled', () => {
let options
let clock
beforeEach(() => {
process.env.CACHE_ENABLED = 'true'
options = { ...defaultOptions(), cacheBuilder }
clock = useFakeTimers()
})

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

it(`caches fn result`, async () => {
const counter = await withCache(counterFrom(0))
const counter = await withCache(counterFrom(0), options)
await callAndExpect(counter, 3, 0)
})

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

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

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

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

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

const counter = await withCache(counterFrom(0), options)
await callAndExpect(counter, 3, 0)
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/external-adapters-js",
"version": "0.1.3",
"version": "0.1.4",
"license": "MIT",
"private": true,
"workspaces": [
Expand Down Expand Up @@ -85,8 +85,8 @@
"test:example-start-server": "node ./helpers/server.js"
},
"dependencies": {
"@chainlink/ea-bootstrap": "^0.1.0",
"@chainlink/external-adapter": "^0.2.4",
"@chainlink/ea-bootstrap": "^0.1.3",
"@chainlink/external-adapter": "^0.2.6",
"@zeit/ncc": "^0.22.1",
"boxhock_google-finance-data": "^0.1.0",
"decimal.js": "^10.2.0",
Expand Down

0 comments on commit 56f0b08

Please sign in to comment.