GoogleCloudPlatform / cloud-sql-nodejs-connector

A JavaScript library for connecting securely to your Cloud SQL instances
Apache License 2.0
66 stars 8 forks source link

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

Closed JoseChavez98 closed 3 months ago

JoseChavez98 commented 3 months ago

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!

hessjcg commented 3 months ago

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 commented 3 months ago

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

JoseChavez98 commented 3 months ago

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!

jackwotherspoon commented 3 months ago

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

JoseChavez98 commented 3 months ago

@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!

jackwotherspoon commented 3 months ago

@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 😄

JoseChavez98 commented 3 months ago

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

jackwotherspoon commented 3 months ago

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 commented 3 months ago

@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.

JoseChavez98 commented 3 months ago

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!

jackwotherspoon commented 3 months ago

@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: "my-iam-user@test.com",  // 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.

JoseChavez98 commented 3 months ago

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.

jackwotherspoon commented 3 months ago

@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.

JoseChavez98 commented 2 months ago

@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

jackwotherspoon commented 2 months ago

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 😄