I have a node js service that consumes messages from Kafka and processes it through various steps of transformation logic. During the processing, services use Redis and mongo for storage and caching purposes. In the end, it sends the transformed message to another destination via UDP packets.
On startup, it starts consuming message from Kafka after a while, it crashes down with the unhandled error: ERR_CANNOT_SEND unable to send data(see below picture).
restarting the application resolves the issue temporarily.
I initially thought it might have to do with the forwarding through UDP sockets, but the forwarding destinations are reachable from the consumer!
I'd appreciate any help here. I'm kinda stuck here.
Consumer code:
const readFromKafka = ({host, topic, source}, transformationService) => {
const logger = createChildLogger(`kafka-consumer-${topic}`);
const options = {
// connect directly to kafka broker (instantiates a KafkaClient)
kafkaHost: host,
groupId: `${topic}-group`,
protocol: ['roundrobin'], // and so on the other kafka config.
};
logger.info(`starting kafka consumer on ${host} for ${topic}`);
const consumer = new ConsumerGroup(options, [topic]);
consumer.on('error', (err) => logger.error(err));
consumer.on('message', async ({value, offset}) => {
logger.info(`recieved ${topic}`, value);
if (value) {
const final = await transformationService([
JSON.parse(Buffer.from(value, 'binary').toString()),
]);
logger.info('Message recieved', {instanceID: final[0].instanceId, trace: final[1]});
} else {
logger.error(`invalid message: ${topic} ${value}`);
}
return;
});
consumer.on('rebalanced', () => {
logger.info('cosumer is rebalancing');
});
return consumer;
};
Consumer Service startup and error handling code:
//init is the async function used to initialise the cache and other config and components.
const init = async() =>{
//initialize cache, configs.
}
//startConsumer is the async function that connects to Kafka,
//and add a callback for the onMessage listener which processes the message through the transformation service.
const startConsumer = async ({ ...config}) => {
//calls to fetch info like topic, transformationService etc.
//readFromKafka function defn pasted above
readFromKafka( {topicConfig}, transformationService);
};
init()
.then(startConsumer)
.catch((err) => {
logger.error(err);
});
Forwarding code through UDP sockets.
Following code throws the unhandled error intermittently as this seemed to work for the first few thousands of messages, and then suddenly it crashes
const udpSender = (msg, destinations) => {
return Object.values(destinations)
.map(({id, host, port}) => {
return new Promise((resolve) => {
dgram.createSocket('udp4').send(msg, 0, msg.length, port, host, (err) => {
resolve({
id,
timestamp: Date.now(),
logs: err || 'Sent succesfully',
});
});
});
});
};
Based on our comment exchange, I believe the issue is just that you're running out of resources.
Throughout the lifetime of your app, every time you send a message you open up a brand new socket. However, you're not doing any cleanup after sending that message, and so that socket stays open indefinitely. Your open sockets then continue to pile up, consuming resources, until you eventually run out of... something. Perhaps memory, perhaps ports, perhaps something else, but ultimately your app crashes.
Luckily, the solution isn't too convoluted: just reuse existing sockets. In fact, you can just reuse one socket for the entirety of the application if you wanted, as internally socket.send handles queueing for you, so no need to do any smart hand-offs. However, if you wanted a little more concurrency, here's a quick implementation of a round-robin queue where we've created a pool of 10 sockets in advance which we just grab from whenever we want to send a message:
const MAX_CONCURRENT_SOCKETS = 10;
var rrIndex = 0;
const rrSocketPool = (() => {
var arr = [];
for (let i = 0; i < MAX_CONCURRENT_SOCKETS; i++) {
let sock = dgram.createSocket('udp4');
arr.push(sock);
}
return arr;
})();
const udpSender = (msg, destinations) => {
return Object.values(destinations)
.map(({ id, host, port }) => {
return new Promise((resolve) => {
var sock = rrSocketPool[rrIndex];
rrIndex = (rrIndex + 1) % MAX_CONCURRENT_SOCKETS;
sock.send(msg, 0, msg.length, port, host, (err) => {
resolve({
id,
timestamp: Date.now(),
logs: err || 'Sent succesfully',
});
});
});
});
};
Be aware that this implementation is still naïve for a few reasons, mostly because there's still no error handling on the sockets themselves, only on their .send method. You should look at the docs for more info about catching events such as error events, especially if this is a production server that's supposed to run indefinitely, but basically the error-handling you've put inside your .send callback will only work... if an error occurs in a call to .send. If between sending messages, while your sockets are idle, some system-level error outside of your control occurs and causes your sockets to break, your socket may then emit an error event, which will go unhandled (like what's happening in your current implementation, with the intermittent errors that you see prior to the fatal one). At that point they may now be permanently unusable, meaning they should be replaced/reinstated or otherwise dealt with (or alternatively, just force the app to restart and call it a day, like I do :-) ).
Related
The App crashes if I do not call client.close in the program below.
The send of message and receiving of data works fine. But if I exit the function and comes back to it again later, the App crashes and I can not receive message anymore. I restart the Smart Phone and it works again only for the first time the function runs.
If I put client.close() inside the client.on('message',, I only get the first data from the host or source, because the socket will close prematurely. Also the App do not crash.
If I remove the client.close(), I get all the data from multiple sources saved in the array I provided let RawMessageUDP = [].
Also I confirmed that the callback function of the client.on('message', will not be executed when there are no more message in the socket.
How can I determine that there are no more message in the socket, so I can close it?
There are two hosts which receives the message and reply data string back to this App. There are no issues in the host I confirmed since they close the connection after sending.
Send_UDP_Multicast = async () => {
const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
let RawMessageUDP = []
let countMessage = 0
client.on('error', (err) => {
console.log(err.stack)
client.close()
})
client.on('message', (data, rinfo) => { //Console: socket-x, bound to address: 0.0.0.0, port: 65000 max
RawMessageUDP[countMessage] = data.toString()
console.log('Receiving remote data.' + RawMessageUDP[countMessage])
countMessage++
//client.close()
})
client.send(message, 0, message.length, 1900, '239.255.255.250', (err) => {
if (err) {
console.log(err);
client.close();
}
})
}
https://github.com/jurniores/SocketUDP resolve your problem. This lib you will find how to disconnect the clients and if the clients fall down it will disconnect them and will not overload your server.
It's been a while since I've worked with Node and Websockets. Basically how do I get socket.send() to work from another function is what I'm stuck on.
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', socket => {
socket.on('message', message => {
console.log(`received from a client: ${message}`);
});
socket.send('yo world!');
});
function onMessageHandler (target, context, msg, self) {
client.say(target, response);
server.socket.send(response);
console.log(response);
}
}
How do I get my onMessageHandler to trigger a socket send, this is fail... server.socket.send(response);
Seeing your question i think there is a lack of understanding on how Websockets work. I am assuming you're using https://github.com/websockets/ws
There are two things. First is the WebSocketerver which you've named as server and then an Individual Socket which you've named as socket
Now the thing to understand is socket is not accessible outside server.on() callback The reason for this is there could be 1000 of sockets connected at a given instance and there would be no way to uniquely identify a particular socket you want to send message to.
So ask yourself the question that your application wants to send message to an individual socket to send to everyone who is connected to your server (basically broadcast)
If you want to send to an individual, you will have to uniquely identify the user
this._wss = new WebSocket.Server({
port: ENV_APP_PORT_WS
});
this._wss.on("connection", async (ws: AppWebSocket, req: IncomingMessage) => {
// const ipAddress = req.connection.remoteAddress; // IP Address of User
logger.info(req);
const queryParams = url.parse(req.url, true).query;
let authUser: User;
try {
authUser = await this._authenticateWebSocket(queryParams);
} catch (e) {
// Terminate connection and return...
}
// WS User INIT
ws.isAlive = true;
ws.userId = authUser.id;
ws.uuid = Helpers.generateUUIDV4();
ws.send(JSON.stringify({
type: "connected",
env: ENV
}));
});
The above code will add a property to each socket object that will enable it to uniquely identify a particular user/socket.
While sending =>
onMessageHandler(targetUserId: number, message: string) {
const allSockets = <AppWebSocket[]>Array.from(this._wss.clients.values());
const targetSocket = allSockets.find(w => w.userId === targetUserId);
targetSocket.send(message);
}
If You want to send to all connect users, it's quite easy:
https://github.com/websockets/ws#server-broadcast
I'm using a service written in Node.js to receive messages via MQTT (https://www.npmjs.com/package/mqtt) which then writes to a database (SQL Server using mssql).
This will work very nicely when everything is functioning normally, I create the mqtt listener and subscribe to new message events.
However, if the connection to the DB fails (this may happen periodically due to a network outage etc.), writing the message to the database will fail and the message will be dropped on the floor.
I would like to tell the MQTT broker - "I couldn't process the message, keep it in the buffer until I can."
var mqtt = require('mqtt')
var client = mqtt.connect('mymqttbroker')
client.on('connect', function () {
client.subscribe('messagequeue')
})
client.on('message', function (topic, message) {
writeMessageToDB(message).then((result) => {console.log('success'};).catch((err) => {/* What can I do here ?*/});
})
Maybe set a timeout on a resend function? Probably should be improved to only try n times before dropping the message, but it's definitely a way to do it. This isn't tested, obviously, but it should hopefully give you some ideas...
var resend = function(message){
writeMessageToDB(message).then((result) => {
console.log('Resend success!')
})
.catch((err) => {
setTimeout(function(message){
resend(message);
}, 60000);
});
}
client.on('message', function (topic, message) {
writeMessageToDB(message).then((result) => {
console.log('success')
})
.catch((err) => {
resend(message);
});
});
I am writing a simple port scanner using core net module from Node.js. I am getting a 'Callback was already called' error with my code. Can you spot please where the error is coming from? Below is my code:
const net = require('net')
const async = require('async')
function findPortStatus(host, port, timeout, cb) {
const socket = new net.Socket()
socket.setTimeout(timeout, () => {
// couldn't establish a connection because of timeout
socket.destroy()
return cb(null, null)
})
socket.connect(port, host, () => {
// connection established
return cb(null, port)
})
socket.on('error', (err) => {
// couldn't establish a connection
return cb(null, null)
})
}
const funcs = []
for (let port = 0; port <= 80; port++) {
funcs.push(function(callback) {
findPortStatus('192.30.253.112', port, 4000, (err, port) => {
if (!err) {
return callback(null, port)
}
})
})
}
async.parallel(funcs, (err, ports) => {
if (err) {
console.error(err.message)
} else {
for (let port of ports) {
if (port) {
console.log(port)
}
}
}
})
Not sure if this is related, but you really should pass something to the callback when you call it. null,null isn't very useful for debugging. What I would suggest is timeout events in your context are probably not errors, but they are informative. You could just cb(null, 'timeout') or cb(null, {state: 'timedOut', port: port}) or something to better keep track of what worked and what didn't.
The most likely candidate for your actual error, though, is if your socket emits an error or timeout event after the connect event was already successful. Dropped connection or the like. If all you're looking for is a 'ping'-like functionality (across more than just ICMP obviously), then you should probably close the connection as soon as you get a connect and/or remove the other event listeners as part of the connect listener's handler.
Finally, the node docs suggest you not call socket.connect() directly, unless implementing a custom socket (which it doesn't seem like you are), but to use net.createConnection() instead; not sure that'll help you but it's worth noting.
It looks like the successfully connected sockets are subsequently timing out (which makes sense, as you connect but then do nothing with the connection, so it times out).
If you disconnect from a socket once you have recorded a successful connection, then that should clear up the error.
I want my application (lets say a simple node file for now) to work as it is even if redis is not available. I'm not able to do it the correct way. This is what I've tried.
var redis = require('redis');
var redisClient = null;
var getRedisClient = function(){
if(redisClient){
return redisClient;
}
try {
redisClient = redis.createClient({connect_timeout : 5000, max_attempts : 1});
redisClient.on("error", function(err) {
console.error("Error connecting to redis", err);
redisClient = null;
});
return redisClient;
} catch(ex){
console.log("error initialising redis client " + ex);
return null;
}
};
try {
var client = getRedisClient();
console.log("done!");
} catch (ex){
console.log("Exception");
}
However, with this code my application exits if redis is not available (it shouldn't because i've not given a process.exit() command).
How can I solve this?
Checking for Successful Connection on Start
Using a promise, you could guarantee that at least initially, you were able to connect to redis without error within a specified time period:
const redis = require('redis');
const Promise = require('bluebird');
function getRedisClient(timeoutMs){
return new Promise((resolve, reject) => {
const redisClient = redis.createClient();
const timer = setTimeout(() => reject('timeout'), timeoutMs);
redisClient.on("ready", () => {
clearTimeout(timer);
resolve(redisClient);
});
redisClient.on("error", (err) => {
clearTimeout(timer);
reject(err);
});
});
};
const redisReadyTimeoutMs = 10000;
getRedisClient(redisReadyTimeoutMs)
.then(redisClient => {
// the client has connected to redis sucessfully
return doSomethingUseful();
}, error => {
console.log("Unable to connect to redis", error);
});
You Need Proper Error Handling
The redis client being non-null does NOT guarantee using it won't throw an error.
you could experience infrastructure misfortune e.g. crashed redis process, out of memory or network being down.
a bug in your code could cause an error e.g. invalid or missing arguments to a redis command.
You should be handling redis client errors as a matter of course.
DON'T null the Redis Client on Error
It won't give you much but it will force you to check for null every time you try and use it.
The redis client also has inbuilt reconnect and retry mechanisms that you'll miss out on if you null it after the first error. See the redis package docs, look for retry_strategy.
DO Wrap your redis client code with try .. catch ... or use .catch in your promise chain.
DO Make use of a retry_strategy.