[SERVER-36902] mongo shell does not abort transaction on quit Created: 28/Aug/18  Updated: 08/Jan/24  Resolved: 03/Jan/19

Status: Closed
Project: Core Server
Component/s: Shell
Affects Version/s: 4.0.1, 4.1.2
Fix Version/s: 4.1.7

Type: Improvement Priority: Major - P3
Reporter: Wan Bachtiar Assignee: A. Jesse Jiryu Davis
Resolution: Fixed Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Sprint: Repl 2018-12-17, Repl 2019-01-14
Participants:

 Description   

After executing below code in mongo shell:

/* First two lines are optional */
use test;
db.createCollection('col1');
session = db.getMongo().startSession();
session.startTransaction();
session.getDatabase("test").col1.insert({"a" : "b"});

If the user then quit (SIGINT) the shell, the transaction will still be alive in the server until transactionLifetimeLimitSeconds is reached.

Snippet MongoDB log showed that there is endSessions sent, but transaction within the session is not aborted.

2018-08-28T17:09:17.572+1000 I COMMAND  [conn4] command config.transactions appName: "MongoDB Shell" command: find { find: "transactions", filter: { _id: { id: UUID("7ba23bf5-a74c-4c71-bfa8-dabb6b3d6f46"), uid: BinData(0, E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855) } }, ntoreturn: 1, singleBatch: true, $db: "config" } planSummary: IDHACK keysExamined:0 docsExamined:0 cursorExhausted:1 numYields:0 nreturned:0 reslen:233 locks:{ Global: { acquireCount: { r: 1 } }, Database: { acquireCount: { r: 1 } }, Collection: { acquireCount: { r: 1 } } } protocol:op_msg 0ms
2018-08-28T17:09:17.572+1000 I COMMAND  [conn4] command test.col1 appName: "MongoDB Shell" command: insert { insert: "col1", ordered: true, lsid: { id: UUID("7ba23bf5-a74c-4c71-bfa8-dabb6b3d6f46") }, $clusterTime: { clusterTime: Timestamp(1535440152, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, txnNumber: 0, autocommit: false, stmtId: 0, startTransaction: true, $db: "test" } ninserted:1 keysInserted:1 numYields:0 reslen:230 locks:{} protocol:op_msg 0ms
2018-08-28T17:09:17.574+1000 I COMMAND  [conn4] command admin.$cmd appName: "MongoDB Shell" command: replSetGetStatus { replSetGetStatus: 1.0, forShell: 1.0, $clusterTime: { clusterTime: Timestamp(1535440152, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "admin" } numYields:0 reslen:893 locks:{} protocol:op_msg 0ms
2018-08-28T17:09:20.638+1000 I COMMAND  [conn4] command admin.$cmd appName: "MongoDB Shell" command: endSessions { endSessions: [ { id: UUID("71e24f05-1ec6-41f8-a333-3da8aac6000e") } ], $db: "admin" } numYields:0 reslen:163 locks:{} protocol:op_msg 0ms
2018-08-28T17:09:20.638+1000 I COMMAND  [conn4] command admin.$cmd appName: "MongoDB Shell" command: endSessions { endSessions: [ { id: UUID("7ba23bf5-a74c-4c71-bfa8-dabb6b3d6f46") } ], $db: "admin" } numYields:0 reslen:163 locks:{} protocol:op_msg 0ms
2018-08-28T17:09:20.638+1000 I NETWORK  [conn4] end connection 127.0.0.1:58100 (1 connection now open)

Until the transaction life time is reached:

2018-08-28T17:10:39.435+1000 D STORAGE  [conn2] create collection test.col1 {}
2018-08-28T17:10:39.435+1000 D STORAGE  [startPeriodicThreadToAbortExpiredTransactions] Slow WT transaction. Lifetime of SnapshotId 673 was 81863ms
2018-08-28T17:10:39.435+1000 D -        [conn2] User Assertion: NamespaceExists: a collection 'test.col1' already exists src/mongo/db/commands/dbcommands.cpp 426
2018-08-28T17:10:39.436+1000 D COMMAND  [conn2] assertion while executing command 'create' on database 'test' with arguments '{ create: "col1", lsid: { id: UUID("cc125e16-60d8-4c39-bd56-362f738aaa20") }, $clusterTime: { clusterTime: Timestamp(1535440132, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "test" }': NamespaceExists: a collection 'test.col1' already exists
2018-08-28T17:10:39.436+1000 I COMMAND  [conn2] command test.col1 appName: "MongoDB Shell" command: create { create: "col1", lsid: { id: UUID("cc125e16-60d8-4c39-bd56-362f738aaa20") }, $clusterTime: { clusterTime: Timestamp(1535440132, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "test" } numYields:0 ok:0 errMsg:"a collection 'test.col1' already exists" errName:NamespaceExists errCode:48 reslen:255 locks:{ Global: { acquireCount: { r: 1, w: 1 } }, Database: { acquireCount: { W: 1 }, acquireWaitCount: { W: 1 }, timeAcquiringMicros: { W: 74156010 } } } protocol:op_msg 74157ms
2018-08-28T17:10:39.438+1000 I COMMAND  [conn2] command admin.$cmd appName: "MongoDB Shell" command: replSetGetStatus { replSetGetStatus: 1.0, forShell: 1.0, $clusterTime: { clusterTime: Timestamp(1535440232, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "admin" } numYields:0 reslen:893 locks:{} protocol:op_msg 0ms



 Comments   
Comment by A. Jesse Jiryu Davis [ 03/Jan/19 ]

Yeah, just waiting a bit for a final patch build before I pushed.

Comment by Githook User [ 03/Jan/19 ]

Author:

{'username': 'ajdavis', 'email': 'jesse@mongodb.com', 'name': 'A. Jesse Jiryu Davis'}

Message: SERVER-36902 Abort transaction on shell exit, try 2
Branch: master
https://github.com/mongodb/mongo/commit/0507035c67a4399482cf562efbd328ec128f06c6

Comment by Eric Milkie [ 03/Jan/19 ]

Was there a final commit for this?

Comment by A. Jesse Jiryu Davis [ 20/Dec/18 ]

I thought I'd handled a rebase issue easily but I made a mistake. Fixing....

Comment by Ramon Fernandez Marina [ 20/Dec/18 ]

Reopening since one of the commits was reverted; best to not ship 4.1.7 without the test and have to make another ticket. Please re-close if this is not the right thing to do.

Comment by Githook User [ 20/Dec/18 ]

Author:

{'username': 'benety', 'email': 'benety@mongodb.com', 'name': 'Benety Goh'}

Message: Revert "SERVER-36902 Abort transaction on shell exit"

This reverts commit 759846ffced5ef84734bd917a99061edf44dd786.
Branch: master
https://github.com/mongodb/mongo/commit/8f64177ceed4bbe2ff4dc7275306a0d2151388fd

Comment by Githook User [ 20/Dec/18 ]

Author:

{'username': 'dgottlieb', 'email': 'daniel.gottlieb@mongodb.com', 'name': 'Daniel Gottlieb'}

Message: Revert "SERVER-36902 Update jsTests for new shell transaction API"

This reverts commit bf58b1ab2abfb2a3ab7a86c154f9f5954ed6f98c.
Branch: master
https://github.com/mongodb/mongo/commit/d0ff20fa00e17a30f2518d556147b178bbefab28

Comment by Githook User [ 20/Dec/18 ]

Author:

{'username': 'ajdavis', 'email': 'jesse@mongodb.com', 'name': 'A. Jesse Jiryu Davis'}

Message: SERVER-36902 Update jsTests for new shell transaction API
Branch: master
https://github.com/mongodb/mongo/commit/bf58b1ab2abfb2a3ab7a86c154f9f5954ed6f98c

Comment by Githook User [ 20/Dec/18 ]

Author:

{'username': 'ajdavis', 'email': 'jesse@mongodb.com', 'name': 'A. Jesse Jiryu Davis'}

Message: SERVER-36902 Abort transaction on shell exit
Branch: master
https://github.com/mongodb/mongo/commit/759846ffced5ef84734bd917a99061edf44dd786

Comment by Kevin Pulo [ 20/Sep/18 ]

The transaction spec already mandates that drivers MUST abort any in progress transaction before ending a ClientSession

Thanks, this is what I had missed. Since it's the responsibility of conforming clients to avoid calling endSessions when there's an active transaction, I agree that fixing the shell is appropriate (and there's no need to adjust server behaviour).

Comment by A. Jesse Jiryu Davis [ 19/Sep/18 ]

SGTM. To clarify Shane's example: a client should end any session it started before the client exits, and that includes trying to abort any transaction that is in progress on any session. Shane's just pointing out that if a client is killed with no chance to clean up, then it might leave sessions open, including sessions with transactions.

Comment by Tess Avitabile (Inactive) [ 19/Sep/18 ]

Thanks, shane.harvey. In that case, I think the shell should run abortTransaction before endSessions, so that it conforms more closely to the drivers spec.

Based on your example, perhaps it is not always the correct behavior for the shell to run endSessions on exit, but I'm not sure this is important to change.

Comment by Shane Harvey [ 19/Sep/18 ]

The transaction spec already mandates that drivers MUST abort any in progress transaction before ending a ClientSession so I think the benefits would be limited to cases where the implicit abortTransaction fails (twice).

It's worth noting that leaving in progress transactions open might be the expected behavior depending on the language. For example, this code would leave a transaction open on the server similar to shell example above:

s = client.start_session()
s.start_transaction()
client.test.col1.insert({"a" : "b"}, session=s)
# SIGINT here leaves the transaction open, neither abortTransaction or endSession is run. 

While this code would automatically abort the transaction:

with client.start_session() as s:
    s.start_transaction()
    client.test.col1.insert({"a" : "b"}, session=s)
    # SIGINT here aborts the transaction when exiting the "with" block. 

Drivers make a best effort to implicitly end sessions/transactions/cursors/etc.. whenever possible but in general it's the user's responsibility to gracefully release their resources.

Comment by Tess Avitabile (Inactive) [ 19/Sep/18 ]

There is probably not a downside to defining the behavior that way, since running abortTransaction is inexpensive. It is not something we considered in the transactions design.

shane.harvey, jesse, would it be beneficial to drivers if endSessions additionally aborted any in-progress transaction? Or do drivers already abort any in-progress transaction prior to running endSessions upon exiting or leaving the scope of the session?

Comment by Kevin Pulo [ 19/Sep/18 ]

tess.avitabile is there defined behaviour somewhere for what happens if a client runs endSessions on a session that has an active in-progress transaction? Surely this should cause the transaction to be aborted, as if the client had first run abortTranaction? Is there a downside to defining the behaviour this way?

Comment by Tess Avitabile (Inactive) [ 18/Sep/18 ]

Based on discussion with max.hirschhorn and mira.carey@mongodb.com, it would be preferable for the shell to abort in-progress transactions on quit. It kills cursors on quit, so it would be consistent and useful to also abort transactions.

Note that the shell uses endSessions instead of killSessions on quit because killSessions is an expensive operation in a sharded cluster that must talk to all replica set members of all shards. killSessions is really only meant to be run by an operator, unlike endSessions, which is cheap and can be run often by drivers and the shell. For this reason, when we implement this work, we should not use killSessions to abort in-progress transactions, but instead run abortTransaction in addition to endSessions. abortTransaction only needs to talk to the primary of each shard that participated in the transaction.

Comment by Kevin Pulo [ 29/Aug/18 ]

So we can fix the shell to abort any in-progress transaction before ending the session, but there's not really anything stopping any client from behaving in this way, ie. sending endSession while there's an in-progress transaction. Since:

  • transactions can only exist in the context of a session,
  • endSession tells the server that it's free to reap the session at any time, and
  • the session being reaped (which can now happen at any time) causes the transaction to be automatically aborted (because otherwise vital state stored on the session might have been removed),

I think it might be better to instead fix the endSession command on the server side, so any in-progress transaction is aborted (and this is also reported in the returned endSession result).

Generated at Thu Feb 08 04:44:25 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.