@@ -20,6 +20,7 @@ import {
2020 getCapturedScopesOnSpan ,
2121 getDynamicSamplingContextFromSpan ,
2222 getStatusMessage ,
23+ LRUMap ,
2324 SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ,
2425 SEMANTIC_ATTRIBUTE_SENTRY_OP ,
2526 SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
@@ -41,6 +42,7 @@ type SpanNodeCompleted = SpanNode & { span: ReadableSpan };
4142
4243const MAX_SPAN_COUNT = 1000 ;
4344const DEFAULT_TIMEOUT = 300 ; // 5 min
45+ const SENT_SPANS_MAX_SIZE = 10000 ;
4446
4547interface FinishedSpanBucket {
4648 timestampInS : number ;
@@ -71,9 +73,7 @@ export class SentrySpanExporter {
7173 private _finishedSpanBucketSize : number ;
7274 private _spansToBucketEntry : WeakMap < ReadableSpan , FinishedSpanBucket > ;
7375 private _lastCleanupTimestampInS : number ;
74- // Essentially a a set of span ids that are already sent. The values are expiration
75- // times in this cache so we don't hold onto them indefinitely.
76- private _sentSpans : Map < string , number > ;
76+ private _sentSpans : LRUMap < string , number > ;
7777 /* Internally, we use a debounced flush to give some wiggle room to the span processor to accumulate more spans. */
7878 private _debouncedFlush : ReturnType < typeof debounce > ;
7979
@@ -85,7 +85,7 @@ export class SentrySpanExporter {
8585 this . _finishedSpanBuckets = new Array ( this . _finishedSpanBucketSize ) . fill ( undefined ) ;
8686 this . _lastCleanupTimestampInS = Math . floor ( _INTERNAL_safeDateNow ( ) / 1000 ) ;
8787 this . _spansToBucketEntry = new WeakMap ( ) ;
88- this . _sentSpans = new Map < string , number > ( ) ;
88+ this . _sentSpans = new LRUMap < string , number > ( SENT_SPANS_MAX_SIZE ) ;
8989 this . _debouncedFlush = debounce ( this . flush . bind ( this ) , 1 , { maxWait : 100 } ) ;
9090 }
9191
@@ -124,7 +124,7 @@ export class SentrySpanExporter {
124124
125125 // If the span doesn't have a local parent ID (it's a root span), we're gonna flush all the ended spans
126126 const localParentId = getLocalParentId ( span ) ;
127- if ( ! localParentId || this . _sentSpans . has ( localParentId ) ) {
127+ if ( ! localParentId || this . _sentSpans . get ( localParentId ) ) {
128128 this . _debouncedFlush ( ) ;
129129 }
130130 }
@@ -137,7 +137,6 @@ export class SentrySpanExporter {
137137 public flush ( ) : void {
138138 const finishedSpans = this . _finishedSpanBuckets . flatMap ( bucket => ( bucket ? Array . from ( bucket . spans ) : [ ] ) ) ;
139139
140- this . _flushSentSpanCache ( ) ;
141140 const sentSpans = this . _maybeSend ( finishedSpans ) ;
142141
143142 const sentSpanCount = sentSpans . size ;
@@ -147,15 +146,14 @@ export class SentrySpanExporter {
147146 `SpanExporter exported ${ sentSpanCount } spans, ${ remainingOpenSpanCount } spans are waiting for their parent spans to finish` ,
148147 ) ;
149148
150- const expirationDate = _INTERNAL_safeDateNow ( ) + DEFAULT_TIMEOUT * 1000 ;
151-
152149 for ( const span of sentSpans ) {
153- this . _sentSpans . set ( span . spanContext ( ) . spanId , expirationDate ) ;
150+ this . _sentSpans . set ( span . spanContext ( ) . spanId , 1 ) ;
154151 const bucketEntry = this . _spansToBucketEntry . get ( span ) ;
155152 if ( bucketEntry ) {
156153 bucketEntry . spans . delete ( span ) ;
157154 }
158155 }
156+
159157 // Cancel a pending debounced flush, if there is one
160158 // This can be relevant if we directly flush, circumventing the debounce
161159 // in that case, we want to cancel any pending debounced flush
@@ -193,7 +191,7 @@ export class SentrySpanExporter {
193191 const transactionEvent = createTransactionForOtelSpan ( span ) ;
194192
195193 // Add an attribute to the transaction event to indicate that this transaction is an orphaned transaction
196- if ( root . parentNode && this . _sentSpans . has ( root . parentNode . id ) ) {
194+ if ( root . parentNode && this . _sentSpans . get ( root . parentNode . id ) ) {
197195 const traceData = transactionEvent . contexts ?. trace ?. data ;
198196 if ( traceData ) {
199197 traceData [ 'sentry.parent_span_already_sent' ] = true ;
@@ -235,20 +233,9 @@ export class SentrySpanExporter {
235233 return sentSpans ;
236234 }
237235
238- /** Remove "expired" span id entries from the _sentSpans cache. */
239- private _flushSentSpanCache ( ) : void {
240- const currentTimestamp = _INTERNAL_safeDateNow ( ) ;
241- // Note, it is safe to delete items from the map as we go: https://stackoverflow.com/a/35943995/90297
242- for ( const [ spanId , expirationTime ] of this . _sentSpans . entries ( ) ) {
243- if ( expirationTime <= currentTimestamp ) {
244- this . _sentSpans . delete ( spanId ) ;
245- }
246- }
247- }
248-
249236 /** Check if a node is a completed root node or a node whose parent has already been sent */
250237 private _nodeIsCompletedRootNodeOrHasSentParent ( node : SpanNode ) : node is SpanNodeCompleted {
251- return ! ! node . span && ( ! node . parentNode || this . _sentSpans . has ( node . parentNode . id ) ) ;
238+ return ! ! node . span && ( ! node . parentNode || ! ! this . _sentSpans . get ( node . parentNode . id ) ) ;
252239 }
253240
254241 /** Get all completed root nodes from a list of nodes */
0 commit comments