Avoid scheduled tasks execution overlapping
MODIFY
src/async-task/AsyncTaskManager.ts
src/async-task/AsyncTaskManager.ts
@@ -138,7 +138,7 @@ export default class AsyncTaskManager {
}
if (abstractAsyncTask) {
// Get the lock
- const asyncTaskLock = await LockingHelper.acquireAsyncTaskLock(Constants.DEFAULT_TENANT, asyncTask);
+ const asyncTaskLock = await LockingHelper.acquireAsyncTaskLock(Constants.DEFAULT_TENANT, asyncTask.id);
if (asyncTaskLock) {
const startAsyncTaskTime = new Date().getTime();
try {

MODIFY
src/locking/LockingHelper.ts
src/locking/LockingHelper.ts
import Lock, { LockEntity } from '../types/Locking';
import Asset from '../types/Asset';
-import AsyncTask from '../types/AsyncTask';
import Constants from '../utils/Constants';
import LockingManager from './LockingManager';
import OCPIEndpoint from '../types/ocpi/OCPIEndpoint';
@@ -9,8 +8,16 @@ import OICPEndpoint from '../types/oicp/OICPEndpoint';
import SiteArea from '../types/SiteArea';
export default class LockingHelper {
- public static async acquireAsyncTaskLock(tenantID: string, asyncTask: AsyncTask): Promise<Lock | null> {
- const lock = LockingManager.createExclusiveLock(tenantID, LockEntity.ASYNC_TASK, `${asyncTask.id}`, 300);
+ public static async acquireAsyncTaskLock(tenantID: string, asyncTaskID: string): Promise<Lock | null> {
+ const lock = LockingManager.createExclusiveLock(tenantID, LockEntity.ASYNC_TASK, asyncTaskID, 5 * 60);
+ if (!(await LockingManager.acquire(lock))) {
+ return null;
+ }
+ return lock;
+ }
+
+ public static async acquireScheduledTaskLock(tenantID: string, scheduledTaskName: string): Promise<Lock | null> {
+ const lock = LockingManager.createExclusiveLock(tenantID, LockEntity.SCHEDULER_TASK, scheduledTaskName, 24 * 60 * 60);
if (!(await LockingManager.acquire(lock))) {
return null;
}

MODIFY
src/scheduler/SchedulerTask.ts
src/scheduler/SchedulerTask.ts
import Constants from '../utils/Constants';
+import LockingHelper from '../locking/LockingHelper';
+import LockingManager from '../locking/LockingManager';
import Logging from '../utils/Logging';
import { ServerAction } from '../types/Server';
import { TaskConfig } from '../types/TaskConfig';
@@ -13,6 +15,10 @@ export default abstract class SchedulerTask {
public async run(name: string, config: TaskConfig): Promise<void> {
this.name = name;
+ // Get the lock
+ const scheduledTaskLock = await LockingHelper.acquireScheduledTaskLock(Constants.DEFAULT_TENANT, name);
+ if (scheduledTaskLock) {
+ try {
const startMigrationTime = moment();
await Logging.logInfo({
tenantID: Constants.DEFAULT_TENANT,
@@ -20,6 +26,8 @@ export default abstract class SchedulerTask {
module: MODULE_NAME, method: 'run',
message: `The task '${name}' is running...`
});
+ // Hook
+ await this.beforeTaskRun(config);
// Get the Tenants
const tenants = await TenantStorage.getTenants({}, Constants.DB_PARAMS_MAX_LIMIT);
// Process them
@@ -32,6 +40,8 @@ export default abstract class SchedulerTask {
module: MODULE_NAME, method: 'run',
message: `The task '${name}' is running...`
});
+ // Hook
+ await this.beforeProcessTenant(tenant, config);
// Process
await this.processTenant(tenant, config);
// Log Total Processing Time in Tenant
@@ -50,8 +60,13 @@ export default abstract class SchedulerTask {
message: `Error while running the task '${name}': ${error.message}`,
detailedMessages: { error: error.stack }
});
+ } finally {
+ // Hook
+ await this.afterProcessTenant(tenant, config);
}
}
+ // Hook
+ await this.afterTaskRun(config);
// Log Total Processing Time
const totalMigrationTimeSecs = moment.duration(moment().diff(startMigrationTime)).asSeconds();
await Logging.logInfo({
@@ -60,9 +75,25 @@ export default abstract class SchedulerTask {
module: MODULE_NAME, method: 'run',
message: `The task '${name}' has been run in ${totalMigrationTimeSecs} secs over ${tenants.count} tenant(s)`
});
+ } finally {
+ // Release lock
+ await LockingManager.release(scheduledTaskLock);
+ }
+ }
+ }
+
+ public async beforeTaskRun(config: TaskConfig): Promise<void> {
+ }
+
+ public async beforeProcessTenant(tenant: Tenant, config: TaskConfig): Promise<void> {
}
public async processTenant(tenant: Tenant, config: TaskConfig): Promise<void> {
- // Empty
+ }
+
+ public async afterProcessTenant(tenant: Tenant, config: TaskConfig): Promise<void> {
+ }
+
+ public async afterTaskRun(config: TaskConfig): Promise<void> {
}
}

MODIFY
src/scheduler/tasks/CheckChargingStationTemplateTask.ts
src/scheduler/tasks/CheckChargingStationTemplateTask.ts
@@ -6,20 +6,12 @@ import Logging from '../../utils/Logging';
import OCPPUtils from '../../server/ocpp/utils/OCPPUtils';
import SchedulerTask from '../SchedulerTask';
import { ServerAction } from '../../types/Server';
-import { TaskConfig } from '../../types/TaskConfig';
import Tenant from '../../types/Tenant';
import Utils from '../../utils/Utils';
const MODULE_NAME = 'CheckChargingStationTemplateTask';
export default class CheckChargingStationTemplateTask extends SchedulerTask {
- public async run(name: string, config: TaskConfig): Promise<void> {
- // Update Template
- this.updateChargingStationTemplates();
- // Call default implementation
- await super.run(name, config);
- }
-
public async processTenant(tenant: Tenant): Promise<void> {
// Get the lock
const checkChargingStationTemplateLock = LockingManager.createExclusiveLock(tenant.id, LockEntity.CHARGING_STATION, 'check-charging-station-template');
@@ -83,13 +75,5 @@ export default class CheckChargingStationTemplateTask extends SchedulerTask {
});
}
}
-
- private updateChargingStationTemplates() {
- // Update current Chargers
- Utils.updateChargingStationTemplatesFromFile().catch(
- (error) => {
- void Logging.logActionExceptionMessage(Constants.DEFAULT_TENANT, ServerAction.UPDATE_CHARGING_STATION_TEMPLATES, error);
- });
- }
}

MODIFY
src/scheduler/tasks/LoggingDatabaseTableCleanupTask.ts
src/scheduler/tasks/LoggingDatabaseTableCleanupTask.ts
@@ -15,13 +15,12 @@ import moment from 'moment';
const MODULE_NAME = 'LoggingDatabaseTableCleanupTask';
export default class LoggingDatabaseTableCleanupTask extends SchedulerTask {
- public async run(name: string, config: TaskConfig): Promise<void> {
+
+ public async beforeTaskRun(config: TaskConfig): Promise<void> {
// Delete Default Tenant Logs
await this.deleteLogs(Constants.DEFAULT_TENANT_OBJECT, config);
// Delete Default Tenant Perfs Records
await this.deletePerformanceRecords(Constants.DEFAULT_TENANT_OBJECT, config);
- // Call for all Tenants
- await super.run(name, config);
}
async processTenant(tenant: Tenant, config: LoggingDatabaseTableCleanupTaskConfig): Promise<void> {
@@ -116,4 +115,3 @@ export default class LoggingDatabaseTableCleanupTask extends SchedulerTask {
}
}
}
-

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
@@ -231,6 +231,9 @@ export default class AuthorizationService {
public static async addUsersAuthorizations(tenant: Tenant, userToken: UserToken, users: UserDataResult, authorizationFilter: AuthorizationFilter): Promise<void> {
users.canCreate = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.USER, Action.CREATE, authorizationFilter);
+ users.canExport = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.USERS, Action.EXPORT, authorizationFilter);
+ users.canImport = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.USERS, Action.IMPORT, authorizationFilter);
+ users.canSynchronizeBilling = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.USERS, Action.SYNCHRONIZE_BILLING_USERS, authorizationFilter);
// Enrich
for (const user of users.result) {
await AuthorizationService.addUserAuthorizations(tenant, userToken, user, authorizationFilter);

MODIFY
src/types/DataResult.ts
src/types/DataResult.ts
@@ -13,6 +13,7 @@ export interface DeletedResult {
acknowledged?: boolean;
deletedCount?: number;
}
+
export interface DataResult<T> {
count: number;
result: T[];
@@ -34,16 +35,21 @@ export type LogDataResult = DataResult<Log>;
export interface SiteAreaDataResult extends DataResult<SiteArea> {
canCreate: boolean;
}
+
export interface CarDataResult extends DataResult<Car> {
canCreate: boolean;
}
-export interface UserDataResult extends DataResult<User> {
- canCreate: boolean;
-}
export interface CarCatalogDataResult extends DataResult<CarCatalog> {
canSync: boolean;
}
+
+export interface UserDataResult extends DataResult<User> {
+ canCreate: boolean;
+ canExport: boolean;
+ canImport: boolean;
+ canSynchronizeBilling: boolean;
+}
export interface TagDataResult extends DataResult<Tag> {
canCreate: boolean;
canDelete: boolean;

MODIFY
src/types/Locking.ts
src/types/Locking.ts
@@ -31,4 +31,5 @@ export enum LockEntity {
TAG = 'tag',
OICP_ENDPOINT = 'oicp-endpoint',
ASYNC_TASK = 'async-task',
+ SCHEDULER_TASK = 'scheduler-task',
}

Avoid scheduled tasks execution overlapping