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

fix: treat external connection as open #1243

Merged
merged 3 commits into from
Aug 6, 2024
Merged

Conversation

nechaido
Copy link
Contributor

@nechaido nechaido commented Aug 5, 2024

This addresses: #885

Here is the resulting "check trace" on why advisory lock is never released for external Client

exhibit A: It checks that the connection is connected and only if it is connected -- releases the lock

if (db.connected()) {
      if (!options.noLock) {
        await unlock(db).catch((error: unknown) => {
          logger.warn((error as Error).message);
        });
      }

      await db.close();
    }

exhibit B: Connection status is decided by this variable

    connected: () => connectionStatus === ConnectionStatus.CONNECTED,

exhibit C: When the client is created the connection status is not updated

const createConnection: DBConnection['createConnection'] = () =>
    new Promise((resolve, reject) => {
      if (isExternalClient || connectionStatus === ConnectionStatus.CONNECTED) {
        resolve();

exhibit D: The initial value is "DISCONNECTED"

let connectionStatus = ConnectionStatus.DISCONNECTED;

Alternatively, this can be fixed by:

  • Setting the connectionStatus variable to ConnectionStatus.CONNECTED
  • Rewriting the lock check to acknowledge the external client n some way

The provded solution seems more like the most one.
If any maintainer can advise on a better solution -- I would be glad to update the PR.

@nechaido nechaido requested a review from Shinigami92 as a code owner August 5, 2024 19:43
@nechaido nechaido changed the title Treat external connection as open feat: treat external connection as open Aug 5, 2024
@Shinigami92 Shinigami92 added c: bug Something isn't working p: 1-normal Nothing urgent labels Aug 5, 2024
@Shinigami92 Shinigami92 added this to the v7.x milestone Aug 5, 2024
Copy link

github-actions bot commented Aug 5, 2024

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 92.03% (🎯 90%)
⬆️ +0.46%
3164 / 3438
🟢 Statements 92.03% (🎯 90%)
⬆️ +0.46%
3164 / 3438
🟢 Functions 95.84% (🎯 90%)
⬆️ +0.37%
254 / 265
🟢 Branches 90.03% (🎯 85%)
⬆️ +0.15%
813 / 903
File Coverage
File Stmts % Branch % Funcs % Lines Uncovered Lines
Unchanged Files
src/db.ts 84.29% 89.28% 87.5% 84.29% 75-77, 107, 110-119, 121-122, 162-164
src/index.ts 100% 100% 100% 100%
src/migration.ts 69.48% 78.57% 61.53% 69.48% 64, 67-69, 71-83, 112-118, 123-127, 130, 132-136, 138-145, 148, 151-156, 158-159, 216-217, 243-245, 253-254, 271-274, 303-304
src/migrationBuilder.ts 95.43% 92.85% 88.88% 95.43% 354-358, 497-499, 501-502, 526-527
src/runner.ts 75.69% 61.4% 90% 75.69% 42, 70-71, 80-81, 130-131, 173-176, 185-189, 202, 206-207, 209-211, 213-219, 238, 240-246, 249, 262, 274, 276-282, 284-287, 290-293, 303-304, 313-315, 324, 326, 328-335
src/sqlMigration.ts 90% 100% 80% 90% 45-46, 48-49
src/types.ts 100% 100% 100% 100%
src/operations/sql.ts 100% 100% 100% 100%
src/operations/casts/createCast.ts 100% 100% 100% 100%
src/operations/casts/dropCast.ts 100% 100% 100% 100%
src/operations/casts/index.ts 100% 100% 100% 100%
src/operations/domains/alterDomain.ts 100% 100% 100% 100%
src/operations/domains/createDomain.ts 100% 100% 100% 100%
src/operations/domains/dropDomain.ts 100% 100% 100% 100%
src/operations/domains/index.ts 100% 100% 100% 100%
src/operations/domains/renameDomain.ts 100% 100% 100% 100%
src/operations/domains/shared.ts 100% 100% 100% 100%
src/operations/extensions/createExtension.ts 100% 100% 100% 100%
src/operations/extensions/dropExtension.ts 100% 100% 100% 100%
src/operations/extensions/index.ts 100% 100% 100% 100%
src/operations/extensions/shared.ts 100% 100% 100% 100%
src/operations/functions/createFunction.ts 95.58% 94.44% 100% 95.58% 71-73
src/operations/functions/dropFunction.ts 100% 100% 100% 100%
src/operations/functions/index.ts 100% 100% 100% 100%
src/operations/functions/renameFunction.ts 100% 100% 100% 100%
src/operations/functions/shared.ts 100% 100% 100% 100%
src/operations/grants/grantOnSchemas.ts 100% 100% 100% 100%
src/operations/grants/grantOnTables.ts 100% 100% 100% 100%
src/operations/grants/grantRoles.ts 100% 100% 100% 100%
src/operations/grants/index.ts 100% 100% 100% 100%
src/operations/grants/revokeOnSchemas.ts 100% 100% 100% 100%
src/operations/grants/revokeOnTables.ts 100% 100% 100% 100%
src/operations/grants/revokeRoles.ts 100% 100% 100% 100%
src/operations/grants/shared.ts 95.65% 85.71% 100% 95.65% 71
src/operations/indexes/createIndex.ts 96.29% 95.23% 100% 96.29% 74-75
src/operations/indexes/dropIndex.ts 100% 100% 100% 100%
src/operations/indexes/index.ts 100% 100% 100% 100%
src/operations/indexes/shared.ts 88% 86.95% 100% 88% 22, 32-35, 47
src/operations/materializedViews/alterMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/createMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/dropMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/index.ts 100% 100% 100% 100%
src/operations/materializedViews/refreshMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/renameMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/renameMaterializedViewColumn.ts 100% 100% 100% 100%
src/operations/materializedViews/shared.ts 100% 87.5% 100% 100%
src/operations/operators/addToOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/createOperator.ts 100% 100% 100% 100%
src/operations/operators/createOperatorClass.ts 100% 83.33% 100% 100%
src/operations/operators/createOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/dropOperator.ts 100% 100% 100% 100%
src/operations/operators/dropOperatorClass.ts 100% 100% 100% 100%
src/operations/operators/dropOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/index.ts 100% 100% 100% 100%
src/operations/operators/removeFromOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/renameOperatorClass.ts 100% 100% 100% 100%
src/operations/operators/renameOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/shared.ts 85.71% 75% 100% 85.71% 24-25, 38
src/operations/policies/alterPolicy.ts 100% 100% 100% 100%
src/operations/policies/createPolicy.ts 100% 100% 100% 100%
src/operations/policies/dropPolicy.ts 100% 100% 100% 100%
src/operations/policies/index.ts 100% 100% 100% 100%
src/operations/policies/renamePolicy.ts 100% 100% 100% 100%
src/operations/policies/shared.ts 100% 100% 100% 100%
src/operations/roles/alterRole.ts 100% 100% 100% 100%
src/operations/roles/createRole.ts 100% 75% 100% 100%
src/operations/roles/dropRole.ts 100% 100% 100% 100%
src/operations/roles/index.ts 100% 100% 100% 100%
src/operations/roles/renameRole.ts 100% 100% 100% 100%
src/operations/roles/shared.ts 98.07% 76.92% 100% 98.07% 78
src/operations/schemas/createSchema.ts 100% 100% 100% 100%
src/operations/schemas/dropSchema.ts 100% 100% 100% 100%
src/operations/schemas/index.ts 100% 100% 100% 100%
src/operations/schemas/renameSchema.ts 100% 100% 100% 100%
src/operations/sequences/alterSequence.ts 94.11% 75% 100% 94.11% 23
src/operations/sequences/createSequence.ts 100% 100% 100% 100%
src/operations/sequences/dropSequence.ts 100% 100% 100% 100%
src/operations/sequences/index.ts 100% 100% 100% 100%
src/operations/sequences/renameSequence.ts 100% 100% 100% 100%
src/operations/sequences/shared.ts 78.57% 68.75% 100% 78.57% 41, 43-44, 49-50, 63-64, 69-70
src/operations/tables/addColumns.ts 100% 80% 100% 100%
src/operations/tables/addConstraint.ts 100% 100% 100% 100%
src/operations/tables/alterColumn.ts 88.57% 76.47% 100% 88.57% 75, 82-85, 87-89
src/operations/tables/alterTable.ts 100% 100% 100% 100%
src/operations/tables/createTable.ts 86.2% 66.66% 100% 86.2% 44-48, 65, 75-76
src/operations/tables/dropColumns.ts 100% 100% 100% 100%
src/operations/tables/dropConstraint.ts 100% 100% 100% 100%
src/operations/tables/dropTable.ts 100% 100% 100% 100%
src/operations/tables/index.ts 100% 100% 100% 100%
src/operations/tables/renameColumn.ts 100% 100% 100% 100%
src/operations/tables/renameConstraint.ts 100% 100% 100% 100%
src/operations/tables/renameTable.ts 100% 100% 100% 100%
src/operations/tables/shared.ts 82.31% 78.46% 80% 82.31% 137-138, 141-142, 195-199, 224, 228-229, 248-249, 274-275, 278-285, 288-289, 426-431, 433-436, 438-448, 450-451
src/operations/triggers/createTrigger.ts 86.41% 68.18% 100% 86.41% 53-54, 66-67, 70-71, 74-77, 113
src/operations/triggers/dropTrigger.ts 100% 100% 100% 100%
src/operations/triggers/index.ts 100% 100% 100% 100%
src/operations/triggers/renameTrigger.ts 100% 100% 100% 100%
src/operations/triggers/shared.ts 100% 100% 100% 100%
src/operations/types/addTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/addTypeValue.ts 100% 100% 100% 100%
src/operations/types/createType.ts 100% 100% 100% 100%
src/operations/types/dropType.ts 100% 100% 100% 100%
src/operations/types/dropTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/index.ts 100% 100% 100% 100%
src/operations/types/renameType.ts 100% 100% 100% 100%
src/operations/types/renameTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/renameTypeValue.ts 100% 100% 100% 100%
src/operations/types/setTypeAttribute.ts 100% 100% 100% 100%
src/operations/views/alterView.ts 100% 100% 100% 100%
src/operations/views/alterViewColumn.ts 100% 100% 100% 100%
src/operations/views/createView.ts 100% 100% 100% 100%
src/operations/views/dropView.ts 100% 100% 100% 100%
src/operations/views/index.ts 100% 100% 100% 100%
src/operations/views/renameView.ts 100% 100% 100% 100%
src/operations/views/shared.ts 100% 66.66% 100% 100%
src/utils/PgLiteral.ts 88.88% 100% 75% 88.88% 37-38
src/utils/StringIdGenerator.ts 100% 100% 100% 100%
src/utils/createSchemalize.ts 100% 100% 100% 100%
src/utils/createTransformer.ts 100% 100% 100% 100%
src/utils/decamelize.ts 100% 100% 100% 100%
src/utils/escapeValue.ts 100% 100% 100% 100%
src/utils/formatLines.ts 100% 100% 100% 100%
src/utils/formatParams.ts 100% 100% 100% 100%
src/utils/getMigrationTableSchema.ts 100% 100% 100% 100%
src/utils/getSchemas.ts 100% 100% 100% 100%
src/utils/identity.ts 100% 100% 100% 100%
src/utils/index.ts 100% 100% 100% 100%
src/utils/intersection.ts 100% 100% 100% 100%
src/utils/makeComment.ts 100% 100% 100% 100%
src/utils/quote.ts 100% 100% 100% 100%
src/utils/toArray.ts 100% 100% 100% 100%
src/utils/types.ts 100% 100% 100% 100%
Generated in workflow #1028

@Shinigami92
Copy link
Collaborator

It looks like it is now the same check as in line 64

if (isExternalClient || connectionStatus === ConnectionStatus.CONNECTED) {

And so this might make sense 🤔

However I'm now not sure anymore how the code behaves when it comes to isExternalClient === true && connectionStatus === ConnectionStatus.ERROR

if (isExternalClient || connectionStatus === ConnectionStatus.CONNECTED) {
resolve();
} else if (connectionStatus === ConnectionStatus.ERROR) {
reject(
new Error('Connection already failed, do not try to connect again')
);
} else {

Do we want to check for connectionStatus when it is an external client? Or do we want to ignore it?
I'm just asking because I just looked around 1 minute at the code, and have not dug deep into it.


Beside that, you need to run the formatter to make lint happy 😸

@Shinigami92 Shinigami92 linked an issue Aug 5, 2024 that may be closed by this pull request
@nechaido
Copy link
Contributor Author

nechaido commented Aug 5, 2024

However I'm now not sure anymore how the code behaves when it comes to isExternalClient === true && connectionStatus === ConnectionStatus.ERROR

So ATM the connection status of the external is ignored and looking at the code it looks like the external connection is assumed to be a valid initialized connection. So the current implementation seems like it is in the spirit of the original.

If you feel like it I can replace the check on

if (isExternalClient || connectionStatus === ConnectionStatus.CONNECTED) {
with a .connected() invocation.

in the meanwhile, i have added a test for this behaviour.

@Shinigami92
Copy link
Collaborator

[...]

If you feel like it I can replace the check on

if (isExternalClient || connectionStatus === ConnectionStatus.CONNECTED) {

with a .connected() invocation.
[...]

I also thought about this.

So we could add an internal function like

const connected: DBConnection['connected'] = () => {
  return isExternalClient || connectionStatus === ConnectionStatus.CONNECTED
}

and then use this in both places.


One more further thinking idea that came into my mind is: Should we add a ConnectionStatus.EXTERNAL? 👀
That one would then have the meaning of "it is externally managed and so not in control of node-pg-migrate" 🤔

@nechaido
Copy link
Contributor Author

nechaido commented Aug 6, 2024

@Shinigami92 I have both extracted a check and introduced a new connection status.
There is no test for the latter as it is an internal private logic that is not exposed anywhere so i do not see a reason to try to cover it.

@Shinigami92
Copy link
Collaborator

I will merge and release this when I have dedicated time and my non-company PC

Do you think this is a bugfix (-> 7.6.1) or is this a new feature (-> 7.7.0)?

@nechaido
Copy link
Contributor Author

nechaido commented Aug 6, 2024

Do you think this is a bugfix (-> 7.6.1) or is this a new feature (-> 7.7.0)?

This is a bug fix for me as I encountered it after starting to scale my API servers horizontally.
I was forced to implement a startup lock myself to ensure that migrations were checked on each server in a queue.
But even after implementing the lock on my side, the server startup was still failing with "Another migration is running".
For now, I have fixed it on my side with noLock options.

So I'd vote on 7.6.1.

P.S.: it would be cool to implement a waiting lock so that instead of failing it would wait for the lock to be released.
Unfortunately, there is something odd with the way 'pg_advisory_lock' works (it returns the response with no data right away instead of waiting for the lock).
I can submit a polling implementation instead. Which will try to acquire the lock, if not acquired it will wait for the TIMEOUT and repeat the process. this will be limited to the ATTEMPTS number.

If you are interested in this -- I can submit the pr sometimes later.

@Shinigami92 Shinigami92 changed the title feat: treat external connection as open fix: treat external connection as open Aug 6, 2024
@Shinigami92
Copy link
Collaborator

P.S.: it would be cool to implement a waiting lock so that instead of failing it would wait for the lock to be released.

Unfortunately, there is something odd with the way 'pg_advisory_lock' works (it returns the response with no data right away instead of waiting for the lock).

I can submit a polling implementation instead. Which will try to acquire the lock, if not acquired it will wait for the TIMEOUT and repeat the process. this will be limited to the ATTEMPTS number.

If you are interested in this -- I can submit the pr sometimes later.

Sounds good to me

@Shinigami92 Shinigami92 merged commit a4bf111 into salsita:main Aug 6, 2024
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: bug Something isn't working p: 1-normal Nothing urgent
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Database not unlocked when External DB Client is used
2 participants