Skip to content

Commit d102d16

Browse files
committed
fix(endpoint-cache): revert removal of additional logic in get operation
TimeStream Service Developer Guide refers to CachePeriodInMinutes as Time to Live (TTL) value. It also specifies calls to be made for the duration of TTL. Refs: https://a.co/2e8J7xT
1 parent 521b4d1 commit d102d16

File tree

3 files changed

+84
-32
lines changed

3 files changed

+84
-32
lines changed

packages/endpoint-cache/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ You probably shouldn't, at least directly.
1313

1414
- uses `mnemonist/lru-cache` for storing the cache.
1515
- the `set` operation stores milliseconds elapsed since the UNIX epoch in Expires param based on CachePeriodInMinutes provided in Endpoint.
16-
- the `get` operation returns all endpoints with their Expires values.
16+
- the `get` operation returns all un-expired endpoints with their Expires values.
17+
- the `getEndpoint` operation returns a randomly selected un-expired endpoint.

packages/endpoint-cache/src/EndpointCache.spec.ts

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ describe(EndpointCache.name, () => {
2828
Expires: now + CachePeriodInMinutes * 60 * 1000,
2929
}));
3030

31+
const getMaxCachePeriodInMins = (endpoints: Endpoint[]) =>
32+
Math.max(...endpoints.map((endpoint) => endpoint.CachePeriodInMinutes));
33+
3134
beforeEach(() => {
3235
((LRUCache as unknown) as jest.Mock).mockReturnValueOnce({
3336
set,
@@ -57,41 +60,71 @@ describe(EndpointCache.name, () => {
5760
jest.spyOn(Date, "now").mockImplementation(() => now);
5861
});
5962

60-
describe("returns undefined", () => {
61-
it("if cache doesn't have key", () => {
62-
has.mockReturnValueOnce(false);
63-
expect(endpointCache.get(key)).toBeUndefined();
64-
expect(has).toHaveBeenCalledWith(key);
65-
expect(peek).not.toHaveBeenCalled();
66-
expect(get).not.toHaveBeenCalled();
67-
expect(set).not.toHaveBeenCalled();
68-
});
63+
const verifyHasAndGetCalls = () => {
64+
expect(has).toHaveBeenCalledTimes(1);
65+
expect(has).toHaveBeenCalledWith(key);
66+
expect(get).toHaveBeenCalledTimes(1);
67+
expect(get).toHaveBeenCalledWith(key);
68+
};
6969

70-
it("if cache has empty array", () => {
71-
peek.mockReturnValueOnce([]);
72-
expect(endpointCache.get(key)).toBeUndefined();
73-
expect(has).toHaveBeenCalledWith(key);
74-
expect(peek).toHaveBeenCalledWith(key);
75-
expect(get).not.toHaveBeenCalled();
70+
it("returns undefined if cache doesn't have key", () => {
71+
has.mockReturnValueOnce(false);
72+
expect(endpointCache.get(key)).toBeUndefined();
73+
expect(has).toHaveBeenCalledTimes(1);
74+
expect(has).toHaveBeenCalledWith(key);
75+
expect(peek).not.toHaveBeenCalled();
76+
expect(get).not.toHaveBeenCalled();
77+
});
78+
79+
it("returns undefined if cache has empty array", () => {
80+
has.mockReturnValueOnce(true);
81+
peek.mockReturnValueOnce([]);
82+
expect(endpointCache.get(key)).toBeUndefined();
83+
expect(has).toHaveBeenCalledTimes(1);
84+
expect(has).toHaveBeenCalledWith(key);
85+
expect(peek).toHaveBeenCalledTimes(1);
86+
expect(peek).toHaveBeenCalledWith(key);
87+
expect(get).not.toHaveBeenCalled();
88+
});
89+
90+
it("returns undefined if cache returns undefined for key", () => {
91+
get.mockReturnValueOnce(undefined);
92+
expect(endpointCache.get(key)).toBeUndefined();
93+
verifyHasAndGetCalls();
94+
expect(set).not.toHaveBeenCalled();
95+
});
96+
97+
it("returns undefined if endpoints have expired", () => {
98+
const maxCachePeriod = getMaxCachePeriodInMins(mockEndpoints);
99+
jest.spyOn(Date, "now").mockImplementation(() => now + (maxCachePeriod + 1) * 60 * 1000);
100+
expect(endpointCache.get(key)).toBeUndefined();
101+
verifyHasAndGetCalls();
102+
expect(set).toHaveBeenCalledTimes(1);
103+
expect(set).toHaveBeenCalledWith(key, []);
104+
});
105+
106+
describe("getEndpoint", () => {
107+
it("returns one of the un-expired endpoints", () => {
108+
expect(mockEndpoints.map((endpoint) => endpoint.Address)).toContain(endpointCache.getEndpoint(key));
109+
verifyHasAndGetCalls();
76110
expect(set).not.toHaveBeenCalled();
77111
});
78112

79-
it("if cache returns undefined for key", () => {
80-
get.mockReturnValueOnce(undefined);
81-
expect(endpointCache.get(key)).toBeUndefined();
82-
expect(has).toHaveBeenCalledWith(key);
83-
expect(peek).toHaveBeenCalledWith(key);
84-
expect(get).toHaveBeenCalledWith(key);
113+
it("returns un-expired endpoint", () => {
114+
jest.spyOn(Date, "now").mockImplementation(() => now + 90 * 1000);
115+
expect(endpointCache.getEndpoint(key)).toEqual(mockEndpoints[1].Address);
116+
verifyHasAndGetCalls();
85117
expect(set).not.toHaveBeenCalled();
86118
});
87-
});
88119

89-
it("returns endpoints if available", () => {
90-
expect(endpointCache.get(key)).toEqual(getEndpointsWithExpiry(mockEndpoints));
91-
expect(has).toHaveBeenCalledWith(key);
92-
expect(peek).toHaveBeenCalledWith(key);
93-
expect(get).toHaveBeenCalledWith(key);
94-
expect(set).not.toHaveBeenCalled();
120+
[0, 1].forEach((index) => {
121+
it(`returns un-expired endpoint at index ${index}`, () => {
122+
jest.spyOn(Math, "floor").mockImplementation(() => index);
123+
expect(mockEndpoints.map((endpoint) => endpoint.Address)).toContain(endpointCache.getEndpoint(key));
124+
verifyHasAndGetCalls();
125+
expect(set).not.toHaveBeenCalled();
126+
});
127+
});
95128
});
96129
});
97130

packages/endpoint-cache/src/EndpointCache.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@ export class EndpointCache {
1414
}
1515

1616
/**
17-
* Returns endpoints for the given key.
17+
* Returns an un-expired endpoint for the given key.
18+
*
19+
* @param endpointsWithExpiry
20+
* @returns
21+
*/
22+
getEndpoint(key: string) {
23+
const endpointsWithExpiry = this.get(key);
24+
if (!endpointsWithExpiry || endpointsWithExpiry.length === 0) {
25+
return undefined;
26+
}
27+
const endpoints = endpointsWithExpiry.map((endpoint) => endpoint.Address);
28+
return endpoints[Math.floor(Math.random() * endpoints.length)];
29+
}
30+
31+
/**
32+
* Returns un-expired endpoints for the given key.
1833
*
1934
* @param key
2035
* @returns
@@ -24,12 +39,15 @@ export class EndpointCache {
2439
return;
2540
}
2641

27-
const endpointsWithExpiry = this.cache.get(key);
28-
if (!endpointsWithExpiry) {
42+
const value = this.cache.get(key);
43+
if (!value) {
2944
return;
3045
}
3146

47+
const now = Date.now();
48+
const endpointsWithExpiry = value.filter((endpoint) => now < endpoint.Expires);
3249
if (endpointsWithExpiry.length === 0) {
50+
this.delete(key);
3351
return undefined;
3452
}
3553

0 commit comments

Comments
 (0)