aetherframework-cluster 1.0.0

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.
@@ -0,0 +1,78 @@
1
+ // packages/cluster/src/utils/cpu-detector.js
2
+ import os from 'os';
3
+
4
+ /**
5
+ * CPUDetector - Utility class for detecting CPU capabilities and recommending worker counts
6
+ */
7
+ class CPUDetector {
8
+ /**
9
+ * Get detailed CPU information
10
+ * @returns {Object} CPU information object
11
+ */
12
+ static getInfo() {
13
+ const cpus = os.cpus();
14
+
15
+ return {
16
+ model: cpus?.model || 'Unknown',
17
+ speed: cpus?.speed || 0, // in MHz
18
+ count: cpus.length,
19
+ architecture: os.arch(),
20
+ platform: os.platform(),
21
+ endianness: os.endianness(),
22
+ loadAvg: os.loadavg(),
23
+ uptime: os.uptime()
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Calculate the optimal number of worker processes
29
+ * Default strategy: Number of logical cores - 1 (to leave room for the master process)
30
+ * Minimum 1 worker.
31
+ *
32
+ * @param {Object} options - Configuration options
33
+ * @param {number} options.reserve - Number of cores to reserve for system/master (default: 1)
34
+ * @param {number} options.max - Maximum number of workers allowed
35
+ * @returns {number} Recommended worker count
36
+ */
37
+ static getOptimalWorkerCount(options = {}) {
38
+ const { reserve = 1, max = Infinity } = options;
39
+ const logicalCores = os.cpus().length;
40
+
41
+ // Calculate base count
42
+ let count = logicalCores - reserve;
43
+
44
+ // Ensure at least 1 worker
45
+ if (count < 1) {
46
+ count = 1;
47
+ }
48
+
49
+ // Apply maximum limit
50
+ if (count > max) {
51
+ count = max;
52
+ }
53
+
54
+ return count;
55
+ }
56
+
57
+ /**
58
+ * Check if the current environment supports multi-core clustering
59
+ * @returns {boolean} True if multi-core is supported
60
+ */
61
+ static isMultiCoreSupported() {
62
+ return os.cpus().length > 1;
63
+ }
64
+
65
+ /**
66
+ * Get memory information relevant to clustering
67
+ * @returns {Object} Memory statistics in bytes
68
+ */
69
+ static getMemoryInfo() {
70
+ return {
71
+ total: os.totalmem(),
72
+ free: os.freemem(),
73
+ used: os.totalmem() - os.freemem()
74
+ };
75
+ }
76
+ }
77
+
78
+ export default CPUDetector;
@@ -0,0 +1,140 @@
1
+ // packages/cluster/src/utils/env-loader.js
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ /**
6
+ * 环境变量加载器
7
+ */
8
+ export function loadEnv() {
9
+ const env = {};
10
+
11
+ // 默认值
12
+ const defaults = {
13
+ CLUSTER_ENABLED: 'true',
14
+ CLUSTER_WORKERS: 'auto',
15
+ CLUSTER_PORT: '3000',
16
+ CLUSTER_GRACEFUL_SHUTDOWN: 'true',
17
+ CLUSTER_RESTART_ON_EXIT: 'true',
18
+ CLUSTER_RESTART_DELAY: '1000',
19
+ CLUSTER_HEALTH_CHECK_ENABLED: 'true',
20
+ CLUSTER_HEALTH_CHECK_INTERVAL: '30000',
21
+ CLUSTER_HEALTH_CHECK_TIMEOUT: '5000',
22
+ CLUSTER_LOAD_BALANCING: 'true',
23
+ CLUSTER_LOAD_BALANCER_TYPE: 'round-robin',
24
+ CLUSTER_MAX_REQUESTS_PER_WORKER: '1000',
25
+ CLUSTER_MONITORING_ENABLED: 'true',
26
+ CLUSTER_MONITORING_INTERVAL: '10000',
27
+ CLUSTER_MEMORY_THRESHOLD: '0.8',
28
+ CLUSTER_CPU_THRESHOLD: '0.7',
29
+ CLUSTER_LOG_LEVEL: 'info',
30
+ CLUSTER_LOG_FORMAT: 'json',
31
+ CLUSTER_WORKER_REUSE_PORT: 'true',
32
+ CLUSTER_ENABLE_IPC: 'true',
33
+ CLUSTER_IPC_TIMEOUT: '5000',
34
+ CLUSTER_ENABLE_SECURITY: 'true',
35
+ CLUSTER_MAX_MEMORY: '512',
36
+ CLUSTER_MAX_OLD_SPACE_SIZE: '4096',
37
+ CLUSTER_DEBUG: 'false',
38
+ CLUSTER_DEBUG_PORT: '9229',
39
+ CLUSTER_INSPECT: 'false',
40
+ CLUSTER_ENABLE_HOT_RELOAD: 'false',
41
+ CLUSTER_ENABLE_PROFILING: 'false',
42
+ CLUSTER_PROFILING_INTERVAL: '60000'
43
+ };
44
+
45
+ // 从 process.env 加载
46
+ for (const [key, defaultValue] of Object.entries(defaults)) {
47
+ env[key] = process.env[key] || defaultValue;
48
+ }
49
+
50
+ // 尝试从 .env 文件加载
51
+ try {
52
+ const envPath = join(process.cwd(), '.env');
53
+ const envContent = readFileSync(envPath, 'utf8');
54
+ const envLines = envContent.split('\n');
55
+
56
+ for (const line of envLines) {
57
+ const trimmed = line.trim();
58
+ if (trimmed && !trimmed.startsWith('#')) {
59
+ const [key, ...valueParts] = trimmed.split('=');
60
+ if (key && valueParts.length > 0) {
61
+ const value = valueParts.join('=').trim();
62
+ // 移除引号
63
+ const cleanValue = value.replace(/^['"]|['"]$/g, '');
64
+ env[key.trim()] = cleanValue;
65
+ }
66
+ }
67
+ }
68
+ } catch (error) {
69
+ // .env 文件不存在,使用默认值
70
+ }
71
+
72
+ // 类型转换
73
+ const booleanKeys = [
74
+ 'CLUSTER_ENABLED',
75
+ 'CLUSTER_GRACEFUL_SHUTDOWN',
76
+ 'CLUSTER_RESTART_ON_EXIT',
77
+ 'CLUSTER_HEALTH_CHECK_ENABLED',
78
+ 'CLUSTER_LOAD_BALANCING',
79
+ 'CLUSTER_MONITORING_ENABLED',
80
+ 'CLUSTER_WORKER_REUSE_PORT',
81
+ 'CLUSTER_ENABLE_IPC',
82
+ 'CLUSTER_ENABLE_SECURITY',
83
+ 'CLUSTER_DEBUG',
84
+ 'CLUSTER_INSPECT',
85
+ 'CLUSTER_ENABLE_HOT_RELOAD',
86
+ 'CLUSTER_ENABLE_PROFILING'
87
+ ];
88
+
89
+ const numberKeys = [
90
+ 'CLUSTER_PORT',
91
+ 'CLUSTER_RESTART_DELAY',
92
+ 'CLUSTER_HEALTH_CHECK_INTERVAL',
93
+ 'CLUSTER_HEALTH_CHECK_TIMEOUT',
94
+ 'CLUSTER_MAX_REQUESTS_PER_WORKER',
95
+ 'CLUSTER_MONITORING_INTERVAL',
96
+ 'CLUSTER_IPC_TIMEOUT',
97
+ 'CLUSTER_MAX_MEMORY',
98
+ 'CLUSTER_MAX_OLD_SPACE_SIZE',
99
+ 'CLUSTER_DEBUG_PORT',
100
+ 'CLUSTER_PROFILING_INTERVAL'
101
+ ];
102
+
103
+ const floatKeys = [
104
+ 'CLUSTER_MEMORY_THRESHOLD',
105
+ 'CLUSTER_CPU_THRESHOLD'
106
+ ];
107
+
108
+ // 转换布尔值
109
+ for (const key of booleanKeys) {
110
+ if (env[key] !== undefined) {
111
+ env[key] = env[key] === 'true' || env[key] === '1';
112
+ }
113
+ }
114
+
115
+ // 转换数字
116
+ for (const key of numberKeys) {
117
+ if (env[key] !== undefined) {
118
+ env[key] = parseInt(env[key], 10);
119
+ }
120
+ }
121
+
122
+ // 转换浮点数
123
+ for (const key of floatKeys) {
124
+ if (env[key] !== undefined) {
125
+ env[key] = parseFloat(env[key]);
126
+ }
127
+ }
128
+
129
+ // 处理 workers 参数
130
+ if (env.CLUSTER_WORKERS === 'auto') {
131
+ env.CLUSTER_WORKERS = 'auto';
132
+ } else {
133
+ env.CLUSTER_WORKERS = parseInt(env.CLUSTER_WORKERS, 10);
134
+ }
135
+
136
+ return env;
137
+ }
138
+
139
+ // 导出默认实例
140
+ export default { loadEnv };
@@ -0,0 +1,90 @@
1
+ // packages/cluster/src/utils/signal-handler.js
2
+ import process from 'process';
3
+
4
+ /**
5
+ * SignalHandler - Manages OS signals for graceful shutdown and restart
6
+ */
7
+ class SignalHandler {
8
+ constructor() {
9
+ this.handlers = new Map();
10
+ this.isShuttingDown = false;
11
+ }
12
+
13
+ /**
14
+ * Register a handler for a specific signal
15
+ * @param {string} signal - Signal name (e.g., 'SIGINT', 'SIGTERM')
16
+ * @param {Function} callback - Async function to execute on signal
17
+ */
18
+ on(signal, callback) {
19
+ if (!this.handlers.has(signal)) {
20
+ this.handlers.set(signal, []);
21
+
22
+ // Attach the listener to the process only once per signal type
23
+ process.on(signal, async () => {
24
+ if (this.isShuttingDown && signal !== 'SIGHUP') {
25
+ console.warn(`Received ${signal} while already shutting down. Forcing exit.`);
26
+ process.exit(1);
27
+ return;
28
+ }
29
+
30
+ if (signal !== 'SIGHUP') {
31
+ this.isShuttingDown = true;
32
+ }
33
+
34
+ console.log(`\n📡 Received signal: ${signal}`);
35
+
36
+ const callbacks = this.handlers.get(signal);
37
+ try {
38
+ // Execute all registered handlers in parallel
39
+ await Promise.all(callbacks.map(cb => cb()));
40
+ } catch (error) {
41
+ console.error(`Error handling signal ${signal}:`, error);
42
+ } finally {
43
+ // Only exit on termination signals, not SIGHUP (reload)
44
+ if (signal !== 'SIGHUP') {
45
+ console.log('✅ Graceful shutdown complete.');
46
+ process.exit(0);
47
+ }
48
+ }
49
+ });
50
+ }
51
+
52
+ this.handlers.get(signal).push(callback);
53
+ }
54
+
55
+ /**
56
+ * Setup default handlers for common termination signals
57
+ * @param {Function} shutdownCallback - Optional global shutdown logic
58
+ */
59
+ setupDefaults(shutdownCallback) {
60
+ // Handle Ctrl+C
61
+ this.on('SIGINT', async () => {
62
+ if (shutdownCallback) await shutdownCallback();
63
+ });
64
+
65
+ // Handle kill command
66
+ this.on('SIGTERM', async () => {
67
+ if (shutdownCallback) await shutdownCallback();
68
+ });
69
+
70
+ // Handle uncaught exceptions to prevent silent crashes
71
+ process.on('uncaughtException', (err) => {
72
+ console.error('💥 Uncaught Exception:', err.message);
73
+ console.error(err.stack);
74
+ // Attempt graceful shutdown on critical error
75
+ if (shutdownCallback) {
76
+ shutdownCallback().finally(() => process.exit(1));
77
+ } else {
78
+ process.exit(1);
79
+ }
80
+ });
81
+
82
+ // Handle unhandled promise rejections
83
+ process.on('unhandledRejection', (reason, promise) => {
84
+ console.error('⚠️ Unhandled Rejection at:', promise, 'reason:', reason);
85
+ });
86
+ }
87
+ }
88
+
89
+ // Export singleton instance
90
+ export default new SignalHandler();