Fix OCPP message sending timeout handling code
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 4 Oct 2021 20:23:40 +0000 (22:23 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 4 Oct 2021 20:23:40 +0000 (22:23 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/utils/Constants.ts
src/utils/Utils.ts

index dde845621781534c7d48ed366fd75b06ec9d1490..6e57931b7f17e1469c0f1be2ab457cbf21935b61 100644 (file)
@@ -703,7 +703,7 @@ export default class ChargingStation {
       }
     } catch (error) {
       // Log
-      logger.error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data, this.requests.get(messageId), error);
+      logger.error('%s Incoming OCPP message %j matching cached request %j processing error %j', this.logPrefix(), data.toString(), this.requests.get(messageId), error);
       // Send error
       messageType === MessageType.CALL_MESSAGE && await this.ocppRequestService.sendError(messageId, error, commandName);
     }
index 4996b9e7aa89beb9f075cc7063898b0045757be5..5d767f0cb0c6726e432d7fc7faf28a733a0b6293 100644 (file)
@@ -12,6 +12,7 @@ import { MeterValue } from '../../types/ocpp/MeterValues';
 import OCPPError from './OCPPError';
 import OCPPResponseService from './OCPPResponseService';
 import PerformanceStatistics from '../../performance/PerformanceStatistics';
+import Utils from '../../utils/Utils';
 import logger from '../../utils/Logger';
 
 export default abstract class OCPPRequestService {
@@ -28,7 +29,7 @@ export default abstract class OCPPRequestService {
     // eslint-disable-next-line @typescript-eslint/no-this-alias
     const self = this;
     // Send a message through wsConnection
-    return new Promise((resolve, reject) => {
+    return Utils.promiseWithTimeout(new Promise((resolve, reject) => {
       const messageToSend = this.buildMessageToSend(messageId, commandParams, messageType, commandName, responseCallback, rejectCallback);
       if (this.chargingStation.getEnableStatistics()) {
         this.chargingStation.performanceStatistics.addRequestStatistic(commandName, messageType);
@@ -58,8 +59,6 @@ export default abstract class OCPPRequestService {
         // Yes: send Ok
         return resolve(commandParams);
       }
-      // Send timeout
-      setTimeout(() => rejectCallback(new OCPPError(ErrorType.GENERIC_ERROR, `Timeout for message id '${messageId}' with content '${messageToSend}'`, commandParams?.details ?? {}), false), Constants.OCPP_SOCKET_TIMEOUT);
 
       /**
        * Function that will receive the request's response
@@ -91,6 +90,8 @@ export default abstract class OCPPRequestService {
         self.chargingStation.requests.delete(messageId);
         reject(error);
       }
+    }), Constants.OCPP_WEBSOCKET_TIMEOUT, new OCPPError(ErrorType.GENERIC_ERROR, `Timeout for message id '${messageId}'`, commandParams?.details ?? {}), () => {
+      messageType === MessageType.CALL_MESSAGE && this.chargingStation.requests.delete(messageId);
     });
   }
 
index f77059632d72f6baba33d791e7b9abc1498e61e4..c10a320bc8d71ed23775e94be0c88b936e1b610e 100644 (file)
@@ -26,7 +26,7 @@ export default class Constants {
   static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED = Object.freeze({ status: TriggerMessageStatus.NOT_IMPLEMENTED });
 
   static readonly OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL = 60000; // Ms
-  static readonly OCPP_SOCKET_TIMEOUT = 60000; // Ms
+  static readonly OCPP_WEBSOCKET_TIMEOUT = 60000; // Ms
   static readonly OCPP_TRIGGER_MESSAGE_DELAY = 2000; // Ms
 
   static readonly CHARGING_STATION_DEFAULT_RESET_TIME = 60000; // Ms
index b04814a7a6437dd6a369b81b5998458f652e3465..6133177f9aeca78c7970b2a123370d9d187f24c9 100644 (file)
@@ -235,6 +235,24 @@ export default class Utils {
     return Configuration.getWorkerProcess() === WorkerProcessType.DYNAMIC_POOL;
   }
 
+  public static async promiseWithTimeout<T>(
+      promise: Promise<T>,
+      timeoutMs: number,
+      timeoutError: Error,
+      timeoutCallback: () => void = () => { /* This is intentional */ }
+  ): Promise<T> {
+    // Create a timeout promise that rejects in timeout milliseconds
+    const timeoutPromise = new Promise<never>((_, reject) => {
+      setTimeout(() => {
+        timeoutCallback;
+        reject(timeoutError);
+      }, timeoutMs);
+    });
+
+    // Returns a race between timeout promise and the passed promise
+    return Promise.race<T>([promise, timeoutPromise]);
+  }
+
   /**
    * Generate a cryptographically secure random number in the [0,1[ range
    *