/**
 * 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, requires_journaling]
 */

load('jstests/libs/parallelTester.js');  // For ScopedThread

(function() {
    'use strict';

    // 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"');
        return;
    }

    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=(archive=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 ScopedThread(
            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);

    // 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=(archive=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);
})();
