@@ -89,9 +89,10 @@ export class TelemetryService {
8989 }
9090
9191 private _module : LlamaCppModule | null = null ;
92- private _handle : number = 0 ; // rac_telemetry_manager_t*
92+ private _handle : number = 0 ; // rac_telemetry_manager_t*
9393 private _httpCallbackPtr : number = 0 ; // Emscripten function table ptr
9494 private _initialized = false ;
95+ private _initPromise : Promise < void > | null = null ; // guards concurrent initialize() calls
9596
9697 private constructor ( ) { }
9798
@@ -102,6 +103,10 @@ export class TelemetryService {
102103 /**
103104 * Initialize the telemetry manager.
104105 * Called from LlamaCppBridge._doLoad() after WASM is loaded.
106+ *
107+ * Concurrent calls are safe: a second caller awaits the in-flight promise
108+ * rather than starting a duplicate initialization, preventing duplicate
109+ * WASM handles and leaked function-table entries.
105110 */
106111 async initialize (
107112 module : LlamaCppModule ,
@@ -112,54 +117,18 @@ export class TelemetryService {
112117 logger . warning ( 'TelemetryService already initialized' ) ;
113118 return ;
114119 }
115-
116- if ( typeof module . _rac_telemetry_manager_create !== 'function' ) {
117- logger . warning ( 'rac_telemetry_manager_create not available — telemetry disabled' ) ;
120+ // If initialization is already in flight, wait for it rather than
121+ // starting a second one — mirrors the LlamaCppBridge.ensureLoaded() pattern.
122+ if ( this . _initPromise ) {
123+ await this . _initPromise ;
118124 return ;
119125 }
120-
121- this . _module = module ;
122-
123- // Map TypeScript SDKEnvironment to C++ rac_environment_t
124- const racEnv = this . mapEnvironment ( environment ) ;
125-
126- const deviceId = getOrCreateDeviceId ( ) ;
127-
128- // Alloc C strings
129- const deviceIdPtr = this . allocString ( deviceId ) ;
130- const platformPtr = this . allocString ( 'web' ) ;
131- const versionPtr = this . allocString ( SDK_VERSION ) ;
132-
133- this . _handle = module . _rac_telemetry_manager_create ! (
134- racEnv , deviceIdPtr , platformPtr , versionPtr ,
135- ) ;
136-
137- this . freeAll ( [ deviceIdPtr , platformPtr , versionPtr ] ) ;
138-
139- if ( ! this . _handle ) {
140- logger . warning ( 'rac_telemetry_manager_create returned null — telemetry disabled' ) ;
141- this . _module = null ;
142- return ;
143- }
144-
145- // Set device info
146- if ( typeof module . _rac_telemetry_manager_set_device_info === 'function' ) {
147- const modelPtr = this . allocString ( deviceInfo . model ?? 'Browser' ) ;
148- const osVersionPtr = this . allocString ( deviceInfo . osVersion ?? 'unknown' ) ;
149- module . _rac_telemetry_manager_set_device_info ! ( this . _handle , modelPtr , osVersionPtr ) ;
150- this . freeAll ( [ modelPtr , osVersionPtr ] ) ;
151- }
152-
153- // Register HTTP callback
154- this . registerHttpCallback ( environment ) ;
155-
156- // Configure HTTPService in dev mode using WASM dev config
157- if ( environment === SDKEnvironment . Development ) {
158- this . configureDevHTTP ( module ) ;
126+ this . _initPromise = this . _doInitialize ( module , environment , deviceInfo ) ;
127+ try {
128+ await this . _initPromise ;
129+ } finally {
130+ this . _initPromise = null ;
159131 }
160-
161- this . _initialized = true ;
162- logger . info ( `TelemetryService initialized (env=${ environment } , device=${ deviceId . substring ( 0 , 8 ) } ...)` ) ;
163132 }
164133
165134 /**
@@ -231,6 +200,63 @@ export class TelemetryService {
231200 // Private
232201 // ---------------------------------------------------------------------------
233202
203+ /**
204+ * Core initialization logic — only called once, guarded by initialize().
205+ */
206+ private async _doInitialize (
207+ module : LlamaCppModule ,
208+ environment : SDKEnvironment ,
209+ deviceInfo : DeviceInfoData ,
210+ ) : Promise < void > {
211+ if ( typeof module . _rac_telemetry_manager_create !== 'function' ) {
212+ logger . warning ( 'rac_telemetry_manager_create not available — telemetry disabled' ) ;
213+ return ;
214+ }
215+
216+ this . _module = module ;
217+
218+ // Map TypeScript SDKEnvironment to C++ rac_environment_t
219+ const racEnv = this . mapEnvironment ( environment ) ;
220+
221+ const deviceId = getOrCreateDeviceId ( ) ;
222+
223+ // Alloc C strings
224+ const deviceIdPtr = this . allocString ( deviceId ) ;
225+ const platformPtr = this . allocString ( 'web' ) ;
226+ const versionPtr = this . allocString ( SDK_VERSION ) ;
227+
228+ this . _handle = module . _rac_telemetry_manager_create ! (
229+ racEnv , deviceIdPtr , platformPtr , versionPtr ,
230+ ) ;
231+
232+ this . freeAll ( [ deviceIdPtr , platformPtr , versionPtr ] ) ;
233+
234+ if ( ! this . _handle ) {
235+ logger . warning ( 'rac_telemetry_manager_create returned null — telemetry disabled' ) ;
236+ this . _module = null ;
237+ return ;
238+ }
239+
240+ // Set device info
241+ if ( typeof module . _rac_telemetry_manager_set_device_info === 'function' ) {
242+ const modelPtr = this . allocString ( deviceInfo . model ?? 'Browser' ) ;
243+ const osVersionPtr = this . allocString ( deviceInfo . osVersion ?? 'unknown' ) ;
244+ module . _rac_telemetry_manager_set_device_info ! ( this . _handle , modelPtr , osVersionPtr ) ;
245+ this . freeAll ( [ modelPtr , osVersionPtr ] ) ;
246+ }
247+
248+ // Register HTTP callback
249+ this . registerHttpCallback ( environment ) ;
250+
251+ // Configure HTTPService in dev mode using WASM dev config
252+ if ( environment === SDKEnvironment . Development ) {
253+ this . configureDevHTTP ( module ) ;
254+ }
255+
256+ this . _initialized = true ;
257+ logger . info ( `TelemetryService initialized (env=${ environment } , device=${ deviceId . substring ( 0 , 8 ) } ...)` ) ;
258+ }
259+
234260 /**
235261 * Registers the HTTP callback with the WASM telemetry manager.
236262 * C++ will call this when it wants to POST a telemetry batch.
0 commit comments