Skip to content

feat: Improve handling of HTTP/HTTPS agent sessions#6498

Open
davidkna-sap wants to merge 15 commits intomainfrom
davidkna-sap_cache-agent
Open

feat: Improve handling of HTTP/HTTPS agent sessions#6498
davidkna-sap wants to merge 15 commits intomainfrom
davidkna-sap_cache-agent

Conversation

@davidkna-sap
Copy link
Copy Markdown
Member

@davidkna-sap davidkna-sap commented Apr 14, 2026

Closes https://github.com/SAP/cloud-sdk-backlog/issues/1298

  • For non-custom http/https agent default to node's global http/https agents (default: keepAlive === true)
  • Cache custom HTTP/HTTPS agents so identical agent configurations are reused instead of creating new agents repeatedly.
  • Bound the agent cache with new LRU eviction to limit memory growth due to caching, since unbounded agent creation lead to noticeable memory consumption.
  • Add getOrInsertComputed() to Cache<_> (like Map.prototype.getOrInsertComputed())

@davidkna-sap davidkna-sap force-pushed the davidkna-sap_cache-agent branch from a1f20f3 to 68fd5d8 Compare April 14, 2026 12:03
Comment thread .changeset/heavy-foxes-deny.md Outdated
Comment thread packages/connectivity/src/http-agent/http-agent.ts
@davidkna-sap davidkna-sap marked this pull request as ready for review April 14, 2026 13:20
Copy link
Copy Markdown
Contributor

@ZhongpinWang ZhongpinWang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall very nice implementation. 👍 I love the idea of making our cache a LRU cache. Btw we also have an async cache, not sure if that one needs to be changed as well. Otherwise, some small suggestions.

@@ -82,10 +102,41 @@ export class Cache<T> implements CacheInterface<T> {
* @param item - The entry to cache.
*/
set(key: string | undefined, item: CacheEntry<T>): void {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] In general, I wonder why this key can be undefined :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude is claiming it's for the benefit of getDestinationCacheKey

* Object that stores all cached entries.
*/
private cache: Record<string, CacheEntry<T>>;
private cache: Map<string, CacheEntry<T>>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍 Map maintains the insertion order.

* @returns A hash of the given value using a cryptographic hash function.
*/
export function hashCacheKey(value: Record<string, unknown>): string {
const serialized = JSON.stringify(value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you checked if this is sufficient? JSON.stringify cannot stringify many things like Map or Set etc.

Maybe check alternatives https://www.npmjs.com/package/fast-json-stable-stringify or something else.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to ohash (v1 instead of v2 for CJS support, v1 is still getting updates), Map/Set wouldn't be an issue here, but the values can contain functions.

I can switch to JSON.stringify's replacer option to handle functions if you prefer that, as TS is also having some issues with ohash in general.

Copy link
Copy Markdown
Contributor

@ZhongpinWang ZhongpinWang Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also did some search, but tbh none of them seems to be very actively maintained. Maybe they just work without problem.

Also maybe we can try require() now since node 20 is (very soon) EOL

@davidkna-sap davidkna-sap force-pushed the davidkna-sap_cache-agent branch from cdf9b3a to e535ea9 Compare April 14, 2026 15:12
@davidkna-sap davidkna-sap force-pushed the davidkna-sap_cache-agent branch from e535ea9 to 002f644 Compare April 14, 2026 15:17
Copy link
Copy Markdown
Contributor

@marikaner marikaner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get through everything yet. Seems like an improvement 😊

Comment thread packages/connectivity/src/http-agent/http-agent.ts Outdated
Comment thread packages/connectivity/src/http-agent/http-agent.ts Outdated
Comment on lines +84 to +96
const entry = key ? this.cache.get(key) : undefined;
if (entry) {
if (isExpired(entry)) {
this.cache.delete(key!);
return undefined;
}
// LRU cache: Move accessed entry to the end of the Map to mark it as recently used
if (this.maxSize !== Infinity) {
this.cache.delete(key!);
this.cache.set(key!, entry);
}
}
return entry?.entry;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[pp]

Suggested change
const entry = key ? this.cache.get(key) : undefined;
if (entry) {
if (isExpired(entry)) {
this.cache.delete(key!);
return undefined;
}
// LRU cache: Move accessed entry to the end of the Map to mark it as recently used
if (this.maxSize !== Infinity) {
this.cache.delete(key!);
this.cache.set(key!, entry);
}
}
return entry?.entry;
if (key) {
const entry = this.cache.get(key);
if (isExpired(entry)) {
this.cache.delete(key!);
return undefined;
}
// LRU cache: Move accessed entry to the end of the Map to mark it as recently used
if (this.maxSize !== Infinity) {
this.cache.delete(key);
this.cache.set(key, entry);
}
return entry.entry;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled this differently because this.cache.get(key); can return undefined.

Comment thread packages/connectivity/src/http-agent/http-agent.ts Outdated
@davidkna-sap davidkna-sap requested a review from marikaner April 16, 2026 09:28
* origin/main: (27 commits)
  chore: Replace mock-fs with memfs/unionfs for fs mocking (#6470)
  chore(deps-dev): bump @changesets/cli from 2.30.0 to 2.31.0 (#6515)
  chore(deps): bump bignumber.js from 10.0.2 to 11.0.0 (#6511)
  chore(deps): bump @changesets/get-release-plan from 4.0.15 to 4.0.16 (#6518)
  chore(check-public-api): Use tempdir instead of mockfs (#6468)
  chore(deps): bump prettier from 3.8.2 to 3.8.3 (#6510)
  chore: Refactor test imports to use @sap-cloud-sdk/test-util-internal pkg (#6467)
  chore: Add license-checker action for pnpm (#6473)
  chore(deps): bump follow-redirects from 1.15.11 to 1.16.0 (#6495)
  chore: Add composite setup action for pnpm (#6507)
  fix: Avoid caching jwt if it needs to be forwarded (#6007)
  Change status from proposed to decided
  chore(deps): bump typescript-eslint from 8.58.1 to 8.58.2 (#6514)
  chore(deps): bump @typescript-eslint/parser from 8.58.1 to 8.58.2 (#6513)
  chore(deps-dev): bump @sap/cds-dk from 9.8.3 to 9.8.4 (#6512)
  chore(deps): bump fast-xml-parser from 5.5.11 to 5.6.0 (#6509)
  chore(deps-dev): bump puppeteer from 24.40.0 to 24.41.0 (#6508)
  chore(deps-dev): bump typedoc from 0.28.18 to 0.28.19 (#6505)
  chore(deps): bump ts-morph from 27.0.2 to 28.0.0 (#6506)
  chore(deps-dev): bump globals from 17.4.0 to 17.5.0 (#6504)
  ...
Copy link
Copy Markdown
Contributor

@marikaner marikaner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a few comments, please especially take note of the ones with "[req]". But no big blockers.

Comment on lines +100 to +101
this.cache.delete(key!);
this.cache.set(key!, entry);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] is the ! needed? There is a nullish check on the top.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was needed before the refactoring, I was able to remove them now.

Comment thread packages/connectivity/src/scp-cf/cache.ts Outdated
Comment thread packages/connectivity/src/scp-cf/cache.ts Outdated
) {
// Evict the least recently used (LRU) entry
const lruKey = this.cache.keys().next().value;
this.cache.delete(lruKey!); // SAFETY: size > 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] What does the comment tell me?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment explains why the ! operator is safe to apply here.

Comment thread packages/connectivity/src/scp-cf/cache.spec.ts Outdated
Comment thread packages/connectivity/src/scp-cf/cache.spec.ts
): T {
if (!key) {
return computeFn().entry;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[req] I wasn't sure about this, because it seems a little off: It is weird, that this just calls the compute function without caching it. But, when you have a case where the key might not be defined and you want to have a default you don't have to differentiate the cases. So I guess it is better as is.

But, I am missing signs that this is intended, though, aka. a test and docs.

Copy link
Copy Markdown
Member Author

@davidkna-sap davidkna-sap Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this special handling, I added in on purpose but is currently not a useful feature in practice.

@davidkna-sap davidkna-sap requested a review from marikaner April 22, 2026 11:03
* origin/main:
  chore: Replace yarn with pnpm (#6448)
  chore: Reorder step configurations (#6521)
  chore(deps): bump bignumber.js from 10.0.2 to 11.0.0 (#6520)
  chore(deps): bump fast-xml-parser from 5.6.0 to 5.7.1 (#6519)
  feat: Add `getIasToken()` and `getIasDestination()` convenience functions (#6431)
  chore(deps-dev): bump memfs from 4.57.1 to 4.57.2 (#6516)
  chore: Correct merge-and-write-changelogs action path in package.json (#6479)
  chore(deps-dev): bump turbo from 2.8.10 to 2.9.6 (#6499)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants