summaryrefslogtreecommitdiff
path: root/jstests/sharding/transactions_stale_database_version_errors.js
blob: 0302162cf3ccbe74c019951f159ded1ed4037674 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Tests mongos behavior on stale database version errors received in a transaction.
//
// @tags: [requires_sharding, uses_transactions, uses_multi_shard_transaction]
(function() {
    "use strict";

    load("jstests/sharding/libs/sharded_transactions_helpers.js");

    const dbName = "test";
    const collName = "foo";

    const st = new ShardingTest({shards: 2, mongos: 1, config: 1});

    enableStaleVersionAndSnapshotRetriesWithinTransactions(st);

    // Set up two unsharded collections in different databases with shard0 as their primary.

    assert.writeOK(st.s.getDB(dbName)[collName].insert({_id: 0}, {writeConcern: {w: "majority"}}));
    assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
    st.ensurePrimaryShard(dbName, st.shard0.shardName);

    const session = st.s.startSession();
    const sessionDB = session.getDatabase(dbName);

    //
    // Stale database version on first overall command should succeed.
    //

    session.startTransaction();

    // No database versioned requests have been sent to Shard0, so it is stale.
    assert.commandWorked(sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}));

    assert.commandWorked(session.commitTransaction_forTesting());

    //
    // Stale database version on second command to a shard should fail.
    //

    st.ensurePrimaryShard(dbName, st.shard1.shardName);

    session.startTransaction();

    // Find is not database versioned so it will not trigger SDV or a refresh on Shard0.
    assert.commandWorked(sessionDB.runCommand({find: collName, filter: {_id: 0}}));

    // Distinct is database versioned, so it will trigger SDV. The router will retry and the retry
    // will discover the transaction was aborted, because a previous statement had completed on
    // Shard0.
    let res = assert.commandFailedWithCode(
        sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}),
        ErrorCodes.NoSuchTransaction);
    assert.eq(res.errorLabels, ["TransientTransactionError"]);

    assertNoSuchTransactionOnAllShards(
        st, session.getSessionId(), session.getTxnNumber_forTesting());
    assert.commandFailedWithCode(session.abortTransaction_forTesting(),
                                 ErrorCodes.NoSuchTransaction);

    //
    // Stale database version on first command to a new shard should succeed.
    //

    // Create a new database on Shard0.
    const otherDbName = "other_test";
    const otherCollName = "bar";

    assert.writeOK(
        st.s.getDB(otherDbName)[otherCollName].insert({_id: 0}, {writeConcern: {w: "majority"}}));
    assert.commandWorked(st.s.adminCommand({enableSharding: otherDbName}));
    st.ensurePrimaryShard(otherDbName, st.shard0.shardName);

    const sessionOtherDB = session.getDatabase(otherDbName);

    // Advance the router's cached last committed opTime for Shard0, so it chooses a read timestamp
    // after the collection is created on shard1, to avoid SnapshotUnavailable.
    assert.commandWorked(
        sessionOtherDB.runCommand({find: otherCollName}));  // Not database versioned.
    assert.writeOK(sessionDB[collName].insert({_id: 1}, {writeConcern: {w: "majority"}}));

    session.startTransaction();

    // Target the first database which is on Shard1.
    assert.commandWorked(sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}));

    // Targets the new database on Shard0 which is stale, so a database versioned request should
    // trigger SDV.
    assert.commandWorked(
        sessionOtherDB.runCommand({distinct: otherCollName, key: "_id", query: {_id: 0}}));

    assert.commandWorked(session.commitTransaction_forTesting());

    //
    // The final StaleDbVersion error should be returned if the router exhausts its retries.
    //

    st.ensurePrimaryShard(dbName, st.shard0.shardName);
    st.ensurePrimaryShard(otherDbName, st.shard1.shardName);

    // Disable database metadata refreshes on the stale shard so it will indefinitely return a stale
    // version error.
    assert.commandWorked(st.rs0.getPrimary().adminCommand(
        {configureFailPoint: "skipDatabaseVersionMetadataRefresh", mode: "alwaysOn"}));

    session.startTransaction();

    // Target Shard1, to verify the transaction on it is implicitly aborted later.
    assert.commandWorked(sessionOtherDB.runCommand({find: otherCollName}));

    // Target the first database which is on Shard0. The shard is stale and won't refresh its
    // metadata, so mongos should exhaust its retries and implicitly abort the transaction.
    res = assert.commandFailedWithCode(
        sessionDB.runCommand({distinct: collName, key: "_id", query: {_id: 0}}),
        ErrorCodes.StaleDbVersion);
    assert.eq(res.errorLabels, ["TransientTransactionError"]);

    // Verify all shards aborted the transaction.
    assertNoSuchTransactionOnAllShards(
        st, session.getSessionId(), session.getTxnNumber_forTesting());
    assert.commandFailedWithCode(session.abortTransaction_forTesting(),
                                 ErrorCodes.NoSuchTransaction);

    assert.commandWorked(st.rs0.getPrimary().adminCommand(
        {configureFailPoint: "skipDatabaseVersionMetadataRefresh", mode: "off"}));

    disableStaleVersionAndSnapshotRetriesWithinTransactions(st);

    st.stop();
})();