RED.stop is not a function

I am working on a way of gracefully stopping a node-RED instance from running to simply the task of bench marking with clinic.js and auto cannon. To allow for this to happen I initially tried using a call to RED.stop() in a function node.

When doing so I received a Type error: RED.stop is not a function.

I then tried installing a simple node that just registers an endpoint, the HTML file is just a script tag:

module.exports = function(RED) {
         try {
                const shutdownEndpointPath = '/my-secret-shutdown-trigger';
                RED.httpAdmin.get(shutdownEndpointPath, function(req, res) {
                    RED.log.info(`Custom shutdown endpoint (${shutdownEndpointPath}) called. Initiating Node-RED shutdown`);
                    res.status(200).json({message: "Node-RED shutdown sequence initiated"});
                    setTimeout(() => {
                        RED.stop();
                    }, 100);
                });
                RED.log.info(`Custom shutdown endpoint registered. Access it at [Your Node-RED Admin Root URL]${shutdownEndpointPath}`);
            } catch(err) {
                RED.log.error("Failed to register custom shutdown endpoint in settings.js " + err.toString());
            }
}

Again same Type error.

I am running v4.0.9 of Node-RED on LInux 22.04 with Node v22.15.0 in a docker container.

Hi @malee

Nodes don't have access to the runtime lifecycle apis; they can't stop the runtime directly.

You'd need to go down the embedded route (docs) as that will give you access to the top level RED api and control over the full lifecycle.

1 Like

Hi @knolleary,

Managed to get a result using autocannon with the clinic Heap Profiler.

It seems that when autocannon completes the exit process is graceful enough to allow for the data to be collected and formatted for analysis.

thanks

Matt

1 Like

Could you share your setup with the community?

Sure no problem, this is the WIP

const http = require("http");
const express = require("express");
const RED = require("node-red");

const app = express();

const server = http.createServer(app);

const settings = {
  httpAdminRoot: "/",
  httpNodeRoot: "/api",
  userDir: "./.node-red-data/",
  flowFile: "flows.json",
  functionGlobalContext: {},
  logging: {
    console: {
      level: "info",
      metrics: false,
      audit: false,
    },
    
  },
};

RED.init(server, settings);

if (settings.httpAdminRoot) {
  app.use(settings.httpAdminRoot, RED.httpAdmin);
}

if (settings.httpNodeRoot) {
  app.use(settings.httpNodeRoot, RED.httpNode);
}

const SHUTDOWN_TIMEOUT_MS = 30000;
let shutdownTimer;

function gracefulShutdown() {
  console.log("Initiating Node-RED shutdown...");
  RED.stop()
    .then(() => {
      console.log("Node-RED runtime stopped.");
      server.close(() => {
        console.log("HTTP server closed.");
        process.exit(0);
      });
    })
    .catch((err) => {
      console.error("Error stopping Node-RED:", err);
      process.exit(1);
    });
}

const controlApp = express();
const controlServer = http.createServer(controlApp);
const CONTROL_PORT = 8099;

controlApp.get("/shutdown-nodered", (req, res) => {
  console.log("Received /shutdown-nodered request.");
  res.send("Shutdown signal received. Node-RED will attempt to stop.");
  if (shutdownTimer) clearTimeout(shutdownTimer);
  gracefulShutdown();
});

controlServer.listen(CONTROL_PORT, () => {
  console.log(`Control server listening on http://localhost:${CONTROL_PORT}`);
  console.log(
    `Call http://localhost:${CONTROL_PORT}/shutdown-nodered to stop Node-RED`
  );
});

RED.start()
  .then(() => {
    const PORT = settings.uiPort || process.env.PORT || 1880;
    server.listen(PORT, () => {
      console.log(
        `Node-RED embedded and running on http://localhost:${PORT}${
          settings.httpAdminRoot || "/"
        }`
      );
      RED.log.info(`Node-RED version ${RED.version()} started`);
    });
  })
  .catch((err) => {
    console.error("Failed to start Node-RED:", err);
    process.exit(1);
  });

process.on("SIGINT", () => {
  console.log("SIGINT received. Shutting down...");
  if (shutdownTimer) clearTimeout(shutdownTimer);
  gracefulShutdown();
});

There is no need to use the shutdown server with the clinic Heap Profiler and autocannon as it shuts down gracefully itself. I have also just found that if I comment out process.exit(0) and shutdown the Heap Profiler with a SIGINT via CTRL+C after calling the shutdown-nodered endpoint thr Heap Profiler compiles data for visualization after being initialized without autocannon

1 Like

I have made some adjustments to the initial script and it now works :slight_smile:

I found that if you kill the process with the process PID after node-RED has been stopped then clinic.JS completes its tasks as gracefully as you would want.

Solution appears to be replacing the gracefulShutdown function with this one:

function gracefulShutdown() {
  console.log("Initiating Node-RED shutdown...");
  RED.stop()
    .then(() => {
      console.log("Node-RED runtime stopped.");
      server.close(() => {
        console.log("HTTP server closed.");
        process.kill(process.pid, 'SIGINT');
      });
    })
    .catch((err) => {
      console.error("Error stopping Node-RED:", err);
      process.exit(1);
    });
}

and remove the following:

process.on("SIGINT", () => {
  console.log("SIGINT received. Shutting down...");
  if (shutdownTimer) clearTimeout(shutdownTimer);
  gracefulShutdown();
});

Result!

When for example you run

clinic heapprofiler -- node start-node-red.js

and call the following endpoint to end the process.

curl http://localhost:8099/shutdown-nodered

If for example you run autocannon then shutdown is automatic

clinic heapprofiler --autocannon [ http://localhost:8080/api/folderlisting -m POST -d 60 ] -- node start-node-red.js
1 Like

Really interesting.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.