@cap-js-community/event-queue 1.2.4 → 1.2.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.
package/README.md CHANGED
@@ -28,7 +28,6 @@ For detailed information check out the [documentation](https://cap-js-community.
28
28
  {
29
29
  "cds": {
30
30
  "eventQueue": {
31
- "plugin": true,
32
31
  "configFilePath": "./srv/eventQueueConfig.yml"
33
32
  }
34
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js-community/event-queue",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "An event queue that enables secure transactional processing of asynchronous and periodic events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -49,15 +49,15 @@
49
49
  "devDependencies": {
50
50
  "@sap/cds": "^7.5.3",
51
51
  "@sap/cds-dk": "^7.5.1",
52
- "eslint": "8.56.0",
53
- "eslint-config-prettier": "9.1.0",
54
- "eslint-plugin-jest": "27.6.3",
55
- "eslint-plugin-node": "11.1.0",
56
- "express": "4.18.2",
57
- "hdb": "0.19.7",
58
- "jest": "29.7.0",
59
- "prettier": "2.8.8",
60
- "sqlite3": "5.1.7"
52
+ "eslint": "^8.56.0",
53
+ "eslint-config-prettier": "^9.1.0",
54
+ "eslint-plugin-jest": "^27.6.3",
55
+ "eslint-plugin-node": "^11.1.0",
56
+ "express": "^4.18.2",
57
+ "hdb": "^0.19.7",
58
+ "jest": "^29.7.0",
59
+ "prettier": "^2.8.8",
60
+ "sqlite3": "^5.1.7"
61
61
  },
62
62
  "homepage": "https://cap-js-community.github.io/event-queue/",
63
63
  "repository": {
@@ -18,6 +18,7 @@ const ERROR_CODES = {
18
18
  NO_MANUEL_INSERT_OF_PERIODIC: "NO_MANUEL_INSERT_OF_PERIODIC",
19
19
  LOAD_HIGHER_THAN_LIMIT: "LOAD_HIGHER_THAN_LIMIT",
20
20
  SCHEMA_TENANT_MISMATCH: "SCHEMA_TENANT_MISMATCH",
21
+ GLOBAL_CDS_CONTEXT_MISSMATCH: "GLOBAL_CDS_CONTEXT_MISSMATCH",
21
22
  };
22
23
 
23
24
  const ERROR_CODES_META = {
@@ -67,6 +68,9 @@ const ERROR_CODES_META = {
67
68
  [ERROR_CODES.SCHEMA_TENANT_MISMATCH]: {
68
69
  message: "The db client associated to the tenant context does not match! Processing will be skipped.",
69
70
  },
71
+ [ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH]: {
72
+ message: "The global cds context does not match the local cds context.",
73
+ },
70
74
  };
71
75
 
72
76
  class EventQueueError extends VError {
@@ -235,6 +239,17 @@ class EventQueueError extends VError {
235
239
  message
236
240
  );
237
241
  }
242
+
243
+ static globalCdsContextNotMatchingLocal(globalProperties, localProperties) {
244
+ const { message } = ERROR_CODES_META[ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH];
245
+ return new EventQueueError(
246
+ {
247
+ name: ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH,
248
+ info: { globalProperties, localProperties },
249
+ },
250
+ message
251
+ );
252
+ }
238
253
  }
239
254
 
240
255
  module.exports = EventQueueError;
@@ -70,6 +70,8 @@ class EventQueueProcessorBase {
70
70
  this.__txMap = {};
71
71
  this.__txRollback = {};
72
72
  this.__queueEntries = [];
73
+
74
+ this.#checkGlobalContextToLocalContext();
73
75
  }
74
76
 
75
77
  /**
@@ -533,7 +535,9 @@ class EventQueueProcessorBase {
533
535
  async getQueueEntriesAndSetToInProgress() {
534
536
  let result = [];
535
537
  const refDateStartAfter = new Date(Date.now() + this.#config.runInterval * 1.2);
538
+ this.#checkGlobalContextToLocalContext();
536
539
  await executeInNewTransaction(this.__baseContext, "eventQueue-getQueueEntriesAndSetToInProgress", async (tx) => {
540
+ this.#checkGlobalContextToLocalContext();
537
541
  await this.checkTxConsistency(tx);
538
542
  const entries = await tx.run(
539
543
  SELECT.from(this.#config.tableNameEventQueue)
@@ -692,7 +696,7 @@ class EventQueueProcessorBase {
692
696
  } catch (err) {
693
697
  errorHandler(err);
694
698
  }
695
- if (txSchema !== serviceManagerSchema) {
699
+ if (serviceManagerSchema && txSchema !== serviceManagerSchema) {
696
700
  const err = EventQueueError.dbClientSchemaMismatch(tx.context.tenant, txSchema, serviceManagerSchema);
697
701
  errorHandler(err);
698
702
  throw err;
@@ -724,6 +728,48 @@ class EventQueueProcessorBase {
724
728
  return entry.lastAttemptsTs;
725
729
  }
726
730
 
731
+ #checkGlobalContextToLocalContext() {
732
+ if (!this.#config.enableTxConsistencyCheck) {
733
+ return;
734
+ }
735
+ if (this.__context.tenant !== cds.context.tenant) {
736
+ throw EventQueueError.globalCdsContextNotMatchingLocal(
737
+ JSON.stringify(
738
+ {
739
+ correlationId: cds.context.id,
740
+ tenantId: cds.context.tenant,
741
+ timestamp: cds.context.timestamp,
742
+ base: JSON.stringify(
743
+ {
744
+ correlationId: cds.context.context?.id,
745
+ tenantId: cds.context.context?.tenant,
746
+ timestamp: cds.context.context?.timestamp,
747
+ },
748
+ null,
749
+ 2
750
+ ),
751
+ },
752
+ null,
753
+ 2
754
+ ),
755
+ JSON.stringify(
756
+ {
757
+ correlationId: this.__context.id,
758
+ tenantId: this.__context.tenant,
759
+ timestamp: this.__context.timestamp,
760
+ base: JSON.stringify({
761
+ correlationId: this.__context.context?.id,
762
+ tenantId: this.__context.context?.tenant,
763
+ timestamp: this.__context.context?.timestamp,
764
+ }),
765
+ },
766
+ null,
767
+ 2
768
+ )
769
+ );
770
+ }
771
+ }
772
+
727
773
  #handleDelayedEvents(delayedEvents) {
728
774
  for (const delayedEvent of delayedEvents) {
729
775
  this.#eventSchedulerInstance.scheduleEvent(
package/src/config.js CHANGED
@@ -57,6 +57,7 @@ class Config {
57
57
  #useAsCAPOutbox;
58
58
  #userId;
59
59
  #enableTxConsistencyCheck;
60
+ #cleanupLocksAndEventsForDev;
60
61
  static #instance;
61
62
  constructor() {
62
63
  this.#logger = cds.log(COMPONENT_NAME);
@@ -340,6 +341,9 @@ class Config {
340
341
  }
341
342
 
342
343
  set runInterval(value) {
344
+ if (!Number.isInteger(value) || value <= 10 * 1000) {
345
+ throw EventQueueError.invalidInterval();
346
+ }
343
347
  this.#runInterval = value;
344
348
  }
345
349
 
@@ -471,6 +475,14 @@ class Config {
471
475
  return this.#enableTxConsistencyCheck;
472
476
  }
473
477
 
478
+ set cleanupLocksAndEventsForDev(value) {
479
+ this.#cleanupLocksAndEventsForDev = value;
480
+ }
481
+
482
+ get cleanupLocksAndEventsForDev() {
483
+ return this.#cleanupLocksAndEventsForDev;
484
+ }
485
+
474
486
  get isMultiTenancy() {
475
487
  return !!cds.requires.multitenancy;
476
488
  }
package/src/initialize.js CHANGED
@@ -14,6 +14,8 @@ const config = require("./config");
14
14
  const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
15
15
  const { closeMainClient } = require("./shared/redis");
16
16
  const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
17
+ const { getAllTenantIds } = require("./shared/cdsHelper");
18
+ const { EventProcessingStatus } = require("./constants");
17
19
 
18
20
  const readFileAsync = promisify(fs.readFile);
19
21
 
@@ -38,6 +40,7 @@ const CONFIG_VARS = [
38
40
  ["useAsCAPOutbox", false],
39
41
  ["userId", null],
40
42
  ["enableTxConsistencyCheck", false],
43
+ ["registerCleanupForDev", true],
41
44
  ];
42
45
 
43
46
  const initialize = async ({
@@ -55,11 +58,8 @@ const initialize = async ({
55
58
  useAsCAPOutbox,
56
59
  userId,
57
60
  enableTxConsistencyCheck,
61
+ cleanupLocksAndEventsForDev,
58
62
  } = {}) => {
59
- // TODO: initialize check:
60
- // - content of yaml check
61
- // - betweenRuns
62
-
63
63
  if (config.initialized) {
64
64
  return;
65
65
  }
@@ -79,7 +79,8 @@ const initialize = async ({
79
79
  thresholdLoggingEventProcessing,
80
80
  useAsCAPOutbox,
81
81
  userId,
82
- enableTxConsistencyCheck
82
+ enableTxConsistencyCheck,
83
+ cleanupLocksAndEventsForDev
83
84
  );
84
85
 
85
86
  const logger = cds.log(COMPONENT);
@@ -90,6 +91,7 @@ const initialize = async ({
90
91
  cds.on("connect", (service) => {
91
92
  if (service.name === "db") {
92
93
  dbHandler.registerEventQueueDbHandler(service);
94
+ config.cleanupLocksAndEventsForDev && registerCleanupForDevDb().catch(() => {});
93
95
  }
94
96
  });
95
97
  }
@@ -108,20 +110,28 @@ const initialize = async ({
108
110
  };
109
111
 
110
112
  const readConfigFromFile = async (configFilepath) => {
111
- const fileData = await readFileAsync(configFilepath);
112
- if (/\.ya?ml$/i.test(configFilepath)) {
113
- return yaml.parse(fileData.toString());
114
- }
115
- if (/\.json$/i.test(configFilepath)) {
116
- return JSON.parse(fileData.toString());
113
+ try {
114
+ const fileData = await readFileAsync(configFilepath);
115
+ if (/\.ya?ml$/i.test(configFilepath)) {
116
+ return yaml.parse(fileData.toString());
117
+ }
118
+ if (/\.json$/i.test(configFilepath)) {
119
+ return JSON.parse(fileData.toString());
120
+ }
121
+
122
+ throw new VError(
123
+ {
124
+ name: VERROR_CLUSTER_NAME,
125
+ info: { configFilepath },
126
+ },
127
+ "configFilepath with unsupported extension, allowed extensions are .yaml and .json"
128
+ );
129
+ } catch (err) {
130
+ if (config.useAsCAPOutbox) {
131
+ return {};
132
+ }
133
+ throw err;
117
134
  }
118
- throw new VError(
119
- {
120
- name: VERROR_CLUSTER_NAME,
121
- info: { configFilepath },
122
- },
123
- "configFilepath with unsupported extension, allowed extensions are .yaml and .json"
124
- );
125
135
  };
126
136
 
127
137
  const registerEventProcessors = () => {
@@ -149,9 +159,11 @@ const monkeyPatchCAPOutbox = () => {
149
159
  if (config.useAsCAPOutbox) {
150
160
  Object.defineProperty(cds, "outboxed", {
151
161
  get: () => eventQueueAsOutbox.outboxed,
162
+ configurable: true,
152
163
  });
153
164
  Object.defineProperty(cds, "unboxed", {
154
165
  get: () => eventQueueAsOutbox.unboxed,
166
+ configurable: true,
155
167
  });
156
168
  }
157
169
  };
@@ -213,6 +225,25 @@ const registerCdsShutdown = () => {
213
225
  });
214
226
  };
215
227
 
228
+ const registerCleanupForDevDb = async () => {
229
+ const profile = cds.env.profiles.find((profile) => profile === "development");
230
+ if (!profile) {
231
+ return;
232
+ }
233
+
234
+ const tenantIds = await getAllTenantIds();
235
+ for (const tenantId of tenantIds) {
236
+ await cds.tx({ tenant: tenantId }, async (tx) => {
237
+ await tx.run(DELETE.from(config.tableNameEventLock));
238
+ await tx.run(
239
+ UPDATE.entity(config.tableNameEventQueue).where({ status: EventProcessingStatus.InProgress }).set({
240
+ status: EventProcessingStatus.Error,
241
+ })
242
+ );
243
+ });
244
+ }
245
+ };
246
+
216
247
  module.exports = {
217
248
  initialize,
218
249
  };
package/src/runner.js CHANGED
@@ -26,9 +26,9 @@ let singleRunDone;
26
26
 
27
27
  const singleTenant = () => _scheduleFunction(_checkPeriodicEventsSingleTenant, _singleTenantDb);
28
28
 
29
- const multiTenancyDb = () => _scheduleFunction(_multiTenancyPeriodicEvents, _multiTenancyDb);
29
+ const multiTenancyDb = () => _scheduleFunction(async () => {}, _multiTenancyDb);
30
30
 
31
- const multiTenancyRedis = () => _scheduleFunction(_multiTenancyPeriodicEvents, _multiTenancyRedis);
31
+ const multiTenancyRedis = () => _scheduleFunction(async () => {}, _multiTenancyRedis);
32
32
 
33
33
  const _scheduleFunction = async (singleRunFn, periodicFn) => {
34
34
  const logger = cds.log(COMPONENT_NAME);
@@ -69,7 +69,7 @@ const _multiTenancyRedis = async () => {
69
69
  const emptyContext = new cds.EventContext({});
70
70
  logger.info("executing event queue run for multi instance and tenant");
71
71
  const tenantIds = await cdsHelper.getAllTenantIds();
72
- _checkAndTriggerPeriodicEventUpdate(tenantIds);
72
+ await _checkPeriodicEventUpdate(tenantIds);
73
73
 
74
74
  const runId = await _acquireRunId(emptyContext);
75
75
 
@@ -78,22 +78,21 @@ const _multiTenancyRedis = async () => {
78
78
  return;
79
79
  }
80
80
 
81
- return _executeEventsAllTenants(tenantIds, runId);
81
+ return await _executeEventsAllTenants(tenantIds, runId);
82
82
  };
83
83
 
84
- const _checkAndTriggerPeriodicEventUpdate = (tenantIds) => {
84
+ const _checkPeriodicEventUpdate = async (tenantIds) => {
85
85
  const hash = hashStringTo32Bit(JSON.stringify(tenantIds));
86
86
  if (!tenantIdHash) {
87
87
  tenantIdHash = hash;
88
- _multiTenancyPeriodicEvents().catch((err) => {
88
+ return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
89
89
  cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events!", err);
90
90
  });
91
- return;
92
91
  }
93
92
  if (tenantIdHash && tenantIdHash !== hash) {
94
93
  tenantIdHash = hash;
95
94
  cds.log(COMPONENT_NAME).info("tenant id hash changed, triggering updating periodic events!");
96
- _multiTenancyPeriodicEvents().catch((err) => {
95
+ return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
97
96
  cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events!", err);
98
97
  });
99
98
  }
@@ -108,91 +107,103 @@ const _executeEventsAllTenants = (tenantIds, runId) => {
108
107
  return result;
109
108
  }, []);
110
109
 
111
- return product.map(async ([tenantId, event]) => {
112
- const subdomain = await getSubdomainForTenantId(tenantId);
113
- const user = new cds.User.Privileged(config.userId);
114
- const tenantContext = {
115
- tenant: tenantId,
116
- user,
117
- // NOTE: we need this because of logging otherwise logs would not contain the subdomain
118
- http: { req: { authInfo: { getSubdomain: () => subdomain } } },
119
- };
120
- return await cds.tx(tenantContext, async ({ context }) => {
110
+ return Promise.allSettled(
111
+ product.map(async ([tenantId, event]) => {
112
+ const subdomain = await getSubdomainForTenantId(tenantId);
113
+ const user = new cds.User.Privileged(config.userId);
114
+ const tenantContext = {
115
+ tenant: tenantId,
116
+ user,
117
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
118
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
119
+ };
121
120
  const label = `${event.type}_${event.subType}`;
122
121
  return await WorkerQueue.instance.addToQueue(event.load, label, async () => {
122
+ return await cds.tx(tenantContext, async ({ context }) => {
123
+ try {
124
+ const lockId = `${runId}_${label}`;
125
+ const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
126
+ expiryTime: eventQueueConfig.runInterval * 0.95,
127
+ });
128
+ if (!couldAcquireLock) {
129
+ return;
130
+ }
131
+ await runEventCombinationForTenant(context, event.type, event.subType, true);
132
+ } catch (err) {
133
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
134
+ tenantId,
135
+ });
136
+ }
137
+ });
138
+ });
139
+ })
140
+ );
141
+ };
142
+
143
+ const _executePeriodicEventsAllTenants = async (tenantIds, runId) => {
144
+ return await Promise.allSettled(
145
+ tenantIds.map(async (tenantId) => {
146
+ const label = `UPDATE_PERIODIC_EVENTS_${tenantId}`;
147
+ return await WorkerQueue.instance.addToQueue(1, label, async () => {
123
148
  try {
124
- const lockId = `${runId}_${label}`;
125
- const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
126
- expiryTime: eventQueueConfig.runInterval * 0.95,
149
+ const subdomain = await getSubdomainForTenantId(tenantId);
150
+ const user = new cds.User.Privileged(config.userId);
151
+ const tenantContext = {
152
+ tenant: tenantId,
153
+ user,
154
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
155
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
156
+ };
157
+
158
+ return await cds.tx(tenantContext, async ({ context }) => {
159
+ const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
160
+ expiryTime: eventQueueConfig.runInterval * 0.95,
161
+ });
162
+ if (!couldAcquireLock) {
163
+ return;
164
+ }
165
+ await _checkPeriodicEventsSingleTenant(context);
127
166
  });
128
- if (!couldAcquireLock) {
129
- return;
130
- }
131
- await runEventCombinationForTenant(context, event.type, event.subType, true);
132
167
  } catch (err) {
133
168
  cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
134
169
  tenantId,
135
170
  });
136
171
  }
137
172
  });
138
- });
139
- });
173
+ })
174
+ );
140
175
  };
141
176
 
142
- const _executePeriodicEventsAllTenants = (tenantIds, runId) => {
143
- tenantIds.forEach((tenantId) => {
144
- const label = `UPDATE_PERIODIC_EVENTS_${tenantId}`;
145
- WorkerQueue.instance.addToQueue(1, label, async () => {
146
- try {
147
- const subdomain = await getSubdomainForTenantId(tenantId);
148
- const user = new cds.User.Privileged(config.userId);
149
- const tenantContext = {
150
- tenant: tenantId,
151
- user,
152
- // NOTE: we need this because of logging otherwise logs would not contain the subdomain
153
- http: { req: { authInfo: { getSubdomain: () => subdomain } } },
154
- };
155
-
177
+ const _singleTenantDb = async (tenantId) => {
178
+ return Promise.allSettled(
179
+ eventQueueConfig.allEvents.map(async (event) => {
180
+ const label = `${event.type}_${event.subType}`;
181
+ const user = new cds.User.Privileged(config.userId);
182
+ const tenantContext = {
183
+ tenant: tenantId,
184
+ user,
185
+ };
186
+ return await WorkerQueue.instance.addToQueue(event.load, label, async () => {
156
187
  return await cds.tx(tenantContext, async ({ context }) => {
157
- const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
158
- expiryTime: eventQueueConfig.runInterval * 0.95,
159
- });
160
- if (!couldAcquireLock) {
161
- return;
188
+ try {
189
+ const lockId = `${EVENT_QUEUE_RUN_ID}_${label}`;
190
+ const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
191
+ expiryTime: eventQueueConfig.runInterval * 0.95,
192
+ });
193
+ if (!couldAcquireLock) {
194
+ return;
195
+ }
196
+ await runEventCombinationForTenant(context, event.type, event.subType, true);
197
+ } catch (err) {
198
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
199
+ tenantId,
200
+ redisEnabled: eventQueueConfig.redisEnabled,
201
+ });
162
202
  }
163
- await _checkPeriodicEventsSingleTenant(context);
164
203
  });
165
- } catch (err) {
166
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
167
- tenantId,
168
- });
169
- }
170
- });
171
- });
172
- };
173
-
174
- const _singleTenantDb = async (tenantId) => {
175
- return eventQueueConfig.allEvents.map((event) => {
176
- const label = `${event.type}_${event.subType}`;
177
- return WorkerQueue.instance.addToQueue(event.load, label, async () => {
178
- try {
179
- const context = new cds.EventContext({ tenant: tenantId });
180
- const lockId = `${EVENT_QUEUE_RUN_ID}_${label}`;
181
- const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
182
- expiryTime: eventQueueConfig.runInterval * 0.95,
183
- });
184
- if (!couldAcquireLock) {
185
- return;
186
- }
187
- await runEventCombinationForTenant(context, event.type, event.subType, true);
188
- } catch (err) {
189
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
190
- tenantId,
191
- redisEnabled: eventQueueConfig.redisEnabled,
192
- });
193
- }
194
- });
195
- });
204
+ });
205
+ })
206
+ );
196
207
  };
197
208
 
198
209
  const _acquireRunId = async (context) => {
@@ -280,19 +291,19 @@ const _multiTenancyDb = async () => {
280
291
  try {
281
292
  logger.info("executing event queue run for single instance and multi tenant");
282
293
  const tenantIds = await cdsHelper.getAllTenantIds();
283
- _checkAndTriggerPeriodicEventUpdate(tenantIds);
284
- return _executeEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
294
+ await _checkPeriodicEventUpdate(tenantIds);
295
+ return await _executeEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
285
296
  } catch (err) {
286
297
  logger.error("Couldn't fetch tenant ids for event queue processing! Next try after defined interval.", err);
287
298
  }
288
299
  };
289
300
 
290
- const _multiTenancyPeriodicEvents = async () => {
301
+ const _multiTenancyPeriodicEvents = async (tenantIds) => {
291
302
  const logger = cds.log(COMPONENT_NAME);
292
303
  try {
293
304
  logger.info("executing event queue update periodic events");
294
- const tenantIds = await cdsHelper.getAllTenantIds();
295
- _executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
305
+ tenantIds = tenantIds ?? (await cdsHelper.getAllTenantIds());
306
+ return await _executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
296
307
  } catch (err) {
297
308
  logger.error("Couldn't fetch tenant ids for updating periodic event processing!", err);
298
309
  }