Skip to content

Commit 893cd62

Browse files
committed
FlagBuffer class and interface, add to scope._contexts. Remove old flag field and methods
1 parent 1ca9d53 commit 893cd62

File tree

7 files changed

+115
-44
lines changed

7 files changed

+115
-44
lines changed

packages/core/src/scope.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type {
1111
EventProcessor,
1212
Extra,
1313
Extras,
14-
FeatureFlag,
1514
Primitive,
1615
PropagationContext,
1716
RequestSession,
@@ -23,7 +22,6 @@ import type {
2322
User,
2423
} from '@sentry/types';
2524
import {
26-
LRUMap,
2725
dateTimestampInSeconds,
2826
generatePropagationContext,
2927
isPlainObject,
@@ -105,12 +103,6 @@ class ScopeClass implements ScopeInterface {
105103
/** Contains the last event id of a captured event. */
106104
protected _lastEventId?: string;
107105

108-
/** LRU cache of flags last evaluated by a feature flag provider. Used by FF integrations. */
109-
protected _flagBuffer: LRUMap<FeatureFlag['flag'], FeatureFlag['result']>;
110-
111-
/** Max size of the flagBuffer */
112-
protected _flagBufferSize: number; // TODO: make const?
113-
114106
// NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
115107

116108
public constructor() {
@@ -125,9 +117,6 @@ class ScopeClass implements ScopeInterface {
125117
this._contexts = {};
126118
this._sdkProcessingMetadata = {};
127119
this._propagationContext = generatePropagationContext();
128-
129-
this._flagBufferSize = 100;
130-
this._flagBuffer = new LRUMap<FeatureFlag['flag'], FeatureFlag['result']>(this._flagBufferSize);
131120
}
132121

133122
/**
@@ -516,24 +505,6 @@ class ScopeClass implements ScopeInterface {
516505
return this._propagationContext;
517506
}
518507

519-
/**
520-
* @inheritDoc
521-
*/
522-
public getFlags(): FeatureFlag[] {
523-
const flags: FeatureFlag[] = [];
524-
this._flagBuffer.keys().forEach(key => {
525-
flags.push({ flag: key, result: this._flagBuffer.get(key) as boolean });
526-
});
527-
return flags;
528-
}
529-
530-
/**
531-
* @inheritDoc
532-
*/
533-
public insertFlag(name: string, value: boolean): void {
534-
this._flagBuffer.set(name, value);
535-
}
536-
537508
/**
538509
* @inheritDoc
539510
*/

packages/launchdarkly/src/core/integration.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => {
4545
export class SentryInspector implements LDInspectionFlagUsedHandler {
4646
public name = 'sentry-flag-auditor';
4747

48-
public synchronous = true; // TODO: T or F?
49-
5048
public type = 'flag-used' as const;
5149

50+
// We don't want the handler to impact the performance of the user's flag evaluations.
51+
public synchronous = false;
52+
5253
/**
5354
* TODO: docstring
5455
*/

packages/types/src/context.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Primitive } from './misc';
22
import type { SpanOrigin } from './span';
3+
import type { FlagBufferInterface } from './flags'
34

45
export type Context = Record<string, unknown>;
56

@@ -13,6 +14,7 @@ export interface Contexts extends Record<string, Context | undefined> {
1314
cloud_resource?: CloudResourceContext;
1415
state?: StateContext;
1516
profile?: ProfileContext;
17+
flags?: FeatureFlagContext;
1618
}
1719

1820
export interface StateContext extends Record<string, unknown> {
@@ -124,3 +126,7 @@ export interface MissingInstrumentationContext extends Record<string, unknown> {
124126
package: string;
125127
['javascript.is_cjs']?: boolean;
126128
}
129+
130+
export interface FeatureFlagContext extends Record<string, unknown> {
131+
flag_buffer: FlagBufferInterface;
132+
}

packages/types/src/flags.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,35 @@
11
// Key names match the type used by Sentry frontend.
22
export type FeatureFlag = { flag: string; result: boolean };
3+
4+
/**
5+
* Ordered LRU cache for storing feature flags in the scope context. The name
6+
* of each flag in the buffer is unique, and the output of getAll() is ordered
7+
* from oldest to newest.
8+
*/
9+
export interface FlagBufferInterface {
10+
readonly maxSize: number;
11+
12+
/**
13+
* Returns a deep copy of the current FlagBuffer.
14+
*/
15+
clone(): FlagBufferInterface;
16+
17+
/**
18+
* Returns an ordered array of the flags currently stored in the buffer.
19+
* This is in the order of insertion (oldest to newest).
20+
*/
21+
getAll(): readonly FeatureFlag[];
22+
23+
/**
24+
* Add a flag to the buffer. After inserting, the flag is guaranteed to be at
25+
* the end of the buffer, with no other flags of the same name in it.
26+
*
27+
* @param flag
28+
*/
29+
insert(name: string, value: boolean): void;
30+
31+
/**
32+
* Clear the buffer. Returns the number of flags removed.
33+
*/
34+
clear(): number;
35+
}

packages/types/src/scope.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
21
import type { Attachment } from './attachment';
32
import type { Breadcrumb } from './breadcrumb';
43
import type { Client } from './client';
54
import type { Context, Contexts } from './context';
65
import type { Event, EventHint } from './event';
76
import type { EventProcessor } from './eventprocessor';
87
import type { Extra, Extras } from './extra';
9-
import type { FeatureFlag } from './flags';
108
import type { Primitive } from './misc';
119
import type { RequestSession, Session } from './session';
1210
import type { SeverityLevel } from './severity';
@@ -233,17 +231,6 @@ export interface Scope {
233231
*/
234232
getPropagationContext(): PropagationContext;
235233

236-
/**
237-
* Return the list of recently accessed feature flags.
238-
*/
239-
getFlags(): FeatureFlag[];
240-
241-
/**
242-
* When an integration sends data that a flag name and its value have been evaluated,
243-
* add it to the list of recently accessed feature flags.
244-
*/
245-
insertFlag(name: string, value: boolean): void;
246-
247234
/**
248235
* Capture an exception for this scope.
249236
*

packages/utils/src/flagBuffer.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { FeatureFlag } from '@sentry/types';
2+
import type { FlagBufferInterface } from '@sentry/types/build/types/flags';
3+
4+
export const DEFAULT_FLAG_BUFFER_SIZE = 100;
5+
6+
/**
7+
* Array implementation of types/FlagBufferInterface.
8+
*
9+
* Ordered LRU cache for storing feature flags in the scope context. The name
10+
* of each flag in the buffer is unique, and the output of getAll() is ordered
11+
* from oldest to newest.
12+
*/
13+
export class FlagBuffer implements FlagBufferInterface {
14+
public readonly maxSize: number;
15+
16+
private readonly _flags: FeatureFlag[];
17+
18+
public constructor(_maxSize: number = DEFAULT_FLAG_BUFFER_SIZE, _initialFlags: FeatureFlag[] = []) {
19+
this.maxSize = _maxSize;
20+
if (_initialFlags.length > _maxSize) {
21+
throw Error(`_initialFlags param exceeds the maxSize of ${_maxSize}`);
22+
}
23+
this._flags = _initialFlags;
24+
}
25+
26+
/**
27+
* @inheritdoc
28+
*/
29+
public clone(): FlagBuffer {
30+
return new FlagBuffer(this.maxSize, this._flags);
31+
}
32+
33+
/**
34+
* @inheritdoc
35+
*/
36+
public getAll(): readonly FeatureFlag[] {
37+
return [...this._flags]; // shallow copy
38+
}
39+
40+
/**
41+
* @inheritdoc
42+
*/
43+
public insert(name: string, value: boolean): void {
44+
// Check if the flag is already in the buffer
45+
const index = this._flags.findIndex(f => f.flag === name);
46+
47+
if (index !== -1) {
48+
// The flag was found, remove it from its current position - O(n)
49+
this._flags.splice(index, 1);
50+
}
51+
52+
if (this._flags.length === this.maxSize) {
53+
// If at capacity, pop the earliest flag - O(n)
54+
this._flags.shift();
55+
}
56+
57+
// Push the flag to the end - O(1)
58+
this._flags.push({
59+
flag: name,
60+
result: value,
61+
});
62+
}
63+
64+
/**
65+
* @inheritdoc
66+
*/
67+
public clear(): number {
68+
const length = this._flags.length;
69+
this._flags.splice(0, length); // O(n)
70+
return length;
71+
}
72+
}

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ export * from './buildPolyfills';
4040
export * from './propagationContext';
4141
export * from './vercelWaitUntil';
4242
export * from './version';
43+
export * from './flagBuffer';

0 commit comments

Comments
 (0)