Alana Storm
Alana Storm

Reputation: 166166

Node.js redis@4 Upgrade: SocketClosedUnexpectedlyError: Socket closed unexpectedly

I've got some legacy code that I'm upgrading from version 3 of the Node.js redis library to version 4 of the Node.js redis library. The basic shape of the code looks like this

var redis = require('redis')
var client = redis.createClient({
    port: '6379',
    host: process.env.REDIS_HOST,
    legacyMode: true
})
client.connect()
client.flushall(function (err, reply) {
    client.hkeys('hash key', function (err, replies) {
      console.log("key set done")
      client.quit()
    })
})
console.log("main done")

When I run this code with [email protected], I get the following error, and node.js exits with a non-zero status code

main done
key set done
events.js:292
      throw er; // Unhandled 'error' event
      ^

SocketClosedUnexpectedlyError: Socket closed unexpectedly
    at Socket.<anonymous> (/Users/astorm/Documents/redis4/node_modules/@redis/client/dist/lib/client/socket.js:182:118)
    at Object.onceWrapper (events.js:422:26)
    at Socket.emit (events.js:315:20)
    at TCP.<anonymous> (net.js:673:12)
Emitted 'error' event on Commander instance at:
    at RedisSocket.<anonymous> (/Users/astorm/Documents/redis4/node_modules/@redis/client/dist/lib/client/index.js:350:14)
    at RedisSocket.emit (events.js:315:20)
    at RedisSocket._RedisSocket_onSocketError (/Users/astorm/Documents/redis4/node_modules/@redis/client/dist/lib/client/socket.js:205:10)
    at Socket.<anonymous> (/Users/astorm/Documents/redis4/node_modules/@redis/client/dist/lib/client/socket.js:182:107)
    at Object.onceWrapper (events.js:422:26)
    at Socket.emit (events.js:315:20)
    at TCP.<anonymous> (net.js:673:12)
 

While in [email protected] it runs (minus the client.connect()) without issue.

I've been able to work around this by replacing client.quit() with client.disconnect(), but the actual code is a little more complex than the above example and I'd rather use the graceful shutdown of client.quit than the harsher "SHUT IT DOWN NOW" of client.disconnect().

Does anyone know what the issue here might be? Why is redis@4 failing with a SocketClosedUnexpectedlyError: Socket closed unexpectedly error.

Upvotes: 2

Views: 3828

Answers (2)

Full-Stack Developer
Full-Stack Developer

Reputation: 438

I have the same bug on a express js project,

  const redisClient = createClient({
    legacyMode: true,
    url: process.env.REDIS_URL,
  });

my error:

web.1 State changed from up to crashed
web.1 ERROR uncaughtException Socket closed unexpectedly
web.1 SocketClosedUnexpectedlyError: Socket closed unexpectedly

i change the code to

  const redisClient = createClient({
    legacyMode: true,
    url: process.env.REDIS_URL,
    pingInterval: 1000,
  });

the error disappear, i waited for an hour. So pingInterval helped me to keep socket alive

Upvotes: 5

Russo Maromba
Russo Maromba

Reputation: 81

What I found so far is that after a while (keepAlive default is 5 minutes) without any requests the Redis client closes and throws an error event, but if you don't handle this event it will crash your application. My solution for that was:

/* eslint-disable no-inline-comments */
import type { RedisClientType } from 'redis'
import { createClient } from 'redis'
import { config } from '@app/config'
import { logger } from '@app/utils/logger'

let redisClient: RedisClientType
let isReady: boolean

const cacheOptions = {
  url: config.redis.tlsFlag ? config.redis.urlTls : config.redis.url,
}

if (config.redis.tlsFlag) {
  Object.assign(cacheOptions, {
    socket: {
      // keepAlive: 300, // 5 minutes DEFAULT
      tls: false,
    },
  })
}

async function getCache(): Promise<RedisClientType> {
  if (!isReady) {
    redisClient = createClient({
      ...cacheOptions,
    })
    redisClient.on('error', err => logger.error(`Redis Error: ${err}`))
    redisClient.on('connect', () => logger.info('Redis connected'))
    redisClient.on('reconnecting', () => logger.info('Redis reconnecting'))
    redisClient.on('ready', () => {
      isReady = true
      logger.info('Redis ready!')
    })
    await redisClient.connect()
  }
  return redisClient
}

getCache().then(connection => {
  redisClient = connection
}).catch(err => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  logger.error({ err }, 'Failed to connect to Redis')
})

export {
  getCache,
}

anyway... in your situation try to handle the error event

client.on('error', err => logger.error(`Redis Error: ${err}`))

Upvotes: 2

Related Questions