ti2 1.0.94 → 1.0.96
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/.github/workflows/tests.yml +7 -3
- package/api.yml +159 -0
- package/cli.js +106 -0
- package/controllers/README.md +70 -1
- package/controllers/__tests__/admin.js +1 -1
- package/controllers/__tests__/allotments.js +1 -2
- package/controllers/__tests__/app.js +1 -1
- package/controllers/__tests__/bookings-searchProducts.js +533 -0
- package/controllers/__tests__/bookings.js +29 -267
- package/controllers/__tests__/cronjobs.js +394 -0
- package/controllers/__tests__/ping.js +1 -1
- package/controllers/__tests__/productsSearch-background.js +1 -1
- package/controllers/__tests__/user.js +1 -1
- package/controllers/app.js +280 -14
- package/controllers/bookings.js +188 -108
- package/index.js +2 -3
- package/migrations/20240130000000-add-token-to-cronjobs.js +12 -0
- package/migrations/20250528_create_apiCronjobs.js +61 -0
- package/models/apiCronJobs.js +96 -0
- package/package.json +2 -2
- package/test/utils.js +54 -26
- package/worker/__tests__/job-callback.js +0 -22
- package/worker/__tests__/job-plugin.js +207 -0
- package/worker/index.js +191 -108
- package/worker/queue.js +70 -5
|
@@ -7,7 +7,7 @@ jobs:
|
|
|
7
7
|
timeout-minutes: 10
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
container:
|
|
10
|
-
image: node:16.
|
|
10
|
+
image: node:16.20.0
|
|
11
11
|
services:
|
|
12
12
|
mysql:
|
|
13
13
|
image: mysql
|
|
@@ -31,13 +31,17 @@ jobs:
|
|
|
31
31
|
- uses: actions/checkout@v2
|
|
32
32
|
- uses: actions/setup-node@v1
|
|
33
33
|
with:
|
|
34
|
-
node-version:
|
|
34
|
+
node-version: 16
|
|
35
35
|
- name: install dependencies
|
|
36
36
|
run: npm ci
|
|
37
37
|
- name: create/migrate the database
|
|
38
38
|
run: npx sequelize db:create && npx sequelize db:migrate
|
|
39
39
|
- name: seed the database
|
|
40
40
|
run: npx sequelize db:seed:all
|
|
41
|
-
-
|
|
41
|
+
- name: start worker process
|
|
42
|
+
run: RUNNER_TRACKING_ID="" node ./test/worker.js > worker.log 2>&1 &
|
|
42
43
|
- name: run the tests
|
|
43
44
|
run: npx jest -i --coverage --forceExit
|
|
45
|
+
- name: display worker logs
|
|
46
|
+
if: ${{ failure() }}
|
|
47
|
+
run: cat worker.log
|
package/api.yml
CHANGED
|
@@ -13,6 +13,78 @@ components:
|
|
|
13
13
|
scheme: bearer
|
|
14
14
|
bearerFormat: JWT
|
|
15
15
|
schemas:
|
|
16
|
+
CronJob:
|
|
17
|
+
type: object
|
|
18
|
+
properties:
|
|
19
|
+
id:
|
|
20
|
+
type: string
|
|
21
|
+
description: uuid of the crojob to remove
|
|
22
|
+
example: '550e8400-e29b-41d4-a716-446655440000'
|
|
23
|
+
method:
|
|
24
|
+
type: string
|
|
25
|
+
description: HTTP method for the API call
|
|
26
|
+
example: 'POST'
|
|
27
|
+
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
|
|
28
|
+
url:
|
|
29
|
+
type: string
|
|
30
|
+
description: URL path for the API call
|
|
31
|
+
example: '/products/travelgate-hotelx/123/search'
|
|
32
|
+
cron:
|
|
33
|
+
type: string
|
|
34
|
+
description: Cron expression for job scheduling
|
|
35
|
+
example: '0 0 * * *'
|
|
36
|
+
userId:
|
|
37
|
+
type: string
|
|
38
|
+
description: User ID associated with the job
|
|
39
|
+
body:
|
|
40
|
+
type: object
|
|
41
|
+
description: body for the scheduled rquest
|
|
42
|
+
removeOnComplete:
|
|
43
|
+
type: boolean
|
|
44
|
+
description: Whether the job should be removed from the queue after successful completion (for one-off executions).
|
|
45
|
+
default: false
|
|
46
|
+
CronJobList:
|
|
47
|
+
type: object
|
|
48
|
+
properties:
|
|
49
|
+
jobs:
|
|
50
|
+
type: array
|
|
51
|
+
items:
|
|
52
|
+
$ref: '#/components/schemas/CronJob'
|
|
53
|
+
CronJobCreate:
|
|
54
|
+
type: object
|
|
55
|
+
required:
|
|
56
|
+
- method
|
|
57
|
+
- url
|
|
58
|
+
- cron
|
|
59
|
+
properties:
|
|
60
|
+
method:
|
|
61
|
+
type: string
|
|
62
|
+
description: HTTP method for the API call
|
|
63
|
+
example: 'POST'
|
|
64
|
+
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
|
|
65
|
+
url:
|
|
66
|
+
type: string
|
|
67
|
+
description: URL path for the API call
|
|
68
|
+
example: '/products/travelgate-hotelx/123/search'
|
|
69
|
+
cron:
|
|
70
|
+
type: string
|
|
71
|
+
description: Cron expression for job scheduling
|
|
72
|
+
example: '0 0 * * *'
|
|
73
|
+
callbackUrl:
|
|
74
|
+
type: string
|
|
75
|
+
description: Optional URL to call after job completion
|
|
76
|
+
example: 'https://example.com/callback'
|
|
77
|
+
payload:
|
|
78
|
+
type: object
|
|
79
|
+
description: Optional payload for the API call
|
|
80
|
+
example:
|
|
81
|
+
startDate: '01/01/2023'
|
|
82
|
+
endDate: '03/04/2023'
|
|
83
|
+
keyPath: 'MAGLUX|7CQLDACMAGLUXDELSUI'
|
|
84
|
+
removeOnComplete:
|
|
85
|
+
type: boolean
|
|
86
|
+
description: Whether the job should be removed from the queue after successful completion (for one-off executions).
|
|
87
|
+
default: false
|
|
16
88
|
ServerInfo:
|
|
17
89
|
type: object
|
|
18
90
|
properties:
|
|
@@ -404,6 +476,93 @@ components:
|
|
|
404
476
|
type: boolean
|
|
405
477
|
default: false
|
|
406
478
|
paths:
|
|
479
|
+
/cronjobs/{userId}:
|
|
480
|
+
get:
|
|
481
|
+
security:
|
|
482
|
+
- bearerAuth: ['admin', 'user']
|
|
483
|
+
tags:
|
|
484
|
+
- cronjobs
|
|
485
|
+
summary: List cronjobs for a user
|
|
486
|
+
operationId: listCronjobs
|
|
487
|
+
parameters:
|
|
488
|
+
- name: userId
|
|
489
|
+
in: path
|
|
490
|
+
description: User ID to list cronjobs for
|
|
491
|
+
required: true
|
|
492
|
+
schema:
|
|
493
|
+
type: string
|
|
494
|
+
responses:
|
|
495
|
+
'200':
|
|
496
|
+
description: List of cronjobs
|
|
497
|
+
content:
|
|
498
|
+
application/json:
|
|
499
|
+
schema:
|
|
500
|
+
$ref: '#/components/schemas/CronJobList'
|
|
501
|
+
post:
|
|
502
|
+
security:
|
|
503
|
+
- bearerAuth: ['admin', 'user']
|
|
504
|
+
tags:
|
|
505
|
+
- cronjobs
|
|
506
|
+
summary: Create a new cronjob
|
|
507
|
+
operationId: createCronjob
|
|
508
|
+
parameters:
|
|
509
|
+
- name: userId
|
|
510
|
+
in: path
|
|
511
|
+
description: User ID to create cronjob for
|
|
512
|
+
required: true
|
|
513
|
+
schema:
|
|
514
|
+
type: string
|
|
515
|
+
requestBody:
|
|
516
|
+
required: true
|
|
517
|
+
content:
|
|
518
|
+
application/json:
|
|
519
|
+
schema:
|
|
520
|
+
$ref: '#/components/schemas/CronJobCreate'
|
|
521
|
+
responses:
|
|
522
|
+
'200':
|
|
523
|
+
description: Created cronjob
|
|
524
|
+
content:
|
|
525
|
+
application/json:
|
|
526
|
+
schema:
|
|
527
|
+
$ref: '#/components/schemas/CronJob'
|
|
528
|
+
/cronjobs/{userId}/{id}:
|
|
529
|
+
delete:
|
|
530
|
+
security:
|
|
531
|
+
- bearerAuth: ['admin', 'user']
|
|
532
|
+
tags:
|
|
533
|
+
- cronjobs
|
|
534
|
+
summary: Delete a cronjob
|
|
535
|
+
operationId: deleteCronjob
|
|
536
|
+
parameters:
|
|
537
|
+
- name: userId
|
|
538
|
+
in: path
|
|
539
|
+
description: User ID the cronjob belongs to
|
|
540
|
+
required: true
|
|
541
|
+
schema:
|
|
542
|
+
type: string
|
|
543
|
+
- name: id
|
|
544
|
+
in: path
|
|
545
|
+
description: Cron job ID (UUID) to delete
|
|
546
|
+
required: true
|
|
547
|
+
schema:
|
|
548
|
+
type: string
|
|
549
|
+
responses:
|
|
550
|
+
'404':
|
|
551
|
+
description: Cronjob not found
|
|
552
|
+
content:
|
|
553
|
+
application/json:
|
|
554
|
+
schema:
|
|
555
|
+
type: object
|
|
556
|
+
properties:
|
|
557
|
+
message:
|
|
558
|
+
type: string
|
|
559
|
+
example: 'Cronjob not found'
|
|
560
|
+
'200':
|
|
561
|
+
description: Success status
|
|
562
|
+
content:
|
|
563
|
+
application/json:
|
|
564
|
+
schema:
|
|
565
|
+
$ref: '#/components/schemas/Status'
|
|
407
566
|
/ping:
|
|
408
567
|
get:
|
|
409
568
|
tags:
|
package/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ const { Command, Argument } = require('commander');
|
|
|
5
5
|
const { migrateApp } = require('./controllers/app')();
|
|
6
6
|
const { migrate } = require('./controllers/admin');
|
|
7
7
|
const { decrypt, encrypt } = require('./lib/security');
|
|
8
|
+
const { queue, redisResults } = require('./worker/queue');
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
@@ -55,4 +56,109 @@ program.command('encrypt')
|
|
|
55
56
|
process.exit(0);
|
|
56
57
|
});
|
|
57
58
|
|
|
59
|
+
program.command('queue:list [pattern]')
|
|
60
|
+
.description('List jobs in the queue, optionally filtering by ID pattern (e.g., "jobId*", "*suffix", "prefix*suffix")')
|
|
61
|
+
.action(async (pattern) => {
|
|
62
|
+
try {
|
|
63
|
+
await queue.isReady(); // Ensure queue is ready
|
|
64
|
+
const jobTypes = ['waiting', 'active', 'completed', 'failed', 'delayed', 'paused'];
|
|
65
|
+
let message = `Fetching jobs of types: ${jobTypes.join(', ')}...`;
|
|
66
|
+
if (pattern) {
|
|
67
|
+
message += ` matching pattern "${pattern}"`;
|
|
68
|
+
}
|
|
69
|
+
console.log(message);
|
|
70
|
+
let jobs = await queue.getJobs(jobTypes);
|
|
71
|
+
|
|
72
|
+
if (pattern) {
|
|
73
|
+
const regexPatternText = '^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
|
|
74
|
+
const regexPattern = new RegExp(regexPatternText);
|
|
75
|
+
console.log(`Using filter regex: ${regexPattern}`);
|
|
76
|
+
jobs = jobs.filter(job => regexPattern.test(job.id.toString()));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (jobs.length === 0) {
|
|
80
|
+
if (pattern) {
|
|
81
|
+
console.log(`No jobs found matching pattern "${pattern}".`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log('No jobs found in the queue.');
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
console.log(`Found ${jobs.length} jobs:`);
|
|
87
|
+
for (const job of jobs) {
|
|
88
|
+
const state = await job.getState();
|
|
89
|
+
const progress = job.progress();
|
|
90
|
+
const timestamp = new Date(job.timestamp).toISOString();
|
|
91
|
+
console.log(`\n- ID: ${job.id}`);
|
|
92
|
+
console.log(` State: ${state}`);
|
|
93
|
+
console.log(` Progress: ${progress}`);
|
|
94
|
+
console.log(` Added: ${timestamp}`);
|
|
95
|
+
console.log(` Name (Queue): ${job.name}`);
|
|
96
|
+
console.log(` Data: ${JSON.stringify(job.data, null, 2)}`);
|
|
97
|
+
if (job.opts.repeat) {
|
|
98
|
+
console.log(` Repeat Options: ${JSON.stringify(job.opts.repeat)}`);
|
|
99
|
+
}
|
|
100
|
+
if (state === 'failed' && job.failedReason) {
|
|
101
|
+
console.log(` Failed Reason: ${job.failedReason}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Error listing queue jobs:', error);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
process.exit(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
program.command('queue:remove')
|
|
113
|
+
.description('Remove job instances from the queue by ID or pattern (e.g., "jobId*", "*suffix", "prefix*suffix")')
|
|
114
|
+
.argument('<pattern>', 'Job ID or glob-like pattern for job IDs to remove')
|
|
115
|
+
.action(async (pattern) => {
|
|
116
|
+
try {
|
|
117
|
+
await queue.isReady(); // Ensure queue is ready
|
|
118
|
+
console.log(`Attempting to remove job instances matching pattern: "${pattern}"`);
|
|
119
|
+
|
|
120
|
+
const jobTypes = ['waiting', 'active', 'completed', 'failed', 'delayed', 'paused'];
|
|
121
|
+
const jobs = await queue.getJobs(jobTypes);
|
|
122
|
+
let removedCount = 0;
|
|
123
|
+
const removedJobIds = [];
|
|
124
|
+
|
|
125
|
+
const regexPatternText = '^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
|
|
126
|
+
const regexPattern = new RegExp(regexPatternText);
|
|
127
|
+
|
|
128
|
+
console.log(`Using regex: ${regexPattern}`);
|
|
129
|
+
|
|
130
|
+
for (const job of jobs) {
|
|
131
|
+
if (regexPattern.test(job.id.toString())) {
|
|
132
|
+
try {
|
|
133
|
+
await job.remove();
|
|
134
|
+
removedJobIds.push(job.id.toString());
|
|
135
|
+
removedCount++;
|
|
136
|
+
console.log(`Removed job ${job.id} from Bull queue.`);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.error(`Failed to remove job ${job.id} from Bull queue:`, e.message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (removedCount > 0) {
|
|
144
|
+
console.log(`Successfully removed ${removedCount} job instance(s) from Bull queue.`);
|
|
145
|
+
if (removedJobIds.length > 0) {
|
|
146
|
+
console.log(`Attempting to remove ${removedJobIds.length} result(s) from Redis for the removed jobs...`);
|
|
147
|
+
try {
|
|
148
|
+
const delCount = await redisResults.del(removedJobIds);
|
|
149
|
+
console.log(`Removed ${delCount} result(s) from Redis.`);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error('Failed to remove results from Redis:', e.message);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
console.log(`No job instances found matching pattern "${pattern}".`);
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Error removing queue jobs:', error);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
process.exit(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
58
164
|
program.parse();
|
package/controllers/README.md
CHANGED
|
@@ -1 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
# Bookings Controller Caching and Locking Strategy
|
|
2
|
+
|
|
3
|
+
The `$bookingsProductSearch` function in `controllers/bookings.js` implements a sophisticated caching and locking strategy to optimize product searches, manage stale data, and prevent redundant operations. This document outlines its core flow and the locking mechanisms involved.
|
|
4
|
+
|
|
5
|
+
## Core Flow of `$bookingsProductSearch`
|
|
6
|
+
|
|
7
|
+
1. **Initialization**:
|
|
8
|
+
* Retrieves user, application (`appKey`), and token details (including `hint`).
|
|
9
|
+
* Determines the specific plugin function to call for product search (e.g., `searchProducts` or `searchProductsForItinerary`).
|
|
10
|
+
* Calculates a `cacheKey` based on `userId`, `hint`, and a static `operationId` ('bookingsProductSearch').
|
|
11
|
+
* Defines two distinct lock keys derived from this `cacheKey`:
|
|
12
|
+
* `pluginExecutionLockKey` (resolves to `${cacheKey}:lock`): Used to serialize direct calls to the plugin.
|
|
13
|
+
* `jobQueueLockKey` (resolves to `${cacheKey}:jobLock`): Used to prevent multiple submissions of background refresh jobs.
|
|
14
|
+
* Fetches the current cache content (`initialActualCacheContent`) and its `lastUpdated` timestamp.
|
|
15
|
+
* Determines if the cache is stale (`isStaleByTTR`) based on the Time-To-Refresh (`ttr`) value from the token or plugin settings.
|
|
16
|
+
* Checks for a `doNotCallPluginForProducts` flag (from token or plugin settings) and whether a `pluginExecutionLockKey` is currently active.
|
|
17
|
+
|
|
18
|
+
2. **Request Handling Logic (Simplified Order):**
|
|
19
|
+
|
|
20
|
+
* **Condition 1: `doNotCallPluginForProducts` is true AND NOT `forceRefresh`**:
|
|
21
|
+
* If this flag is set and the request is not a forced refresh, the system serves data directly from the cache if available.
|
|
22
|
+
* If the cache is empty, it returns an empty product list.
|
|
23
|
+
* No plugin calls are made, and no background jobs are queued in this path.
|
|
24
|
+
|
|
25
|
+
* **Condition 2: `forceRefresh` is true**:
|
|
26
|
+
* The system attempts to fetch fresh data directly from the plugin.
|
|
27
|
+
* The `fetchFromPluginAndCache` helper function is invoked. This function:
|
|
28
|
+
* Sets the `pluginExecutionLockKey` before calling the plugin to prevent other concurrent direct calls.
|
|
29
|
+
* Calls the plugin's product search method.
|
|
30
|
+
* If the plugin returns valid products, these are saved to the cache (both `cacheKey` for data and `${cacheKey}:lastUpdated` for timestamp).
|
|
31
|
+
* Drops the `pluginExecutionLockKey` after completion.
|
|
32
|
+
* The (potentially filtered) results from the plugin are returned to the client.
|
|
33
|
+
|
|
34
|
+
* **Condition 3: Cache Exists AND NOT `forceRefresh`**:
|
|
35
|
+
* **Sub-condition 3a: Cache is fresh OR `pluginExecutionLockKey` is active**:
|
|
36
|
+
* If the cache is not stale according to its TTR, or if a direct plugin execution (like a `forceRefresh` or initial population) is already in progress (indicated by an active `pluginExecutionLockKey`), the system serves data from the existing cache.
|
|
37
|
+
* **Sub-condition 3b: Cache is stale AND no `pluginExecutionLockKey` is active**:
|
|
38
|
+
* The system checks for an active `jobQueueLockKey`.
|
|
39
|
+
* If `jobQueueLockKey` IS active: It implies that another request very recently detected the stale cache and has already queued a background refresh job. The current request serves the stale data from the cache without queueing another job.
|
|
40
|
+
* If `jobQueueLockKey` is NOT active: This request is the first (or among the first) to find the stale cache without an ongoing direct refresh or a recently queued job. It will:
|
|
41
|
+
1. Set the `jobQueueLockKey` with a short TTL (e.g., 60 seconds).
|
|
42
|
+
2. Queue a background job using `addJob`. This job will eventually call the plugin to refresh the data and then use `$updateProductSearchCache` to update the cache.
|
|
43
|
+
3. Serve the stale data from the cache to the current client.
|
|
44
|
+
|
|
45
|
+
* **Condition 4: No Cache Content AND NOT `forceRefresh` AND NOT `doNotCallPluginForProducts`**:
|
|
46
|
+
* If there's no existing cache content and none of the preceding conditions (like `forceRefresh` or `doNotCallPluginForProducts`) were met, the system needs to populate the cache.
|
|
47
|
+
* It calls `fetchFromPluginAndCache` (which sets `pluginExecutionLockKey`, calls the plugin, caches results, and drops the lock) to get initial data.
|
|
48
|
+
* The (potentially filtered) results are returned to the client.
|
|
49
|
+
|
|
50
|
+
## Locking Mechanisms Explained
|
|
51
|
+
|
|
52
|
+
The system uses two types of locks, both based on the primary `cacheKey`, to manage concurrency and prevent redundant operations:
|
|
53
|
+
|
|
54
|
+
1. **`pluginExecutionLockKey` (derived from `${cacheKey}:lock`)**:
|
|
55
|
+
* **Purpose**: To prevent multiple simultaneous *direct calls* to the external plugin for the same product search parameters. This is crucial during `forceRefresh` scenarios or when the cache is being populated for the first time by concurrent requests.
|
|
56
|
+
* **Behavior**:
|
|
57
|
+
* This lock is set by the `fetchFromPluginAndCache` helper function immediately before it makes an actual call to the plugin's `searchProducts` (or equivalent) method.
|
|
58
|
+
* It is configured with a TTL (e.g., 120 seconds) to ensure it automatically expires if the process holding the lock crashes or fails to release it.
|
|
59
|
+
* The lock is explicitly dropped by `fetchFromPluginAndCache` after the plugin call completes (whether successfully or with an error).
|
|
60
|
+
* Other parts of the main logic (e.g., in Condition 3a) check for the presence of this lock (`hasPluginExecutionLock`). If active, it signals that a direct plugin data fetch is already in progress, prompting the current request to, for example, serve stale data or wait, rather than initiating another direct plugin call.
|
|
61
|
+
|
|
62
|
+
2. **`jobQueueLockKey` (derived from `${cacheKey}:jobLock`)**:
|
|
63
|
+
* **Purpose**: To prevent the submission of multiple identical *background refresh jobs* by nearly simultaneous requests when the cache is found to be stale and no direct plugin execution (covered by `pluginExecutionLockKey`) is active.
|
|
64
|
+
* **Behavior**:
|
|
65
|
+
* This lock is checked specifically when the cache is determined to be effectively stale (`isEffectivelyStale`) and no `pluginExecutionLockKey` is currently active (Condition 3b).
|
|
66
|
+
* If the `jobQueueLockKey` is NOT found in the cache, the current request assumes responsibility for queuing the refresh job. It sets this lock with a short TTL (e.g., 60 seconds) *before* calling `addJob`.
|
|
67
|
+
* If the `jobQueueLockKey` IS found, it indicates that another request has very recently detected the stale state and has already taken action to queue the refresh job. The current request will then proceed to serve stale data without attempting to queue another job.
|
|
68
|
+
* This mechanism ensures that even if numerous requests detect stale data at virtually the same moment, only one of them will succeed in setting the `jobQueueLockKey` and thereby be responsible for queueing the single background refresh task.
|
|
69
|
+
|
|
70
|
+
These two locks work in tandem: `pluginExecutionLockKey` manages contention for direct, immediate plugin interactions, while `jobQueueLockKey` manages contention for initiating background refresh tasks when stale data is being served. This dual-lock strategy helps maintain system performance and avoids overwhelming external plugin services or the background job queue.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* globals beforeAll describe it expect */
|
|
2
2
|
|
|
3
3
|
const chance = require('chance').Chance();
|
|
4
|
-
const testUtils = require('../../test/utils');
|
|
5
4
|
const slugify = require('../../test/slugify');
|
|
6
5
|
|
|
7
6
|
const { env: { adminKey } } = process;
|
|
8
7
|
|
|
9
8
|
describe('admin', () => {
|
|
9
|
+
const testUtils = require('../../test/utils');
|
|
10
10
|
const appName = slugify(
|
|
11
11
|
chance.company(),
|
|
12
12
|
);
|
|
@@ -3,11 +3,10 @@ const axios = require('axios');
|
|
|
3
3
|
const MockAdapter = require('axios-mock-adapter');
|
|
4
4
|
const chance = require('chance').Chance();
|
|
5
5
|
|
|
6
|
-
const testUtils = require('../../test/utils');
|
|
7
|
-
|
|
8
6
|
const { env: { adminKey } } = process;
|
|
9
7
|
|
|
10
8
|
describe('allotment', () => {
|
|
9
|
+
const testUtils = require('../../test/utils');
|
|
11
10
|
const newApp = {
|
|
12
11
|
name: 'tourplantest',
|
|
13
12
|
packageName: 'ti2-tourplanTest',
|
|
@@ -3,13 +3,13 @@ const chance = require('chance').Chance();
|
|
|
3
3
|
const jwt = require('jwt-promise');
|
|
4
4
|
const R = require('ramda');
|
|
5
5
|
|
|
6
|
-
const testUtils = require('../../test/utils');
|
|
7
6
|
const slugify = require('../../test/slugify');
|
|
8
7
|
let appController = require('../app');
|
|
9
8
|
|
|
10
9
|
const { env: { adminKey, jwtSecret } } = process;
|
|
11
10
|
|
|
12
11
|
describe('app', () => {
|
|
12
|
+
const testUtils = require('../../test/utils');
|
|
13
13
|
const appName = slugify(
|
|
14
14
|
chance.company(),
|
|
15
15
|
);
|