/**
 * This test is only for the WiredTiger storage engine. Test to reproduce recovery bugs in WT from
 * WT-2696 and WT-2706.  Have several threads inserting unique data.  Kill -9 mongod.  After
 * restart and recovery verify that all expected records inserted are there and no records in the
 * middle of the data set are lost.
 *
 * @tags: [requires_wiredtiger]
 */

import {Thread} from "jstests/libs/parallelTester.js";

// Skip this test if not running with the "wiredTiger" storage engine.
if (jsTest.options().storageEngine && jsTest.options().storageEngine !== 'wiredTiger') {
    jsTest.log('Skipping test because storageEngine is not "wiredTiger"');
    quit();
}

var dbpath = MongoRunner.dataPath + 'wt_unclean_shutdown';
resetDbpath(dbpath);

var conn = MongoRunner.runMongod({
    dbpath: dbpath,
    noCleanData: true,
    // Modify some WT settings:
    // - Disable checkpoints based on log size so that we know no checkpoint gets written.
    // - Explicitly set checkpoints to 60 seconds in case the default ever changes.
    // - Turn off archiving and compression for easier debugging if there is a failure.
    // - Make the maximum file size small to encourage lots of file changes.  WT-2706 was
    // related to log file switches.
    wiredTigerEngineConfigString:
        'checkpoint=(wait=60,log_size=0),log=(remove=false,compressor=none,file_max=10M)'
});
assert.neq(null, conn, 'mongod was unable to start up');

var insertWorkload = function(host, start, end) {
    var conn = new Mongo(host);
    var testDB = conn.getDB('test');

    // Create a record larger than 128K which is the threshold to doing an unbuffered log
    // write in WiredTiger.
    var largeString = 'a'.repeat(1024 * 128);

    for (var i = start; i < end; i++) {
        var doc = {_id: i, x: 0};
        // One of the bugs, WT-2696, was related to large records that used the unbuffered
        // log code.  Periodically insert the large record to stress that code path.
        if (i % 30 === 0) {
            doc.x = largeString;
        }

        try {
            testDB.coll.insert(doc);
        } catch (e) {
            // Terminate the loop when mongod is killed.
            break;
        }
    }
    // Return i, the last record we were trying to insert.  It is possible that mongod gets
    // killed in the middle but not finding a record at the end is okay.  We're only
    // interested in records missing in the middle.
    return {start: start, end: i};
};

// Start the insert workload threads with partitioned input spaces.
// We don't run long enough for threads to overlap.  Adjust the per thread value if needed.
var max_per_thread = 1000000;
var num_threads = 8;
var threads = [];
for (var i = 0; i < num_threads; i++) {
    var t = new Thread(
        insertWorkload, conn.host, i * max_per_thread, max_per_thread + (i * max_per_thread));
    threads.push(t);
    t.start();
}

// Sleep for sometime less than a minute so that mongod has not yet written a checkpoint.
// That will force WT to run recovery all the way from the beginning and we can detect missing
// records.  Sleep for 40 seconds to generate plenty of workload.
sleep(40000);

// Mongod needs an unclean shutdown so that WT recovery is forced on restart and we can detect
// any missing records.
MongoRunner.stopMongod(conn, 9, {allowedExitCode: MongoRunner.EXIT_SIGKILL});

// Retrieve the start and end data from each thread.
var retData = [];
threads.forEach(function(t) {
    t.join();
    retData.push(t.returnData());
});

// Restart the mongod.  This forces WT to run recovery.
conn = MongoRunner.runMongod({
    dbpath: dbpath,
    noCleanData: true,
    wiredTigerEngineConfigString: 'log=(remove=false,compressor=none,file_max=10M)'
});
assert.neq(null, conn, 'mongod should have restarted');

// Verify that every item between start and end for every thread exists in the collection now
// that recovery has completed.
var coll = conn.getDB('test').coll;
for (var i = 0; i < retData.length; i++) {
    // For each start and end, verify every data item exists.
    var thread_data = retData[i];
    var absent = null;
    var missing = null;
    for (var j = thread_data.start; j <= thread_data.end; j++) {
        var idExists = coll.find({_id: j}).count() > 0;
        // The verification is a bit complex.  We only want to fail if records in the middle
        // of the range are missing.  Records at the end may be missing due to when mongod
        // was killed and records in memory are lost.  It is only a bug if a record is missing
        // and a subsequent record exists.
        if (!idExists) {
            absent = j;
        } else if (absent !== null) {
            missing = absent;
            break;
        }
    }
    assert.eq(null,
              missing,
              'Thread ' + i + ' missing id ' + missing +
                  ' start and end for all threads: ' + tojson(retData));
}

MongoRunner.stopMongod(conn);
