@@ -8,6 +8,31 @@ import {
88 type ContextProvider ,
99} from '@github/copilot-language-server' ;
1010import { sendInfo } from "vscode-extension-telemetry-wrapper" ;
11+
12+ /**
13+ * TelemetryQueue - Asynchronous telemetry queue to avoid blocking main thread
14+ * Based on the PromiseQueue pattern from copilot-client
15+ */
16+ class TelemetryQueue {
17+ private promises = new Set < Promise < unknown > > ( ) ;
18+
19+ register ( promise : Promise < unknown > ) : void {
20+ this . promises . add ( promise ) ;
21+ // Use void to avoid blocking - the key pattern from PromiseQueue
22+ void promise . finally ( ( ) => this . promises . delete ( promise ) ) ;
23+ }
24+
25+ async flush ( ) : Promise < void > {
26+ await Promise . allSettled ( this . promises ) ;
27+ }
28+
29+ get size ( ) : number {
30+ return this . promises . size ;
31+ }
32+ }
33+
34+ // Global telemetry queue instance
35+ const globalTelemetryQueue = new TelemetryQueue ( ) ;
1136/**
1237 * Error classes for Copilot context provider cancellation handling
1338 */
@@ -211,14 +236,61 @@ export class ContextProviderResolverError extends Error {
211236}
212237
213238/**
214- * Send consolidated telemetry data for Java context resolution
215- * This is the centralized function for sending context resolution telemetry
239+ * Asynchronously send telemetry data preparation and sending
240+ * This function prepares telemetry data and handles the actual sending asynchronously
241+ */
242+ async function _sendContextResolutionTelemetry (
243+ request : ResolveRequest ,
244+ start : number ,
245+ items : SupportedContextItem [ ] ,
246+ status : string ,
247+ error ?: string ,
248+ dependenciesEmptyReason ?: string ,
249+ importsEmptyReason ?: string ,
250+ dependenciesCount ?: number ,
251+ importsCount ?: number
252+ ) : Promise < void > {
253+ try {
254+ const duration = Math . round ( performance . now ( ) - start ) ;
255+ const tokenCount = JavaContextProviderUtils . calculateTokenCount ( items ) ;
256+ const telemetryData : any = {
257+ "action" : "resolveJavaContext" ,
258+ "completionId" : request . completionId ,
259+ "duration" : duration ,
260+ "itemCount" : items . length ,
261+ "tokenCount" : tokenCount ,
262+ "status" : status ,
263+ "dependenciesCount" : dependenciesCount ?? 0 ,
264+ "importsCount" : importsCount ?? 0
265+ } ;
266+
267+ // Add empty reasons if present
268+ if ( dependenciesEmptyReason ) {
269+ telemetryData . dependenciesEmptyReason = dependenciesEmptyReason ;
270+ }
271+ if ( importsEmptyReason ) {
272+ telemetryData . importsEmptyReason = importsEmptyReason ;
273+ }
274+ if ( error ) {
275+ telemetryData . error = error ;
276+ }
277+
278+ // Actual telemetry sending - this is synchronous but network is async
279+ sendInfo ( "" , telemetryData ) ;
280+ } catch ( telemetryError ) {
281+ // Silently ignore telemetry errors to not affect main functionality
282+ console . error ( 'Failed to send Java context resolution telemetry:' , telemetryError ) ;
283+ }
284+ }
285+
286+ /**
287+ * Send consolidated telemetry data for Java context resolution asynchronously
288+ * This function immediately returns and sends telemetry in the background without blocking
216289 *
217290 * @param request The resolve request from Copilot
218291 * @param start Performance timestamp when resolution started
219292 * @param items The resolved context items
220293 * @param status Status of the resolution ("succeeded", "cancelled_by_copilot", "cancelled_internally", "error_partial_results")
221- * @param sendInfo The sendInfo function from vscode-extension-telemetry-wrapper
222294 * @param error Optional error message
223295 * @param dependenciesEmptyReason Optional reason why dependencies were empty
224296 * @param importsEmptyReason Optional reason why imports were empty
@@ -236,29 +308,26 @@ export function sendContextResolutionTelemetry(
236308 dependenciesCount ?: number ,
237309 importsCount ?: number
238310) : void {
239- const duration = Math . round ( performance . now ( ) - start ) ;
240- const tokenCount = JavaContextProviderUtils . calculateTokenCount ( items ) ;
241- const telemetryData : any = {
242- "action" : "resolveJavaContext" ,
243- "completionId" : request . completionId ,
244- "duration" : duration ,
245- "itemCount" : items . length ,
246- "tokenCount" : tokenCount ,
247- "status" : status ,
248- "dependenciesCount" : dependenciesCount ?? 0 ,
249- "importsCount" : importsCount ?? 0
250- } ;
251-
252- // Add empty reasons if present
253- if ( dependenciesEmptyReason ) {
254- telemetryData . dependenciesEmptyReason = dependenciesEmptyReason ;
255- }
256- if ( importsEmptyReason ) {
257- telemetryData . importsEmptyReason = importsEmptyReason ;
258- }
259- if ( error ) {
260- telemetryData . error = error ;
261- }
311+ // Register the telemetry promise for non-blocking execution
312+ // This follows the PromiseQueue pattern from copilot-client
313+ globalTelemetryQueue . register (
314+ _sendContextResolutionTelemetry (
315+ request ,
316+ start ,
317+ items ,
318+ status ,
319+ error ,
320+ dependenciesEmptyReason ,
321+ importsEmptyReason ,
322+ dependenciesCount ,
323+ importsCount
324+ )
325+ ) ;
326+ }
262327
263- sendInfo ( "" , telemetryData ) ;
328+ /**
329+ * Get the global telemetry queue instance (useful for testing and monitoring)
330+ */
331+ export function getTelemetryQueue ( ) : TelemetryQueue {
332+ return globalTelemetryQueue ;
264333}
0 commit comments