cloud-sql-nodejs-connector icon indicating copy to clipboard operation
cloud-sql-nodejs-connector copied to clipboard

Error "address already in use" when trying to connect to google cloud SQL

Open JoseChavez98 opened this issue 9 months ago • 3 comments

Question

I'm connecting my NextJS app to my Cloud SQL instance. I've followed the documentation and I'm stuck in the following error.

Error: listen EADDRINUSE: address already in use /Users/josechavez/projectName/projectName/.s.PGSQL.5432
at Server.setupListenHandle [as _listen2] (node:net:1812:21)
at listenInCluster (node:net:1877:12)
at Server.listen (node:net:1976:5)
at node:internal/util:410:7
at new Promise (<anonymous>)
at Server.<anonymous> (node:internal/util:396:12)
at Connector.startLocalProxy (webpack-internal:///(rsc)/./node_modules/@google-cloud/cloud-sql-connector/dist/mjs/connector.js:207:22)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async connect (webpack-internal:///(rsc)/./src/app/lib/connect.ts:16:5)
at async prismaClientSingleton (webpack-internal:///(rsc)/./src/app/lib/prisma.ts:17:16) {
code: 'EADDRINUSE',

   errno: -48,
   syscall: 'listen',
   address: '/Users/josechavez/projectName/projectName/.s.PGSQL.5432',
   port: -1
}

Basically, the error is saying that the address is already in use, and I have no clue why. In the console, my instance seems to be correctly configured (with the basics configs, like public IP, etc.)

I've followed your examples on how to connect to the Cloud SQL

This is what my connection code looks like.

import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { connect } from './connect';

const instanceConnectionName = process.env.GOOGLE_DB_CONNECTION_NAME ?? '';
const user = process.env.GOOGLE_DB_USERNAME ?? '';
const database = process.env.GOOGLE_DB_NAME ?? '';

const prismaClientSingleton = async () => {
  try {
    return await connect({ instanceConnectionName, user, database });
  } catch (error) {
    console.error(error);
  }
};

const globalForPrisma = globalThis as unknown as {
  prisma: any | undefined;
};

const { prisma, close } = await (globalForPrisma.prisma ??
  prismaClientSingleton());
const adapter = new PrismaAdapter(prisma.session, prisma.user);

export default prisma;
export { adapter };

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

Additional Context

I've tried with 5432, 5433, ... and so on. but nothing still.

I've also tried to use this whole path that is mentioned in the docs /cloudsql/INSTANCE_CONNECTION_NAME/.s.PGSQL.5432

But this time I get an error that says no access/view permissions, but I'm not sure if this is the way.

Someone please help me, Thanks in advance!

JoseChavez98 avatar May 07 '24 14:05 JoseChavez98

Hi @JoseChavez98,

Here's my best guess at what is wrong: The startLocalProxy() function opens and listens on a unix socket on the local host. If startLocalProxy() was called by multiple node processes running at the same time, then it would attempt to open same unix socket for listening more than once, resulting in the error Error: listen EADDRINUSE: address already in use for all but the first node process.

Is this a possible cause?

-Jonathan

hessjcg avatar May 09 '24 16:05 hessjcg

Hi @JoseChavez98, I wasn't able to reproduce this behavior. Feel free to comment back if want more help.

hessjcg avatar May 13 '24 16:05 hessjcg

Hello Jonathan @hessjcg , thanks for the help. I'm not sure if your above assumption is happening. I'm just running the library as in the example you guys provided.

This is how I connect to the cloud.

import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { connect } from './connect';

const instanceConnectionName = process.env.GOOGLE_DB_CONNECTION_NAME ?? '';
const user = process.env.GOOGLE_DB_USERNAME ?? '';
const database = process.env.GOOGLE_DB_NAME ?? '';

const prismaClientSingleton = async () => {
  try {
    return await connect({ instanceConnectionName, user, database });
  } catch (error) {
    console.error(error);
  }
};

const globalForPrisma = globalThis as unknown as {
  prisma: any | undefined;
};

const { prisma, close } = await (globalForPrisma.prisma ??
  prismaClientSingleton());
const adapter = new PrismaAdapter(prisma.session, prisma.user);

export default prisma;
export { adapter };

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

and the connect function is exactly like in your examples.

import { resolve } from 'node:path';
import {
  AuthTypes,
  Connector,
  IpAddressTypes,
} from '@google-cloud/cloud-sql-connector';
import { PrismaClient } from '@prisma/client';

export async function connect({
  instanceConnectionName,
  user,
  database,
}: {
  instanceConnectionName: string;
  user: string;
  database: string;
}) {
  const path = resolve('.s.PGSQL.5432'); // postgres-required socket filename
  const connector = new Connector();
  await connector.startLocalProxy({
    instanceConnectionName,
    ipType: IpAddressTypes.PUBLIC,
    authType: AuthTypes.IAM,
    listenOptions: { path },
  });

  // note that the host parameter needs to point to the parent folder of
  // the socket provided in the `path` Connector option, in this example
  // that is going to be the current working directory
  const datasourceUrl = `postgresql://${user}@localhost/${database}?host=${process.cwd()}`;
  const prisma = new PrismaClient({ datasourceUrl });
  

  return {
    prisma,
    async close() {
      await prisma.$disconnect();
      connector.close();
    },
  };
}

This is currently stopping me from moving forward. I will be happy if you let me add you to my project for testing purposes. Thanks in advance!

JoseChavez98 avatar May 14 '24 12:05 JoseChavez98

I wonder if the cause of this error is maybe related to https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/issues/349

jackwotherspoon avatar May 17 '24 20:05 jackwotherspoon

@jackwotherspoon I'm gonna say maybe. I made some tests and I was able to successfully connect to my DB using the traditional connection (without the proxy). So the problem is related to that localProxy implementation/connection. Is there a chance We can have this looked up? Its a real deal breaker for me .

Thanks!

JoseChavez98 avatar May 22 '24 01:05 JoseChavez98

@jackwotherspoon I'm gonna say maybe. I made some tests and I was able to successfully connect to my DB using the traditional connection (without the proxy). So the problem is related to that localProxy implementation/connection. Is there a chance We can have this looked up? Its a real deal breaker for me .

@JoseChavez98 Yes something isn't working the way we would like it to. I will be digging into this over the next day or so and fixing these issues 😄

jackwotherspoon avatar May 22 '24 15:05 jackwotherspoon

thanks @jackwotherspoon, let me know if I can help with anything.

JoseChavez98 avatar May 22 '24 16:05 JoseChavez98

thanks @jackwotherspoon, let me know if I can help with anything.

@JoseChavez98 thanks! Will keep you in the loop if I have any additional questions 😄

jackwotherspoon avatar May 22 '24 20:05 jackwotherspoon

@JoseChavez98 I was able to reproduce this error and now the cause of it makes sense to me. So let me try and explain what is happening to cause the address already in use error.

TLDR; The close() returned from const { prisma, close } = await connect({...}); must be called to properly delete the unix socket file ending in s.PGSQL.5432. (we can probably update our docs/sample to have a try/catch to make sure the close() is being called)

Let me give a detailed explanation using the sample you used as reference:

https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/blob/dc7e0d0a23a419f539ce971350bf76694abcc4c8/examples/prisma/postgresql/connect.ts#L15-L46

In the sample connector.startLocalProxy(...) creates a local unix socket file at the location specified by path in the listenOptions. So in this example and yours it will create the .s.PGSQL.5432 file in the current working directory.

The connect function returns the Prisma client, along with a close() function that when called will disconnect the client as well as closes the Cloud SQL Connector object by callingconnector.close(). This last part is the critical piece that is resulting in your error.

If the close() is not called, the connector is not properly cleaned up. Part of the Connector's cleanup is to close and delete all local unix socket files. @JoseChavez98 from what I can see, your code never calls close() thus the local unix domain socket file is not being deleted, hence why on the next call/time your program is run you are seeing the error address already in use, because the file is still there from the previous run.

Steps to fix the error:

  1. Delete the stale local unix domain socket file located at /Users/josechavez/projectName/projectName/.s.PGSQL.5432
  2. Add the call await close() to your code. You will want to call it whenever you are done interacting with the Prisma client to disconnect the client and close the connector cleanly. (Note: Probably will want to add a try/catch that also calls await close() when an error is thrown. This way the client and connector are also cleaned up when errors are thrown from the client and before you program exits.

I'll leave this issue open and take an action item for myself of updating our README, examples to clearly point out the close() must be called.

jackwotherspoon avatar May 26 '24 14:05 jackwotherspoon

I confirm that that was the problem, thanks for the help @jackwotherspoon . I know this might be unrelated, but I'm having another issue. It looks like It starts the local proxy but didn't quite make it through the PrismaClient. It feels like it's in a loop or if It is waiting for something. Below is my modest way of debugging and the result in the console.

Screenshot 2024-05-26 at 10 11 02 PM

Logs

Screenshot 2024-05-26 at 10 14 42 PM

As you can see, It doesn't make it through the construction of PrismaClient object.

I'll appreciate your input again.

Thanks for all the help!

JoseChavez98 avatar May 27 '24 03:05 JoseChavez98

@JoseChavez98 do you mind trying this basic SELECT NOW() example test the basic connectivity. If it passes then it is most likely your prisma usage. Also I think since the original question is answered it might be beneficial to create a new issue to fix the Prisma client hanging if the basis test does not work 😄

const { connect } = await import('./connect.js');

const { prisma, close } = await connect({
    instanceConnectionName: "my-project:my-region:my-instance",  // change these values
    user: "[email protected]",  // change these values
    database: "my-db",  // change these values
});
try {
    const [{ now }] = await prisma.$queryRaw`SELECT NOW() as now`
    console.log(now); // prints returned time value from server)
    await close();
} catch (e) {
    console.log("Error occured, closing!");
    await close();
    throw e
}

If the time is properly printed from the above example then again its probably your usage of Prisma that may be causing the client to hang.

jackwotherspoon avatar May 27 '24 17:05 jackwotherspoon

Thanks for all the help @jackwotherspoon .

To respond to my last question, There was a mismatch between prisma and prismaClient library versions. That was causing the weird behavior.

JoseChavez98 avatar May 28 '24 01:05 JoseChavez98

@JoseChavez98 Glad you were able to get it to work! Thanks for being patient here 😄

I've put up a PR adding a more detailed comment to the examples for Prisma clearly mentioning that close() should be called. Hopefully this helps future users not run into the same issue you faced here. Thanks for raising this issue and bringing it to our attention, we always appreciated the feedback and finding areas to improve our libraries/docs.

jackwotherspoon avatar May 28 '24 13:05 jackwotherspoon

@jackwotherspoon thanks for the help. I'd appreciate it if you could take a look at this question since it is slightly related to this issue. Or maybe you know someone who could help me. Thanks in advance

JoseChavez98 avatar May 30 '24 13:05 JoseChavez98

I'd appreciate it if you could take a look at this question since it is slightly related to this issue.

I will take a look at this question sometime today or tomorrow at the latest 😄

jackwotherspoon avatar May 30 '24 17:05 jackwotherspoon

Hey @jackwotherspoon. I'm hitting the same issue here, confirmed to be caused by my application attempting to create another socket file when one already exists.

I have a question here regarding what the expected process is for handling this. For example, since my database calls are asynchronous, the connect() is called before the previous one has a chance to properly close().

This can be mitigated by making the socket filename dynamic (such as including the timestamp), but this feels very wrong.

Additionally, how is this supposed to work if I have multiple users connecting to the application at one time? I feel like I'm missing something important here in order to move from the example => production.

Any guidance would be helpful.

Thanks!

brandinchiu avatar Feb 06 '25 16:02 brandinchiu

@brandinchiu This is great feedback! I have gone ahead and created https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector/issues/416 where I outline how we should make our Prisma examples more production friendly.

TLDR; You will want to move the following outside of connect and have the Connector and local proxy shared across PrismaClients for different users. You only need one Connector and one local proxy per Cloud SQL instance.

You will want to move these lines outside of connect and then pass your connector into connect

const path = resolve('.s.PGSQL.5432'); // postgres-required socket filename 
const connector = new Connector(); 
await connector.startLocalProxy({ 
   instanceConnectionName, 
   ipType: IpAddressTypes.PUBLIC, 
   authType: AuthTypes.IAM, 
   listenOptions: {path}, 
}); 

jackwotherspoon avatar Feb 06 '25 16:02 jackwotherspoon

Thanks, this is very helpful. I'll keep an eye on #416 from here.

brandinchiu avatar Feb 06 '25 16:02 brandinchiu