Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(scheduler-utils): do not cache the redirected url in the byOwner cache #548

Merged
merged 2 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions scheduler-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ import { locate } from "@permaweb/ao-scheduler-utils";
let { url, address } = await locate('<process-id>');
```

If you already know the `Scheduler` used by the process, you can provide it as a second parameter to `locate` as `schedulerHint`:

```js
import { locate } from "@permaweb/ao-scheduler-utils";

let { url, address } = await locate('<process-id>', 'scheduler-owner-id');
```

This will skip querying the gateway for the process, in order to obtain it's `Scheduler` tag, and instead will directly query for the `Scheduler-Location` record.

> This is useful when a process has just been spawned, so might not be indexed by the gateway yet.


#### `validate`

Check whether the wallet address is a valid `ao` Scheduler
Expand Down
27 changes: 12 additions & 15 deletions scheduler-utils/src/index.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,36 @@ const DEFAULT_GATEWAY_URL = 'https://arweave.net'
* @param {ConnectParams} [params]
*/
export function connect ({ cacheSize = 100, GATEWAY_URL = DEFAULT_GATEWAY_URL, followRedirects = false } = {}) {
const cache = InMemoryClient.createLruCache({ size: cacheSize })

const getByOwner = InMemoryClient.getByOwnerWith({ cache })
const getByProcess = InMemoryClient.getByProcessWith({ cache })
const setByOwner = InMemoryClient.setByOwnerWith({ cache })
const setByProcess = InMemoryClient.setByProcessWith({ cache })
const _cache = InMemoryClient.createLruCache({ size: cacheSize })

const loadScheduler = GatewayClient.loadSchedulerWith({ fetch, GATEWAY_URL })
const cache = {
getByProcess: InMemoryClient.getByProcessWith({ cache: _cache }),
getByOwner: InMemoryClient.getByOwnerWith({ cache: _cache }),
setByProcess: InMemoryClient.setByProcessWith({ cache: _cache }),
setByOwner: InMemoryClient.setByOwnerWith({ cache: _cache })
}
/**
* Locate the scheduler for the given process.
*/
const locate = locateWith({
loadProcessScheduler: GatewayClient.loadProcessSchedulerWith({ fetch, GATEWAY_URL }),
cache: { getByProcess, getByOwner, setByProcess, setByOwner },
loadScheduler,
cache,
followRedirects,
checkForRedirect: SchedulerClient.checkForRedirectWith({ fetch })
})

/**
* Validate whether the given wallet address is an ao Scheduler
*/
const validate = validateWith({
loadScheduler: GatewayClient.loadSchedulerWith({ fetch, GATEWAY_URL }),
cache: { getByProcess, getByOwner, setByProcess, setByOwner }
})
const validate = validateWith({ loadScheduler, cache })

/**
* Return the `Scheduler-Location` record for the address
* or undefined, if it cannot be found
*/
const raw = rawWith({
loadScheduler: GatewayClient.loadSchedulerWith({ fetch, GATEWAY_URL }),
cache: { getByProcess, getByOwner, setByProcess, setByOwner }
})
const raw = rawWith({ loadScheduler, cache })

return { locate, validate, raw }
}
65 changes: 40 additions & 25 deletions scheduler-utils/src/locate.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,51 @@
export function locateWith ({ loadProcessScheduler, cache, followRedirects, checkForRedirect }) {
const cacheResults = async (process, scheduler, url) => {
const res = { url, address: scheduler.owner }
await Promise.all([
cache.setByProcess(process, res, scheduler.ttl),
cache.setByOwner(scheduler.owner, url, scheduler.ttl)
])
return res
}

export function locateWith ({ loadProcessScheduler, loadScheduler, cache, followRedirects, checkForRedirect }) {
/**
* Locate the scheduler for the given process.
*
* Later on, this implementation could encompass the automatic swapping
* of decentralized sequencers
*
* @param {string} process - the id of the process
* @param {string} [schedulerHint] - the id of owner of the scheduler, which prevents having to query the process
* from a gateway, and instead skips to querying Scheduler-Location
* @returns {Promise<{ url: string, address: string }>} - an object whose url field is the Scheduler Location
*/
return (process) =>
cache.getByProcess(process).then(cached => {
if (cached) return cached
return loadProcessScheduler(process).then(async scheduler => {
let finalUrl = scheduler.url
/**
* If following redirects, then the initial request will be
* to a router. So we go hit the router and cache the
* redirected url for performance.
*/
if (followRedirects) {
finalUrl = await checkForRedirect(scheduler.url, process)
}
return cacheResults(process, scheduler, finalUrl)
return (process, schedulerHint) =>
cache.getByProcess(process)
.then(async (cached) => {
if (cached) return cached

return Promise.resolve()
.then(async () => {
/**
* The the scheduler hint was provided,
* so skip querying the process and instead
* query the Scheduler-Location record directly
*/
if (schedulerHint) {
const byOwner = await cache.getByOwner(schedulerHint)
if (byOwner) return (byOwner)

return loadScheduler(schedulerHint).then((scheduler) => {
cache.setByOwner(scheduler.owner, scheduler.url, scheduler.ttl)
return scheduler
})
}

return loadProcessScheduler(process)
})
.then(async (scheduler) => {
let finalUrl = scheduler.url
/**
* If following redirects, then the initial request will be
* to a router. So we go hit the router and cache the
* redirected url for performance.
*/
if (followRedirects) finalUrl = await checkForRedirect(scheduler.url, process)

const byProcess = { url: finalUrl, address: scheduler.owner }
await cache.setByProcess(process, byProcess, scheduler.ttl)
return byProcess
})
})
})
}
89 changes: 87 additions & 2 deletions scheduler-utils/src/locate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('locateWith', () => {
assert.equal(process, PROCESS)
return { url: DOMAIN, ttl: TEN_MS, owner: SCHEDULER }
},
loadScheduler: async () => assert.fail('should not load the scheduler if no hint'),
cache: {
getByProcess: async (process) => {
assert.equal(process, PROCESS)
Expand Down Expand Up @@ -45,11 +46,15 @@ describe('locateWith', () => {
loadProcessScheduler: async () => {
assert.fail('should never call on chain if in cache')
},
loadScheduler: async () => assert.fail('should not load the scheduler if no hint'),
cache: {
getByProcess: async (process) => {
assert.equal(process, PROCESS)
return { url: DOMAIN, address: SCHEDULER }
}
},
getByOwner: async () => assert.fail('should not check cache by owner if cached by process'),
setByProcess: async () => assert.fail('should not set cache by process if cached by process'),
setByOwner: async () => assert.fail('should not set cache by owner if cached by process')
}
})

Expand All @@ -63,6 +68,7 @@ describe('locateWith', () => {
assert.equal(process, PROCESS)
return { url: DOMAIN, ttl: TEN_MS, owner: SCHEDULER }
},
loadScheduler: async () => assert.fail('should not load the scheduler if no hint'),
cache: {
getByProcess: async (process) => {
assert.equal(process, PROCESS)
Expand All @@ -76,7 +82,10 @@ describe('locateWith', () => {
},
setByOwner: async (owner, url, ttl) => {
assert.equal(owner, SCHEDULER)
assert.equal(url, DOMAIN_REDIRECT)
/**
* Original DOMAIN not the redirect
*/
assert.equal(url, DOMAIN)
assert.equal(ttl, TEN_MS)
}
},
Expand All @@ -91,4 +100,80 @@ describe('locateWith', () => {
await locate(PROCESS)
.then((res) => assert.deepStrictEqual(res, { url: DOMAIN_REDIRECT, address: SCHEDULER }))
})

test('should use the scheduler hint and skip querying for the process', async () => {
const locate = locateWith({
loadProcessScheduler: async () => assert.fail('should not load process if given a scheduler hint'),
loadScheduler: async (owner) => {
assert.equal(owner, SCHEDULER)
return { url: DOMAIN, ttl: TEN_MS, owner: SCHEDULER }
},
cache: {
getByProcess: async (process) => {
assert.equal(process, PROCESS)
return undefined
},
getByOwner: async (owner) => {
assert.equal(owner, SCHEDULER)
return undefined
},
setByProcess: async (process, { url, address }, ttl) => {
assert.equal(process, PROCESS)
assert.equal(url, DOMAIN_REDIRECT)
assert.equal(address, SCHEDULER)
assert.equal(ttl, TEN_MS)
},
setByOwner: async (owner, url, ttl) => {
assert.equal(owner, SCHEDULER)
/**
* Original DOMAIN not the redirect
*/
assert.equal(url, DOMAIN)
assert.equal(ttl, TEN_MS)
}
},
followRedirects: true,
checkForRedirect: async (url, process) => {
assert.equal(process, PROCESS)
assert.equal(url, DOMAIN)
return DOMAIN_REDIRECT
}
})

await locate(PROCESS, SCHEDULER)
.then((res) => assert.deepStrictEqual(res, { url: DOMAIN_REDIRECT, address: SCHEDULER }))
})

test('should use the scheduler hint and use the cached owner', async () => {
const locate = locateWith({
loadProcessScheduler: async () => assert.fail('should not load process if given a scheduler hint'),
loadScheduler: async () => assert.fail('should not load the scheduler if cached'),
cache: {
getByProcess: async (process) => {
assert.equal(process, PROCESS)
return undefined
},
getByOwner: async (owner) => {
assert.equal(owner, SCHEDULER)
return { url: DOMAIN, ttl: TEN_MS, owner: SCHEDULER }
},
setByProcess: async (process, { url, address }, ttl) => {
assert.equal(process, PROCESS)
assert.equal(url, DOMAIN_REDIRECT)
assert.equal(address, SCHEDULER)
assert.equal(ttl, TEN_MS)
},
setByOwner: async () => assert.fail('should not cache by owner if cached')
},
followRedirects: true,
checkForRedirect: async (url, process) => {
assert.equal(process, PROCESS)
assert.equal(url, DOMAIN)
return DOMAIN_REDIRECT
}
})

await locate(PROCESS, SCHEDULER)
.then((res) => assert.deepStrictEqual(res, { url: DOMAIN_REDIRECT, address: SCHEDULER }))
})
})