-
Notifications
You must be signed in to change notification settings - Fork 482
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Datablock Storage: rate limiting (#58479)
* Implement rate limiting for datablock storage at the block level. * Fixes #55481 --------- Co-authored-by: Cassi Brenci <cassi.brenci@code.org>
- Loading branch information
Showing
3 changed files
with
172 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// rateLimit() throws an error if called more than RATE_LIMIT times per RATE_LIMIT_INTERVAL_MS | ||
// we use this in Applab commands.js to rate limit access to Datablock Storage from student code | ||
let rateLimitAccessLog = []; | ||
export const RATE_LIMIT = 600; | ||
export const RATE_LIMIT_INTERVAL_MS = 60000; | ||
export function rateLimit(now = Date.now()) { | ||
const timeSinceEarliestLog = () => now - rateLimitAccessLog[0]; | ||
|
||
// Drop log entries older than RATE_LIMIT_INTERVAL_MS | ||
while ( | ||
rateLimitAccessLog.length > 0 && | ||
timeSinceEarliestLog() >= RATE_LIMIT_INTERVAL_MS | ||
) { | ||
rateLimitAccessLog.shift(); | ||
} | ||
|
||
// If we're over the rate limit, throw an error | ||
if (rateLimitAccessLog.length >= RATE_LIMIT) { | ||
const waitTime = Math.ceil( | ||
(RATE_LIMIT_INTERVAL_MS - timeSinceEarliestLog()) / 1000 | ||
); | ||
throw new Error( | ||
`Data access rate limit exceeded; Please wait ${waitTime} seconds before retrying. ` + | ||
'The app is reading/writing data too many times per second. ' + | ||
"If you were trying to write data, it wasn't written." | ||
); | ||
} else { | ||
// Log the current access | ||
rateLimitAccessLog.push(now); | ||
} | ||
} | ||
|
||
export function resetRateLimit() { | ||
rateLimitAccessLog = []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import {expect} from '../../util/reconfiguredChai'; | ||
import { | ||
rateLimit, | ||
resetRateLimit, | ||
RATE_LIMIT, | ||
RATE_LIMIT_INTERVAL_MS, | ||
} from '../../../src/storage/rateLimit'; | ||
|
||
describe('DatablockStorage', () => { | ||
beforeEach(() => { | ||
resetRateLimit(); | ||
}); | ||
afterEach(() => { | ||
resetRateLimit(); | ||
}); | ||
|
||
describe('rate limiting', () => { | ||
it('succeeds if calling less times than the rate limit', done => { | ||
const now = Date.now(); | ||
|
||
for (let i = 0; i < RATE_LIMIT; i++) { | ||
const time = now + i; | ||
rateLimit(time); | ||
} | ||
|
||
done(); | ||
}); | ||
it('fails if called one more time than the rate limit', done => { | ||
const now = Date.now(); | ||
|
||
for (let i = 0; i < RATE_LIMIT; i++) { | ||
const time = now + i; | ||
rateLimit(time); | ||
} | ||
|
||
// This should be over the rate limit | ||
expect(() => rateLimit(now + RATE_LIMIT)).to.throw(Error); | ||
|
||
done(); | ||
}); | ||
it('it succeeds if called more than the rate limit, but after waiting rate limit interval', done => { | ||
let now = Date.now(); | ||
|
||
for (let i = 0; i < RATE_LIMIT; i++) { | ||
const time = now + i; | ||
rateLimit(time); | ||
} | ||
|
||
now += RATE_LIMIT_INTERVAL_MS; | ||
for (let i = 0; i < RATE_LIMIT; i++) { | ||
const time = now + i; | ||
rateLimit(time); | ||
} | ||
done(); | ||
}); | ||
}); | ||
}); |