All files / src/services ConnectionService.ts

17.5% Statements 7/40
0% Branches 0/7
0% Functions 0/11
17.94% Lines 7/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124    1x   1x 1x 1x 1x 1x                   1x                                                                                                                                                                                                                  
// SPDX-License-Identifier: Apache-2.0
 
import net from 'net';
import { IService } from './IService';
import { LoggerService } from './LoggerService';
import { ServiceLocator } from './ServiceLocator';
import { CLIService } from './CLIService';
import { Errors } from '../Errors/LocalNodeErrors';
import debounce from '../utils/debounce';
 
/**
 * ConnectionService is a service class that handles network connections.
 * It implements the IService interface.
 * It uses the 'net' module to create connections and check their status.
 * 
 * @class
 * @public
 */
export class ConnectionService implements IService{
    /**
     * The logger service used for logging.
     * @private
     */
    private logger: LoggerService;
 
    /**
     * The name of the service.
     * @private
     */
    private serviceName: string;
 
    /**
     * The CLI service used for command line interface operations.
     * @private
     */
    private cliService: CLIService;
 
    // Debounced function to print error message at most once every N seconds
    private readonly debouncedErrorLog;
 
    /**
     * Constructs a new instance of the ConnectionService.
     * Initializes the logger and CLI service, and logs the initialization of the connection service.
     */
    constructor() {
        this.serviceName = ConnectionService.name;
        this.logger = ServiceLocator.Current.get<LoggerService>(LoggerService.name);
        this.cliService = ServiceLocator.Current.get<CLIService>(CLIService.name);
        this.logger.trace('Connection Service Initialized!', this.serviceName);
 
        this.debouncedErrorLog = debounce((message: string) => {
            this.logger.info(message, this.serviceName);
        }, 5000);
 
    }
 
    /**
     * Waits for the local node to fire up by continuously trying to establish a connection.
     * If the connection is not ready after 100 retries, it throws a CONNECTION_ERROR.
     * 
     * @param {number} port - The port to connect to.
     * @throws CONNECTION_ERROR if the port is not ready after a certain number of retries.
     * @returns {Promise<void>} A promise that resolves when the port is ready for connection.
     * @public
     */
    public async waitForFiringUp(port: number, serviceName: string): Promise<void> {
        const { host } = this.cliService.getCurrentArgv();
        let isReady = false;
        // this means that we wait around 100 seconds, normal consensus node startup takes around 60 seconds
        let retries = parseInt(process.env.FIRING_UP_RETRY_ATTEMPTS ?? '100');
        while (!isReady) {
          net
            .createConnection(port, host)
            .on('data', () => {
              isReady = true;
            })
            .on('error', (err: any) => {
              if (err.code === 'ECONNREFUSED') {
                  this.debouncedErrorLog(`${serviceName} not yet available at: ${host}:${port}. Retrying...`);
              }
              else {
                  this.logger.error(err.message, this.serviceName);
              }
            });
 
            retries--;
          await new Promise((r) => setTimeout(r, 100));
          Iif (retries < 0) {
            throw Errors.CONNECTION_ERROR(port);
          }
        }
    }
 
    /**
     * Checks the connection to the local node.
     * If the connection is not established within a timeout of 3000ms, it rejects the promise with 'timeout'.
     * If the connection is established, it resolves the promise and ends the socket.
     * If there is an error during the connection, it rejects the promise with the error.
     * 
     * @param {number} port - The port to connect to.
     * @public
     * @returns {Promise<void>} - A promise that resolves when the connection is established, and rejects when there is an error or timeout.
     */
    public checkConnection(port: number): Promise<void> {
        const { host } = this.cliService.getCurrentArgv();
        return new Promise<void>((resolve, reject) => {
            const timeout = 3000;
            const timer = setTimeout(() => {
              reject('timeout');
              socket.end();
            }, timeout);
            let socket = net.createConnection(port, host, () => {
              clearTimeout(timer);
              resolve();
              socket.end();
            });
            socket.on('error', (err) => {
              clearTimeout(timer);
              reject(err);
            });
        });
    }
}