s3db.js 11.0.2 → 11.0.4

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.
@@ -139,31 +139,8 @@ export class S3QueuePlugin extends Plugin {
139
139
 
140
140
  this.queueResource = this.database.resources[queueName];
141
141
 
142
- // Create lock resource for distributed locking (enabled by default)
143
- const lockName = `${this.config.resource}_locks`;
144
- const [okLock, errLock] = await tryFn(() =>
145
- this.database.createResource({
146
- name: lockName,
147
- attributes: {
148
- id: 'string|required',
149
- workerId: 'string|required',
150
- timestamp: 'number|required',
151
- ttl: 'number|default:5000'
152
- },
153
- behavior: 'body-overflow',
154
- timestamps: false
155
- })
156
- );
157
-
158
- if (okLock || this.database.resources[lockName]) {
159
- this.lockResource = this.database.resources[lockName];
160
- } else {
161
- // Locks disabled if creation fails
162
- this.lockResource = null;
163
- if (this.config.verbose) {
164
- console.log(`[S3QueuePlugin] Lock resource creation failed, locking disabled: ${errLock?.message}`);
165
- }
166
- }
142
+ // Locks are now managed by PluginStorage with TTL - no Resource needed
143
+ // Lock acquisition is handled via storage.acquireLock() with automatic expiration
167
144
 
168
145
  // Add helper methods to target resource
169
146
  this.addHelperMethods();
@@ -273,14 +250,7 @@ export class S3QueuePlugin extends Plugin {
273
250
  }
274
251
  }, 5000);
275
252
 
276
- // Start lock cleanup (every 10 seconds, remove expired locks)
277
- this.lockCleanupInterval = setInterval(() => {
278
- this.cleanupStaleLocks().catch(err => {
279
- if (this.config.verbose) {
280
- console.log(`[lockCleanup] Error: ${err.message}`);
281
- }
282
- });
283
- }, 10000);
253
+ // Lock cleanup no longer needed - TTL handles expiration automatically
284
254
 
285
255
  // Start N workers
286
256
  for (let i = 0; i < concurrency; i++) {
@@ -306,11 +276,7 @@ export class S3QueuePlugin extends Plugin {
306
276
  this.cacheCleanupInterval = null;
307
277
  }
308
278
 
309
- // Stop lock cleanup
310
- if (this.lockCleanupInterval) {
311
- clearInterval(this.lockCleanupInterval);
312
- this.lockCleanupInterval = null;
313
- }
279
+ // Lock cleanup interval no longer exists (TTL handles it)
314
280
 
315
281
  // Wait for workers to finish current tasks
316
282
  await Promise.all(this.workers);
@@ -383,59 +349,21 @@ export class S3QueuePlugin extends Plugin {
383
349
  }
384
350
 
385
351
  /**
386
- * Acquire a distributed lock using ETag-based conditional updates
352
+ * Acquire a distributed lock using PluginStorage TTL
387
353
  * This ensures only one worker can claim a message at a time
388
- *
389
- * Uses a two-step process:
390
- * 1. Create lock resource (similar to queue resource) if not exists
391
- * 2. Try to claim lock using ETag-based conditional update
392
354
  */
393
355
  async acquireLock(messageId) {
394
- if (!this.lockResource) {
395
- return true; // Locks disabled
396
- }
397
-
398
- const lockId = `lock-${messageId}`;
399
- const now = Date.now();
356
+ const storage = this.getStorage();
357
+ const lockKey = `msg-${messageId}`;
400
358
 
401
359
  try {
402
- // Try to get existing lock
403
- const [okGet, errGet, existingLock] = await tryFn(() =>
404
- this.lockResource.get(lockId)
405
- );
406
-
407
- if (existingLock) {
408
- // Lock exists - check if expired
409
- const lockAge = now - existingLock.timestamp;
410
- if (lockAge < existingLock.ttl) {
411
- // Lock still valid, owned by another worker
412
- return false;
413
- }
414
- // Lock expired - try to claim it with ETag
415
- const [ok, err, result] = await tryFn(() =>
416
- this.lockResource.updateConditional(lockId, {
417
- workerId: this.workerId,
418
- timestamp: now,
419
- ttl: 5000
420
- }, {
421
- ifMatch: existingLock._etag
422
- })
423
- );
424
-
425
- return ok && result.success;
426
- }
360
+ const lock = await storage.acquireLock(lockKey, {
361
+ ttl: 5, // 5 seconds
362
+ timeout: 0, // Don't wait if locked
363
+ workerId: this.workerId
364
+ });
427
365
 
428
- // Lock doesn't exist - create it
429
- const [okCreate, errCreate] = await tryFn(() =>
430
- this.lockResource.insert({
431
- id: lockId,
432
- workerId: this.workerId,
433
- timestamp: now,
434
- ttl: 5000
435
- })
436
- );
437
-
438
- return okCreate;
366
+ return lock !== null;
439
367
  } catch (error) {
440
368
  // On any error, skip this message
441
369
  if (this.config.verbose) {
@@ -446,17 +374,14 @@ export class S3QueuePlugin extends Plugin {
446
374
  }
447
375
 
448
376
  /**
449
- * Release a distributed lock by deleting the lock record
377
+ * Release a distributed lock via PluginStorage
450
378
  */
451
379
  async releaseLock(messageId) {
452
- if (!this.lockResource) {
453
- return; // Locks disabled
454
- }
455
-
456
- const lockId = `lock-${messageId}`;
380
+ const storage = this.getStorage();
381
+ const lockKey = `msg-${messageId}`;
457
382
 
458
383
  try {
459
- await this.lockResource.delete(lockId);
384
+ await storage.releaseLock(lockKey);
460
385
  } catch (error) {
461
386
  // Ignore errors on release (lock may have expired or been cleaned up)
462
387
  if (this.config.verbose) {
@@ -466,36 +391,12 @@ export class S3QueuePlugin extends Plugin {
466
391
  }
467
392
 
468
393
  /**
469
- * Clean up stale locks (older than TTL)
470
- * This prevents deadlocks if a worker crashes while holding a lock
394
+ * Clean up stale locks - NO LONGER NEEDED
395
+ * TTL handles automatic expiration, no manual cleanup required
471
396
  */
472
397
  async cleanupStaleLocks() {
473
- if (!this.lockResource) {
474
- return; // Locks disabled
475
- }
476
-
477
- const now = Date.now();
478
-
479
- try {
480
- // List all locks
481
- const locks = await this.lockResource.list();
482
-
483
- // Delete expired locks
484
- for (const lock of locks) {
485
- const lockAge = now - lock.timestamp;
486
- if (lockAge > lock.ttl) {
487
- await this.lockResource.delete(lock.id);
488
- if (this.config.verbose) {
489
- console.log(`[cleanupStaleLocks] Removed expired lock: ${lock.id}`);
490
- }
491
- }
492
- }
493
- } catch (error) {
494
- // Ignore errors in cleanup (non-critical)
495
- if (this.config.verbose) {
496
- console.log(`[cleanupStaleLocks] Error during cleanup: ${error.message}`);
497
- }
498
- }
398
+ // TTL automatically expires locks - no manual cleanup needed!
399
+ return;
499
400
  }
500
401
 
501
402
  async attemptClaim(msg) {
@@ -163,7 +163,6 @@ export class SchedulerPlugin extends Plugin {
163
163
  };
164
164
 
165
165
  this.database = null;
166
- this.lockResource = null;
167
166
  this.jobs = new Map();
168
167
  this.activeJobs = new Map();
169
168
  this.timers = new Map();
@@ -218,9 +217,7 @@ export class SchedulerPlugin extends Plugin {
218
217
  }
219
218
 
220
219
  async onInstall() {
221
-
222
- // Create lock resource for distributed locking
223
- await this._createLockResource();
220
+ // Locks are now managed by PluginStorage with TTL - no Resource needed
224
221
 
225
222
  // Create job execution history resource
226
223
  if (this.config.persistJobs) {
@@ -258,27 +255,6 @@ export class SchedulerPlugin extends Plugin {
258
255
  this.emit('initialized', { jobs: this.jobs.size });
259
256
  }
260
257
 
261
- async _createLockResource() {
262
- const [ok, err, lockResource] = await tryFn(() =>
263
- this.database.createResource({
264
- name: 'plg_scheduler_job_locks',
265
- attributes: {
266
- id: 'string|required',
267
- jobName: 'string|required',
268
- lockedAt: 'number|required',
269
- instanceId: 'string|optional'
270
- },
271
- behavior: 'body-only',
272
- timestamps: false
273
- })
274
- );
275
-
276
- if (!ok && !this.database.resources.plg_scheduler_job_locks) {
277
- throw new Error(`Failed to create lock resource: ${err?.message}`);
278
- }
279
-
280
- this.lockResource = ok ? lockResource : this.database.resources.plg_scheduler_job_locks;
281
- }
282
258
 
283
259
  async _createJobHistoryResource() {
284
260
  const [ok] = await tryFn(() => this.database.createResource({
@@ -416,19 +392,17 @@ export class SchedulerPlugin extends Plugin {
416
392
  // Mark as active immediately (will be updated with executionId later)
417
393
  this.activeJobs.set(jobName, 'acquiring-lock');
418
394
 
419
- // Acquire distributed lock to prevent concurrent execution across instances
420
- const lockId = `lock-${jobName}`;
421
- const [lockAcquired, lockErr] = await tryFn(() =>
422
- this.lockResource.insert({
423
- id: lockId,
424
- jobName,
425
- lockedAt: Date.now(),
426
- instanceId: process.pid ? String(process.pid) : 'unknown'
427
- })
428
- );
395
+ // Acquire distributed lock with TTL to prevent concurrent execution across instances
396
+ const storage = this.getStorage();
397
+ const lockKey = `job-${jobName}`;
398
+ const lock = await storage.acquireLock(lockKey, {
399
+ ttl: Math.ceil(job.timeout / 1000) + 60, // Job timeout + 60 seconds buffer
400
+ timeout: 0, // Don't wait if locked
401
+ workerId: process.pid ? String(process.pid) : 'unknown'
402
+ });
429
403
 
430
404
  // If lock couldn't be acquired, another instance is executing this job
431
- if (!lockAcquired) {
405
+ if (!lock) {
432
406
  if (this.config.verbose) {
433
407
  console.log(`[SchedulerPlugin] Job '${jobName}' already running on another instance`);
434
408
  }
@@ -577,7 +551,7 @@ export class SchedulerPlugin extends Plugin {
577
551
  }
578
552
  } finally {
579
553
  // Always release the distributed lock
580
- await tryFn(() => this.lockResource.delete(lockId));
554
+ await tryFn(() => storage.releaseLock(lockKey));
581
555
  }
582
556
  }
583
557