Error loading context data

Hi all,
I have used context data for a few days. But today my flow stop working due to some error when loading context data. Any idea why and how to fix this?

Start Node-RED
 
Once Node-RED has started, point a browser at http://192.168.0.99:1880
On Pi Node-RED works better with the Firefox or Chrome browser
 
Use   node-red-stop                          to stop Node-RED
Use   node-red-start                         to start Node-RED again
Use   node-red-log                           to view the recent log output
Use   sudo systemctl enable nodered.service  to autostart Node-RED at every boot
Use   sudo systemctl disable nodered.service to disable autostart on boot
 
To find more nodes and example flows - go to http://flows.nodered.org
 
Starting as a systemd service.
1 Jun 13:10:37 - [info]
Welcome to Node-RED
===================
1 Jun 13:10:37 - [info] Node-RED version: v0.20.8
1 Jun 13:10:37 - [info] Node.js  version: v10.15.2
1 Jun 13:10:37 - [info] Linux 4.19.75-v7+ arm LE
1 Jun 13:10:41 - [info] Loading palette nodes
1 Jun 13:10:46 - [info] Dashboard version 2.21.0 started at /ui
1 Jun 13:10:48 - [info] Settings file  : /home/pi/.node-red/settings.js
1 Jun 13:10:48 - [info] Context store  : 'default' [module=localfilesystem]
1 Jun 13:10:48 - [error] Failed to start server:
1 Jun 13:10:48 - [error] Error: Error loading context store: SyntaxError: Unexpected token u in JSON at position 0
    at /usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/context/index.js:177:15
nodered.service: Succeeded.

The index.js is:

/**
 * 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) {
                return undefined;
            }
        },
        set: {
            value: function(key, value, storage, callback) {
            }
        },
        keys: {
            value: function(storage, callback) {
                return undefined;
            }
        }
    });
    return obj;
}

function getContext(localId,flowId,parent) {
    var contextId = localId;
    if (flowId) {
        contextId = localId+":"+flowId;
    }
    if (contexts.hasOwnProperty(contextId)) {
        return contexts[contextId];
    }
    var newContext = createContext(contextId,undefined,parent);
    if (flowId) {
        var node = flows.get(flowId);
        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,
    delete: deleteContext,
    clean: clean,
    close: close
};

unless you modified index.js, i don't think it is relevant to this issue.

I suspect that you injected incorrectly formatted data into some context store and now it tries to read it and it fails.

if you do:

cd ~/.node-red/context 
ls -ltr

the latest update contexts are at the bottom, go into each directory (if managable) and check each file and its content.

This could be circumstancial; example. injecting incorrect json data into context, node-red flushed it to the file and node-red was stopped after that.

Hi how was node red shutdown? Did it crash or stop suddenly?

Its been seen where context data is not fully written on shutdown.

Have a look in your context file. Try parsing it in an online json validator.

by running the command, i have this:

~/.node-red/context $ ls -ltr
total 20
drwxr-xr-x 2 pi pi 4096 May 12 11:12 d2dd1bf4.363198
drwxr-xr-x 2 pi pi 4096 May 12 11:19 global
drwxr-xr-x 2 pi pi 4096 May 13 14:28 3bcf2c34.ec10c4
drwxr-xr-x 2 pi pi 4096 May 27 02:32 95dd4ae2.7726a8
drwxr-xr-x 2 pi pi 4096 May 30 20:37 e00520cd.fdfcf

Maybe you're right, the problem occurred after i play around with some context data. This is the flow that could cause the problem:

[{"id":"aaa55d02.43bd2","type":"ui_chart","z":"95dd4ae2.7726a8","name":"","group":"26ef9932.54eb96","order":1,"width":0,"height":0,"label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"bezier","nodata":"","ymin":"","ymax":"","removeOlder":"15","removeOlderPoints":"100","removeOlderUnit":"60","cutout":0,"outputs":1,"x":1905,"y":992.5,"wires":[["eba98efb.f6b99","2620aa99.28b576"]]},{"id":"40b46c0f.f31604","type":"file","z":"95dd4ae2.7726a8","name":"","filename":"/tmp/chart.log","appendNewline":true,"createDir":false,"overwriteFile":"true","x":2050.000030517578,"y":1095.000015258789,"wires":[[]]},{"id":"d6ec987a.24cc38","type":"file in","z":"95dd4ae2.7726a8","name":"","filename":"/tmp/chart.log","format":"utf8","x":1708.7500228881836,"y":937.5000138282776,"wires":[["6c8592a4.14afdc"]]},{"id":"eba98efb.f6b99","type":"json","z":"95dd4ae2.7726a8","name":"","x":2045,"y":992.5,"wires":[["7c85b3f7.d7528c"]]},{"id":"6c8592a4.14afdc","type":"json","z":"95dd4ae2.7726a8","name":"","x":1703.7500228881836,"y":991.250011920929,"wires":[["aaa55d02.43bd2"]]},{"id":"7c85b3f7.d7528c","type":"function","z":"95dd4ae2.7726a8","name":"Send data to file","func":"if (msg.topic === \"save\") {\n    msg.payload = context.last;\n    return msg;\n}\nelse {\n    context.last = msg.payload;\n}\nreturn null;","outputs":1,"noerr":0,"x":2078.750030517578,"y":1041.250015258789,"wires":[["40b46c0f.f31604"]]},{"id":"a2883709.c7bf78","type":"inject","z":"95dd4ae2.7726a8","name":"Auto restore data in chart","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":1709.1666564941406,"y":881.2500133514404,"wires":[["d6ec987a.24cc38"]]},{"id":"2620aa99.28b576","type":"function","z":"95dd4ae2.7726a8","name":"","func":"msg.topic = \"save\";\nmsg.payload = null;\nreturn msg;","outputs":1,"noerr":0,"x":1902.5000267028809,"y":1041.6666412353516,"wires":[["7c85b3f7.d7528c"]]},{"id":"967d91ba.bf4d2","type":"ui_button","z":"95dd4ae2.7726a8","name":"","group":"26ef9932.54eb96","order":2,"width":"2","height":"1","passthru":false,"label":"Delete","tooltip":"","color":"","bgcolor":"","icon":"","payload":"[ ]","payloadType":"json","topic":"","x":1906.2500267028809,"y":943.7500143051147,"wires":[["aaa55d02.43bd2","b90edf30.2d7d3"]]},{"id":"d4451419.715668","type":"inject","z":"95dd4ae2.7726a8","name":"change this to input","topic":"1","payload":"9","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":1521.2500228881836,"y":1040.0000162124634,"wires":[["aaa55d02.43bd2"]]},{"id":"b90edf30.2d7d3","type":"file","z":"95dd4ae2.7726a8","name":"","filename":"/tmp/chart.log","appendNewline":true,"createDir":false,"overwriteFile":"delete","x":2066.250030517578,"y":897.5000133514404,"wires":[[]]},{"id":"a33cece2.0c384","type":"inject","z":"95dd4ae2.7726a8","name":"change this to input","topic":"2","payload":"5","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":1523.75,"y":1087.5,"wires":[["aaa55d02.43bd2"]]},{"id":"26ef9932.54eb96","type":"ui_group","z":"","name":"Gauge","tab":"4695098f.c199f8","order":2,"disp":true,"width":"6"},{"id":"4695098f.c199f8","type":"ui_tab","z":"","name":"Restore","icon":"dashboard"}]

In this flow, i was trying to re-plot the chart after restarting nodered or rebooting the PI. Basically juts write to a file and read it back.

i got power shut yesterday. So yes.
Now even when i disable context in setting.js -> Delete all the flows -> enable context. still have error reading.
I'll have a look at json validator.
Thanks

Note you shouldn't be able to corrupt there context file by setting any values in node-red runtime so I don't think that's the cause. A sudden power of is very likely the cause of corrupt context file.

Thanks Steve,
i can't verify using online json validator yet. There are too many errors even with my running well PI. So i have to go line by line again later.
So if you think the context file is corrupted. Can i just delete it and copy this file from my running PI? (The other PI doesn't have any context data, and i don't care of losing data anyway. Like copy from a fresh installed system).

Delete any of the context files that contain undefined.

This may be this issue https://github.com/node-red/node-red/issues/2369

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