-
Notifications
You must be signed in to change notification settings - Fork 2.1k
fix(server-core): rebuild the cached driver when its configuration changes #11453
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
Open
MikeNitsenko
wants to merge
4
commits into
master
Choose a base branch
from
mikhail/cub-3599-rebuild-driver-on-config-change
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
0baa89a
fix(server-core): rebuild the cached driver when its configuration ch…
MikeNitsenko 7317b2c
fix(server-core): invalidate both pre-aggregation driver keys together
MikeNitsenko 207b319
fix(server-core): make the driver rebuild path concurrency-safe
MikeNitsenko 72260f7
fix(server-core): surface driver rebuilds at the default log level
MikeNitsenko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
105 changes: 105 additions & 0 deletions
105
packages/cubejs-server-core/src/core/driver-config-fingerprint.ts
This file contains hidden or 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,105 @@ | ||
| /** | ||
| * @copyright Cube Dev, Inc. | ||
| * @license Apache-2.0 | ||
| * @fileoverview Fingerprinting for driver configurations and security contexts. | ||
| */ | ||
|
|
||
| import crypto from 'crypto'; | ||
|
|
||
| /** | ||
| * Deterministic JSON used for fingerprinting. Object keys are emitted in sorted | ||
| * order so two structures that differ only in property order hash the same, and | ||
| * values JSON cannot represent are reduced to stable placeholders rather than | ||
| * silently disappearing. Throws on a circular structure, which callers treat as | ||
| * "not fingerprintable". | ||
| */ | ||
| function stableStringify(value: unknown, seen: Set<unknown>): string { | ||
| if (value === undefined || value === null) { | ||
| return 'null'; | ||
| } | ||
|
|
||
| const type = typeof value; | ||
|
|
||
| if (type === 'string' || type === 'number' || type === 'boolean') { | ||
| return JSON.stringify(value); | ||
| } | ||
|
|
||
| if (type === 'bigint') { | ||
| return JSON.stringify((value as bigint).toString()); | ||
| } | ||
|
|
||
| // A closure's identity cannot be compared meaningfully across calls, so it | ||
| // contributes a constant. Two configs differing only in a function body are | ||
| // therefore treated as equal — deliberately conservative: it can only lead to | ||
| // reusing a connection, never to swapping one out unnecessarily. | ||
| // | ||
| // The practical consequence is that a config carrying its credential as a | ||
| // provider callback rather than a resolved value fingerprints identically | ||
| // however the credential rotates, so such a driver is never rebuilt. A | ||
| // `driverFactory` that needs rotation to be noticed has to return the | ||
| // resolved value. | ||
| if (type === 'function' || type === 'symbol') { | ||
| return JSON.stringify(`[${type}]`); | ||
| } | ||
|
claude[bot] marked this conversation as resolved.
|
||
|
|
||
| if (value instanceof Date) { | ||
| return JSON.stringify(value.toISOString()); | ||
| } | ||
|
|
||
| if (seen.has(value)) { | ||
| throw new Error('Circular structure cannot be fingerprinted'); | ||
| } | ||
|
|
||
| seen.add(value); | ||
|
|
||
| try { | ||
| if (Array.isArray(value)) { | ||
| return `[${value.map((item) => stableStringify(item, seen)).join(',')}]`; | ||
| } | ||
|
|
||
| // Own enumerable keys only, so a class instance holding its values behind | ||
| // prototype accessors fingerprints as `{}` — constant, and therefore another | ||
| // shape whose rotation goes unnoticed. Plain configs are unaffected. | ||
| const entries = Object.keys(value as Record<string, unknown>) | ||
| .sort() | ||
| .reduce<string[]>((acc, key) => { | ||
| const entry = (value as Record<string, unknown>)[key]; | ||
|
|
||
| // Match JSON.stringify: undefined-valued properties are absent, so | ||
| // `{ a: undefined }` and `{}` fingerprint the same. | ||
| if (entry !== undefined) { | ||
| acc.push(`${JSON.stringify(key)}:${stableStringify(entry, seen)}`); | ||
| } | ||
|
|
||
| return acc; | ||
| }, []); | ||
|
|
||
| return `{${entries.join(',')}}`; | ||
| } finally { | ||
| seen.delete(value); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A short, stable digest of `value`, or `null` when it cannot be fingerprinted. | ||
| * | ||
| * Hashed rather than kept verbatim because the values being compared include | ||
| * database passwords and OAuth access tokens: a raw copy would live for the | ||
| * lifetime of the process and surface in any heap dump. `null` means "cannot | ||
| * tell whether this changed", and every caller must treat that as "assume it | ||
| * did not" so behaviour falls back to the previous resolve-once semantics. | ||
| */ | ||
| export function fingerprint(value: unknown): string | null { | ||
| try { | ||
| return crypto | ||
| .createHash('sha256') | ||
| .update(stableStringify(value, new Set())) | ||
| // 32 hex chars = 128 bits, which is far more than an equality check over | ||
| // the handful of configurations one process resolves needs, and keeps the | ||
| // digest short enough to sit in a log line. | ||
| .digest('hex') | ||
| .slice(0, 32); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.