@abtnode/core 1.17.4-beta-20251204-080001-08643fbe → 1.17.4-beta-20251204-152224-243ff54f

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/lib/api/team.js CHANGED
@@ -3062,7 +3062,11 @@ class TeamAPI extends EventEmitter {
3062
3062
 
3063
3063
  return result;
3064
3064
  } catch (err) {
3065
- logger.error('Failed to create org', { err, teamDid });
3065
+ logger.error('Failed to create org', err, {
3066
+ teamDid,
3067
+ name: rest.name,
3068
+ userDid: rest.ownerDid || context.user.did || '',
3069
+ });
3066
3070
  throw err;
3067
3071
  }
3068
3072
  }
@@ -45,7 +45,7 @@ const {
45
45
  EVENTS,
46
46
  USER_PROFILE_SYNC_FIELDS,
47
47
  } = require('@abtnode/constant');
48
-
48
+ const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
49
49
  const { getBlockletEngine } = require('@blocklet/meta/lib/engine');
50
50
  const {
51
51
  isDeletableBlocklet,
@@ -245,7 +245,7 @@ const { installExternalDependencies } = require('../../util/install-external-dep
245
245
  const { dockerExecChown } = require('../../util/docker/docker-exec-chown');
246
246
  const checkDockerRunHistory = require('../../util/docker/check-docker-run-history');
247
247
  const { shouldJobBackoff } = require('../../util/env');
248
- const ensureBlockletRunning = require('./ensure-blocklet-running');
248
+ const { ensureBlockletRunning } = require('./ensure-blocklet-running');
249
249
 
250
250
  const { transformNotification } = require('../../util/notification');
251
251
  const { generateUserUpdateData } = require('../../util/user');
@@ -1916,7 +1916,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1916
1916
  if (!aliasDomainSite) {
1917
1917
  return null;
1918
1918
  }
1919
- targetDid = (aliasDomainSite.domain || '').replace('.blocklet-domain-group', '');
1919
+ targetDid = (aliasDomainSite.domain || '').replace(BLOCKLET_SITE_GROUP_SUFFIX, '');
1920
1920
  }
1921
1921
 
1922
1922
  if (!targetDid) {
@@ -1,14 +1,22 @@
1
+ /* eslint-disable no-await-in-loop */
1
2
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet-status-checker');
2
3
  const pAll = require('p-all');
3
4
  const { BlockletStatus } = require('@blocklet/constant');
4
5
  const sleep = require('@abtnode/util/lib/sleep');
5
6
  const { getDisplayName } = require('@blocklet/meta/lib/util');
6
- const { isValid } = require('@arcblock/did');
7
-
8
7
  const states = require('../../states');
9
8
  const { isBlockletPortHealthy, shouldCheckHealthy } = require('../../util/blocklet');
10
9
 
11
- const inProgressStatuses = [BlockletStatus.stopping, BlockletStatus.restarting, BlockletStatus.waiting];
10
+ const inProgressStatuses = [
11
+ BlockletStatus.stopping,
12
+ BlockletStatus.restarting,
13
+ BlockletStatus.waiting,
14
+ BlockletStatus.starting,
15
+ BlockletStatus.downloading,
16
+ ];
17
+
18
+ // Restart queue concurrency, 这个改大,容易 blocklet 超时导致启动失败
19
+ const RESTART_CONCURRENCY = 2;
12
20
 
13
21
  class EnsureBlockletRunning {
14
22
  canRunEnsureBlockletRunning = false;
@@ -22,11 +30,9 @@ class EnsureBlockletRunning {
22
30
 
23
31
  minCheckInterval = 30_000;
24
32
 
25
- everyBlockletCheckInterval = 2000;
26
-
27
- everyBlockletDoingInterval = 5000;
33
+ preCheckInterval = 1000;
28
34
 
29
- fakeRunningToWaitingOnce = false;
35
+ everyBlockletCheckInterval = 2000;
30
36
 
31
37
  highLoadCpu = +process.env.ABT_NODE_ENSURE_RUNNING_HIGH_LOAD_CPU || 0.85;
32
38
 
@@ -34,27 +40,41 @@ class EnsureBlockletRunning {
34
40
 
35
41
  highLoadDisk = +process.env.ABT_NODE_ENSURE_RUNNING_HIGH_LOAD_DISK || 0.95;
36
42
 
37
- // 进行中状态超时时间(毫秒)
38
- inProgressTimeout = +process.env.ABT_NODE_ENSURE_RUNNING_IN_PROGRESS_TIMEOUT || 5 * 60 * 1000;
43
+ // 各个状态的超时阈值(毫秒)
44
+ // 如果是首次调用(whenCycleCheck false),这些值应该是 0
45
+ stoppingTimeout = +process.env.ABT_NODE_ENSURE_RUNNING_STOPPING_TIMEOUT || 60 * 1000;
39
46
 
40
- runningBlocklets = {};
47
+ restartingTimeout = +process.env.ABT_NODE_ENSURE_RUNNING_RESTARTING_TIMEOUT || 6 * 60 * 1000;
48
+
49
+ waitingTimeout = +process.env.ABT_NODE_ENSURE_RUNNING_WAITING_TIMEOUT || 60 * 1000;
41
50
 
42
- runningRootBlocklets = {};
51
+ downloadingTimeout = +process.env.ABT_NODE_ENSURE_RUNNING_DOWNLOADING_TIMEOUT || 3 * 60 * 1000;
43
52
 
44
- fakeRunningBlocklets = {};
53
+ startingTimeout = +process.env.ABT_NODE_ENSURE_RUNNING_STARTING_TIMEOUT || 6 * 60 * 1000;
45
54
 
46
- needRestartBlocklets = {};
55
+ runningBlocklets = {};
47
56
 
48
- restartingBlocklets = {};
57
+ rootBlockletsInfo = {};
49
58
 
50
- errorStartBlocklets = {};
59
+ progressBlockletsTime = {};
51
60
 
52
61
  stopped = false;
53
62
 
63
+ // Queue for restarting fake running blocklets
64
+ restartQueue = [];
65
+
66
+ // Set to track queue keys for fast lookup
67
+ restartQueueKeys = new Set();
68
+
69
+ restartQueueProcessing = false;
70
+
71
+ // Track pending jobs by componentDid to prevent duplicate processing
72
+ pendingJobs = {};
73
+
54
74
  // Ease to mock
55
75
  isBlockletPortHealthy = isBlockletPortHealthy;
56
76
 
57
- isBlockletPortHealthyWithRetries = async (blocklet, fastCheck = false) => {
77
+ isBlockletPortHealthyWithRetries = async (blocklet) => {
58
78
  let error;
59
79
  if (!this.whenCycleCheck) {
60
80
  try {
@@ -81,9 +101,7 @@ class EnsureBlockletRunning {
81
101
  } catch (e) {
82
102
  error = e;
83
103
  // eslint-disable-next-line no-await-in-loop
84
- await sleep(
85
- fastCheck && this.whenCycleCheck ? this.everyBlockletDoingInterval : this.everyBlockletCheckInterval
86
- );
104
+ await sleep(this.everyBlockletCheckInterval);
87
105
  }
88
106
  }
89
107
  logger.error('blocklet port is not healthy', error);
@@ -106,27 +124,40 @@ class EnsureBlockletRunning {
106
124
  this.checkSystemHighLoad = checkSystemHighLoad;
107
125
  logger.info('check and fix blocklet status interval', this.checkInterval);
108
126
  const task = async () => {
127
+ await sleep(this.preCheckInterval);
128
+
129
+ // 完全停止,后续也不再继续检查
130
+ if (this.stopped) {
131
+ logger.info('blocklet status checker stopped');
132
+ return;
133
+ }
109
134
  // 如果还没进入到需要检查的阶段,则等待 1 秒后继续检查
110
135
  if (!this.canRunEnsureBlockletRunning) {
111
- await sleep(1000);
112
136
  task();
113
137
  return;
114
138
  }
115
- if (this.whenCycleCheck) {
116
- await sleep(Math.max(this.checkInterval, this.minCheckInterval));
117
- }
139
+
140
+ // 首次检查前不等待(whenCycleCheck 为 false)
118
141
  try {
119
142
  await this.checkAndFix();
143
+
144
+ // 每次检查完之后查看消耗的时间
145
+ await sleep(Math.max(this.checkInterval, this.minCheckInterval));
146
+ this.whenCycleCheck = true;
120
147
  } catch (e) {
121
148
  logger.error('check and fix blocklet status failed', e);
149
+ // 出错时也要等待,避免频繁重试
150
+ if (this.whenCycleCheck) {
151
+ await sleep(Math.max(this.checkInterval, this.minCheckInterval));
152
+ }
122
153
  }
123
154
  task();
124
155
  };
125
156
  task();
126
157
  };
127
158
 
128
- getDisplayNameByRootDid = (rootDid) => {
129
- const rootBlocklet = this.runningRootBlocklets[rootDid];
159
+ getDisplayNameByRootDid = async (rootDid) => {
160
+ const rootBlocklet = this.rootBlockletsInfo[rootDid] || (await this.states.blocklet.getBlocklet(rootDid));
130
161
  if (rootBlocklet) {
131
162
  return getDisplayName(rootBlocklet);
132
163
  }
@@ -137,6 +168,42 @@ class EnsureBlockletRunning {
137
168
  return blocklet.meta.title || blocklet.meta.name || blocklet.meta.did;
138
169
  };
139
170
 
171
+ /**
172
+ * Get timeout threshold for a specific status
173
+ * @param {string} status - Blocklet status
174
+ * @returns {number} Timeout threshold in milliseconds
175
+ */
176
+ getStatusTimeout = (status) => {
177
+ // 如果是首次调用,所有阈值都是 0
178
+ if (!this.whenCycleCheck) {
179
+ return 0;
180
+ }
181
+
182
+ let timeout = 0;
183
+ switch (status) {
184
+ case BlockletStatus.stopping:
185
+ timeout = this.stoppingTimeout;
186
+ break;
187
+ case BlockletStatus.restarting:
188
+ timeout = this.restartingTimeout;
189
+ break;
190
+ case BlockletStatus.waiting:
191
+ timeout = this.waitingTimeout;
192
+ break;
193
+ case BlockletStatus.downloading:
194
+ timeout = this.downloadingTimeout;
195
+ break;
196
+ case BlockletStatus.starting:
197
+ timeout = this.startingTimeout;
198
+ break;
199
+ default:
200
+ timeout = this.downloadingTimeout;
201
+ break;
202
+ }
203
+ // 需要减去检查间隔时间,因为每次检查都会在第二次检查之后才比对时间,最少间隔时间不能小于 waitingTimeout
204
+ return Math.max(timeout - this.checkInterval, this.waitingTimeout);
205
+ };
206
+
140
207
  checkAndFix = async () => {
141
208
  logger.info('check and fix blocklet status');
142
209
  const systemHighLoad = this.checkSystemHighLoad({
@@ -147,50 +214,51 @@ class EnsureBlockletRunning {
147
214
 
148
215
  if (this.whenCycleCheck && systemHighLoad.isHighLoad) {
149
216
  logger.warn('Skip once ensure blocklet running because system high load', systemHighLoad);
150
- return;
217
+ return 0;
151
218
  }
152
219
 
153
220
  this.runningBlocklets = {};
154
- this.fakeRunningBlocklets = {};
155
- this.needRestartBlocklets = {};
156
-
157
221
  const startTime = Date.now();
158
222
  try {
223
+ this.startRestartQueueProcessor();
159
224
  await this.getRunningBlocklets();
160
225
  await this.getFakeRunningBlocklets();
161
- await this.restartFakeRunningBlocklets();
162
226
  } catch (e) {
163
227
  logger.error('ensure blocklet status failed', e);
164
228
  }
229
+ const elapsedTime = Date.now() - startTime;
165
230
  logger.info(
166
- `ensure blocklet status finished in ${Date.now() - startTime}ms. It's server first start: ${!this.whenCycleCheck}`
231
+ `ensure blocklet status finished in ${elapsedTime}ms. It's server first start: ${!this.whenCycleCheck}`
167
232
  );
168
- this.runningRootBlocklets = {};
169
- this.whenCycleCheck = true;
233
+ return elapsedTime;
170
234
  };
171
235
 
172
236
  getRunningBlocklets = async () => {
173
- const runningStatuses = this.whenCycleCheck
174
- ? [BlockletStatus.running, BlockletStatus.waiting]
175
- : [BlockletStatus.running, BlockletStatus.waiting, BlockletStatus.error];
176
-
177
- const blocklets = await this.states.blocklet.getBlocklets();
178
- for (const rootBlocklet of blocklets) {
179
- const { did } = rootBlocklet.meta;
237
+ const rootBlocklets = await this.states.blocklet.getBlocklets();
238
+ for (const rootBlocklet of rootBlocklets) {
239
+ const rootDid = rootBlocklet.appPid || rootBlocklet.meta.did;
180
240
  if (rootBlocklet.children) {
181
241
  for (const childBlocklet of rootBlocklet.children) {
182
242
  const isRunning =
183
- runningStatuses.includes(childBlocklet.status) || childBlocklet.greenStatus === BlockletStatus.running;
184
- const isInProgress = inProgressStatuses.includes(childBlocklet.status);
243
+ childBlocklet.status === BlockletStatus.running || childBlocklet.greenStatus === BlockletStatus.running;
244
+ const isInProgress =
245
+ inProgressStatuses.includes(childBlocklet.status) || inProgressStatuses.includes(childBlocklet.greenStatus);
246
+ const isStopped =
247
+ childBlocklet.status === BlockletStatus.stopped && childBlocklet.greenStatus === BlockletStatus.stopped;
248
+
249
+ // 如果处于过 running, 或 stopped,则删除 progressBlockletsTime
250
+ if (isRunning || isStopped) {
251
+ delete this.progressBlockletsTime[`${rootDid}-${childBlocklet.meta.did}`];
252
+ }
185
253
  if (isRunning || isInProgress) {
186
- if (!this.runningBlocklets[did]) {
187
- this.runningBlocklets[did] = [];
254
+ if (!this.runningBlocklets[rootDid]) {
255
+ this.runningBlocklets[rootDid] = [];
188
256
  }
189
- if (this.runningBlocklets[did].find((b) => b.meta.did === childBlocklet.meta.did)) {
257
+ if (this.runningBlocklets[rootDid].find((child) => child.meta.did === childBlocklet.meta.did)) {
190
258
  continue;
191
259
  }
192
- this.runningBlocklets[did].push(childBlocklet);
193
- this.runningRootBlocklets[did] = rootBlocklet;
260
+ this.runningBlocklets[rootDid].push(childBlocklet);
261
+ this.rootBlockletsInfo[rootDid] = rootBlocklet;
194
262
  }
195
263
  }
196
264
  }
@@ -201,195 +269,294 @@ class EnsureBlockletRunning {
201
269
  getFakeRunningBlocklets = async () => {
202
270
  const rootDids = Object.keys(this.runningBlocklets);
203
271
  await pAll(
204
- rootDids.map((did) => {
272
+ rootDids.map((rootDid) => {
205
273
  return async () => {
206
- const blocklets = this.runningBlocklets[did];
274
+ // runningBlocklets[rootDid] 存储的是该根 blocklet 下的所有子组件(childBlocklets)
275
+ const childBlocklets = this.runningBlocklets[rootDid];
207
276
  // eslint-disable-next-line
277
+ const fakeDids = [];
208
278
  await pAll(
209
- blocklets.map((blocklet) => {
279
+ childBlocklets.map((childBlocklet) => {
210
280
  return async () => {
211
- if (!shouldCheckHealthy(blocklet)) {
212
- // 如果 blocklet 是不需要启动的,并且不是 running,则设置为 running 状态
213
- if (blocklet.status !== BlockletStatus.running && blocklet.greenStatus !== BlockletStatus.running) {
214
- await this.states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
215
- componentDids: [blocklet.meta.did],
281
+ if (!shouldCheckHealthy(childBlocklet)) {
282
+ // 如果 childBlocklet 是不需要启动的,并且不是 running,则设置为 running 状态
283
+ if (
284
+ childBlocklet.status !== BlockletStatus.running &&
285
+ childBlocklet.greenStatus !== BlockletStatus.running
286
+ ) {
287
+ await this.states.blocklet.setBlockletStatus(rootDid, BlockletStatus.running, {
288
+ componentDids: [childBlocklet.meta.did],
216
289
  });
217
290
  }
218
291
  return;
219
292
  }
220
293
 
221
- // Skip health check if blocklet is in progress status
222
- if (inProgressStatuses.includes(blocklet.status)) {
223
- // Check if it's user-initiated operation
224
- // 如果 operator 是 z 开头的字符串,表示是 did, 跳过健康检查
225
- if (blocklet.operator && isValid(blocklet.operator)) {
226
- logger.info('Skip ensure running check for user-initiated operation', {
227
- did,
228
- componentDid: blocklet.meta.did,
229
- status: blocklet.status,
230
- operator: blocklet.operator,
231
- });
232
- return;
233
- }
294
+ const isInProgress =
295
+ inProgressStatuses.includes(childBlocklet.status) ||
296
+ inProgressStatuses.includes(childBlocklet.greenStatus);
234
297
 
235
- // Check timeout for daemon-initiated or no operator recorded operations
236
- if (blocklet.inProgressStart) {
237
- const elapsedTime = Date.now() - new Date(blocklet.inProgressStart).getTime();
238
- if (elapsedTime < this.inProgressTimeout) {
239
- logger.info('Skip ensure running check due to timeout not reached', {
240
- did,
241
- componentDid: blocklet.meta.did,
242
- status: blocklet.status,
243
- elapsedTime,
244
- timeout: this.inProgressTimeout,
245
- });
246
- return;
247
- }
298
+ // 如果处于进行中状态,则记录上次检查时间
299
+ if (isInProgress) {
300
+ const key = `${rootDid}-${childBlocklet.meta.did}`;
301
+ if (!this.progressBlockletsTime[key]) {
302
+ this.progressBlockletsTime[key] = Date.now();
303
+ }
304
+ const lastProgressTime = this.progressBlockletsTime[key];
305
+ // 首次调用或者超过阈值时间,则认为是 fake running
306
+ if (
307
+ !this.whenCycleCheck ||
308
+ Date.now() - lastProgressTime > this.getStatusTimeout(childBlocklet.status)
309
+ ) {
248
310
  logger.warn('InProgress timeout reached, proceeding with health check', {
249
- did,
250
- componentDid: blocklet.meta.did,
251
- status: blocklet.status,
252
- elapsedTime,
253
- timeout: this.inProgressTimeout,
311
+ did: rootDid,
312
+ componentDid: childBlocklet.meta.did,
313
+ status: childBlocklet.status,
314
+ });
315
+ } else if (this.whenCycleCheck) {
316
+ // 如果没有 inProgressStart 时间戳,且非首次调用,跳过检查
317
+ logger.info('Skip ensure running check: no inProgressStart timestamp', {
318
+ did: rootDid,
319
+ componentDid: childBlocklet.meta.did,
320
+ status: childBlocklet.status,
254
321
  });
322
+ return;
255
323
  }
256
324
  }
257
325
 
258
- const health = await this.isBlockletPortHealthyWithRetries(
259
- blocklet,
260
- inProgressStatuses.includes(blocklet.status)
261
- );
262
-
263
- if (health) {
264
- return;
326
+ if (!isInProgress) {
327
+ const health = await this.isBlockletPortHealthyWithRetries(childBlocklet);
328
+ if (health) {
329
+ return;
330
+ }
265
331
  }
266
332
 
267
- logger.warn('check blocklet port healthy', did, blocklet.meta.did, 'no healthy');
333
+ logger.warn('check blocklet port healthy', rootDid, childBlocklet.meta.did, 'no healthy');
268
334
 
269
- if (!this.fakeRunningBlocklets[did]) {
270
- this.fakeRunningBlocklets[did] = [];
271
- }
272
- if (this.fakeRunningBlocklets[did].find((b) => b.meta.did === blocklet.meta.did)) {
273
- return;
274
- }
275
- this.fakeRunningBlocklets[did].push(blocklet);
335
+ // Add to restart queue immediately
336
+ fakeDids.push(childBlocklet.meta.did);
276
337
  };
277
338
  }),
278
339
  { concurrency: 10 }
279
340
  );
341
+ if (fakeDids.length > 0) {
342
+ this.addToRestartQueue(rootDid, fakeDids);
343
+ }
280
344
  };
281
345
  }),
282
346
  { concurrency: 8 }
283
347
  );
348
+ };
284
349
 
285
- logger.info('get fake running blocklets', Object.keys(this.fakeRunningBlocklets).length);
350
+ /**
351
+ * Add a childBlocklet to the restart queue
352
+ * @param {string} rootDid - Root blocklet DID
353
+ * @param {Object} childBlocklet - Child blocklet (component) to restart
354
+ */
355
+ addToRestartQueue = (rootDid, dids) => {
356
+ // Check if job is pending (being processed)
357
+ if (this.restartQueueKeys.has(rootDid) || this.pendingJobs[rootDid]) {
358
+ return;
359
+ }
360
+
361
+ this.restartQueue.push({
362
+ rootDid,
363
+ componentDids: dids,
364
+ firstCycle: !this.whenCycleCheck,
365
+ });
366
+ this.restartQueueKeys.add(rootDid);
286
367
  };
287
368
 
288
- restartFakeRunningBlocklets = async () => {
289
- // blocklet 一组组重启
290
- const blockletDids = Object.keys(this.fakeRunningBlocklets);
291
- await pAll(
292
- blockletDids.map((did) => {
293
- return async () => {
294
- const blocklets = this.fakeRunningBlocklets[did];
295
- const componentDids = blocklets.map((b) => b.meta.did);
296
- if (componentDids.length > 0) {
297
- const key = `${did}-${componentDids.join('-')}`;
298
- this.needRestartBlocklets[key] = true;
299
- if (this.restartingBlocklets[key] && this.restartingBlocklets[key] + this.checkInterval < Date.now()) {
300
- return;
301
- }
302
- this.restartingBlocklets[key] = Date.now();
369
+ // 启动重启队列,保持 4 worker 并发处理,如果有一个完成了,则会补充到队列中
370
+ startRestartQueueProcessor = () => {
371
+ if (this.restartQueueProcessing) {
372
+ return;
373
+ }
374
+ this.restartQueueProcessing = true;
303
375
 
304
- const blockletDisplayName = this.getDisplayNameByRootDid(did);
305
- const restartTitle = 'Blocklet health check failed';
306
- const restartDescription = `Blocklet ${blockletDisplayName} with components ${componentDids.map((v) => this.getDisplayName(blocklets.find((b) => b.meta.did === v))).join(', ')} health check failed, restarting...`;
307
- if (this.whenCycleCheck) {
308
- this.notification(did, restartTitle, restartDescription, 'warning');
309
- }
376
+ const runWorker = async () => {
377
+ while (!this.stopped) {
378
+ const item = this.restartQueue.shift();
379
+ if (!item) {
380
+ return;
381
+ }
310
382
 
311
- try {
312
- logger.info('restart blocklet:', did, componentDids);
313
- await this.start({
314
- did,
315
- componentDids,
316
- checkHealthImmediately: true,
317
- atomic: true,
318
- operator: 'ensure-blocklet-running',
319
- });
320
- if (this.whenCycleCheck) {
321
- this.createAuditLog({
322
- action: 'ensureBlockletRunning',
323
- args: {
324
- teamDid: did,
325
- componentDids,
326
- },
327
- context: {
328
- user: {
329
- did,
330
- role: 'daemon',
331
- blockletDid: did,
332
- fullName: blockletDisplayName,
333
- elevated: false,
334
- },
335
- },
336
- result: {
337
- title: restartTitle,
338
- description: restartDescription,
339
- },
340
- });
341
- }
342
-
343
- delete this.restartingBlocklets[key];
344
- } catch (e) {
345
- logger.error('restart blocklet failed', did, componentDids, e);
346
- if (!this.errorStartBlocklets[key]) {
347
- this.errorStartBlocklets[key] = 0;
348
- }
349
- this.errorStartBlocklets[key] += 1;
350
-
351
- // 如果重启失败次数超过 3 次,则发送通知, 如果 server 是第一次启动遇到失败,则立刻发送通知
352
- if (this.errorStartBlocklets[key] >= 3 || !this.whenCycleCheck) {
353
- const title = 'Restart blocklet failed when health check failed';
354
- const description = `Restart blocklet ${blockletDisplayName} with components ${componentDids.map((v) => this.getDisplayName(blocklets.find((b) => b.meta.did === v))).join(', ')} failed`;
355
- this.notification(did, title, description, 'error');
356
- delete this.errorStartBlocklets[key];
357
- logger.error('restart many times blocklet failed', did, componentDids, e);
358
- try {
359
- // 失败了应该保持 error 状态
360
- await this.states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
361
- this.createAuditLog({
362
- action: 'ensureBlockletRunning',
363
- args: {
364
- blockletDisplayName,
365
- teamDid: did,
366
- componentDids,
367
- },
368
- context: {
369
- user: {
370
- did,
371
- role: 'daemon',
372
- blockletDid: did,
373
- fullName: blockletDisplayName,
374
- elevated: false,
375
- },
376
- },
377
- result: {
378
- title,
379
- description,
380
- },
381
- });
382
- } catch (err) {
383
- logger.error('ensure blocklet running, create audit log failed', did, componentDids, err);
384
- }
385
- }
386
- }
387
- }
388
- };
389
- }),
390
- { concurrency: 8 }
391
- );
383
+ this.restartQueueKeys.delete(item.rootDid);
384
+
385
+ try {
386
+ await this.restartBlockletFromQueue(item);
387
+ } catch (err) {
388
+ logger.error('restart blocklet failed', err);
389
+ }
390
+ }
391
+ };
392
+
393
+ const processQueue = async () => {
394
+ while (!this.stopped) {
395
+ // 防止没有重启队列时,快速空转
396
+ await sleep(this.preCheckInterval);
397
+
398
+ if (this.restartQueue.length === 0) {
399
+ continue; // 等下一轮
400
+ }
401
+
402
+ // 创建固定数量 worker
403
+ const workers = [];
404
+ for (let i = 0; i < RESTART_CONCURRENCY; i++) {
405
+ workers.push(runWorker());
406
+ }
407
+
408
+ try {
409
+ await Promise.all(workers);
410
+ } catch (err) {
411
+ logger.error('restart queue processor batch failed', err);
412
+ }
413
+ }
414
+ };
415
+
416
+ processQueue().catch((err) => {
417
+ logger.error('restart queue processor failed', err);
418
+ this.restartQueueProcessing = false;
419
+ });
420
+ };
421
+
422
+ /**
423
+ * Create restart notification and audit log context
424
+ * @param {string} rootDid - Root blocklet DID
425
+ * @param {Object} childBlocklet - Child blocklet object
426
+ * @param {string[]} componentDids - Component DIDs
427
+ * @returns {Object} Context object with displayName, title, description
428
+ */
429
+ createRestartContext = async (rootDid, componentDids) => {
430
+ const blockletDisplayName = await this.getDisplayNameByRootDid(rootDid);
431
+ const componentNames = componentDids.map((componentDid) => {
432
+ const child = this.rootBlockletsInfo[rootDid]?.children?.find((bl) => bl.meta.did === componentDid);
433
+ return child ? this.getDisplayName(child) : componentDid;
434
+ });
435
+ const componentNamesStr = componentNames.length === 1 ? componentNames[0] : componentNames.join(', ');
436
+
437
+ return {
438
+ blockletDisplayName,
439
+ title: 'Blocklet health check failed',
440
+ description: `Blocklet ${blockletDisplayName} with component${componentNames.length > 1 ? 's' : ''} ${componentNamesStr} health check failed, restarting...`,
441
+ };
442
+ };
443
+
444
+ /**
445
+ * Handle restart success
446
+ * @param {string} key - Queue item key
447
+ * @param {string} rootDid - Root blocklet DID
448
+ * @param {string} componentDid - Component DID
449
+ * @param {Object} context - Restart context
450
+ */
451
+ handleRestartSuccess = (rootDid, componentDids, firstCycle, context) => {
452
+ if (firstCycle) {
453
+ return;
454
+ }
455
+ this.createAuditLog({
456
+ action: 'ensureBlockletRunning',
457
+ args: {
458
+ teamDid: rootDid,
459
+ componentDids,
460
+ },
461
+ context: {
462
+ user: {
463
+ did: rootDid,
464
+ role: 'daemon',
465
+ blockletDid: rootDid,
466
+ fullName: context.blockletDisplayName,
467
+ elevated: false,
468
+ },
469
+ },
470
+ result: {
471
+ title: context.title,
472
+ description: context.description,
473
+ },
474
+ });
475
+ };
476
+
477
+ /**
478
+ * Handle restart failure
479
+ * @param {string} key - Queue item key
480
+ * @param {string} rootDid - Root blocklet DID
481
+ * @param {string[]} componentDids - Component DIDs
482
+ * @param {Object} context - Restart context
483
+ * @param {Error} error - Error object
484
+ */
485
+ handleRestartFailure = (rootDid, componentDids, context, error) => {
486
+ const title = 'Restart blocklet failed when health check failed';
487
+ const description = `Ensure blocklet running failed, restart blocklet ${context.blockletDisplayName} with component${componentDids.length > 1 ? 's' : ''} ${componentDids.join(', ')} failed`;
488
+ this.notification(rootDid, title, description, 'error');
489
+ logger.error('restart many times blocklet failed', rootDid, componentDids, error);
490
+ try {
491
+ this.createAuditLog({
492
+ action: 'ensureBlockletRunning',
493
+ args: {
494
+ blockletDisplayName: context.blockletDisplayName,
495
+ teamDid: rootDid,
496
+ componentDids,
497
+ },
498
+ context: {
499
+ user: {
500
+ did: rootDid,
501
+ role: 'daemon',
502
+ blockletDid: rootDid,
503
+ fullName: context.blockletDisplayName,
504
+ elevated: false,
505
+ },
506
+ },
507
+ result: {
508
+ title,
509
+ description,
510
+ },
511
+ });
512
+ } catch (err) {
513
+ logger.error('ensure blocklet running, create audit log failed', rootDid, componentDids, err);
514
+ }
515
+ };
516
+
517
+ /**
518
+ * Restart a childBlocklet from the queue
519
+ * @param {Object} item - Queue item with rootDid, componentDids, firstCycle
520
+ */
521
+ restartBlockletFromQueue = async ({ rootDid, componentDids, firstCycle }) => {
522
+ // Set pending status to prevent duplicate processing
523
+ if (this.pendingJobs[rootDid]) {
524
+ logger.warn('Skip restart: job is already pending', { rootDid });
525
+ return;
526
+ }
527
+ this.pendingJobs[rootDid] = true;
528
+
529
+ try {
530
+ const context = await this.createRestartContext(rootDid, componentDids);
531
+ if (!firstCycle) {
532
+ this.notification(rootDid, context.title, context.description, 'warning');
533
+ }
534
+
535
+ logger.info('restart blocklet:', rootDid, componentDids);
536
+ await this.start({
537
+ did: rootDid,
538
+ componentDids,
539
+ checkHealthImmediately: true,
540
+ atomic: true,
541
+ operator: 'ensure-blocklet-running',
542
+ });
543
+ this.handleRestartSuccess(rootDid, componentDids, firstCycle, context);
544
+ } catch (e) {
545
+ await this.handleRestartFailure(rootDid, componentDids, context, e);
546
+ } finally {
547
+ // Clear pending status after processing
548
+ delete this.pendingJobs[rootDid];
549
+ // Clear progress blocklets time
550
+ for (const componentDid of componentDids) {
551
+ delete this.progressBlockletsTime[`${rootDid}-${componentDid}`];
552
+ }
553
+ }
392
554
  };
393
555
  }
394
556
 
395
- module.exports = new EnsureBlockletRunning();
557
+ const ensureBlockletRunning = new EnsureBlockletRunning();
558
+
559
+ module.exports = {
560
+ ensureBlockletRunning,
561
+ EnsureBlockletRunning,
562
+ };
@@ -1,5 +1,6 @@
1
1
  const { outputJson } = require('fs-extra');
2
2
  const { join } = require('path');
3
+ const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
3
4
  const states = require('../../../states');
4
5
  const { BaseBackup } = require('./base');
5
6
  const { getFileObject } = require('../utils/disk');
@@ -24,7 +25,7 @@ class RoutingRuleBackup extends BaseBackup {
24
25
  */
25
26
  async export() {
26
27
  const routingRule = await states.site.findOne({
27
- domain: `${this.blocklet.meta.did}.blocklet-domain-group`,
28
+ domain: `${this.blocklet.meta.did}${BLOCKLET_SITE_GROUP_SUFFIX}`,
28
29
  });
29
30
 
30
31
  await outputJson(this.routingRuleExportPath, routingRule);
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-continue */
2
2
  /* eslint-disable no-await-in-loop */
3
3
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
4
+ const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
4
5
 
5
6
  const findBlocklet = (site, blocklets) => {
6
7
  // prefix = /
@@ -101,7 +102,7 @@ module.exports = async ({ states, node, printInfo }) => {
101
102
  // generate new blocklet site for every installed blocklet
102
103
  const newBlockletSites = {}; // <blockletDid>: <site>
103
104
  for (const blocklet of blocklets) {
104
- const domain = `${blocklet.meta.did}.blocklet-domain-group`;
105
+ const domain = `${blocklet.meta.did}${BLOCKLET_SITE_GROUP_SUFFIX}`;
105
106
  newBlockletSites[blocklet.meta.did] = {
106
107
  domain,
107
108
  domainAliases: [],
@@ -980,7 +980,7 @@ const startBlockletProcess = async (
980
980
  try {
981
981
  await promiseSpawn(nextOptions.env.connectInternalDockerNetwork, { mute: true });
982
982
  } catch (err) {
983
- logger.error('blocklet connect internal docker network failed', { processId: processIdName, error: err });
983
+ logger.warn('blocklet connect internal docker network failed', { processId: processIdName, error: err });
984
984
  }
985
985
  }
986
986
 
@@ -1,7 +1,7 @@
1
1
  const { Joi } = require('@arcblock/validator');
2
2
 
3
3
  const createOrgInputSchema = Joi.object({
4
- name: Joi.string().required().trim().min(1).max(20),
4
+ name: Joi.string().required().trim().min(1).max(64),
5
5
  description: Joi.string().optional().allow('').trim().min(1).max(255),
6
6
  ownerDid: Joi.DID().optional().allow('').allow(null),
7
7
  });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.17.4-beta-20251204-080001-08643fbe",
6
+ "version": "1.17.4-beta-20251204-152224-243ff54f",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -17,21 +17,21 @@
17
17
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
18
18
  "license": "Apache-2.0",
19
19
  "dependencies": {
20
- "@abtnode/analytics": "1.17.4-beta-20251204-080001-08643fbe",
21
- "@abtnode/auth": "1.17.4-beta-20251204-080001-08643fbe",
22
- "@abtnode/certificate-manager": "1.17.4-beta-20251204-080001-08643fbe",
23
- "@abtnode/constant": "1.17.4-beta-20251204-080001-08643fbe",
24
- "@abtnode/cron": "1.17.4-beta-20251204-080001-08643fbe",
25
- "@abtnode/db-cache": "1.17.4-beta-20251204-080001-08643fbe",
26
- "@abtnode/docker-utils": "1.17.4-beta-20251204-080001-08643fbe",
27
- "@abtnode/logger": "1.17.4-beta-20251204-080001-08643fbe",
28
- "@abtnode/models": "1.17.4-beta-20251204-080001-08643fbe",
29
- "@abtnode/queue": "1.17.4-beta-20251204-080001-08643fbe",
30
- "@abtnode/rbac": "1.17.4-beta-20251204-080001-08643fbe",
31
- "@abtnode/router-provider": "1.17.4-beta-20251204-080001-08643fbe",
32
- "@abtnode/static-server": "1.17.4-beta-20251204-080001-08643fbe",
33
- "@abtnode/timemachine": "1.17.4-beta-20251204-080001-08643fbe",
34
- "@abtnode/util": "1.17.4-beta-20251204-080001-08643fbe",
20
+ "@abtnode/analytics": "1.17.4-beta-20251204-152224-243ff54f",
21
+ "@abtnode/auth": "1.17.4-beta-20251204-152224-243ff54f",
22
+ "@abtnode/certificate-manager": "1.17.4-beta-20251204-152224-243ff54f",
23
+ "@abtnode/constant": "1.17.4-beta-20251204-152224-243ff54f",
24
+ "@abtnode/cron": "1.17.4-beta-20251204-152224-243ff54f",
25
+ "@abtnode/db-cache": "1.17.4-beta-20251204-152224-243ff54f",
26
+ "@abtnode/docker-utils": "1.17.4-beta-20251204-152224-243ff54f",
27
+ "@abtnode/logger": "1.17.4-beta-20251204-152224-243ff54f",
28
+ "@abtnode/models": "1.17.4-beta-20251204-152224-243ff54f",
29
+ "@abtnode/queue": "1.17.4-beta-20251204-152224-243ff54f",
30
+ "@abtnode/rbac": "1.17.4-beta-20251204-152224-243ff54f",
31
+ "@abtnode/router-provider": "1.17.4-beta-20251204-152224-243ff54f",
32
+ "@abtnode/static-server": "1.17.4-beta-20251204-152224-243ff54f",
33
+ "@abtnode/timemachine": "1.17.4-beta-20251204-152224-243ff54f",
34
+ "@abtnode/util": "1.17.4-beta-20251204-152224-243ff54f",
35
35
  "@aigne/aigne-hub": "^0.10.10",
36
36
  "@arcblock/did": "^1.27.12",
37
37
  "@arcblock/did-connect-js": "^1.27.12",
@@ -43,15 +43,15 @@
43
43
  "@arcblock/pm2-events": "^0.0.5",
44
44
  "@arcblock/validator": "^1.27.12",
45
45
  "@arcblock/vc": "^1.27.12",
46
- "@blocklet/constant": "1.17.4-beta-20251204-080001-08643fbe",
46
+ "@blocklet/constant": "1.17.4-beta-20251204-152224-243ff54f",
47
47
  "@blocklet/did-space-js": "^1.2.6",
48
- "@blocklet/env": "1.17.4-beta-20251204-080001-08643fbe",
48
+ "@blocklet/env": "1.17.4-beta-20251204-152224-243ff54f",
49
49
  "@blocklet/error": "^0.3.3",
50
- "@blocklet/meta": "1.17.4-beta-20251204-080001-08643fbe",
51
- "@blocklet/resolver": "1.17.4-beta-20251204-080001-08643fbe",
52
- "@blocklet/sdk": "1.17.4-beta-20251204-080001-08643fbe",
53
- "@blocklet/server-js": "1.17.4-beta-20251204-080001-08643fbe",
54
- "@blocklet/store": "1.17.4-beta-20251204-080001-08643fbe",
50
+ "@blocklet/meta": "1.17.4-beta-20251204-152224-243ff54f",
51
+ "@blocklet/resolver": "1.17.4-beta-20251204-152224-243ff54f",
52
+ "@blocklet/sdk": "1.17.4-beta-20251204-152224-243ff54f",
53
+ "@blocklet/server-js": "1.17.4-beta-20251204-152224-243ff54f",
54
+ "@blocklet/store": "1.17.4-beta-20251204-152224-243ff54f",
55
55
  "@blocklet/theme": "^3.2.11",
56
56
  "@fidm/x509": "^1.2.1",
57
57
  "@ocap/mcrypto": "^1.27.12",
@@ -116,5 +116,5 @@
116
116
  "express": "^4.18.2",
117
117
  "unzipper": "^0.10.11"
118
118
  },
119
- "gitHead": "740b2884f1f0cede865a6e7df2db394f3cef1997"
119
+ "gitHead": "090b2f960b834168dfa12b2a559b9256a98be312"
120
120
  }