Skip to content

Commit 60d9563

Browse files
committed
fix(api): resolve startup race condition with initialization gate
Implements an initialization gate using a `Completer` in `bin/server.dart` to prevent a race condition where requests could be processed before asynchronous setup (DB connection, dependency injection) was complete. - A new `_initializationGate` middleware now wraps the root handler. - This middleware awaits a `Completer`'s future, effectively pausing all incoming requests. - The completer is only marked as complete after all async initialization in `main()` is finished and the server is listening. - This guarantees that the `DependencyContainer` is fully populated before any request attempts to access it, resolving the `LateInitializationError`.
1 parent 0f2e38a commit 60d9563

File tree

1 file changed

+32
-5
lines changed

1 file changed

+32
-5
lines changed

bin/server.dart

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import 'dart:io';
1+
import 'dart:async';
2+
import 'dart:io' show InternetAddress, Platform, ProcessSignal, exit;
23

34
import 'package:dart_frog/dart_frog.dart';
45
import 'package:ht_api/src/config/dependency_container.dart';
56
import 'package:ht_api/src/config/environment_config.dart';
67
import 'package:ht_api/src/rbac/permission_service.dart';
78
import 'package:ht_api/src/services/auth_service.dart';
8-
import 'package:ht_api/src/services/auth_token_service.dart';
99
import 'package:ht_api/src/services/dashboard_summary_service.dart';
10-
import 'package:ht_api/src/services/database_seeding_service.dart';
1110
import 'package:ht_api/src/services/default_user_preference_limit_service.dart';
1211
import 'package:ht_api/src/services/jwt_auth_token_service.dart';
1312
import 'package:ht_api/src/services/token_blacklist_service.dart';
14-
import 'package:ht_api/src/services/user_preference_limit_service.dart';
1513
import 'package:ht_api/src/services/verification_code_storage_service.dart';
1614
import 'package:ht_data_client/ht_data_client.dart';
1715
import 'package:ht_data_postgres/ht_data_postgres.dart';
@@ -21,6 +19,7 @@ import 'package:ht_email_repository/ht_email_repository.dart';
2119
import 'package:ht_shared/ht_shared.dart';
2220
import 'package:logging/logging.dart';
2321
import 'package:postgres/postgres.dart';
22+
import 'package:ht_api/src/services/database_seeding_service.dart';
2423
import 'package:uuid/uuid.dart';
2524

2625
// This is the generated file from Dart Frog.
@@ -33,6 +32,27 @@ final _log = Logger('ht_api');
3332
// Global PostgreSQL connection instance.
3433
late final Connection _connection;
3534

35+
/// A completer that signals when all asynchronous server initialization is
36+
/// complete.
37+
///
38+
/// This is used by the [_initializationGate] middleware to hold requests
39+
/// until the server is fully ready to process them, preventing race conditions
40+
/// where a request arrives before the database is connected or dependencies
41+
/// are initialized.
42+
final _initCompleter = Completer<void>();
43+
44+
/// A top-level middleware that waits for the async initialization to complete
45+
/// before processing any requests.
46+
///
47+
/// This acts as a "gate," ensuring that no request is handled until the future
48+
/// in [_initCompleter] is completed.
49+
Handler _initializationGate(Handler innerHandler) {
50+
return (context) async {
51+
await _initCompleter.future;
52+
return innerHandler(context);
53+
};
54+
}
55+
3656
// Creates a data repository for a given type [T].
3757
HtDataRepository<T> _createRepository<T>({
3858
required String tableName,
@@ -192,10 +212,17 @@ Future<void> main() async {
192212
// 7. Build the handler and start the server
193213
final ip = InternetAddress.anyIPv4;
194214
final port = int.parse(Platform.environment['PORT'] ?? '8080');
195-
final handler = buildRootHandler();
215+
// The root handler from Dart Frog is wrapped with our initialization gate.
216+
// This ensures that `_initializationGate` runs for every request, pausing
217+
// it until `_initCompleter` is marked as complete.
218+
final handler = _initializationGate(buildRootHandler());
196219
final server = await serve(handler, ip, port);
197220
_log.info('Server listening on port ${server.port}');
198221

222+
// Now that the server is running, we complete the completer to open the gate
223+
// and allow requests to be processed.
224+
_initCompleter.complete();
225+
199226
// 8. Handle graceful shutdown
200227
ProcessSignal.sigint.watch().listen((_) async {
201228
_log.info('Received SIGINT. Shutting down...');

0 commit comments

Comments
 (0)