@sovovs/bycli 2.1.43 → 2.1.44

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.
@@ -40,12 +40,14 @@ export interface AdapterSchedulerOptions {
40
40
  leaseExpiryMs?: number;
41
41
  runtimeCeiling?: number;
42
42
  releasedLeaseRetentionMs?: number;
43
+ startIntervalMs?: number;
43
44
  }
44
45
  export declare class AdapterScheduler {
45
46
  private readonly now;
46
47
  private readonly leaseExpiryMs;
47
48
  private readonly runtimeCeiling;
48
49
  private readonly releasedLeaseRetentionMs;
50
+ private readonly startIntervalMs;
49
51
  private readonly pools;
50
52
  private readonly releasedLeaseIds;
51
53
  private readonly generations;
@@ -74,6 +76,9 @@ export declare class AdapterScheduler {
74
76
  grants: number;
75
77
  };
76
78
  private schedule;
79
+ private rejectExpiredQueued;
80
+ private clearPoolTimer;
81
+ private armPoolTimer;
77
82
  private scheduleResources;
78
83
  private releaseAllResourcesForLease;
79
84
  private pruneReleasedLeaseIds;
@@ -12,6 +12,7 @@ export class AdapterScheduler {
12
12
  leaseExpiryMs;
13
13
  runtimeCeiling;
14
14
  releasedLeaseRetentionMs;
15
+ startIntervalMs;
15
16
  pools = new Map();
16
17
  releasedLeaseIds = new Map();
17
18
  generations = new Map();
@@ -20,10 +21,15 @@ export class AdapterScheduler {
20
21
  resourceQueue = [];
21
22
  sequence = 0;
22
23
  constructor(options = {}) {
24
+ const startIntervalMs = options.startIntervalMs ?? 5_000;
25
+ if (!Number.isInteger(startIntervalMs) || startIntervalMs < 0) {
26
+ throw new Error('Adapter start interval must be a non-negative integer');
27
+ }
23
28
  this.now = options.now ?? Date.now;
24
29
  this.leaseExpiryMs = options.leaseExpiryMs ?? 45_000;
25
30
  this.runtimeCeiling = options.runtimeCeiling ?? 3;
26
31
  this.releasedLeaseRetentionMs = options.releasedLeaseRetentionMs ?? 300_000;
32
+ this.startIntervalMs = startIntervalMs;
27
33
  }
28
34
  acquire(request) {
29
35
  this.validateRequest(request);
@@ -73,6 +79,7 @@ export class AdapterScheduler {
73
79
  this.releasedLeaseIds.set(lease.leaseId, this.now() + this.releasedLeaseRetentionMs);
74
80
  if (release.reason === 'auth_gate' || release.reason === 'rate_limited') {
75
81
  pool.closed = release.reason;
82
+ this.clearPoolTimer(pool);
76
83
  const error = this.poolClosedError(release.reason);
77
84
  for (const pending of pool.queued.splice(0)) {
78
85
  if (pending.timer)
@@ -144,14 +151,7 @@ export class AdapterScheduler {
144
151
  const now = this.now();
145
152
  this.pruneReleasedLeaseIds(now);
146
153
  for (const pool of [...this.pools.values()]) {
147
- for (const pending of [...pool.queued]) {
148
- if (pending.deadline > now)
149
- continue;
150
- pool.queued.splice(pool.queued.indexOf(pending), 1);
151
- if (pending.timer)
152
- clearTimeout(pending.timer);
153
- pending.reject(new AdapterSchedulerError('ADAPTER_QUEUE_TIMEOUT', 'Timed out waiting for an Adapter command lease'));
154
- }
154
+ this.rejectExpiredQueued(pool, now);
155
155
  for (const lease of [...pool.running.values()]) {
156
156
  if (lease.heartbeatDeadline > now)
157
157
  continue;
@@ -176,6 +176,7 @@ export class AdapterScheduler {
176
176
  }
177
177
  reset() {
178
178
  for (const pool of this.pools.values()) {
179
+ this.clearPoolTimer(pool);
179
180
  for (const pending of pool.queued) {
180
181
  if (pending.timer)
181
182
  clearTimeout(pending.timer);
@@ -209,34 +210,69 @@ export class AdapterScheduler {
209
210
  };
210
211
  }
211
212
  schedule(pool) {
212
- while (!pool.closed && pool.running.size < pool.maxParallel) {
213
- const eligible = pool.queued
214
- .filter(entry => !pool.activeSessions.has(entry.request.adapterSession)
215
- && pool.running.size < pool.maxParallel)
216
- .sort((a, b) => a.enqueuedAt - b.enqueuedAt || a.sequence - b.sequence)[0];
217
- if (!eligible)
218
- return;
219
- pool.queued.splice(pool.queued.indexOf(eligible), 1);
220
- if (eligible.timer)
221
- clearTimeout(eligible.timer);
222
- const grantedAt = this.now();
223
- const lease = {
224
- leaseId: crypto.randomUUID(),
225
- requestId: eligible.request.requestId,
226
- poolKey: pool.key,
227
- contextId: eligible.request.contextId,
228
- surface: 'adapter',
229
- site: eligible.request.site,
230
- adapterSession: eligible.request.adapterSession,
231
- sessionKey: eligible.request.sessionKey,
232
- generation: pool.generation,
233
- grantedAt,
234
- heartbeatDeadline: grantedAt + this.leaseExpiryMs,
235
- };
236
- pool.running.set(lease.leaseId, lease);
237
- pool.activeSessions.add(lease.adapterSession);
238
- eligible.resolve({ ...lease });
213
+ if (pool.closed)
214
+ return;
215
+ const now = this.now();
216
+ this.rejectExpiredQueued(pool, now);
217
+ if (pool.running.size >= pool.maxParallel)
218
+ return;
219
+ const eligible = pool.queued
220
+ .filter(entry => !pool.activeSessions.has(entry.request.adapterSession))
221
+ .sort((a, b) => a.enqueuedAt - b.enqueuedAt || a.sequence - b.sequence)[0];
222
+ if (!eligible)
223
+ return;
224
+ if (now < pool.nextGrantAt) {
225
+ this.armPoolTimer(pool, pool.nextGrantAt);
226
+ return;
239
227
  }
228
+ pool.queued.splice(pool.queued.indexOf(eligible), 1);
229
+ if (eligible.timer)
230
+ clearTimeout(eligible.timer);
231
+ const grantedAt = now;
232
+ const lease = {
233
+ leaseId: crypto.randomUUID(),
234
+ requestId: eligible.request.requestId,
235
+ poolKey: pool.key,
236
+ contextId: eligible.request.contextId,
237
+ surface: 'adapter',
238
+ site: eligible.request.site,
239
+ adapterSession: eligible.request.adapterSession,
240
+ sessionKey: eligible.request.sessionKey,
241
+ generation: pool.generation,
242
+ grantedAt,
243
+ heartbeatDeadline: grantedAt + this.leaseExpiryMs,
244
+ };
245
+ pool.nextGrantAt = grantedAt + this.startIntervalMs;
246
+ pool.running.set(lease.leaseId, lease);
247
+ pool.activeSessions.add(lease.adapterSession);
248
+ eligible.resolve({ ...lease });
249
+ this.schedule(pool);
250
+ }
251
+ rejectExpiredQueued(pool, now) {
252
+ for (const pending of [...pool.queued]) {
253
+ if (pending.deadline > now)
254
+ continue;
255
+ pool.queued.splice(pool.queued.indexOf(pending), 1);
256
+ if (pending.timer)
257
+ clearTimeout(pending.timer);
258
+ pending.reject(new AdapterSchedulerError('ADAPTER_QUEUE_TIMEOUT', 'Timed out waiting for an Adapter command lease'));
259
+ }
260
+ }
261
+ clearPoolTimer(pool) {
262
+ if (pool.scheduleTimer)
263
+ clearTimeout(pool.scheduleTimer);
264
+ pool.scheduleTimer = undefined;
265
+ }
266
+ armPoolTimer(pool, dueAt) {
267
+ if (pool.scheduleTimer)
268
+ return;
269
+ pool.scheduleTimer = setTimeout(() => {
270
+ pool.scheduleTimer = undefined;
271
+ if (this.pools.get(pool.key) !== pool)
272
+ return;
273
+ this.sweepExpired();
274
+ }, Math.max(0, dueAt - this.now()));
275
+ pool.scheduleTimer.unref?.();
240
276
  }
241
277
  scheduleResources() {
242
278
  for (const pending of [...this.resourceQueue].sort((a, b) => a.sequence - b.sequence)) {
@@ -319,6 +355,7 @@ export class AdapterScheduler {
319
355
  key,
320
356
  generation,
321
357
  maxParallel: Math.min(request.maxParallel, this.runtimeCeiling),
358
+ nextGrantAt: 0,
322
359
  running: new Map(),
323
360
  activeSessions: new Set(),
324
361
  queued: [],
@@ -327,8 +364,14 @@ export class AdapterScheduler {
327
364
  return pool;
328
365
  }
329
366
  removeDrainedPool(pool) {
330
- if (pool.running.size === 0 && pool.queued.length === 0)
331
- this.pools.delete(pool.key);
367
+ if (pool.running.size !== 0 || pool.queued.length !== 0)
368
+ return;
369
+ if (!pool.closed && this.now() < pool.nextGrantAt) {
370
+ this.armPoolTimer(pool, pool.nextGrantAt);
371
+ return;
372
+ }
373
+ this.clearPoolTimer(pool);
374
+ this.pools.delete(pool.key);
332
375
  }
333
376
  poolClosedError(reason) {
334
377
  return reason === 'auth_gate'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sovovs/bycli",
3
- "version": "2.1.43",
3
+ "version": "2.1.44",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },