hammerkit 2.0.0-next.4 → 2.0.0-next.6

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.
Files changed (46) hide show
  1. package/dist/cache/explain.js +7 -5
  2. package/dist/dashboard/assets/graph-canvas-DLioOiRN.css +1 -0
  3. package/dist/dashboard/assets/graph-canvas-HmkW_-XJ.js +8 -0
  4. package/dist/dashboard/assets/index-gIBDC_m8.css +2 -0
  5. package/dist/dashboard/assets/index-vE22S5E_.js +17 -0
  6. package/dist/dashboard/index.html +3 -3
  7. package/dist/dashboard/logo.png +0 -0
  8. package/dist/executer/docker-task.js +3 -0
  9. package/dist/executer/execute-work-task.js +3 -1
  10. package/dist/executer/local-task.js +3 -0
  11. package/dist/executer/scheduler/enqueue-next.js +6 -1
  12. package/dist/executer/scheduler/state-key.js +71 -34
  13. package/dist/executer/watch-loop.js +18 -5
  14. package/dist/file/get-file-context.js +5 -1
  15. package/dist/kubernetes/claim-layout.js +10 -1
  16. package/dist/kubernetes/ensure-persistent-data.js +45 -5
  17. package/dist/kubernetes/follow-pod-logs.js +124 -27
  18. package/dist/kubernetes/network-policy.js +4 -4
  19. package/dist/kubernetes/restore-kubernetes-data.js +3 -1
  20. package/dist/kubernetes/store-kubernetes-data.js +3 -1
  21. package/dist/kubernetes/volumes.js +6 -6
  22. package/dist/optimizer/calculate-checksum.js +46 -3
  23. package/dist/optimizer/get-work-cache-stats.js +36 -24
  24. package/dist/planner/work-item-executions.js +27 -0
  25. package/dist/planner/work-runtime-kubernetes.js +33 -38
  26. package/dist/remote/remote-work-commands.js +11 -2
  27. package/dist/server/capacity.js +63 -0
  28. package/dist/server/dashboard.js +27 -9
  29. package/dist/server/endpoints.js +10 -1
  30. package/dist/server/executor.js +10 -0
  31. package/dist/server/oauth.js +16 -1
  32. package/dist/server/overview.js +80 -11
  33. package/dist/server/plan.js +16 -3
  34. package/dist/server/policy.js +9 -3
  35. package/dist/server/run-worker.js +37 -4
  36. package/dist/server/runs.js +74 -6
  37. package/dist/server/server-config.js +4 -1
  38. package/dist/server/server.js +45 -10
  39. package/dist/server/service-runs.js +8 -1
  40. package/dist/server/timings.js +23 -3
  41. package/dist/utils/limit.js +30 -0
  42. package/dist/utils/web-links.js +34 -0
  43. package/package.json +1 -1
  44. package/dist/dashboard/assets/index-CjnKm5-3.css +0 -2
  45. package/dist/dashboard/assets/index-zPXa4klZ.js +0 -13
  46. package/dist/dashboard/favicon.svg +0 -1
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <meta name="robots" content="noindex" />
7
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
7
+ <link rel="icon" href="/logo.png" type="image/png" />
8
8
  <title>hammerkit</title>
9
- <script type="module" crossorigin src="/assets/index-zPXa4klZ.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-CjnKm5-3.css">
9
+ <script type="module" crossorigin src="/assets/index-vE22S5E_.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-gIBDC_m8.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
Binary file
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dockerTask = exports.getCapabilityHostConfig = exports.buildCreateOptions = void 0;
4
+ const work_item_executions_1 = require("../planner/work-item-executions");
4
5
  const run_labels_1 = require("../docker/run-labels");
5
6
  const execute_docker_1 = require("./execute-docker");
6
7
  const abort_1 = require("./abort");
@@ -106,7 +107,9 @@ async function dockerTask(docker, item, environment, options) {
106
107
  for (const cmd of item.data.cmds) {
107
108
  (0, abort_1.checkForAbort)(options.abort);
108
109
  item.status.write('info', `execute cmd "${cmd.cmd}" in container`);
110
+ const commandStarted = new Date();
109
111
  const result = await (0, execute_docker_1.execCommand)(item.status, environment, container, (0, execute_docker_1.convertToPosixPath)(cmd.cwd), [item.data.shell, '-c', cmd.cmd], item.data.user, undefined, options.abort);
112
+ (0, work_item_executions_1.recordExecution)(item.data, commandStarted, new Date());
110
113
  if (result.type === 'timeout') {
111
114
  throw new Error(`command ${cmd.cmd} timed out`);
112
115
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeWorkTask = void 0;
4
+ const work_item_executions_1 = require("../planner/work-item-executions");
4
5
  const work_task_1 = require("../planner/work-task");
5
6
  const states_1 = require("./states");
6
7
  const abort_1 = require("./abort");
@@ -83,7 +84,7 @@ dependents = null) {
83
84
  // build summary can show the cause column. Reporting only.
84
85
  let missCauses;
85
86
  if (options.explain) {
86
- const explanation = await (0, explain_1.explainTask)(work, options.cacheDefault, environment);
87
+ const explanation = await (0, explain_1.explainTask)(work, options.cacheDefault, environment, undefined, undefined, (0, watch_loop_1.getSharedStateKeys)(work, options));
87
88
  missCauses = explanation.causes.map(explain_1.describeCause);
88
89
  for (const cause of missCauses) {
89
90
  work.status.write('info', `cache miss: ${cause}`);
@@ -103,6 +104,7 @@ dependents = null) {
103
104
  });
104
105
  const timeout = (0, deadline_1.getTaskTimeout)(work.data.timeout, options);
105
106
  const deadline = (0, deadline_1.withDeadline)(abort, timeout);
107
+ (0, work_item_executions_1.clearExecutions)(work.data);
106
108
  try {
107
109
  await work.runtime.execute(environment, {
108
110
  cache: cacheState,
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.localTask = void 0;
4
+ const work_item_executions_1 = require("../planner/work-item-executions");
4
5
  const abort_1 = require("./abort");
5
6
  const execute_command_1 = require("./execute-command");
6
7
  const log_1 = require("../log");
@@ -18,7 +19,9 @@ async function localTask(item, environment, options) {
18
19
  for (const cmd of item.data.cmds) {
19
20
  (0, abort_1.checkForAbort)(options.abort);
20
21
  item.status.write('info', `execute cmd "${cmd.cmd}" locally`);
22
+ const commandStarted = new Date();
21
23
  const exitCode = await (0, execute_command_1.executeCommand)(item.status, options.abort, cmd.cwd, cmd.cmd, envs, environment);
24
+ (0, work_item_executions_1.recordExecution)(item.data, commandStarted, new Date());
22
25
  if (exitCode !== 0) {
23
26
  options.state.set({
24
27
  type: 'crash',
@@ -18,6 +18,7 @@ async function checkCacheState(item, defaultCacheMethod, environment,
18
18
  // the cache is off
19
19
  // restore: false only looks a backend hit up, its outputs are downloaded once
20
20
  // something that runs depends on them (`run --no-restore`, see deferred-restore)
21
+ // memo: the keys of the execution so far, see getExecutionStateKeys
21
22
  options = {}) {
22
23
  (0, deferred_restore_1.clearDeferredRestore)(item);
23
24
  // A task whose cache is off is never looked up, so hashing its sources buys
@@ -28,7 +29,11 @@ options = {}) {
28
29
  item.status.write('debug', `${item.name} is skipping cache check, because caching is disabled`);
29
30
  return { cached: false, stateKey: exports.UNCACHED_STATE_KEY, resolved: declared, provable: false };
30
31
  }
31
- const { stateKey, stats: currentStats, resolved, provable, unmatched, } = await (0, state_key_1.computeStateKey)(item, defaultCacheMethod, environment);
32
+ const { stateKey, stats: currentStats, resolved, provable, unmatched, } = await (0, state_key_1.computeStateKey)(item, defaultCacheMethod, environment, options.memo);
33
+ // what depends on it takes its key from here
34
+ if (options.memo && !options.memo.has(item.id())) {
35
+ options.memo.set(item.id(), Promise.resolve({ stateKey, provable }));
36
+ }
32
37
  for (const src of unmatched) {
33
38
  item.status.write('warn', `src "${src.source}" matches no files`);
34
39
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.computeStateKey = exports.combineStateKeys = exports.isOwnProvable = exports.resolveEffective = void 0;
3
+ exports.computeStateKey = exports.combineStateKeys = exports.getExecutionStateKeys = exports.isOwnProvable = exports.resolveEffective = void 0;
4
4
  const get_work_cache_stats_1 = require("../../optimizer/get-work-cache-stats");
5
5
  const work_task_1 = require("../../planner/work-task");
6
6
  // The state-key engine is shared by the scheduler's hit/miss decision
@@ -29,6 +29,43 @@ function isOwnProvable(item, unmatched) {
29
29
  return unmatched.length < item.data.src.filter((src) => !src.inherited).length;
30
30
  }
31
31
  exports.isOwnProvable = isOwnProvable;
32
+ // What an item on its own way down stands for. Items that depend on or need each other in a
33
+ // loop fail to plan (checkForLoop) and never run: this only has to end. The loop is told by
34
+ // the path of one computation, never by the shared memo, which tasks checked at the same time
35
+ // read, so a placeholder can never end up in another task's key.
36
+ const LOOP = { stateKey: 'loop', provable: true };
37
+ // Keep a computation in the memo, and take it out again if it fails, so a later check
38
+ // computes it anew instead of failing the same way.
39
+ function memoized(memo, key, compute) {
40
+ const existing = memo.get(key);
41
+ if (existing) {
42
+ return existing;
43
+ }
44
+ const promise = compute();
45
+ memo.set(key, promise);
46
+ promise.catch(() => {
47
+ if (memo.get(key) === promise) {
48
+ memo.delete(key);
49
+ }
50
+ });
51
+ return promise;
52
+ }
53
+ const executionKeys = new WeakMap();
54
+ // The keys of one execution (the options of `exec`, which all its items share): its items
55
+ // are checked once, before they run, from the files as they are then. Not for watch mode,
56
+ // where a key is computed again because files changed.
57
+ // The items it holds keys of must have no loop: checkForLoop fails the tree before it runs.
58
+ // Two tasks checked at once that enter a loop of needs from opposite ends would each wait for
59
+ // the other's computation.
60
+ function getExecutionStateKeys(options) {
61
+ let memo = executionKeys.get(options);
62
+ if (!memo) {
63
+ memo = new Map();
64
+ executionKeys.set(options, memo);
65
+ }
66
+ return memo;
67
+ }
68
+ exports.getExecutionStateKeys = getExecutionStateKeys;
32
69
  // Fold a task's own state key together with the state keys of its dependencies.
33
70
  // A change anywhere in the dependency subtree must change the resulting key, so
34
71
  // that a downstream task is invalidated when an upstream source changes — even
@@ -51,34 +88,36 @@ function concreteMethod(resolved, defaultCacheMethod) {
51
88
  // state keys of its dependencies, recursively. This is derived purely from the
52
89
  // work tree and the filesystem, so it does not depend on scheduler/run state —
53
90
  // important because it runs before dependencies are awaited.
54
- async function resolveEffectiveStateKey(item, defaultCacheMethod, environment, memo) {
55
- const existing = memo.get(item.id());
56
- if (existing !== undefined) {
57
- return existing;
91
+ function resolveEffectiveStateKey(item, defaultCacheMethod, environment, memo, path) {
92
+ if (path.has(item.id())) {
93
+ return Promise.resolve(LOOP);
58
94
  }
59
- const { stateKey, provable } = await computeStateKey(item, defaultCacheMethod, environment, memo);
60
- const effective = { stateKey, provable };
61
- memo.set(item.id(), effective);
62
- return effective;
95
+ return memoized(memo, item.id(), async () => {
96
+ const { stateKey, provable } = await computeStateKey(item, defaultCacheMethod, environment, memo, path);
97
+ return { stateKey, provable };
98
+ });
63
99
  }
64
100
  // Compute the combined state key for a single item together with the source
65
101
  // stats it was derived from, sharing one filesystem walk with callers that also
66
102
  // need the stats (the scheduler, the explain engine).
67
- async function computeStateKey(item, defaultCacheMethod, environment, memo = new Map()) {
103
+ async function computeStateKey(item, defaultCacheMethod, environment, memo = new Map(),
104
+ // the items being computed on the way here, see LOOP
105
+ parentPath = new Set()) {
106
+ const path = new Set(parentPath).add(item.id());
68
107
  const resolved = resolveEffective(item, defaultCacheMethod);
69
108
  const { stats, unmatched } = await (0, get_work_cache_stats_1.getWorkItemCacheStats)(item, environment);
70
109
  const ownKey = (0, get_work_cache_stats_1.getStateKey)(stats, concreteMethod(resolved, defaultCacheMethod));
71
110
  const depKeys = [];
72
111
  let provable = isOwnProvable(item, unmatched);
73
112
  for (const dep of item.deps) {
74
- const effective = await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo);
113
+ const effective = await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo, path);
75
114
  depKeys.push(effective.stateKey);
76
115
  provable = provable && effective.provable;
77
116
  }
78
117
  // what a task that needs a service passed against: the sources of the service and of the
79
118
  // tasks it depends on (its definition is in the task id, getWorkServiceCacheIdentity)
80
119
  for (const need of item.needs) {
81
- const effective = await resolveNeedStateKey(need.service, defaultCacheMethod, environment, memo);
120
+ const effective = await resolveNeedStateKey(need.service, defaultCacheMethod, environment, memo, path);
82
121
  depKeys.push(`${need.name}=${effective.stateKey}`);
83
122
  }
84
123
  return { stateKey: combineStateKeys(ownKey, depKeys), stats, resolved, provable, unmatched };
@@ -87,29 +126,27 @@ exports.computeStateKey = computeStateKey;
87
126
  // A needed service's sources folded with the state keys of its dependencies and needs. It
88
127
  // does not make the task that needs it unprovable: a dependency without `src` (resetting the
89
128
  // service's data, say) is skipped with the service when the task is a cache hit.
90
- async function resolveNeedStateKey(service, defaultCacheMethod, environment, memo) {
129
+ function resolveNeedStateKey(service, defaultCacheMethod, environment, memo, parentPath) {
91
130
  const memoKey = `need\0${service.name}`;
92
- const existing = memo.get(memoKey);
93
- if (existing !== undefined) {
94
- return existing;
95
- }
96
- // services that need each other in a loop fail to plan (checkForLoop): this only has to end
97
- memo.set(memoKey, { stateKey: 'loop', provable: true });
98
- const resolved = resolveEffective(service, defaultCacheMethod);
99
- const { stats } = await (0, get_work_cache_stats_1.getWorkItemCacheStats)(service, environment);
100
- const keys = [];
101
- for (const dep of service.deps) {
102
- keys.push((await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo)).stateKey);
103
- }
104
- for (const need of service.needs) {
105
- const effective = await resolveNeedStateKey(need.service, defaultCacheMethod, environment, memo);
106
- keys.push(`${need.name}=${effective.stateKey}`);
131
+ if (parentPath.has(memoKey)) {
132
+ return Promise.resolve(LOOP);
107
133
  }
108
- const effective = {
109
- stateKey: combineStateKeys((0, get_work_cache_stats_1.getStateKey)(stats, concreteMethod(resolved, defaultCacheMethod)), keys),
110
- provable: true,
111
- };
112
- memo.set(memoKey, effective);
113
- return effective;
134
+ const path = new Set(parentPath).add(memoKey);
135
+ return memoized(memo, memoKey, async () => {
136
+ const resolved = resolveEffective(service, defaultCacheMethod);
137
+ const { stats } = await (0, get_work_cache_stats_1.getWorkItemCacheStats)(service, environment);
138
+ const keys = [];
139
+ for (const dep of service.deps) {
140
+ keys.push((await resolveEffectiveStateKey(dep, defaultCacheMethod, environment, memo, path)).stateKey);
141
+ }
142
+ for (const need of service.needs) {
143
+ const effective = await resolveNeedStateKey(need.service, defaultCacheMethod, environment, memo, path);
144
+ keys.push(`${need.name}=${effective.stateKey}`);
145
+ }
146
+ return {
147
+ stateKey: combineStateKeys((0, get_work_cache_stats_1.getStateKey)(stats, concreteMethod(resolved, defaultCacheMethod)), keys),
148
+ provable: true,
149
+ };
150
+ });
114
151
  }
115
152
  //# sourceMappingURL=state-key.js.map
@@ -1,20 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.watchLoop = void 0;
3
+ exports.watchLoop = exports.getSharedStateKeys = exports.isWatchMode = void 0;
4
4
  const watch_state_key_1 = require("./watch-state-key");
5
5
  const state_1 = require("./state");
6
6
  const abort_event_1 = require("../utils/abort-event");
7
7
  const state_resolver_1 = require("./state-resolver");
8
8
  const abort_1 = require("./abort");
9
9
  const enqueue_next_1 = require("./scheduler/enqueue-next");
10
- async function watchLoop(work, environment, options, factory) {
11
- // a container service that watches its own files (`continuous`); a kubernetes-service and a
12
- // task have no such field
10
+ const state_key_1 = require("./scheduler/state-key");
11
+ // Whether an item is rechecked when its files change: not in daemon mode, and not for a
12
+ // container service that watches its own files (`continuous`); a kubernetes-service and a
13
+ // task have no such field
14
+ function isWatchMode(work, options) {
13
15
  const continuous = 'continuous' in work.data ? work.data.continuous : false;
14
- const watchMode = options.watch && !options.daemon && !continuous;
16
+ return !!options.watch && !options.daemon && !continuous;
17
+ }
18
+ exports.isWatchMode = isWatchMode;
19
+ // The keys of the execution to share, see getExecutionStateKeys: none in watch mode, where a
20
+ // key is computed again because files changed
21
+ function getSharedStateKeys(work, options) {
22
+ return isWatchMode(work, options) ? undefined : (0, state_key_1.getExecutionStateKeys)(options);
23
+ }
24
+ exports.getSharedStateKeys = getSharedStateKeys;
25
+ async function watchLoop(work, environment, options, factory) {
26
+ const watchMode = isWatchMode(work, options);
15
27
  const currentStats = await (0, enqueue_next_1.checkCacheState)(work, options.cacheDefault, environment, {
16
28
  watch: watchMode,
17
29
  restore: options.restore,
30
+ memo: getSharedStateKeys(work, options),
18
31
  });
19
32
  (0, abort_1.checkForAbort)(environment.abortCtrl.signal);
20
33
  const watchState = watchMode
@@ -39,7 +39,11 @@ function getFileContext(cwd) {
39
39
  resolve({ type: 'directory' });
40
40
  }
41
41
  else if (stats.isFile()) {
42
- resolve({ type: 'file', lastModified: stats.mtimeMs });
42
+ resolve({
43
+ type: 'file',
44
+ lastModified: stats.mtimeMs,
45
+ identity: { size: stats.size, changed: stats.ctimeMs, inode: stats.ino },
46
+ });
43
47
  }
44
48
  else {
45
49
  resolve({ type: 'other' });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasOutputsFlatteningAlike = exports.hasPathsEndingAlike = exports.byDepth = exports.getClaimSubPath = void 0;
3
+ exports.hasOutputsFlatteningAlike = exports.hasPathsEndingAlike = exports.byDepth = exports.getInheritedSourceSubPath = exports.getClaimSubPath = void 0;
4
4
  const crypto_1 = require("crypto");
5
5
  const path_1 = require("path");
6
6
  const portable_path_1 = require("../planner/utils/portable-path");
@@ -45,6 +45,15 @@ function getClaimSubPath(work, path) {
45
45
  return `${name}-${(0, crypto_1.createHash)('sha256').update(own).digest('hex').slice(0, 12)}`;
46
46
  }
47
47
  exports.getClaimSubPath = getClaimSubPath;
48
+ // Where a src of one of its deps is in the claim of `work`: always with the hash of its
49
+ // path, so it is apart from the paths of the task itself, which getClaimSubPath names
50
+ // without looking at them (they are not part of the task's id, see claimLayout), and from
51
+ // the other srcs of its deps (`package.json` and `core/package.json` of an `npm ci`)
52
+ function getInheritedSourceSubPath(work, path) {
53
+ const name = (0, path_1.basename)(path) || 'root';
54
+ return `${name}-${(0, crypto_1.createHash)('sha256').update((0, portable_path_1.portablePath)(work.data.projectRoot, path)).digest('hex').slice(0, 12)}`;
55
+ }
56
+ exports.getInheritedSourceSubPath = getInheritedSourceSubPath;
48
57
  function byDepth(a, b) {
49
58
  return a.split('/').length - b.split('/').length || (a < b ? -1 : a > b ? 1 : 0);
50
59
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensurePersistentData = exports.packSource = exports.clearStateFiles = exports.restoreLiveFiles = exports.syncLiveFiles = exports.SYNC_FILES = exports.getStateDirsInUse = exports.REMOVE_OTHER_STATES = exports.execInPod = exports.clearClaim = exports.getPodForPersistence = void 0;
3
+ exports.ensurePersistentData = exports.packSource = exports.clearStateFiles = exports.restoreLiveFiles = exports.syncLiveFiles = exports.SYNC_FILES = exports.getStateDirsInUse = exports.REMOVE_OTHER_STATES = exports.execInPod = exports.clearClaim = exports.getPodForPersistence = exports.applyWithinQuota = exports.QUOTA_TIMEOUT = exports.TRANSFER_POD_RESOURCES = void 0;
4
4
  const volumes_1 = require("./volumes");
5
5
  const apply_1 = require("./apply");
6
6
  const path_1 = require("path");
@@ -8,6 +8,7 @@ const tar_1 = require("../utils/tar");
8
8
  const resources_1 = require("./resources");
9
9
  const await_running_state_1 = require("./await-running-state");
10
10
  const sleep_1 = require("../utils/sleep");
11
+ const abort_1 = require("../executer/abort");
11
12
  const version_1 = require("../version");
12
13
  const log_1 = require("../log");
13
14
  const ensure_kubernetes_persistent_volume_claim_exists_1 = require("./ensure-kubernetes-persistent-volume-claim-exists");
@@ -42,7 +43,44 @@ async function removeStalePod(instance, namespace, name) {
42
43
  await (0, sleep_1.sleep)(500);
43
44
  }
44
45
  }
45
- async function getPodForPersistence(instance, env, service, persistence, type, fn) {
46
+ // What the pod that copies files into and out of a claim gets: tar and find, through
47
+ // exec. Without its own, the default limit of the namespace is taken, which is sized for
48
+ // tasks (4Gi on a server), and a run's quota then has no room for the pods of its tasks.
49
+ exports.TRANSFER_POD_RESOURCES = {
50
+ requests: { cpu: '50m', memory: '64Mi' },
51
+ limits: { memory: '512Mi' },
52
+ };
53
+ // how long a transfer pod waits for room under the quota of its namespace, such as the
54
+ // pods of other tasks of the run ending, before its task fails
55
+ exports.QUOTA_TIMEOUT = 10 * 60 * 1000;
56
+ // A Job waits for room under a full quota, a pod is refused: so a transfer pod is
57
+ // created again until the pods of the tasks it runs beside are done and make room
58
+ async function applyWithinQuota(instance, podSpec, service, timeout = exports.QUOTA_TIMEOUT, pollInterval = 2000,
59
+ // a run stopped while it waits: it stops waiting
60
+ signal) {
61
+ const deadline = Date.now() + timeout;
62
+ for (let waiting = false;; waiting = true) {
63
+ if (signal) {
64
+ (0, abort_1.checkForAbort)(signal);
65
+ }
66
+ try {
67
+ return await (0, apply_1.apply)(instance, podSpec);
68
+ }
69
+ catch (e) {
70
+ if ((0, apply_1.statusCodeOf)(e) !== 403 || !/exceeded quota/i.test((0, log_1.getErrorMessage)(e)) || Date.now() >= deadline) {
71
+ throw e;
72
+ }
73
+ if (!waiting) {
74
+ service.status.write('info', `wait for room under the quota of ${podSpec.metadata.namespace}`);
75
+ }
76
+ await (0, sleep_1.sleep)(pollInterval);
77
+ }
78
+ }
79
+ }
80
+ exports.applyWithinQuota = applyWithinQuota;
81
+ async function getPodForPersistence(instance, env, service, persistence, type,
82
+ // stops a wait for room under the quota
83
+ signal, fn) {
46
84
  if (persistence.volumes.length === 0) {
47
85
  return;
48
86
  }
@@ -102,6 +140,7 @@ async function getPodForPersistence(instance, env, service, persistence, type, f
102
140
  command: ['sh'],
103
141
  tty: true,
104
142
  volumeMounts: mounts,
143
+ resources: exports.TRANSFER_POD_RESOURCES,
105
144
  ...(0, job_placement_1.getContainerSecurity)(env),
106
145
  },
107
146
  ],
@@ -110,7 +149,7 @@ async function getPodForPersistence(instance, env, service, persistence, type, f
110
149
  };
111
150
  service.status.write('debug', `create pod ${name}`);
112
151
  await removeStalePod(instance, env.namespace, name);
113
- const pod = await (0, apply_1.apply)(instance, podSpec);
152
+ const pod = await applyWithinQuota(instance, podSpec, service, exports.QUOTA_TIMEOUT, 2000, signal);
114
153
  if (pod.status?.phase !== 'Running') {
115
154
  service.status.write('debug', `await running state ${name}`);
116
155
  await (0, await_running_state_1.awaitRunningState)(instance, env, name, 'Running');
@@ -273,7 +312,7 @@ exports.syncLiveFiles = syncLiveFiles;
273
312
  // its files mounts them from: a move that did not get up leaves the service at the commit
274
313
  // it was at
275
314
  async function restoreLiveFiles(instance, env, work, persistence, from) {
276
- await getPodForPersistence(instance, env, work, persistence, 'write', (pod) => syncLiveFiles(instance, env, work, persistence, pod, from));
315
+ await getPodForPersistence(instance, env, work, persistence, 'write', undefined, (pod) => syncLiveFiles(instance, env, work, persistence, pod, from));
277
316
  }
278
317
  exports.restoreLiveFiles = restoreLiveFiles;
279
318
  // Prepares the folder of a state in the claim of a work item of a service run before its
@@ -367,7 +406,8 @@ options = {}) {
367
406
  inUse.add(dir);
368
407
  }
369
408
  service.status.write('debug', 'start upload container');
370
- await getPodForPersistence(instance, env, service, persistence, 'write', async (name) => {
409
+ const { signal } = environment.abortCtrl;
410
+ await getPodForPersistence(instance, env, service, persistence, 'write', signal, async (name) => {
371
411
  if (options.reset) {
372
412
  await clearClaim(instance, env, service, name);
373
413
  }