@joystick.js/db-canary 0.0.0-canary.2250 → 0.0.0-canary.2252
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/database.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/server/cluster/master.js +4 -4
- package/dist/server/cluster/worker.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/lib/auto_index_manager.js +1 -1
- package/dist/server/lib/backup_manager.js +1 -1
- package/dist/server/lib/index_manager.js +1 -1
- package/dist/server/lib/operation_dispatcher.js +1 -1
- package/dist/server/lib/operations/admin.js +1 -1
- package/dist/server/lib/operations/bulk_write.js +1 -1
- package/dist/server/lib/operations/create_index.js +1 -1
- package/dist/server/lib/operations/delete_many.js +1 -1
- package/dist/server/lib/operations/delete_one.js +1 -1
- package/dist/server/lib/operations/find.js +1 -1
- package/dist/server/lib/operations/find_one.js +1 -1
- package/dist/server/lib/operations/insert_one.js +1 -1
- package/dist/server/lib/operations/update_one.js +1 -1
- package/dist/server/lib/send_response.js +1 -1
- package/dist/server/lib/tcp_protocol.js +1 -1
- package/package.json +2 -2
- package/src/client/database.js +92 -119
- package/src/client/index.js +279 -345
- package/src/server/cluster/master.js +265 -156
- package/src/server/cluster/worker.js +26 -18
- package/src/server/index.js +553 -330
- package/src/server/lib/auto_index_manager.js +85 -23
- package/src/server/lib/backup_manager.js +117 -70
- package/src/server/lib/index_manager.js +63 -25
- package/src/server/lib/operation_dispatcher.js +339 -168
- package/src/server/lib/operations/admin.js +343 -205
- package/src/server/lib/operations/bulk_write.js +458 -194
- package/src/server/lib/operations/create_index.js +127 -34
- package/src/server/lib/operations/delete_many.js +204 -67
- package/src/server/lib/operations/delete_one.js +164 -52
- package/src/server/lib/operations/find.js +563 -201
- package/src/server/lib/operations/find_one.js +544 -188
- package/src/server/lib/operations/insert_one.js +147 -52
- package/src/server/lib/operations/update_one.js +334 -93
- package/src/server/lib/send_response.js +37 -17
- package/src/server/lib/tcp_protocol.js +158 -53
- package/tests/server/cluster/master_read_write_operations.test.js +5 -14
- package/tests/server/integration/authentication_integration.test.js +18 -10
- package/tests/server/integration/backup_integration.test.js +35 -27
- package/tests/server/lib/api_key_manager.test.js +88 -32
- package/tests/server/lib/development_mode.test.js +2 -2
- package/tests/server/lib/operations/admin.test.js +20 -12
- package/tests/server/lib/operations/delete_one.test.js +10 -4
- package/tests/server/lib/operations/find_array_queries.test.js +261 -0
|
@@ -96,98 +96,149 @@ class ClusterMaster extends EventEmitter {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
100
|
-
* @
|
|
99
|
+
* Determines database path from settings or uses default.
|
|
100
|
+
* @returns {string} Database path
|
|
101
101
|
*/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
get_database_path() {
|
|
103
|
+
let database_path = './data';
|
|
105
104
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// NOTE: Initialize database and auth manager.
|
|
110
|
-
// NOTE: Initialize database with data_path from settings if available.
|
|
111
|
-
let database_path = './data'; // Default path
|
|
112
|
-
try {
|
|
113
|
-
const settings = get_settings();
|
|
114
|
-
if (settings?.data_path) {
|
|
115
|
-
database_path = settings.data_path;
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
// NOTE: Settings not available, use default path.
|
|
105
|
+
const settings = get_settings();
|
|
106
|
+
if (settings?.data_path) {
|
|
107
|
+
database_path = settings.data_path;
|
|
119
108
|
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// Settings not available, use default path
|
|
111
|
+
}
|
|
112
|
+
return database_path;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Initializes core database and authentication systems.
|
|
117
|
+
*/
|
|
118
|
+
async initialize_core_systems() {
|
|
119
|
+
const database_path = this.get_database_path();
|
|
120
|
+
|
|
121
|
+
initialize_database(database_path);
|
|
122
|
+
initialize_auth_manager();
|
|
123
|
+
await initialize_api_key_manager();
|
|
124
|
+
|
|
125
|
+
this.log.info('Database and auth manager initialized');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Handles startup database restore if configured.
|
|
130
|
+
*/
|
|
131
|
+
async handle_startup_restore() {
|
|
132
|
+
if (!this.settings?.restore_from) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
this.log.info('Startup restore requested', { backup_filename: this.settings.restore_from });
|
|
120
138
|
|
|
121
|
-
|
|
122
|
-
initialize_auth_manager();
|
|
123
|
-
await initialize_api_key_manager();
|
|
124
|
-
this.log.info('Database and auth manager initialized');
|
|
139
|
+
const restore_result = await restore_backup(this.settings.restore_from);
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const restore_result = await restore_backup(this.settings.restore_from);
|
|
132
|
-
|
|
133
|
-
this.log.info('Startup restore completed', {
|
|
134
|
-
backup_filename: this.settings.restore_from,
|
|
135
|
-
duration_ms: restore_result.duration_ms
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// NOTE: Remove restore_from from settings after successful restore.
|
|
139
|
-
const updated_settings = { ...this.settings };
|
|
140
|
-
delete updated_settings.restore_from;
|
|
141
|
-
|
|
142
|
-
writeFileSync(this.settings_file, JSON.stringify(updated_settings, null, 2));
|
|
143
|
-
|
|
144
|
-
// NOTE: Reload settings to reflect the change.
|
|
145
|
-
this.settings = load_settings(this.settings_file);
|
|
146
|
-
|
|
147
|
-
this.log.info('Removed restore_from from settings after successful restore');
|
|
148
|
-
} catch (restore_error) {
|
|
149
|
-
this.log.error('Startup restore failed', {
|
|
150
|
-
backup_filename: this.settings.restore_from,
|
|
151
|
-
error: restore_error.message
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// NOTE: Continue with existing database if restore fails.
|
|
155
|
-
this.log.info('Continuing with existing database after restore failure');
|
|
156
|
-
}
|
|
157
|
-
}
|
|
141
|
+
this.log.info('Startup restore completed', {
|
|
142
|
+
backup_filename: this.settings.restore_from,
|
|
143
|
+
duration_ms: restore_result.duration_ms
|
|
144
|
+
});
|
|
158
145
|
|
|
159
|
-
|
|
160
|
-
if (this.settings?.s3) {
|
|
161
|
-
try {
|
|
162
|
-
start_backup_schedule();
|
|
163
|
-
this.log.info('Backup scheduling started');
|
|
164
|
-
} catch (backup_schedule_error) {
|
|
165
|
-
this.log.warn('Failed to start backup scheduling', { error: backup_schedule_error.message });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
146
|
+
this.remove_restore_from_settings();
|
|
168
147
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (http_server_instance) {
|
|
175
|
-
this.log.info('HTTP setup server started', { http_port });
|
|
176
|
-
}
|
|
177
|
-
} catch (http_error) {
|
|
178
|
-
this.log.warn('Failed to start HTTP setup server', { error: http_error.message });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
148
|
+
} catch (restore_error) {
|
|
149
|
+
this.log.error('Startup restore failed', {
|
|
150
|
+
backup_filename: this.settings.restore_from,
|
|
151
|
+
error: restore_error.message
|
|
152
|
+
});
|
|
181
153
|
|
|
182
|
-
|
|
183
|
-
|
|
154
|
+
this.log.info('Continuing with existing database after restore failure');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Removes restore_from setting after successful restore.
|
|
160
|
+
*/
|
|
161
|
+
remove_restore_from_settings() {
|
|
162
|
+
const updated_settings = { ...this.settings };
|
|
163
|
+
delete updated_settings.restore_from;
|
|
164
|
+
|
|
165
|
+
writeFileSync(this.settings_file, JSON.stringify(updated_settings, null, 2));
|
|
166
|
+
this.settings = load_settings(this.settings_file);
|
|
167
|
+
|
|
168
|
+
this.log.info('Removed restore_from from settings after successful restore');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Starts backup scheduling if S3 is configured.
|
|
173
|
+
*/
|
|
174
|
+
start_backup_scheduling() {
|
|
175
|
+
if (!this.settings?.s3) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
start_backup_schedule();
|
|
181
|
+
this.log.info('Backup scheduling started');
|
|
182
|
+
} catch (backup_schedule_error) {
|
|
183
|
+
this.log.warn('Failed to start backup scheduling', { error: backup_schedule_error.message });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Starts HTTP setup server if authentication is not configured.
|
|
189
|
+
*/
|
|
190
|
+
async start_setup_server() {
|
|
191
|
+
if (!is_setup_required()) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const { http_port } = get_port_configuration();
|
|
197
|
+
const http_server_instance = await start_http_server(http_port);
|
|
198
|
+
if (http_server_instance) {
|
|
199
|
+
this.log.info('HTTP setup server started', { http_port });
|
|
184
200
|
}
|
|
201
|
+
} catch (http_error) {
|
|
202
|
+
this.log.warn('Failed to start HTTP setup server', { error: http_error.message });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Spawns all worker processes.
|
|
208
|
+
*/
|
|
209
|
+
spawn_all_workers() {
|
|
210
|
+
for (let i = 0; i < this.worker_count; i++) {
|
|
211
|
+
this.spawn_worker();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Displays development mode startup message if applicable.
|
|
217
|
+
*/
|
|
218
|
+
display_development_message() {
|
|
219
|
+
if (is_development_mode()) {
|
|
220
|
+
const { tcp_port, http_port } = get_port_configuration();
|
|
221
|
+
display_development_startup_message(tcp_port, http_port);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Starts the cluster master and spawns worker processes.
|
|
227
|
+
* @throws {Error} When startup fails
|
|
228
|
+
*/
|
|
229
|
+
async start() {
|
|
230
|
+
const start_time = Date.now();
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
this.settings = load_settings(this.settings_file);
|
|
234
|
+
this.log.info('Settings loaded successfully', { settings_file: this.settings_file });
|
|
185
235
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
236
|
+
await this.initialize_core_systems();
|
|
237
|
+
await this.handle_startup_restore();
|
|
238
|
+
this.start_backup_scheduling();
|
|
239
|
+
await this.start_setup_server();
|
|
240
|
+
this.spawn_all_workers();
|
|
241
|
+
this.display_development_message();
|
|
191
242
|
|
|
192
243
|
const duration_ms = Date.now() - start_time;
|
|
193
244
|
this.log.info('Master process started successfully', {
|
|
@@ -657,30 +708,33 @@ await client.ping();
|
|
|
657
708
|
}
|
|
658
709
|
|
|
659
710
|
/**
|
|
660
|
-
*
|
|
711
|
+
* Stops HTTP server gracefully.
|
|
661
712
|
*/
|
|
662
|
-
async
|
|
663
|
-
const shutdown_start = Date.now();
|
|
664
|
-
this.log.info('Initiating graceful shutdown');
|
|
665
|
-
this.shutting_down = true;
|
|
666
|
-
|
|
667
|
-
// NOTE: Stop HTTP server.
|
|
713
|
+
async stop_http_server_gracefully() {
|
|
668
714
|
try {
|
|
669
715
|
await stop_http_server();
|
|
670
716
|
this.log.info('HTTP server stopped');
|
|
671
717
|
} catch (http_stop_error) {
|
|
672
718
|
this.log.warn('Failed to stop HTTP server', { error: http_stop_error.message });
|
|
673
719
|
}
|
|
674
|
-
|
|
675
|
-
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Stops backup scheduling gracefully.
|
|
724
|
+
*/
|
|
725
|
+
stop_backup_scheduling_gracefully() {
|
|
676
726
|
try {
|
|
677
727
|
stop_backup_schedule();
|
|
678
728
|
this.log.info('Backup scheduling stopped');
|
|
679
729
|
} catch (backup_stop_error) {
|
|
680
730
|
this.log.warn('Failed to stop backup scheduling', { error: backup_stop_error.message });
|
|
681
731
|
}
|
|
682
|
-
|
|
683
|
-
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Sends shutdown signal to all workers.
|
|
736
|
+
*/
|
|
737
|
+
send_shutdown_signals() {
|
|
684
738
|
for (const [worker_id, worker_info] of this.workers) {
|
|
685
739
|
try {
|
|
686
740
|
worker_info.worker.send({ type: 'shutdown' });
|
|
@@ -688,28 +742,37 @@ await client.ping();
|
|
|
688
742
|
this.log.warn('Error sending shutdown signal to worker', { worker_id, error: error.message });
|
|
689
743
|
}
|
|
690
744
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
this.log.warn('Timeout waiting for writes to complete, proceeding with shutdown');
|
|
700
|
-
resolve();
|
|
701
|
-
}, process.env.NODE_ENV === 'test' ? 1000 : 5000);
|
|
702
|
-
|
|
703
|
-
this.once('writes_completed', () => {
|
|
704
|
-
clearTimeout(timeout);
|
|
705
|
-
resolve();
|
|
706
|
-
});
|
|
707
|
-
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Waits for pending writes to complete with timeout.
|
|
749
|
+
*/
|
|
750
|
+
async wait_for_pending_writes() {
|
|
751
|
+
if (this.write_queue.length === 0) {
|
|
752
|
+
return;
|
|
708
753
|
}
|
|
709
|
-
|
|
710
|
-
this.log.info('
|
|
711
|
-
|
|
712
|
-
|
|
754
|
+
|
|
755
|
+
this.log.info('Waiting for pending writes to complete', {
|
|
756
|
+
pending_writes: this.write_queue.length
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
await new Promise((resolve) => {
|
|
760
|
+
const timeout = setTimeout(() => {
|
|
761
|
+
this.log.warn('Timeout waiting for writes to complete, proceeding with shutdown');
|
|
762
|
+
resolve();
|
|
763
|
+
}, process.env.NODE_ENV === 'test' ? 1000 : 5000);
|
|
764
|
+
|
|
765
|
+
this.once('writes_completed', () => {
|
|
766
|
+
clearTimeout(timeout);
|
|
767
|
+
resolve();
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Disconnects all workers gracefully.
|
|
774
|
+
*/
|
|
775
|
+
disconnect_all_workers() {
|
|
713
776
|
for (const [worker_id, worker_info] of this.workers) {
|
|
714
777
|
try {
|
|
715
778
|
worker_info.worker.disconnect();
|
|
@@ -717,22 +780,32 @@ await client.ping();
|
|
|
717
780
|
this.log.warn('Error disconnecting worker', { worker_id, error: error.message });
|
|
718
781
|
}
|
|
719
782
|
}
|
|
720
|
-
|
|
721
|
-
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Force kills remaining workers after timeout.
|
|
787
|
+
*/
|
|
788
|
+
force_kill_remaining_workers() {
|
|
789
|
+
for (const [worker_id, worker_info] of this.workers) {
|
|
790
|
+
this.log.warn('Force killing worker after timeout', { worker_id });
|
|
791
|
+
try {
|
|
792
|
+
worker_info.worker.kill('SIGKILL');
|
|
793
|
+
} catch (error) {
|
|
794
|
+
this.log.warn('Error force killing worker', { worker_id, error: error.message });
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
this.workers.clear();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Waits for workers to exit gracefully with timeout.
|
|
802
|
+
*/
|
|
803
|
+
async wait_for_workers_to_exit() {
|
|
722
804
|
const graceful_timeout = process.env.NODE_ENV === 'test' ? 500 : 3000;
|
|
723
805
|
|
|
724
806
|
await new Promise((resolve) => {
|
|
725
807
|
const timeout = setTimeout(() => {
|
|
726
|
-
|
|
727
|
-
for (const [worker_id, worker_info] of this.workers) {
|
|
728
|
-
this.log.warn('Force killing worker after timeout', { worker_id });
|
|
729
|
-
try {
|
|
730
|
-
worker_info.worker.kill('SIGKILL');
|
|
731
|
-
} catch (error) {
|
|
732
|
-
this.log.warn('Error force killing worker', { worker_id, error: error.message });
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
this.workers.clear();
|
|
808
|
+
this.force_kill_remaining_workers();
|
|
736
809
|
resolve();
|
|
737
810
|
}, graceful_timeout);
|
|
738
811
|
|
|
@@ -746,49 +819,85 @@ await client.ping();
|
|
|
746
819
|
};
|
|
747
820
|
check_workers();
|
|
748
821
|
});
|
|
749
|
-
|
|
750
|
-
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Cleans up database connections.
|
|
826
|
+
*/
|
|
827
|
+
cleanup_database_connections() {
|
|
751
828
|
try {
|
|
752
829
|
cleanup_database();
|
|
753
830
|
this.log.info('Database cleanup completed');
|
|
754
831
|
} catch (cleanup_error) {
|
|
755
832
|
this.log.warn('Error during database cleanup', { error: cleanup_error.message });
|
|
756
833
|
}
|
|
757
|
-
|
|
758
|
-
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Clears all internal state.
|
|
838
|
+
*/
|
|
839
|
+
clear_internal_state() {
|
|
759
840
|
this.authenticated_sessions.clear();
|
|
760
841
|
this.write_queue.length = 0;
|
|
761
842
|
this.pending_writes.clear();
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Performs aggressive cleanup for test environment.
|
|
847
|
+
*/
|
|
848
|
+
perform_test_environment_cleanup() {
|
|
849
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
try {
|
|
854
|
+
// Force kill any remaining workers in the global cluster state
|
|
855
|
+
for (const worker_id in cluster.workers) {
|
|
856
|
+
const worker = cluster.workers[worker_id];
|
|
857
|
+
if (worker && !worker.isDead()) {
|
|
858
|
+
this.log.info('Force killing remaining cluster worker', { worker_id, worker_pid: worker.process.pid });
|
|
859
|
+
try {
|
|
860
|
+
worker.kill('SIGKILL');
|
|
861
|
+
} catch (kill_error) {
|
|
862
|
+
this.log.warn('Error force killing remaining worker', { worker_id, error: kill_error.message });
|
|
776
863
|
}
|
|
777
864
|
}
|
|
778
|
-
|
|
779
|
-
// Clear the global cluster workers registry
|
|
780
|
-
for (const worker_id in cluster.workers) {
|
|
781
|
-
delete cluster.workers[worker_id];
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// Remove all cluster event listeners to prevent memory leaks
|
|
785
|
-
cluster.removeAllListeners();
|
|
786
|
-
|
|
787
|
-
this.log.info('Aggressive cluster cleanup completed for test environment');
|
|
788
|
-
} catch (cleanup_error) {
|
|
789
|
-
this.log.warn('Error during aggressive cluster cleanup', { error: cleanup_error.message });
|
|
790
865
|
}
|
|
866
|
+
|
|
867
|
+
// Clear the global cluster workers registry
|
|
868
|
+
for (const worker_id in cluster.workers) {
|
|
869
|
+
delete cluster.workers[worker_id];
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Remove all cluster event listeners to prevent memory leaks
|
|
873
|
+
cluster.removeAllListeners();
|
|
874
|
+
|
|
875
|
+
this.log.info('Aggressive cluster cleanup completed for test environment');
|
|
876
|
+
} catch (cleanup_error) {
|
|
877
|
+
this.log.warn('Error during aggressive cluster cleanup', { error: cleanup_error.message });
|
|
791
878
|
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Performs graceful shutdown of the cluster master and all workers.
|
|
883
|
+
*/
|
|
884
|
+
async shutdown() {
|
|
885
|
+
const shutdown_start = Date.now();
|
|
886
|
+
this.log.info('Initiating graceful shutdown');
|
|
887
|
+
this.shutting_down = true;
|
|
888
|
+
|
|
889
|
+
await this.stop_http_server_gracefully();
|
|
890
|
+
this.stop_backup_scheduling_gracefully();
|
|
891
|
+
this.send_shutdown_signals();
|
|
892
|
+
await this.wait_for_pending_writes();
|
|
893
|
+
|
|
894
|
+
this.log.info('All writes completed, disconnecting workers');
|
|
895
|
+
this.disconnect_all_workers();
|
|
896
|
+
await this.wait_for_workers_to_exit();
|
|
897
|
+
|
|
898
|
+
this.cleanup_database_connections();
|
|
899
|
+
this.clear_internal_state();
|
|
900
|
+
this.perform_test_environment_cleanup();
|
|
792
901
|
|
|
793
902
|
const shutdown_duration = Date.now() - shutdown_start;
|
|
794
903
|
this.log.info('Shutdown complete', { shutdown_duration_ms: shutdown_duration });
|
|
@@ -196,29 +196,37 @@ class ClusterWorker {
|
|
|
196
196
|
});
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Validates and processes a single parsed message.
|
|
201
|
+
* @param {Object} socket - Socket connection
|
|
202
|
+
* @param {Object} parsed_data - Parsed message data
|
|
203
|
+
* @returns {boolean} True if message was processed successfully
|
|
204
|
+
*/
|
|
205
|
+
process_single_message(socket, parsed_data) {
|
|
206
|
+
const op_type = parsed_data?.op || null;
|
|
207
|
+
|
|
208
|
+
if (!op_type) {
|
|
209
|
+
send_error(socket, { message: 'Missing operation type' });
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const is_valid_op_type = this.check_op_type(op_type);
|
|
214
|
+
|
|
215
|
+
if (!is_valid_op_type) {
|
|
216
|
+
send_error(socket, { message: 'Invalid operation type' });
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.route_operation(socket, op_type, parsed_data?.data || {});
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
199
224
|
handle_socket_data(socket, raw_data) {
|
|
200
225
|
try {
|
|
201
|
-
// NOTE: Use the message parser to handle length-prefixed MessagePack messages.
|
|
202
226
|
const messages = socket.message_parser.parse_messages(raw_data);
|
|
203
227
|
|
|
204
228
|
for (const message of messages) {
|
|
205
|
-
|
|
206
|
-
const parsed_data = message;
|
|
207
|
-
const op_type = parsed_data?.op || null;
|
|
208
|
-
|
|
209
|
-
if (!op_type) {
|
|
210
|
-
send_error(socket, { message: 'Missing operation type' });
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const is_valid_op_type = this.check_op_type(op_type);
|
|
215
|
-
|
|
216
|
-
if (!is_valid_op_type) {
|
|
217
|
-
send_error(socket, { message: 'Invalid operation type' });
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
this.route_operation(socket, op_type, parsed_data?.data || {});
|
|
229
|
+
this.process_single_message(socket, message);
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
} catch (error) {
|