summaryrefslogtreecommitdiff
path: root/jstests/sharding/transactions_stale_database_version_errors.js
blob: 0fafc6c00e572c52d27279a78fed159422102abb (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
130
131
132
133
134
135
136
// 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();

    // Run first statement on different database so distinct still triggers a SDV.
    const dbName2 = "test2";
    const sessionDB2 = session.getDatabase(dbName2);

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

    assert.commandWorked(sessionDB2.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();
})();