@abtnode/core 1.17.3-beta-20251119-102907-28b69b76 → 1.17.3-beta-20251121-135300-8e451e6e

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/node.js CHANGED
@@ -2,6 +2,7 @@
2
2
  /* eslint-disable no-underscore-dangle */
3
3
  const assert = require('assert');
4
4
  const os = require('os');
5
+ const isUrl = require('is-url');
5
6
  const { Hasher } = require('@ocap/mcrypto');
6
7
  const isDocker = require('@abtnode/util/lib/is-docker');
7
8
  const isGitpod = require('@abtnode/util/lib/is-gitpod');
@@ -52,7 +53,7 @@ class NodeAPI {
52
53
  }
53
54
 
54
55
  try {
55
- if (entity.registerUrl) {
56
+ if (entity.registerUrl && isUrl(entity.registerUrl)) {
56
57
  const launcherInfo = await getLauncherInfo(entity.registerUrl);
57
58
  entity.registerInfo = launcherInfo;
58
59
  logger.info(`Updated launcher info from ${entity.registerUrl}`, launcherInfo);
@@ -259,13 +259,13 @@ const { formatEnvironments, getBlockletMeta, validateOwner, isCLI } = util;
259
259
 
260
260
  const statusLock = new DBCache(() => ({
261
261
  prefix: 'blocklet-status-lock',
262
- ttl: 1000 * 60,
262
+ ttl: 120_000,
263
263
  ...getAbtNodeRedisAndSQLiteUrl(),
264
264
  }));
265
265
 
266
266
  const startLock = new DBCache(() => ({
267
267
  prefix: 'blocklet-start-lock',
268
- ttl: process.env.NODE_ENV === 'test' ? 100 : 1000 * 60,
268
+ ttl: process.env.NODE_ENV === 'test' ? 1_000 : 120_000,
269
269
  ...getAbtNodeRedisAndSQLiteUrl(),
270
270
  }));
271
271
 
@@ -740,6 +740,8 @@ class DiskBlockletManager extends BaseBlockletManager {
740
740
  e2eMode,
741
741
  context,
742
742
  atomic,
743
+ onStarted,
744
+ onError,
743
745
  }) {
744
746
  if (!blocklet.children) {
745
747
  return componentDids;
@@ -771,11 +773,17 @@ class DiskBlockletManager extends BaseBlockletManager {
771
773
  // start by dependency
772
774
  for (const dids of requiredDidsLayers) {
773
775
  if (atomic) {
774
- await this._start({ blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: dids }, context);
776
+ await this._start(
777
+ { blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: dids, onStarted, onError },
778
+ context
779
+ );
775
780
  } else {
776
781
  const tasks = dids.map(
777
782
  (x) => () =>
778
- this._start({ blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: [x] }, context)
783
+ this._start(
784
+ { blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: [x], onStarted, onError },
785
+ context
786
+ )
779
787
  );
780
788
  await pAll(tasks, { concurrency: 4 }).catch((err) => {
781
789
  throw new Error(err.errors ? err.errors.join(', ') : err);
@@ -801,8 +809,8 @@ class DiskBlockletManager extends BaseBlockletManager {
801
809
  },
802
810
  context
803
811
  ) {
804
- const lockName = `start-not-input-component-dids-${did}`;
805
- const shouldLock = !inputComponentDids?.length;
812
+ const lockName = `${did}-start`;
813
+ const shouldLock = inputComponentDids?.length === 0;
806
814
 
807
815
  if (shouldLock) {
808
816
  await startLock.acquire(lockName);
@@ -814,8 +822,16 @@ class DiskBlockletManager extends BaseBlockletManager {
814
822
  inputComponentDids?.length ? inputComponentDids : blocklet.children.map((x) => x.meta.did)
815
823
  );
816
824
 
825
+ try {
826
+ await this.deleteProcess({ did, componentDids: baseComponentDids, isGreen: true });
827
+ } catch (error) {
828
+ logger.warn('Failed to delete process; this warning can be safely ignored.', { error });
829
+ }
830
+
831
+ await this.checkControllerStatus(blocklet, 'start');
832
+
817
833
  // 优先启动资源型组件
818
- let componentDids = [];
834
+ const componentDids = [];
819
835
  const nonEntryComponentIds = [];
820
836
  for (const b of blocklet.children) {
821
837
  if (!b?.meta?.did) {
@@ -844,7 +860,6 @@ class DiskBlockletManager extends BaseBlockletManager {
844
860
  operator,
845
861
  });
846
862
  }
847
-
848
863
  // sync component config before at first to ensure resource component config is ready
849
864
  const serverSk = (await states.node.read()).sk;
850
865
  await Promise.all(
@@ -853,7 +868,16 @@ class DiskBlockletManager extends BaseBlockletManager {
853
868
  )
854
869
  );
855
870
 
856
- componentDids = await this.startRequiredComponents({
871
+ const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
872
+ componentDids,
873
+ operator,
874
+ });
875
+
876
+ this.emit(BlockletEvents.statusChange, doc1);
877
+ const startedBlockletDids = [];
878
+ const errorBlockletDids = [];
879
+
880
+ const notStartedComponentDids = await this.startRequiredComponents({
857
881
  componentDids,
858
882
  inputComponentDids,
859
883
  blocklet,
@@ -862,26 +886,15 @@ class DiskBlockletManager extends BaseBlockletManager {
862
886
  e2eMode,
863
887
  context,
864
888
  atomic,
889
+ onStarted: (subDid) => {
890
+ startedBlockletDids.push({ did: subDid });
891
+ },
892
+ onError: (subDid, error) => {
893
+ errorBlockletDids.push({ did: subDid, error });
894
+ },
865
895
  });
866
896
 
867
- if (atomic || !blocklet.structVersion) {
868
- const nextBlocklet = await this._start(
869
- { blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids, operator },
870
- context
871
- );
872
-
873
- // 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
874
- if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
875
- this.emit(BlockletEvents.blurOrGreenStarted, {
876
- blocklet: nextBlocklet,
877
- componentDids: inputComponentDids,
878
- context,
879
- });
880
- }
881
- return nextBlocklet;
882
- }
883
-
884
- const tasks = componentDids.map(
897
+ const tasks = notStartedComponentDids.map(
885
898
  (componentDid) => () =>
886
899
  this._start(
887
900
  {
@@ -891,25 +904,104 @@ class DiskBlockletManager extends BaseBlockletManager {
891
904
  e2eMode,
892
905
  componentDids: [componentDid],
893
906
  operator,
907
+ onStarted: (subDid) => {
908
+ startedBlockletDids.push({ did: subDid });
909
+ },
910
+ onError: (subDid, error) => {
911
+ errorBlockletDids.push({ did: subDid, error });
912
+ },
894
913
  },
895
914
  context
896
915
  )
897
916
  );
898
917
 
899
- const rest = await pAll(tasks, { concurrency: 1 });
918
+ await pAll(tasks, { concurrency: 6 });
900
919
 
901
920
  const nextBlocklet = await this.ensureBlocklet(did, { e2eMode });
921
+ let errorDescription = '';
922
+ let resultBlocklet = nextBlocklet;
923
+
924
+ if (startedBlockletDids.length) {
925
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
926
+ componentDids: startedBlockletDids.map((x) => x.did),
927
+ operator,
928
+ });
929
+
930
+ const finalBlocklet = await this.getBlocklet(did);
931
+ resultBlocklet = finalBlocklet;
932
+
933
+ await this.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
934
+ const componentsInfo = getComponentsInternalInfo(finalBlocklet);
935
+ this.emit(BlockletInternalEvents.componentUpdated, {
936
+ appDid: blocklet.appDid,
937
+ components: componentsInfo,
938
+ });
939
+ this.emit(BlockletInternalEvents.componentStarted, {
940
+ appDid: blocklet.appDid,
941
+ components: (componentDids || []).map((x) => ({ did: x })),
942
+ });
943
+
944
+ this.emit(BlockletEvents.statusChange, finalBlocklet);
945
+ this.emit(BlockletEvents.started, { ...finalBlocklet, componentDids });
946
+
947
+ launcher.notifyBlockletStarted(finalBlocklet);
948
+ }
949
+
950
+ if (errorBlockletDids.length) {
951
+ const error = errorBlockletDids[0];
952
+ for (const item of errorBlockletDids) {
953
+ logger.error('Failed to start blocklet', {
954
+ error: item.error,
955
+ title: nextBlocklet.meta.title,
956
+ name: getComponentNamesWithVersion(nextBlocklet, [item.did]),
957
+ });
958
+ }
959
+
960
+ errorDescription = `${getComponentNamesWithVersion(
961
+ nextBlocklet,
962
+ errorBlockletDids.map((x) => x.did)
963
+ )} start failed for ${getDisplayName(nextBlocklet)}: ${errorBlockletDids.map((x) => x.error.message).join('. ')}`;
964
+ this._createNotification(did, {
965
+ title: 'Component start failed',
966
+ description: errorDescription,
967
+ entityType: 'blocklet',
968
+ entityId: did,
969
+ severity: 'error',
970
+ });
971
+
972
+ await this.deleteProcess({
973
+ did,
974
+ componentDids: errorBlockletDids.map((x) => x.did),
975
+ shouldUpdateBlockletStatus: false,
976
+ });
977
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
978
+ componentDids: errorBlockletDids.map((x) => x.did),
979
+ operator,
980
+ });
981
+ const finalBlocklet = await this.getBlocklet(did);
982
+ resultBlocklet = finalBlocklet;
983
+ this.emit(BlockletEvents.startFailed, {
984
+ ...finalBlocklet,
985
+ componentDids: errorBlockletDids.map((x) => x.did),
986
+ error: { message: error.message },
987
+ });
988
+ this.emit(BlockletEvents.statusChange, { ...finalBlocklet, error: { message: error.message } });
989
+ }
902
990
 
903
991
  // 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
904
992
  if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
905
993
  this.emit(BlockletEvents.blurOrGreenStarted, {
906
- blocklet: nextBlocklet,
994
+ blocklet: resultBlocklet,
907
995
  componentDids: inputComponentDids,
908
996
  context,
909
997
  });
910
998
  }
911
999
 
912
- return rest[0];
1000
+ if (throwOnError && errorDescription) {
1001
+ throw new Error(errorDescription);
1002
+ }
1003
+
1004
+ return resultBlocklet;
913
1005
  } finally {
914
1006
  if (shouldLock) {
915
1007
  await startLock.releaseLock(lockName);
@@ -926,17 +1018,13 @@ class DiskBlockletManager extends BaseBlockletManager {
926
1018
  e2eMode = false,
927
1019
  componentDids,
928
1020
  operator: _operator,
1021
+ onStarted,
1022
+ onError,
929
1023
  },
930
1024
  context
931
1025
  ) {
932
1026
  const operator = _operator || context?.user?.did;
933
1027
 
934
- try {
935
- await this.deleteProcess({ did, componentDids, isGreen: true });
936
- } catch {
937
- // do nothing
938
- }
939
-
940
1028
  logger.info('start blocklet', {
941
1029
  did: did || inputBlocklet.meta.did,
942
1030
  componentDids,
@@ -960,7 +1048,6 @@ class DiskBlockletManager extends BaseBlockletManager {
960
1048
 
961
1049
  const entryComponentIds = [];
962
1050
  const nonEntryComponentIds = [];
963
- let nonEntryComponentRes;
964
1051
  const componentDidsSet = new Set(componentDids);
965
1052
  try {
966
1053
  await forEachBlocklet(
@@ -996,18 +1083,9 @@ class DiskBlockletManager extends BaseBlockletManager {
996
1083
  { parallel: true, concurrencyLimit: 4 }
997
1084
  );
998
1085
  if (nonEntryComponentIds.length) {
999
- const params = {
1000
- did,
1001
- context,
1002
- minConsecutiveTime: 3000,
1003
- timeout: 5000,
1004
- componentDids: nonEntryComponentIds,
1005
- };
1006
- nonEntryComponentRes = await this._onCheckIfStarted(params, {
1007
- throwOnError,
1008
- skipRunningCheck: true,
1009
- needUpdateBlueStatus: false,
1010
- });
1086
+ for (const subDid of nonEntryComponentIds) {
1087
+ onStarted?.(subDid);
1088
+ }
1011
1089
  }
1012
1090
  } catch (err) {
1013
1091
  logger.error('Failed to categorize components into entry and non-entry types', { error: err.message });
@@ -1015,7 +1093,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1015
1093
  }
1016
1094
 
1017
1095
  if (!entryComponentIds.length) {
1018
- return nonEntryComponentRes;
1096
+ return;
1019
1097
  }
1020
1098
 
1021
1099
  try {
@@ -1036,13 +1114,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1036
1114
  }
1037
1115
  }
1038
1116
 
1039
- const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
1040
- componentDids: entryComponentIds,
1041
- operator,
1042
- });
1043
-
1044
1117
  blocklet1.status = BlockletStatus.starting;
1045
- this.emit(BlockletEvents.statusChange, doc1);
1046
1118
  const blocklet = await ensureAppPortsNotOccupied({
1047
1119
  blocklet: blocklet1,
1048
1120
  componentDids: entryComponentIds,
@@ -1097,8 +1169,6 @@ class DiskBlockletManager extends BaseBlockletManager {
1097
1169
  });
1098
1170
  };
1099
1171
 
1100
- await this.configSynchronizer.syncAppConfig(blocklet);
1101
-
1102
1172
  await startBlockletProcess(blocklet, {
1103
1173
  ...context,
1104
1174
  preFlight: getHookFn('preFlight'),
@@ -1116,6 +1186,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1116
1186
  checkHealthImmediately,
1117
1187
  componentDids,
1118
1188
  });
1189
+
1119
1190
  const params = {
1120
1191
  did,
1121
1192
  context,
@@ -1124,47 +1195,19 @@ class DiskBlockletManager extends BaseBlockletManager {
1124
1195
  componentDids: entryComponentIds,
1125
1196
  };
1126
1197
 
1127
- await this._onCheckIfStarted(params, { throwOnError, needUpdateBlueStatus: false });
1128
- blocklet.status = BlockletStatus.running;
1198
+ await this._onCheckIfStarted(params, { throwOnError });
1129
1199
 
1130
- return blocklet;
1200
+ for (const subDid of entryComponentIds) {
1201
+ onStarted?.(subDid);
1202
+ }
1131
1203
  } catch (err) {
1132
1204
  const status = await states.blocklet.getBlockletStatus(did);
1133
1205
  if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
1134
1206
  logger.info('Failed to start blocklet maybe due to manually stopped');
1135
- return states.blocklet.getBlocklet(did);
1136
1207
  }
1137
-
1138
- const error = Array.isArray(err) ? err[0] : err;
1139
- logger.error('Failed to start blocklet', { error, did, title: blocklet1.meta.title });
1140
- const description = `${getComponentNamesWithVersion(blocklet1, entryComponentIds)} start failed for ${getDisplayName(
1141
- blocklet1
1142
- )}: ${error.message}`;
1143
- this._createNotification(did, {
1144
- title: 'Component start failed',
1145
- description,
1146
- entityType: 'blocklet',
1147
- entityId: did,
1148
- severity: 'error',
1149
- });
1150
-
1151
- await this.deleteProcess({ did, componentDids: entryComponentIds });
1152
- const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
1153
- componentDids: entryComponentIds,
1154
- operator,
1155
- });
1156
- this.emit(BlockletEvents.startFailed, {
1157
- ...res,
1158
- componentDids: entryComponentIds,
1159
- error: { message: error.message },
1160
- });
1161
- this.emit(BlockletEvents.statusChange, { ...res, error: { message: error.message } });
1162
-
1163
- if (throwOnError) {
1164
- throw new Error(description);
1208
+ for (const subDid of entryComponentIds) {
1209
+ onError?.(subDid, err);
1165
1210
  }
1166
-
1167
- return res;
1168
1211
  }
1169
1212
  }
1170
1213
 
@@ -3590,141 +3633,24 @@ class DiskBlockletManager extends BaseBlockletManager {
3590
3633
  await blueGreenStartBlocklet({ did, componentDids, operator }, context, this, states);
3591
3634
  }
3592
3635
 
3593
- _onCheckIfStarted = async (
3594
- jobInfo,
3595
- { throwOnError, skipRunningCheck = false, isGreen = false, needUpdateBlueStatus = true } = {}
3596
- ) => {
3597
- const startedAt = Date.now();
3598
- const { did, context, minConsecutiveTime = 2000, timeout, componentDids } = jobInfo;
3599
- const nodeInfo = await states.node.read();
3636
+ _onCheckIfStarted = async (jobInfo, { skipRunningCheck = false, isGreen = false } = {}) => {
3637
+ const { did, minConsecutiveTime = 2000, timeout, componentDids } = jobInfo;
3600
3638
  const blocklet = await this.getBlocklet(did);
3601
3639
 
3602
- const { meta } = blocklet;
3603
- const { name } = meta;
3604
-
3605
- const successBlockletIds = new Set();
3606
-
3607
- try {
3608
- if (!skipRunningCheck) {
3609
- await checkBlockletProcessHealthy(blocklet, {
3610
- minConsecutiveTime,
3611
- timeout,
3612
- componentDids,
3613
- isGreen,
3614
- appDid: did,
3615
- enableDocker: nodeInfo.enableDocker,
3616
- getBlocklet: () => {
3617
- return this.getBlocklet(did);
3618
- },
3619
- setBlockletRunning: async (componentDid) => {
3620
- successBlockletIds.add(componentDid);
3621
- await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
3622
- componentDids: [componentDid],
3623
- isGreen,
3624
- });
3625
- },
3626
- });
3627
- }
3640
+ const nodeInfo = await states.node.read();
3628
3641
 
3629
- const runningRes = await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
3630
- componentDids: successBlockletIds.size > 0 ? Array.from(successBlockletIds) : componentDids,
3642
+ if (!skipRunningCheck) {
3643
+ await checkBlockletProcessHealthy(blocklet, {
3644
+ minConsecutiveTime,
3645
+ timeout,
3646
+ componentDids,
3631
3647
  isGreen,
3648
+ appDid: did,
3649
+ enableDocker: nodeInfo.enableDocker,
3650
+ getBlocklet: () => {
3651
+ return this.getBlocklet(did);
3652
+ },
3632
3653
  });
3633
-
3634
- if (needUpdateBlueStatus) {
3635
- await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
3636
- componentDids,
3637
- isGreen: !isGreen,
3638
- });
3639
- }
3640
-
3641
- const nextBlocklet = await this.getBlocklet(did);
3642
-
3643
- await this.configSynchronizer.throttledSyncAppConfig(nextBlocklet);
3644
- const componentsInfo = getComponentsInternalInfo(nextBlocklet);
3645
- this.emit(BlockletInternalEvents.componentUpdated, {
3646
- appDid: blocklet.appDid,
3647
- components: componentsInfo,
3648
- });
3649
- this.emit(BlockletInternalEvents.componentStarted, {
3650
- appDid: blocklet.appDid,
3651
- components: (componentDids || []).map((x) => ({ did: x })),
3652
- });
3653
-
3654
- this.emit(BlockletEvents.statusChange, nextBlocklet);
3655
- this.emit(BlockletEvents.started, { ...nextBlocklet, componentDids });
3656
-
3657
- launcher.notifyBlockletStarted(blocklet);
3658
-
3659
- logger.info('blocklet healthy', { did, name, time: Date.now() - startedAt });
3660
-
3661
- if (needUpdateBlueStatus) {
3662
- try {
3663
- await this.deleteProcess({ did, componentDids, isGreen: !isGreen }, context);
3664
- } catch {
3665
- logger.error('delete process failed', { did, componentDids, isGreen });
3666
- }
3667
- }
3668
-
3669
- return runningRes;
3670
- } catch (error) {
3671
- const errorBlockletIds = [];
3672
- for (const componentDid of componentDids) {
3673
- if (!successBlockletIds.has(componentDid)) {
3674
- errorBlockletIds.push(componentDid);
3675
- }
3676
- }
3677
- if (errorBlockletIds.length === 0) {
3678
- return this.getBlocklet(did);
3679
- }
3680
-
3681
- const status = await states.blocklet.getBlockletStatus(did);
3682
- if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
3683
- logger.info(`Check blocklet healthy failing because blocklet is ${fromBlockletStatus(status)}`);
3684
- return {};
3685
- }
3686
-
3687
- logger.error('check blocklet if started failed', { did, name, context, timeout, error });
3688
-
3689
- await this.deleteProcess({ did, componentDids: errorBlockletIds, isGreen }, context);
3690
-
3691
- let doc;
3692
- if (isGreen) {
3693
- doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
3694
- componentDids: errorBlockletIds,
3695
- isGreen,
3696
- });
3697
- } else {
3698
- doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids: errorBlockletIds });
3699
- }
3700
-
3701
- const componentNames = getComponentNamesWithVersion(blocklet, errorBlockletIds);
3702
- const description = componentNames
3703
- ? `${getComponentNamesWithVersion(blocklet, errorBlockletIds)} start failed for ${getDisplayName(blocklet)}: ${
3704
- error.message
3705
- }`
3706
- : `${blocklet.meta.title} start failed: ${error.message}`;
3707
-
3708
- this._createNotification(did, {
3709
- title: 'Component start failed',
3710
- description,
3711
- entityType: 'blocklet',
3712
- entityId: did,
3713
- severity: 'error',
3714
- });
3715
-
3716
- this.emit(BlockletEvents.startFailed, {
3717
- ...doc,
3718
- componentDids: errorBlockletIds,
3719
- error: { message: error.message },
3720
- });
3721
- this.emit(BlockletEvents.statusChange, { ...doc, error });
3722
-
3723
- if (throwOnError) {
3724
- throw error;
3725
- }
3726
-
3727
- return doc;
3728
3654
  }
3729
3655
  };
3730
3656
 
@@ -10,6 +10,7 @@ const {
10
10
  } = require('@blocklet/meta/lib/util');
11
11
  const { getBlockletEngine, hasStartEngine } = require('@blocklet/meta/lib/engine');
12
12
  const { getComponentsInternalInfo } = require('@blocklet/meta/lib/blocklet');
13
+ const pAll = require('p-all');
13
14
  const {
14
15
  forEachBlocklet,
15
16
  validateBlocklet,
@@ -18,6 +19,7 @@ const {
18
19
  getHealthyCheckTimeout,
19
20
  getHookArgs,
20
21
  shouldSkipComponent,
22
+ getComponentNamesWithVersion,
21
23
  } = require('../../../util/blocklet');
22
24
  const { startBlockletProcess } = require('../../../util/blocklet');
23
25
  const hooks = require('../../hooks');
@@ -169,143 +171,256 @@ const blueGreenStartBlocklet = async (
169
171
 
170
172
  const blueGreenComponentIds = await blueGreenGetComponentIds(blocklet1, entryComponentIds);
171
173
 
172
- // eslint-disable-next-line no-unreachable-loop
174
+ const startedBlockletDids = [];
175
+ const errorBlockletDids = [];
176
+
173
177
  for (const item of blueGreenComponentIds) {
174
178
  if (!item.componentDids.length) {
175
179
  continue;
176
180
  }
177
181
 
178
- try {
179
- const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
180
- componentDids: item.componentDids,
181
- operator,
182
- isGreen: item.changeToGreen,
183
- });
182
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
183
+ componentDids: item.componentDids,
184
+ operator,
185
+ isGreen: item.changeToGreen,
186
+ });
184
187
 
185
- const nextBlocklet = await ensureAppPortsNotOccupied({
186
- blocklet: blocklet1,
187
- componentDids: item.componentDids,
188
- states,
189
- manager,
190
- isGreen: item.changeToGreen,
191
- });
188
+ await ensureAppPortsNotOccupied({
189
+ blocklet: blocklet1,
190
+ componentDids: item.componentDids,
191
+ states,
192
+ manager,
193
+ isGreen: item.changeToGreen,
194
+ });
195
+ }
192
196
 
193
- manager.emit(BlockletEvents.statusChange, doc1);
197
+ const nextBlocklet = await manager.ensureBlocklet(did, { e2eMode });
194
198
 
195
- const nodeInfo = await states.node.read();
196
- const nodeEnvironments = await states.node.getEnvironments();
199
+ manager.emit(BlockletEvents.statusChange, nextBlocklet);
197
200
 
198
- // 钩子函数设置
199
- const getHookFn =
200
- (hookName) =>
201
- async (b, { env }) => {
202
- const hookArgs = getHookArgs(b);
203
- const needRunDocker = await checkNeedRunDocker(b.meta, env, nodeInfo, isExternalBlocklet(nextBlocklet));
204
- if (!b.meta.scripts?.[hookName]) {
205
- return null;
206
- }
207
- if (needRunDocker) {
208
- return dockerExec({
209
- blocklet: nextBlocklet,
210
- meta: b.meta,
211
- script: b.meta.scripts?.[hookName],
212
- hookName,
213
- nodeInfo,
214
- env,
215
- ...hookArgs,
216
- });
217
- }
218
- return hooks[hookName](b, {
219
- appDir: b.env.appDir,
220
- hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
221
- env,
222
- did, // root blocklet did,
223
- teamManager: manager.teamManager,
224
- ...hookArgs,
201
+ // 收集所有任务
202
+ const tasks = [];
203
+ for (const item of blueGreenComponentIds) {
204
+ if (!item.componentDids.length) {
205
+ continue;
206
+ }
207
+
208
+ for (const subDid of item.componentDids) {
209
+ tasks.push(async () => {
210
+ try {
211
+ const nodeInfo = await states.node.read();
212
+ const nodeEnvironments = await states.node.getEnvironments();
213
+
214
+ // 钩子函数设置
215
+ const getHookFn =
216
+ (hookName) =>
217
+ async (b, { env }) => {
218
+ const hookArgs = getHookArgs(b);
219
+ const needRunDocker = await checkNeedRunDocker(b.meta, env, nodeInfo, isExternalBlocklet(nextBlocklet));
220
+ if (!b.meta.scripts?.[hookName]) {
221
+ return null;
222
+ }
223
+ if (needRunDocker) {
224
+ return dockerExec({
225
+ blocklet: nextBlocklet,
226
+ meta: b.meta,
227
+ script: b.meta.scripts?.[hookName],
228
+ hookName,
229
+ nodeInfo,
230
+ env,
231
+ ...hookArgs,
232
+ });
233
+ }
234
+ return hooks[hookName](b, {
235
+ appDir: b.env.appDir,
236
+ hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
237
+ env,
238
+ did, // root blocklet did,
239
+ teamManager: manager.teamManager,
240
+ ...hookArgs,
241
+ });
242
+ };
243
+
244
+ await startBlockletProcess(nextBlocklet, {
245
+ ...context,
246
+ preFlight: getHookFn('preFlight'),
247
+ preStart: getHookFn('preStart'),
248
+ postStart: getHookFn('postStart'),
249
+ nodeEnvironments,
250
+ nodeInfo,
251
+ e2eMode,
252
+ componentDids: [subDid],
253
+ configSynchronizer: manager.configSynchronizer,
254
+ isGreen: item.changeToGreen,
255
+ });
256
+
257
+ // 健康检查绿色环境
258
+ const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(nextBlocklet, {
259
+ checkHealthImmediately,
260
+ componentDids: [subDid],
225
261
  });
226
- };
227
-
228
- await startBlockletProcess(nextBlocklet, {
229
- ...context,
230
- preFlight: getHookFn('preFlight'),
231
- preStart: getHookFn('preStart'),
232
- postStart: getHookFn('postStart'),
233
- nodeEnvironments,
234
- nodeInfo,
235
- e2eMode,
236
- componentDids: item.componentDids,
237
- configSynchronizer: manager.configSynchronizer,
238
- isGreen: item.changeToGreen,
262
+
263
+ await manager._onCheckIfStarted(
264
+ {
265
+ did,
266
+ context,
267
+ minConsecutiveTime,
268
+ timeout: startTimeout,
269
+ componentDids: [subDid],
270
+ },
271
+ { throwOnError: true, isGreen: item.changeToGreen }
272
+ );
273
+
274
+ // 收集成功的组件(排除已经在 errorBlockletDids 中的组件)
275
+ startedBlockletDids.push({ did: subDid, isGreen: item.changeToGreen });
276
+
277
+ logger.info('Green environment started successfully', {
278
+ did,
279
+ componentDids: [subDid],
280
+ });
281
+ } catch (err) {
282
+ const error = Array.isArray(err) ? err[0] : err;
283
+ logger.error('Failed to start green environment', { error, did, title: blocklet1.meta.title });
284
+
285
+ // 收集失败的组件
286
+ errorBlockletDids.push({ did: subDid, error, isGreen: item.changeToGreen });
287
+
288
+ try {
289
+ await manager.deleteProcess({ did, componentDids: [subDid], isGreen: item.changeToGreen });
290
+ } catch (cleanupError) {
291
+ logger.error('Failed to cleanup green environment', { cleanupError });
292
+ }
293
+ }
294
+ });
295
+ }
296
+ }
297
+
298
+ await pAll(tasks, { concurrency: 6 });
299
+
300
+ const lastBlocklet = await manager.ensureBlocklet(did, { e2eMode });
301
+ let errorDescription = '';
302
+
303
+ // 处理启动失败的组件
304
+ if (errorBlockletDids.length) {
305
+ const { error } = errorBlockletDids[0];
306
+ for (const item of errorBlockletDids) {
307
+ logger.error('Failed to start blocklet', {
308
+ error: item.error,
309
+ title: lastBlocklet.meta.title,
310
+ name: getComponentNamesWithVersion(lastBlocklet, [item.did]),
311
+ });
312
+ }
313
+
314
+ errorDescription = `${getComponentNamesWithVersion(
315
+ lastBlocklet,
316
+ errorBlockletDids.map((x) => x.did)
317
+ )} start failed for ${getDisplayName(lastBlocklet)}: ${errorBlockletDids.map((x) => x.error.message).join('. ')}`;
318
+
319
+ if (!ignoreErrorNotification) {
320
+ manager._createNotification(did, {
321
+ title: 'Blue-Green Deployment: Green Start Failed',
322
+ description: errorDescription,
323
+ entityType: 'blocklet',
324
+ entityId: did,
325
+ severity: 'error',
239
326
  });
327
+ }
328
+
329
+ const greenBlockletDids = errorBlockletDids.filter((x) => x.isGreen).map((x) => x.did);
330
+ const blueBlockletDids = errorBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
240
331
 
241
- // 健康检查绿色环境
242
- const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(nextBlocklet, {
243
- checkHealthImmediately,
244
- componentDids: item.componentDids,
332
+ if (greenBlockletDids.length) {
333
+ await manager.deleteProcess({
334
+ did,
335
+ componentDids: greenBlockletDids,
336
+ shouldUpdateBlockletStatus: false,
337
+ isGreen: true,
245
338
  });
339
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
340
+ componentDids: greenBlockletDids,
341
+ operator,
342
+ isGreen: true,
343
+ });
344
+ }
246
345
 
247
- await manager._onCheckIfStarted(
248
- {
249
- did,
250
- context,
251
- minConsecutiveTime,
252
- timeout: startTimeout,
253
- componentDids: item.componentDids,
254
- },
255
- { throwOnError: true, isGreen: item.changeToGreen, needUpdateBlueStatus: true }
256
- );
257
-
258
- logger.info('Green environment started successfully', {
346
+ if (blueBlockletDids.length) {
347
+ await manager.deleteProcess({
259
348
  did,
260
- componentDids: item.componentDids,
349
+ componentDids: blueBlockletDids,
350
+ shouldUpdateBlockletStatus: false,
351
+ isGreen: false,
261
352
  });
262
- } catch (err) {
263
- const error = Array.isArray(err) ? err[0] : err;
264
- logger.error('Failed to start green environment', { error, did, title: blocklet1.meta.title });
265
-
266
- try {
267
- await manager.deleteProcess({ did, componentDids: item.componentDids, isGreen: item.changeToGreen });
268
- // 如果需要把失败状态设置成 error:
269
- // const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
270
- // componentDids: item.componentDids,
271
- // operator,
272
- // isGreen: item.changeToGreen,
273
- // });
274
- // manager.emit(BlockletEvents.statusChange, doc1);
275
- manager.emit(BlockletEvents.statusChange, blocklet1);
276
- } catch (cleanupError) {
277
- logger.error('Failed to cleanup green environment', { cleanupError });
278
- }
353
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
354
+ componentDids: blueBlockletDids,
355
+ operator,
356
+ isGreen: false,
357
+ });
358
+ }
279
359
 
280
- const description = `Green environment start failed for ${getDisplayName(blocklet1)}: ${error.message}`;
281
- if (!ignoreErrorNotification) {
282
- manager._createNotification(did, {
283
- title: 'Blue-Green Deployment: Green Start Failed',
284
- description,
285
- entityType: 'blocklet',
286
- entityId: did,
287
- severity: 'error',
288
- });
289
- }
360
+ const finalBlocklet = await manager.getBlocklet(did);
361
+ manager.emit(BlockletEvents.startFailed, {
362
+ ...finalBlocklet,
363
+ componentDids: errorBlockletDids.map((x) => x.did),
364
+ error: { message: error.message },
365
+ });
366
+ manager.emit(BlockletEvents.statusChange, { ...finalBlocklet, error: { message: error.message } });
367
+ }
290
368
 
291
- if (throwOnError) {
292
- throw new Error(description);
293
- }
294
- throw error;
369
+ // 处理成功启动的组件
370
+ if (startedBlockletDids.length) {
371
+ const startedGreenBlockletDids = startedBlockletDids.filter((x) => x.isGreen).map((x) => x.did);
372
+ const startedBlueBlockletDids = startedBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
373
+
374
+ if (startedGreenBlockletDids.length) {
375
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
376
+ componentDids: startedGreenBlockletDids,
377
+ operator,
378
+ isGreen: true,
379
+ });
380
+ await manager.deleteProcess({ did, componentDids: startedGreenBlockletDids, isGreen: false });
295
381
  }
296
382
 
297
- const nextBlocklet = await manager.ensureBlocklet(did, { e2eMode });
298
- manager.emit(BlockletEvents.blurOrGreenStarted, { blocklet: nextBlocklet, componentDids, context });
383
+ if (startedBlueBlockletDids.length) {
384
+ await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
385
+ componentDids: startedBlueBlockletDids,
386
+ operator,
387
+ isGreen: false,
388
+ });
389
+ await manager.deleteProcess({ did, componentDids: startedBlueBlockletDids, isGreen: true });
390
+ }
391
+
392
+ const finalBlocklet = await manager.getBlocklet(did);
393
+
394
+ await manager.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
395
+ const componentsInfo = getComponentsInternalInfo(finalBlocklet);
396
+ manager.emit(BlockletInternalEvents.componentUpdated, {
397
+ appDid: blocklet1.appDid,
398
+ components: componentsInfo,
399
+ });
400
+ manager.emit(BlockletInternalEvents.componentStarted, {
401
+ appDid: blocklet1.appDid,
402
+ components: startedBlockletDids.map((x) => ({ did: x.did })),
403
+ });
404
+
405
+ manager.emit(BlockletEvents.statusChange, finalBlocklet);
406
+ manager.emit(BlockletEvents.started, {
407
+ ...finalBlocklet,
408
+ componentDids: startedBlockletDids.map((x) => x.did),
409
+ });
299
410
  }
300
411
 
301
- const nextBlocklet = await manager.getBlocklet(did);
302
- const componentsInfo = getComponentsInternalInfo(nextBlocklet);
303
- manager.emit(BlockletInternalEvents.componentStarted, {
304
- appDid: nextBlocklet.appDid,
305
- components: componentsInfo,
306
- });
412
+ // 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
413
+ if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
414
+ manager.emit(BlockletEvents.blurOrGreenStarted, {
415
+ blocklet: lastBlocklet,
416
+ componentDids,
417
+ context,
418
+ });
419
+ }
307
420
 
308
- manager.emit(BlockletEvents.statusChange, nextBlocklet);
421
+ if (throwOnError && errorDescription) {
422
+ throw new Error(errorDescription);
423
+ }
309
424
  };
310
425
 
311
426
  module.exports = {
@@ -158,9 +158,19 @@ const blueGreenUpgradeBlocklet = async (
158
158
  return blocklet;
159
159
  } catch (err) {
160
160
  logger.error('failed to upgrade blocklet', { did, version, name, error: err });
161
+ const nextBlocklet = await manager.ensureBlocklet(did);
162
+ for (const child of nextBlocklet.children) {
163
+ for (const oldChild of oldBlocklet.children) {
164
+ if (child && oldChild && child.meta.did === oldChild.meta.did) {
165
+ oldChild.status = child.status;
166
+ oldChild.greenStatus = child.greenStatus;
167
+ }
168
+ }
169
+ }
161
170
  await states.blocklet.upgradeBlocklet({ ...oldBlocklet });
162
171
  manager.configSynchronizer.throttledSyncAppConfig(oldBlocklet);
163
172
  await manager._updateDependents(did);
173
+ manager.emit(BlockletEvents.statusChange, { ...oldBlocklet, error: { message: err.message } });
164
174
 
165
175
  const actionName = action === INSTALL_ACTIONS.INSTALL_COMPONENT ? 'install' : 'upgrade';
166
176
  const notificationEvent =
@@ -786,7 +786,9 @@ module.exports = function getRouterHelpers({
786
786
  const trafficInsight = states.trafficInsight;
787
787
 
788
788
  const downloadCert = async ({ domain, url }) => {
789
- const destFolder = getTmpDir(path.join(`certificate-${Date.now()}`));
789
+ const destFolder = getTmpDir(path.join(`certificate-${md5(domain)}-${Date.now()}`));
790
+ logger.info('download certificate start', { domain, url, destFolder });
791
+
790
792
  try {
791
793
  const filename = path.join(destFolder, 'certificate.tar.gz');
792
794
  fs.ensureDirSync(destFolder);
@@ -821,6 +823,7 @@ module.exports = function getRouterHelpers({
821
823
  throw error;
822
824
  } finally {
823
825
  fs.removeSync(destFolder);
826
+ logger.info('remove certificate dest folder', { domain, url, destFolder });
824
827
  }
825
828
  };
826
829
 
@@ -2,7 +2,6 @@
2
2
  /* eslint-disable no-await-in-loop */
3
3
  /* eslint-disable function-paren-newline */
4
4
  /* eslint-disable no-underscore-dangle */
5
- const pick = require('lodash/pick');
6
5
  const omit = require('lodash/omit');
7
6
  const uniq = require('lodash/uniq');
8
7
  const cloneDeep = require('@abtnode/util/lib/deep-clone');
@@ -15,7 +14,6 @@ const {
15
14
  forEachBlocklet,
16
15
  forEachBlockletSync,
17
16
  forEachComponentV2,
18
- forEachComponentV2Sync,
19
17
  getBlockletServices,
20
18
  hasStartEngine,
21
19
  } = require('@blocklet/meta/lib/util');
@@ -591,14 +589,13 @@ class BlockletState extends BaseState {
591
589
  return res;
592
590
  }
593
591
 
594
- // update children status
595
- forEachComponentV2Sync(doc, (component) => {
592
+ for (const component of doc.children || []) {
596
593
  if (component.meta.group === BlockletGroup.gateway) {
597
- return;
594
+ continue;
598
595
  }
599
596
 
600
597
  if (shouldSkipComponent(component.meta.did, componentDids)) {
601
- return;
598
+ continue;
602
599
  }
603
600
 
604
601
  component[isGreen ? 'greenStatus' : 'status'] = status;
@@ -616,11 +613,16 @@ class BlockletState extends BaseState {
616
613
  }
617
614
  component.operator = operator;
618
615
  component.inProgressStart = Date.now();
619
- });
616
+ }
620
617
 
621
- const isSetStatus = status === BlockletStatus.downloading || status === BlockletStatus.waiting;
622
- const updateData = isSetStatus ? pick(doc, ['status', 'children']) : pick(doc, ['children']);
623
- updateData.operator = operator;
618
+ const shouldSetStatus = status === BlockletStatus.downloading || status === BlockletStatus.waiting;
619
+ const updateData = {
620
+ children: doc.children,
621
+ operator,
622
+ };
623
+ if (shouldSetStatus) {
624
+ updateData.status = status;
625
+ }
624
626
 
625
627
  const res = await this.updateBlocklet(did, updateData);
626
628
  return res;
@@ -1,11 +1,13 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  const semver = require('semver');
3
+ const isUrl = require('is-url');
3
4
  const omit = require('lodash/omit');
4
5
  const isEmpty = require('lodash/isEmpty');
5
6
  const security = require('@abtnode/util/lib/security');
6
7
  const { CustomError } = require('@blocklet/error');
7
8
  const { generateRandomString } = require('@abtnode/models/lib/util');
8
9
  const { isFromPublicKey } = require('@arcblock/did');
10
+ const { getLauncherInfo } = require('@abtnode/auth/lib/launcher');
9
11
  const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
10
12
  const {
11
13
  NODE_MODES,
@@ -124,8 +126,7 @@ class NodeState extends BaseState {
124
126
 
125
127
  const initialized = this.isInitialized({ nodeOwner, enablePassportIssuance, trustedPassports });
126
128
  const defaultConfigs = await getDefaultConfigs();
127
-
128
- doc = await this.insert({
129
+ const entity = {
129
130
  ...(defaultConfigs || {}),
130
131
  name,
131
132
  description,
@@ -158,7 +159,15 @@ class NodeState extends BaseState {
158
159
  nftDomainUrl: DEFAULT_NFT_DOMAIN_URL,
159
160
  sessionSalt: generateRandomString(16),
160
161
  enableFileSystemIsolation,
161
- });
162
+ };
163
+
164
+ if (entity.registerUrl && isUrl(entity.registerUrl)) {
165
+ const launcherInfo = await getLauncherInfo(entity.registerUrl);
166
+ entity.registerInfo = launcherInfo;
167
+ logger.info(`Init launcher info from ${entity.registerUrl}`, launcherInfo);
168
+ }
169
+
170
+ doc = await this.insert(entity);
162
171
 
163
172
  if (dek) {
164
173
  doc.sk = security.decrypt(doc.sk, doc.did, dek);
@@ -781,15 +781,15 @@ const startBlockletProcess = async (
781
781
  namespace: 'blocklets',
782
782
  name: processIdName,
783
783
  cwd,
784
- // FIXME @linchen [] does not work, so use () here
785
784
  log_date_format: '(YYYY-MM-DD HH:mm:ss)',
786
785
  output: path.join(logsDir, 'output.log'),
787
786
  error: path.join(logsDir, 'error.log'),
788
- wait_ready: process.env.NODE_ENV !== 'test',
787
+ // wait_ready: process.env.NODE_ENV !== 'test',
788
+ wait_ready: false,
789
789
  listen_timeout: 3000,
790
790
  max_memory_restart: `${maxMemoryRestart}M`,
791
791
  max_restarts: b.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 3,
792
- min_uptime: 60 * 1000,
792
+ min_uptime: 10_000,
793
793
  exp_backoff_restart_delay: 300,
794
794
  env: omit(
795
795
  {
@@ -1142,9 +1142,52 @@ const _checkProcessHealthy = async (
1142
1142
  timeout,
1143
1143
  doConsecutiveCheck: blocklet.mode !== BLOCKLET_MODES.DEVELOPMENT,
1144
1144
  waitTCP: !webInterface,
1145
+ shouldAbort: async () => {
1146
+ // Check if pm2 process exists and is online
1147
+ try {
1148
+ const info = await getProcessInfo(processId, { timeout: 3_000 });
1149
+ const currentStatus = info.pm2_env.status;
1150
+ if (currentStatus !== 'online') {
1151
+ throw new Error(`pm2 process ${processId} status is ${currentStatus}, not online`);
1152
+ }
1153
+ } catch (err) {
1154
+ // If process doesn't exist or has error, abort immediately
1155
+ logger.error('pm2 process check failed in shouldAbort', { appDid, error: err, processId, name });
1156
+ const isProcessNotExist =
1157
+ err.message &&
1158
+ (err.message.includes('not found') ||
1159
+ err.message.includes('does not exist') ||
1160
+ err.message.includes('not running'));
1161
+ if (isProcessNotExist) {
1162
+ throw new Error(`pm2 process ${processId} (${name}) died or does not exist: ${err.message}`);
1163
+ }
1164
+ throw new Error(`pm2 process ${processId} (${name}) check failed: ${err.message}`);
1165
+ }
1166
+ },
1145
1167
  });
1146
1168
  } catch (error) {
1147
- logger.error('ensure endpoint healthy failed', { appDid, port, minConsecutiveTime, timeout });
1169
+ const isProcessDead =
1170
+ error.message &&
1171
+ (error.message.includes('pm2 process') ||
1172
+ error.message.includes('died') ||
1173
+ error.message.includes('does not exist'));
1174
+ if (isProcessDead) {
1175
+ logger.error('blocklet process died during health check', {
1176
+ appDid,
1177
+ processId,
1178
+ name,
1179
+ port,
1180
+ error: error.message,
1181
+ });
1182
+ throw error;
1183
+ }
1184
+ logger.error('ensure endpoint healthy failed', {
1185
+ appDid,
1186
+ port,
1187
+ minConsecutiveTime,
1188
+ timeout,
1189
+ error: error.message,
1190
+ });
1148
1191
  throw error;
1149
1192
  }
1150
1193
  } catch (error) {
@@ -3,6 +3,7 @@ const fs = require('fs');
3
3
  const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
4
4
  const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
5
5
  const logger = require('@abtnode/logger')('@abtnode/docker-exec-chown');
6
+ const sleep = require('@abtnode/util/lib/sleep');
6
7
  const debianChmodDockerfile = require('./debian-chmod-dockerfile');
7
8
  const parseDockerName = require('./parse-docker-name');
8
9
  const { checkDockerHasImage } = require('./check-docker-has-image');
@@ -65,10 +66,11 @@ async function dockerExecChown({ name, dirs, code = 777, force = false }) {
65
66
  await promiseSpawn(
66
67
  `docker rm -fv ${realName} > /dev/null 2>&1 || true && docker run --rm --name ${realName} ${volumes} ${image} sh -c '${command}'`,
67
68
  {},
68
- { timeout: 1000 * 120, retry: 3 }
69
+ { timeout: 1000 * 120, retry: 0 }
69
70
  );
70
71
  } finally {
71
72
  await promiseSpawn(`docker rm -fv ${realName} > /dev/null 2>&1 || true`, {}, { timeout: 1000 * 10, retry: 3 });
73
+ await sleep(500);
72
74
  await lockFile.releaseLock(realName);
73
75
  }
74
76
 
@@ -5,6 +5,7 @@ const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
5
5
  const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
6
6
  const logger = require('@abtnode/logger')('@abtnode/docker-exec');
7
7
 
8
+ const sleep = require('@abtnode/util/lib/sleep');
8
9
  const parseDockerOptionsFromPm2 = require('./parse-docker-options-from-pm2');
9
10
  const { checkDockerInstalled } = require('./check-docker-installed');
10
11
 
@@ -26,7 +27,7 @@ async function dockerExec({
26
27
  runScriptParams,
27
28
  nodeInfo,
28
29
  timeout = 120_000,
29
- retry = 3,
30
+ retry = 0,
30
31
  output,
31
32
  error,
32
33
  }) {
@@ -81,10 +82,11 @@ async function dockerExec({
81
82
  { timeout, retry }
82
83
  );
83
84
  } finally {
84
- await lock.releaseLock(lockKey);
85
85
  if (nodeInfo.isDockerInstalled) {
86
86
  await promiseSpawn(`docker rm -f ${options.env.dockerName} > /dev/null 2>&1 || true`, {}, { timeout: 1000 * 10 });
87
87
  }
88
+ await sleep(500);
89
+ await lock.releaseLock(lockKey);
88
90
  }
89
91
  logger.info(`dockerExec ${options.env.dockerName} cost time: ${Date.now() - startTime}ms`);
90
92
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.17.3-beta-20251119-102907-28b69b76",
6
+ "version": "1.17.3-beta-20251121-135300-8e451e6e",
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.3-beta-20251119-102907-28b69b76",
21
- "@abtnode/auth": "1.17.3-beta-20251119-102907-28b69b76",
22
- "@abtnode/certificate-manager": "1.17.3-beta-20251119-102907-28b69b76",
23
- "@abtnode/constant": "1.17.3-beta-20251119-102907-28b69b76",
24
- "@abtnode/cron": "1.17.3-beta-20251119-102907-28b69b76",
25
- "@abtnode/db-cache": "1.17.3-beta-20251119-102907-28b69b76",
26
- "@abtnode/docker-utils": "1.17.3-beta-20251119-102907-28b69b76",
27
- "@abtnode/logger": "1.17.3-beta-20251119-102907-28b69b76",
28
- "@abtnode/models": "1.17.3-beta-20251119-102907-28b69b76",
29
- "@abtnode/queue": "1.17.3-beta-20251119-102907-28b69b76",
30
- "@abtnode/rbac": "1.17.3-beta-20251119-102907-28b69b76",
31
- "@abtnode/router-provider": "1.17.3-beta-20251119-102907-28b69b76",
32
- "@abtnode/static-server": "1.17.3-beta-20251119-102907-28b69b76",
33
- "@abtnode/timemachine": "1.17.3-beta-20251119-102907-28b69b76",
34
- "@abtnode/util": "1.17.3-beta-20251119-102907-28b69b76",
20
+ "@abtnode/analytics": "1.17.3-beta-20251121-135300-8e451e6e",
21
+ "@abtnode/auth": "1.17.3-beta-20251121-135300-8e451e6e",
22
+ "@abtnode/certificate-manager": "1.17.3-beta-20251121-135300-8e451e6e",
23
+ "@abtnode/constant": "1.17.3-beta-20251121-135300-8e451e6e",
24
+ "@abtnode/cron": "1.17.3-beta-20251121-135300-8e451e6e",
25
+ "@abtnode/db-cache": "1.17.3-beta-20251121-135300-8e451e6e",
26
+ "@abtnode/docker-utils": "1.17.3-beta-20251121-135300-8e451e6e",
27
+ "@abtnode/logger": "1.17.3-beta-20251121-135300-8e451e6e",
28
+ "@abtnode/models": "1.17.3-beta-20251121-135300-8e451e6e",
29
+ "@abtnode/queue": "1.17.3-beta-20251121-135300-8e451e6e",
30
+ "@abtnode/rbac": "1.17.3-beta-20251121-135300-8e451e6e",
31
+ "@abtnode/router-provider": "1.17.3-beta-20251121-135300-8e451e6e",
32
+ "@abtnode/static-server": "1.17.3-beta-20251121-135300-8e451e6e",
33
+ "@abtnode/timemachine": "1.17.3-beta-20251121-135300-8e451e6e",
34
+ "@abtnode/util": "1.17.3-beta-20251121-135300-8e451e6e",
35
35
  "@aigne/aigne-hub": "^0.10.9",
36
36
  "@arcblock/did": "^1.27.7",
37
37
  "@arcblock/did-connect-js": "^1.27.7",
@@ -43,15 +43,15 @@
43
43
  "@arcblock/pm2-events": "^0.0.5",
44
44
  "@arcblock/validator": "^1.27.7",
45
45
  "@arcblock/vc": "^1.27.7",
46
- "@blocklet/constant": "1.17.3-beta-20251119-102907-28b69b76",
46
+ "@blocklet/constant": "1.17.3-beta-20251121-135300-8e451e6e",
47
47
  "@blocklet/did-space-js": "^1.2.4",
48
- "@blocklet/env": "1.17.3-beta-20251119-102907-28b69b76",
48
+ "@blocklet/env": "1.17.3-beta-20251121-135300-8e451e6e",
49
49
  "@blocklet/error": "^0.3.3",
50
- "@blocklet/meta": "1.17.3-beta-20251119-102907-28b69b76",
51
- "@blocklet/resolver": "1.17.3-beta-20251119-102907-28b69b76",
52
- "@blocklet/sdk": "1.17.3-beta-20251119-102907-28b69b76",
53
- "@blocklet/server-js": "1.17.3-beta-20251119-102907-28b69b76",
54
- "@blocklet/store": "1.17.3-beta-20251119-102907-28b69b76",
50
+ "@blocklet/meta": "1.17.3-beta-20251121-135300-8e451e6e",
51
+ "@blocklet/resolver": "1.17.3-beta-20251121-135300-8e451e6e",
52
+ "@blocklet/sdk": "1.17.3-beta-20251121-135300-8e451e6e",
53
+ "@blocklet/server-js": "1.17.3-beta-20251121-135300-8e451e6e",
54
+ "@blocklet/store": "1.17.3-beta-20251121-135300-8e451e6e",
55
55
  "@blocklet/theme": "^3.2.6",
56
56
  "@fidm/x509": "^1.2.1",
57
57
  "@ocap/mcrypto": "^1.27.7",
@@ -116,5 +116,5 @@
116
116
  "express": "^4.18.2",
117
117
  "unzipper": "^0.10.11"
118
118
  },
119
- "gitHead": "6ae74784386f183ac410a226b6b9334c0f722fcc"
119
+ "gitHead": "e34d7abbe02a8b6eb754b7e2590a9e3d9d663c7d"
120
120
  }