Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The log shows - "ClientClosedError: The client is closed" even having explicit connect & disconnect for each call #2607

Closed
billsenxu opened this issue Sep 3, 2023 · 11 comments
Labels

Comments

@billsenxu
Copy link

billsenxu commented Sep 3, 2023

Description

Sometime the node.js application would throw out the error message - "ClientClosedError: The client is closed" even having explicit connect & disconnect for each call in the code. As redis is used frequenty in the program, the redis is initiated when starting the program(with built-in createClient() method). In my scenario of every express call(get or post), the redis connect will be called if "redisClient.isOpen" is false, after the redis access, the redis disconnect will be excuted explicitly. Don't know why this error was caused.

By the way, if the redis diconnect method is unnecessary(as the front-end users will access the program from time to time, so logically the redis would disconnect and connect alternatively all along)?

Appreciate for the kind assistance.

Node.js Version

v18.14.2

Redis Server Version

7.2.0

Node Redis Version

redis@4.6.7

Platform

Ubuntu 20.04(Linux5.4.0-148-genericx86_64)

Logs

/home/abc/node_modules/@redis/client/dist/lib/client/index.js:490
        return Promise.reject(new errors_1.ClientClosedError());
                              ^

ClientClosedError: The client is closed
    at Commander._RedisClient_sendCommand (/home/abc/node_modules/@redis/client/dist/lib/client/index.js:490:31)
    at Commander.commandsExecutor (/home/abc/node_modules/@redis/client/dist/lib/client/index.js:188:154)
    at BaseClass.<computed> [as rPush] (/home/abc/node_modules/@redis/client/dist/lib/commander.js:8:29)
    at /home/abc/Frontend.js:870:29
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
@billsenxu billsenxu added the Bug label Sep 3, 2023
@adadgio
Copy link

adadgio commented Sep 21, 2023

Any update on this issue? Still happening...

const client = createClient()
await client.connect()
// do stuff
await client.disconnect() OR await client.quit()

---> UnhandledPromiseRejectionWarning: Error: The client is closed
```

@leibale
Copy link
Collaborator

leibale commented Sep 21, 2023

You shouldn't connect and disconnect every time you want to run a command, you should connect when the application starts and disconnect when you want to gracefully close the application (if you ever need to).

Assuming you are using it with an HTTP server, if you connect and disconnect in a request handler, when there are 2 requests in parallel, whenever the first request finishes, it'll close the connection for the second request as well...

Hope that makes sense..

@billsenxu
Copy link
Author

As recommended by leibale, no need to disconnect for every http request, as the program normally is running all along. The reason I executed the "disconnect" previously for each request was trying to prevent the possible more and more open connections, based on the observation, this case wouldn't happen.

@leibale leibale closed this as completed Sep 25, 2023
@radirantast
Copy link

You shouldn't connect and disconnect every time you want to run a command, you should connect when the application starts and disconnect when you want to gracefully close the application (if you ever need to).

Assuming you are using it with an HTTP server, if you connect and disconnect in a request handler, when there are 2 requests in parallel, whenever the first request finishes, it'll close the connection for the second request as well...

Hope that makes sense..

actually that sounds like a great idea, but how I can do this?

@leibale
Copy link
Collaborator

leibale commented Dec 12, 2023

@radirantast

redis.js:

import { createClient } from 'redis';

export const client = await createClient()
  .on('error', err => console.error('Redis Client Error', err))
  .connect();

index.js:

import { createServer } from 'node:http';
import { client } from './redis.js';

createServer(async (req, res) => {
  try {
    res.end(await client.get('key'));
  } catch (err) {
    console.error(err);
    res.end('Internal Server Error');
  }
}).listen(3000);

@noodles-1
Copy link

You shouldn't connect and disconnect every time you want to run a command, you should connect when the application starts and disconnect when you want to gracefully close the application (if you ever need to).

Assuming you are using it with an HTTP server, if you connect and disconnect in a request handler, when there are 2 requests in parallel, whenever the first request finishes, it'll close the connection for the second request as well...

Hope that makes sense..

This is what I have done in my Next.js project, but for some reason, it still gives the same error "ClientClosedError: The client is closed" when fetch onto the server that runs redis commands. I have looked at all my files and there is no await client.disconnect() nor await client.quit().

@joshpwrk
Copy link

joshpwrk commented Dec 14, 2023

running into the same exact issue.

This is how the client connection is made

export async function getRedisCluster(hostEnvName: string, portEnvName: string): Promise<RedisCluster> {
  const args = getRedisArgs(hostEnvName, portEnvName);

  logger.info(`connecting to Redis cluster at: ${args.redisHost}:${args.redisPort}...`);

  const redisCluster = redis.createCluster({
    rootNodes: [
      {
        url: `redis://${args.redisHost}:${+args.redisPort}`,
      },
    ],
    // minimizeConnections: true,
  });

  redisCluster.on('error', (err) => console.log('Redis Cluster Error', err));

  await redisCluster.connect();

  return redisCluster;
}

export const subscriptionClient = await getRedisCluster('REDIS_SUBSCRIPTION_HOST', 'REDIS_SUBSCRIPTION_PORT');

This is an example of how the unsubscribe happens.

export async function maybeRemoveGlobalSubscription(channel: string) {
  liveWebSockets.forEach((wsc) => {
    if (wsc.subscribedChannels?.includes(channel)) {
      return; // early return if any other client is still subscribed
    }
  });

  if (globalSubscriptions.has(channel)) {
    globalSubscriptions.delete(channel);

    await subscriptionClient.sUnsubscribe(channel);
  }
}

And this is the error I get

Error: The client is closed
    at RedisSocket.disconnect (/app/node_modules/@redis/client/dist/lib/client/socket.js:63:19)
    at Commander.disconnect (/app/node_modules/@redis/client/dist/lib/client/index.js:343:64)
    at RedisClusterSlots.executeShardedUnsubscribeCommand (/app/node_modules/@redis/client/dist/lib/cluster/cluster-slots.js:154:26)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async maybeRemoveGlobalSubscription (file:///app/build/routes/subscribe/relayer.js:16:9)
    at async file:///app/build/routes/subscribe/relayer.js:5:9 [/app/build/utils/errors.js]
ErrorMessage: The client is closed
StackTrace: Error: The client is closed
    at RedisSocket.disconnect (/app/node_modules/@redis/client/dist/lib/client/socket.js:63:19)
    at Commander.disconnect (/app/node_modules/@redis/client/dist/lib/client/index.js:343:64)
    at RedisClusterSlots.executeShardedUnsubscribeCommand (/app/node_modules/@redis/client/dist/lib/cluster/cluster-slots.js:154:26)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async maybeRemoveGlobalSubscription (file:///app/build/routes/subscribe/relayer.js:16:9)
    at async file:///app/build/routes/subscribe/relayer.js:5:9

@Fatimaarshad10
Copy link

I encountered an issue with the redis library when using version 4, but switching to version 3.1 resolved the problem. It seems there might be compatibility or changes in behavior between versions 4 and 3.1 that affected my application. I wanted to share this information in case others face a similar issue and might find it helpful to try using version 3.1 instead

@noodles-1
Copy link

This is what I've done on my Node/Express project:

redis.js:

import { createClient } from 'redis';

export const client = createClient({
    url: process.env.REDIS_URL,
})

if (!client.isOpen) {
    await client.connect()
    console.log('Connected to Redis')
}

index.js:

import 'dotenv/config'

import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import { router } from './api.js'

const corsConfig = {
    origin: process.env.CORS_ORIGIN
}

const app = express()
const PORT = process.env.PORT

app.use(cors(corsConfig))
app.use(bodyParser.json())
app.use('/api', router)

app.listen(PORT, () => console.log(`Listening to port ${PORT}`))

In api.js, I'm simply importing functions from my actions directory such as in my case, updating a note:

updateNote.js:

import { client } from "../redis.js"
import { noteSchema } from "../schemas/index.js"
import { Repository } from "redis-om"

export async function updateNote(note) {
    const repository = new Repository(noteSchema, client)
    const result = await repository.fetch(note.noteId)

    result.title = note.title
    result.body = note.body
    result.category = note.category
    result.modified = note.modified
    await repository.save(result)
}

When running npm run start, it automatically connects to Redis and preserves that connection.

@BrentLayne
Copy link
Contributor

@joshpwrk I've opened a new issue because I'm running into the same problem as you and I've definitely found a bug with how SUNSUBSCRIBE works: #2685

@Singh-Paramvir
Copy link

please use this

const redis = require('redis');
const client = redis.createClient();

client.connect();
client.on('connect', async function() {
console.log('Connected!');
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants