Node-red won't start

It may be unrelated, but Windows installed some updates and rebooted my PC last night.
When I logged in this morning Node-RED would not start. I keep getting the following error:

I have deleted the file in question and installed node-red again, but I keep getting the same error message.

I have also uninstalled and re-installed node-red (without deleting all of the node-red folder, as I'm concerned how long it will take to set everything up again), yet the exact same error occurs:

This is the contents of the file in question:

/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var clone = require("clone");
var log = require("@node-red/util").log;
var util = require("@node-red/util").util;
var memory = require("./memory");
var flows;

var settings;

// A map of scope id to context instance
var contexts = {};

// A map of store name to instance
var stores = {};
var storeList = [];
var defaultStore;

// Whether there context storage has been configured or left as default
var hasConfiguredStore = false;

// Unknown Stores
var unknownStores = {};

function logUnknownStore(name) {
    if (name) {
        var count = unknownStores[name] || 0;
        if (count == 0) {
            log.warn(log._("context.unknown-store", {name: name}));
            count++;
            unknownStores[name] = count;
        }
    }
}

function init(_settings) {
    flows = require("../flows");
    settings = _settings;
    contexts = {};
    stores = {};
    storeList = [];
    hasConfiguredStore = false;
    var seed = settings.functionGlobalContext || {};
    contexts['global'] = createContext("global",seed);
    // create a default memory store - used by the unit tests that skip the full
    // `load()` initialisation sequence.
    // If the user has any stores configured, this will be disgarded
    stores["_"] = new memory();
    defaultStore = "memory";
}

function load() {
    return new Promise(function(resolve,reject) {
        // load & init plugins in settings.contextStorage
        var plugins = settings.contextStorage || {};
        var defaultIsAlias = false;
        var promises = [];
        if (plugins && Object.keys(plugins).length > 0) {
            var hasDefault = plugins.hasOwnProperty('default');
            var defaultName;
            for (var pluginName in plugins) {
                if (plugins.hasOwnProperty(pluginName)) {
                    // "_" is a reserved name - do not allow it to be overridden
                    if (pluginName === "_") {
                        continue;
                    }
                    if (!/^[a-zA-Z0-9_]+$/.test(pluginName)) {
                        return reject(new Error(log._("context.error-invalid-module-name", {name:pluginName})));
                    }

                    // Check if this is setting the 'default' context to be a named plugin
                    if (pluginName === "default" && typeof plugins[pluginName] === "string") {
                        // Check the 'default' alias exists before initialising anything
                        if (!plugins.hasOwnProperty(plugins[pluginName])) {
                            return reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]})));
                        }
                        defaultIsAlias = true;
                        continue;
                    }
                    if (!hasDefault && !defaultName) {
                        defaultName = pluginName;
                    }
                    var plugin;
                    if (plugins[pluginName].hasOwnProperty("module")) {
                        // Get the provided config and copy in the 'approved' top-level settings (eg userDir)
                        var config = plugins[pluginName].config || {};
                        copySettings(config, settings);

                        if (typeof plugins[pluginName].module === "string") {
                            // This config identifies the module by name - assume it is a built-in one
                            // TODO: check it exists locally, if not, try to require it as-is
                            try {
                                plugin = require("./"+plugins[pluginName].module);
                            } catch(err) {
                                return reject(new Error(log._("context.error-loading-module2", {module:plugins[pluginName].module,message:err.toString()})));
                            }
                        } else {
                            // Assume `module` is an already-required module we can use
                            plugin = plugins[pluginName].module;
                        }
                        try {
                            // Create a new instance of the plugin by calling its module function
                            stores[pluginName] = plugin(config);
                            var moduleInfo = plugins[pluginName].module;
                            if (typeof moduleInfo !== 'string') {
                                if (moduleInfo.hasOwnProperty("toString")) {
                                    moduleInfo = moduleInfo.toString();
                                } else {
                                    moduleInfo = "custom";
                                }
                            }
                            log.info(log._("context.log-store-init", {name:pluginName, info:"module="+moduleInfo}));
                        } catch(err) {
                            return reject(new Error(log._("context.error-loading-module2",{module:pluginName,message:err.toString()})));
                        }
                    } else {
                        // Plugin does not specify a 'module'
                        return reject(new Error(log._("context.error-module-not-defined", {storage:pluginName})));
                    }
                }
            }

            // Open all of the configured contexts
            for (var plugin in stores) {
                if (stores.hasOwnProperty(plugin)) {
                    promises.push(stores[plugin].open());
                }
            }
            // There is a 'default' listed in the configuration
            if (hasDefault) {
                // If 'default' is an alias, point it at the right module - we have already
                // checked that it exists. If it isn't an alias, then it will
                // already be set to a configured store
                if (defaultIsAlias) {
                    stores["_"] =  stores[plugins["default"]];
                    defaultStore = plugins["default"];
                } else {
                    stores["_"] = stores["default"];
                    defaultStore = "default";
                }
            } else if (defaultName) {
                // No 'default' listed, so pick first in list as the default
                stores["_"] = stores[defaultName];
                defaultStore = defaultName;
                defaultIsAlias = true;
            } else {
                // else there were no stores list the config object - fall through
                // to below where we default to a memory store
                storeList = ["memory"];
                defaultStore = "memory";
            }
            hasConfiguredStore = true;
            storeList = Object.keys(stores).filter(n=>!(defaultIsAlias && n==="default") && n!== "_");
        } else {
            // No configured plugins
            log.info(log._("context.log-store-init", {name:"default", info:"module=memory"}));
            promises.push(stores["_"].open())
            storeList = ["memory"];
            defaultStore = "memory";
        }
        return resolve(Promise.all(promises));
    }).catch(function(err) {
        throw new Error(log._("context.error-loading-module",{message:err.toString()}));
    });
}

function copySettings(config, settings){
    var copy = ["userDir"]
    config.settings = {};
    copy.forEach(function(setting){
        config.settings[setting] = clone(settings[setting]);
    });
}

function getContextStorage(storage) {
    if (stores.hasOwnProperty(storage)) {
        // A known context
        return stores[storage];
    } else if (stores.hasOwnProperty("_")) {
        // Not known, but we have a default to fall back to
        if (storage !== defaultStore) {
            // It isn't the default store either, so log it
            logUnknownStore(storage);
        }
        return stores["_"];
    }
}

function followParentContext(parent, key) {
    if (key === "$parent") {
        return [parent, undefined];
    }
    else if (key.startsWith("$parent.")) {
        var len = "$parent.".length;
        var new_key = key.substring(len);
        var ctx = parent;
        while (ctx && new_key.startsWith("$parent.")) {
            ctx = ctx.$parent;
            new_key = new_key.substring(len);
        }
        return [ctx, new_key];
    }
    return null;
}

function createContext(id,seed,parent) {
    // Seed is only set for global context - sourced from functionGlobalContext
    var scope = id;
    var obj = seed || {};
    var seedKeys;
    var insertSeedValues;
    if (seed) {
        seedKeys = Object.keys(seed);
        insertSeedValues = function(keys,values) {
            if (!Array.isArray(keys)) {
                if (values[0] === undefined) {
                    try {
                        values[0] = util.getObjectProperty(seed,keys);
                    } catch(err) {
                        if (err.code === "INVALID_EXPR") {
                            throw err;
                        }
                        values[0] = undefined;
                    }
                }
            } else {
                for (var i=0;i<keys.length;i++) {
                    if (values[i] === undefined) {
                        try {
                            values[i] = util.getObjectProperty(seed,keys[i]);
                        } catch(err) {
                            if (err.code === "INVALID_EXPR") {
                                throw err;
                            }
                            values[i] = undefined;
                        }
                    }
                }
            }
        }
    }
    Object.defineProperties(obj, {
        get: {
            value: function(key, storage, callback) {
                var context;

                if (!callback && typeof storage === 'function') {
                    callback = storage;
                    storage = undefined;
                }
                if (callback && typeof callback !== 'function'){
                    throw new Error("Callback must be a function");
                }

                if (!Array.isArray(key)) {
                    var keyParts = util.parseContextStore(key);
                    key = keyParts.key;
                    if (!storage) {
                        storage = keyParts.store || "_";
                    }
                    var result = followParentContext(parent, key);
                    if (result) {
                        var [ctx, new_key] = result;
                        if (ctx && new_key) {
                            return ctx.get(new_key, storage, callback);
                        }
                        else {
                            if (callback) {
                                return callback(undefined);
                            }
                            else {
                                return undefined;
                            }
                        }
                    }
                } else {
                    if (!storage) {
                        storage = "_";
                    }
                }
                context = getContextStorage(storage);

                if (callback) {
                    if (!seed) {
                        context.get(scope,key,callback);
                    } else {
                        context.get(scope,key,function() {
                            if (arguments[0]) {
                                callback(arguments[0]);
                                return;
                            }
                            var results = Array.prototype.slice.call(arguments,[1]);
                            try {
                                insertSeedValues(key,results);
                            } catch(err) {
                                callback.apply(err);
                                return
                            }
                            // Put the err arg back
                            results.unshift(undefined);
                            callback.apply(null,results);
                        });
                    }
                } else {
                    // No callback, attempt to do this synchronously
                    var results = context.get(scope,key);
                    if (seed) {
                        if (Array.isArray(key)) {
                            insertSeedValues(key,results);
                        } else if (results === undefined){
                            try {
                                results = util.getObjectProperty(seed,key);
                            } catch(err) {
                                if (err.code === "INVALID_EXPR") {
                                    throw err;
                                }
                                results = undefined;
                            }
                        }
                    }
                    return results;
                }
            }
        },
        set: {
            value: function(key, value, storage, callback) {
                var context;

                if (!callback && typeof storage === 'function') {
                    callback = storage;
                    storage = undefined;
                }
                if (callback && typeof callback !== 'function'){
                    throw new Error("Callback must be a function");
                }

                if (!Array.isArray(key)) {
                    var keyParts = util.parseContextStore(key);
                    key = keyParts.key;
                    if (!storage) {
                        storage = keyParts.store || "_";
                    }
                    var result = followParentContext(parent, key);
                    if (result) {
                        var [ctx, new_key] = result;
                        if (ctx && new_key) {
                            return ctx.set(new_key, value, storage, callback);
                        }
                        else {
                            if (callback) {
                                return callback();
                            }
                            return undefined;
                        }
                    }
                } else {
                    if (!storage) {
                        storage = "_";
                    }
                }
                context = getContextStorage(storage);

                context.set(scope, key, value, callback);
            }
        },
        keys: {
            value: function(storage, callback) {
                var context;
                if (!storage && !callback) {
                    context = stores["_"];
                } else {
                    if (typeof storage === 'function') {
                        callback = storage;
                        storage = "_";
                    }
                    if (callback && typeof callback !== 'function') {
                        throw new Error("Callback must be a function");
                    }
                    context = getContextStorage(storage);
                }
                if (seed && settings.exportGlobalContextKeys !== false) {
                    if (callback) {
                        context.keys(scope, function(err,keys) {
                            callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
                        });
                    } else {
                        var keys = context.keys(scope);
                        return Array.from(new Set(seedKeys.concat(keys)).keys())
                    }
                } else {
                    return context.keys(scope, callback);
                }
            }
        }
    });
    if (parent) {
        Object.defineProperty(obj, "$parent", {
            value: parent
        });
    }
    return obj;
}

function createRootContext() {
    var obj = {};
    Object.defineProperties(obj, {
        get: {
            value: function(key, storage, callback) {
                if (!callback && typeof storage === 'function') {
                    callback = storage;
                    storage = undefined;
                }
                if (callback) {
                    callback()
                    return;
                }
                return undefined;
            }
        },
        set: {
            value: function(key, value, storage, callback) {
                if (!callback && typeof storage === 'function') {
                    callback = storage;
                    storage = undefined;
                }
                if (callback) {
                    callback()
                    return
                }
            }
        },
        keys: {
            value: function(storage, callback) {
                if (!callback && typeof storage === 'function') {
                    callback = storage;
                    storage = undefined;
                }
                if (callback) {
                    callback();
                    return;
                }
                return undefined;
            }
        }
    });
    return obj;
}

/**
 * Get a flow-level context object.
 * @param  {string} flowId       [description]
 * @param  {string} parentFlowId the id of the parent flow. undefined
 * @return {object}}             the context object
 */
function getFlowContext(flowId,parentFlowId) {
    if (contexts.hasOwnProperty(flowId)) {
        return contexts[flowId];
    }
    var parentContext = contexts[parentFlowId];
    if (!parentContext) {
        parentContext = createRootContext();
        contexts[parentFlowId] = parentContext;
        // throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId);
    }
    var newContext = createContext(flowId,undefined,parentContext);
    contexts[flowId] = newContext;
    return newContext;

}

function getContext(nodeId, flowId) {
    var contextId = nodeId;
    if (flowId) {
        contextId = nodeId+":"+flowId;
    }
    if (contexts.hasOwnProperty(contextId)) {
        return contexts[contextId];
    }
    var newContext = createContext(contextId);

    if (flowId) {
        var flowContext = contexts[flowId];
        if (!flowContext) {
            // This is most likely due to a unit test for a node which doesn't
            // initialise the flow properly.
            // To keep things working, initialise the missing context.
            // This *does not happen* in normal node-red operation
            flowContext = createContext(flowId,undefined,createRootContext());
            contexts[flowId] = flowContext;
        }
        Object.defineProperty(newContext, 'flow', {
            value: flowContext
        });
    }
    Object.defineProperty(newContext, 'global', {
        value: contexts['global']
    })
    contexts[contextId] = newContext;
    return newContext;
}

//
// function getContext(localId,flowId,parent) {
//     var contextId = localId;
//     if (flowId) {
//         contextId = localId+":"+flowId;
//     }
//     console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId));
//     if (contexts.hasOwnProperty(contextId)) {
//         return contexts[contextId];
//     }
//     var newContext = createContext(contextId,undefined,parent);
//     if (flowId) {
//         var node = flows.get(flowId);
//         console.log("flows,get",flowId,node&&node.type)
//         var parent = undefined;
//         if (node && node.type.startsWith("subflow:")) {
//             parent = node.context().flow;
//         }
//         else {
//             parent = createRootContext();
//         }
//         var flowContext = getContext(flowId,undefined,parent);
//         Object.defineProperty(newContext, 'flow', {
//             value: flowContext
//         });
//     }
//     Object.defineProperty(newContext, 'global', {
//         value: contexts['global']
//     })
//     contexts[contextId] = newContext;
//     return newContext;
// }

function deleteContext(id,flowId) {
    if(!hasConfiguredStore){
        // only delete context if there's no configured storage.
        var contextId = id;
        if (flowId) {
            contextId = id+":"+flowId;
        }
        delete contexts[contextId];
        return stores["_"].delete(contextId);
    }else{
        return Promise.resolve();
    }
}

function clean(flowConfig) {
    var promises = [];
    for(var plugin in stores){
        if(stores.hasOwnProperty(plugin)){
            promises.push(stores[plugin].clean(Object.keys(flowConfig.allNodes)));
        }
    }
    for (var id in contexts) {
        if (contexts.hasOwnProperty(id) && id !== "global") {
            var idParts = id.split(":");
            if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) {
                delete contexts[id];
            }
        }
    }
    return Promise.all(promises);
}

function close() {
    var promises = [];
    for(var plugin in stores){
        if(stores.hasOwnProperty(plugin)){
            promises.push(stores[plugin].close());
        }
    }
    return Promise.all(promises);
}

function listStores() {
    return {default:defaultStore,stores:storeList};
}

module.exports = {
    init: init,
    load: load,
    listStores: listStores,
    get: getContext,
    getFlowContext:getFlowContext,
    delete: deleteContext,
    clean: clean,
    close: close
};

Which file did you delete exactly? If it was the lib/nodes/context/index.js file then that was not the right thing to do. That was the file reporting the error about loading context - it was not the file that contained the unexpected token.

The context store files are under ~/.node-red/context.

Yes, I deleted the index.js file, but I took a backup, however on re-install, of node-red, the whole folder was replaced, so the backup was lost anyway

Either way, the index.js file is still being reported as the error on load, and if you look at the contents of the file, that I pasted in the post above, it is a JS file, not an error log.

I'm still not clear what to do next.

How do I make head or tails of the folders in ~/.node-red/context .?
image

Or do i just delete all these folders?

For now, try renaming the context folder that Nick has indicated. Then restart node-red to see if the problem goes away.

Of course, that will also remove your retained variables but at least we can then try to analyse the data to see if it can be recovered.

Each of those folders contains a json file and possibly some .tmp files. They contain your retained variables. So if you don't care about the retained variables, you could delete the contents after stopping Node-RED.

But it might be interesting to find out which of those files has been corrupted. If you have a decent code editor that "understands" JSON formats, you could open each of the files into the editor to find out which is invalid.

This is a bit of a worry in that it might indicate an edge case in the file handling for retained variables.

1 Like

I found the offending file. Deleted it and Node-RED is starting again.
Although as you suggested, I have lost all my global variables.

The bottom of the file appears to have been corrupted.... I guess this happened with Windows forcibly rebooted overnight without Node-RED shutting down properly?

Well, I'd call that a bug in Node-RED I'm afraid. It possibly shows that the file update isn't quite safe. Of course, it is impossible to be 100% certain that a file write won't get cut off and so corrupt the file - that's the downside of large-file data stores. Managed databases only need to update parts of the data and often do it transactionally to absolutely minimise the amount of time spent writing. It is also common to have a transaction log as well as the actual data. So corruption can be recovered from.

I wonder if the .json files in this case should have backups like the flow file does? Although it might simply be better to have each variable as a separate file to minimise writes (of course, this may already be the case, I haven't really studied the file structure).

I wish I had more time so that I could look at alternative storage methods. I did start to look at GunJS for this but it was more complex than I had time for in the end.

I would have assumed that Windows deciding to reboot would do a graceful shutdown of all processes, not kill them wantonly. This is Windows though, so perhaps I am wrong.

Are you sure the reboot was an intentional Windows Update reboot and not a crash of some sort?

I would have expected so as well and I was suffering with a lot of unexpected overnight reboots a short while back but never had a problem like this. Though I think my PC generally goes to sleep on its own when I ignore it for a while :smiley: So Node-RED would be suspended at that point.

You can check the event log to see if Windows crashed or something else caused the reboot. Even so, it should take a blue-screen crash to stop a file-system flush. Either way, file corruption should be pretty well never seen and if encountered, should be recoverable.

If you really want to running Windows actively overnight, I would suggest changing the parameters for Windows Update to prevent it from doing an auto-reboot. Windows does these because the predominant use-case for a Windows PC is a user-facing device and users rarely bother to restart their machines.

It was definitely a scheduled reboot. I saw the message the night before.

I checked the windows settings but from a cursory glance couldn't find the, do not forcefully reboot option.... I am sure it was there before, make they removed it recently.

Then again, now that this has happened I will manually reboot and make sure to nicely shut down node red before hand.

Ps: there is always a small chance that windows crashed and rebooted after and the scheduled reboot was a red herring.

Try these.

Thanks for that. I'll look into it.

I check the event viewer and no critical errors in the past 7 days, which is as far back as it goes, so I am going to assume the most likely event, Windows completed a reboot in the morning and perhaps the globals file was being written to and was corrupted in the process.

There should be no need to manually shutdown node-red, if there is then something odd is going on.

I am wondering whether a useful minor enhancement would be to include the file name in the error message when this occurs, assuming the code knows what it is at the point it generates the error message.

Ok, so I mean that I hit ctrl + c each time I want to stop node-red. I don't just close the CMD window and reboot.

Are you saying that ctrl + c is just a nice process that isn't required?

And yes, to your 2nd post. A filename and path would help a lot.

I had assumed that you had node-red running automatically on boot, in which case that system would have closed node-red appropriately. I don't know what happens on a Windows system if you have something running in a terminal and then close the terminal or reboot. Someone with some knowledge of Windows would have to answer that.

Your first assumption is correct. Node red starts on boot.
I was told by the person who introduced me to node red to always stop node red with ctrl + c, which is what I generally do.

Oh, I hadn't realised that if you configure windows to start node-red on boot that it runs in a terminal rather than as a service. I think I had better shut up as the fact that I know virtually nothing about Windows is becoming ever more obvious.

I wouldn't jump to that conclusion. I've never looked into running it as a service, but I just found this topic:

One to add to the list of things to do :wink: