|
1 | 1 | // ignore_for_file: public_member_api_docs |
2 | 2 |
|
| 3 | +import 'dart:async'; |
3 | 4 | import 'package:core/core.dart'; |
4 | 5 | import 'package:data_mongodb/data_mongodb.dart'; |
5 | 6 | import 'package:data_repository/data_repository.dart'; |
@@ -40,11 +41,12 @@ class AppDependencies { |
40 | 41 | /// Provides access to the singleton instance. |
41 | 42 | static AppDependencies get instance => _instance; |
42 | 43 |
|
43 | | - bool _isInitialized = false; |
44 | | - Object? _initializationError; |
45 | | - StackTrace? _initializationStackTrace; |
46 | 44 | final _log = Logger('AppDependencies'); |
47 | 45 |
|
| 46 | + // A Completer to manage the one-time asynchronous initialization. |
| 47 | + // This ensures the initialization logic runs only once. |
| 48 | + Completer<void>? _initCompleter; |
| 49 | + |
48 | 50 | // --- Late-initialized fields for all dependencies --- |
49 | 51 |
|
50 | 52 | // Database |
@@ -79,16 +81,30 @@ class AppDependencies { |
79 | 81 | /// Initializes all application dependencies. |
80 | 82 | /// |
81 | 83 | /// This method is idempotent; it will only run the initialization logic once. |
82 | | - Future<void> init() async { |
83 | | - // If initialization previously failed, re-throw the original error. |
84 | | - if (_initializationError != null) { |
85 | | - return Future.error(_initializationError!, _initializationStackTrace); |
| 84 | + Future<void> init() { |
| 85 | + // If _initCompleter is not null, it means initialization is either in |
| 86 | + // progress or has already completed. Return its future to allow callers |
| 87 | + // to await the single, shared initialization process. |
| 88 | + if (_initCompleter != null) { |
| 89 | + return _initCompleter!.future; |
86 | 90 | } |
87 | 91 |
|
88 | | - if (_isInitialized) return; |
| 92 | + // This is the first call to init(). Create the completer and start the |
| 93 | + // initialization process. |
| 94 | + _initCompleter = Completer<void>(); |
| 95 | + _log.info('Starting application dependency initialization...'); |
| 96 | + // We intentionally don't await this future here. The completer's future, |
| 97 | + // which is returned below, is what callers will await. |
| 98 | + unawaited(_initializeDependencies()); |
89 | 99 |
|
90 | | - _log.info('Initializing application dependencies...'); |
| 100 | + // Return the future from the completer. |
| 101 | + return _initCompleter!.future; |
| 102 | + } |
91 | 103 |
|
| 104 | + /// The core logic for initializing all dependencies. |
| 105 | + /// This method is private and should only be called once by [init]. |
| 106 | + Future<void> _initializeDependencies() async { |
| 107 | + _log.info('Initializing application dependencies...'); |
92 | 108 | try { |
93 | 109 | // 1. Initialize Database Connection |
94 | 110 | _mongoDbConnectionManager = MongoDbConnectionManager(); |
@@ -271,24 +287,45 @@ class AppDependencies { |
271 | 287 | cacheDuration: EnvironmentConfig.countryServiceCacheDuration, |
272 | 288 | ); |
273 | 289 |
|
274 | | - _isInitialized = true; |
275 | 290 | _log.info('Application dependencies initialized successfully.'); |
| 291 | + // Signal that initialization has completed successfully. |
| 292 | + _initCompleter!.complete(); |
276 | 293 | } catch (e, s) { |
277 | 294 | _log.severe('Failed to initialize application dependencies', e, s); |
278 | | - _initializationError = e; |
279 | | - _initializationStackTrace = s; |
| 295 | + // Signal that initialization has failed. |
| 296 | + _initCompleter!.completeError(e, s); |
280 | 297 | rethrow; |
281 | 298 | } |
282 | 299 | } |
283 | 300 |
|
284 | 301 | /// Disposes of resources, such as closing the database connection. |
285 | 302 | Future<void> dispose() async { |
286 | | - if (!_isInitialized) return; |
| 303 | + // Only attempt to dispose if initialization has been started. |
| 304 | + if (_initCompleter == null) { |
| 305 | + _log.info('Dispose called, but dependencies were never initialized.'); |
| 306 | + return; |
| 307 | + } |
| 308 | + |
| 309 | + // Wait for initialization to complete before disposing resources. |
| 310 | + // This prevents a race condition if dispose() is called during init(). |
| 311 | + try { |
| 312 | + await _initCompleter!.future; |
| 313 | + } catch (_) { |
| 314 | + // Initialization may have failed, but we still proceed to dispose |
| 315 | + // any partially initialized resources. |
| 316 | + _log.warning( |
| 317 | + 'Disposing dependencies after a failed initialization attempt.', |
| 318 | + ); |
| 319 | + } |
| 320 | + |
| 321 | + _log.info('Disposing application dependencies...'); |
287 | 322 | await _mongoDbConnectionManager.close(); |
288 | 323 | tokenBlacklistService.dispose(); |
289 | 324 | rateLimitService.dispose(); |
290 | 325 | countryQueryService.dispose(); // Dispose the new service |
291 | | - _isInitialized = false; |
292 | | - _log.info('Application dependencies disposed.'); |
| 326 | + |
| 327 | + // Reset the completer to allow for re-initialization (e.g., in tests). |
| 328 | + _initCompleter = null; |
| 329 | + _log.info('Application dependencies disposed and state reset.'); |
293 | 330 | } |
294 | 331 | } |
0 commit comments