/*-
 * Public Domain 2014-2017 MongoDB, Inc.
 * Public Domain 2008-2014 WiredTiger, Inc.
 *
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
#include "test_util.h"

/*
 * JIRA ticket reference: HELP-4355
 * Test rwlock collapse under load.
 */
#define	MAX_THREADS 1000
#define	READS_PER_WRITE	10000
//#define	READS_PER_WRITE	1000000
//#define	READS_PER_WRITE	100

#define	CHECK_CORRECTNESS 1
//#define	USE_POSIX	1

static WT_RWLOCK rwlock;
static pthread_rwlock_t p_rwlock;
static bool running;
static uint64_t shared_counter;

void *thread_rwlock(void *);
void *thread_dump(void *);

int
main(int argc, char *argv[])
{
	TEST_OPTS *opts, _opts;
	struct timespec te, ts;
	pthread_t dump_id, id[MAX_THREADS];
	int i;

	if (!testutil_enable_long_tests())	/* Ignore unless requested */
		return (EXIT_SUCCESS);

	opts = &_opts;
	memset(opts, 0, sizeof(*opts));
	opts->nthreads = 100;
	opts->nops = 1000000; /* per thread */
	testutil_check(testutil_parse_opts(argc, argv, opts));
	running = true;

	testutil_make_work_dir(opts->home);
	testutil_check(wiredtiger_open(opts->home, NULL,
	    "create,session_max=1000,statistics=(fast)", &opts->conn));

	testutil_check(__wt_rwlock_init(NULL, &rwlock));
	testutil_check(pthread_rwlock_init(&p_rwlock, NULL));

	testutil_check(pthread_create(
	    &dump_id, NULL, thread_dump, (void *)opts));

	__wt_epoch(NULL, &ts);
	for (i = 0; i < (int)opts->nthreads; ++i)
		testutil_check(pthread_create(
		    &id[i], NULL, thread_rwlock, (void *)opts));

	while (--i >= 0)
		testutil_check(pthread_join(id[i], NULL));
	__wt_epoch(NULL, &te);
	printf("%.2lf\n", WT_TIMEDIFF_MS(te, ts) / 1000.0);

	running = false;
	testutil_check(pthread_join(dump_id, NULL));

	testutil_check(pthread_rwlock_destroy(&p_rwlock));
	testutil_cleanup(opts);
	return (EXIT_SUCCESS);
}

/*
 * Acquire a rwlock, every Nth operation, acquire exclusive.
 */
void *
thread_rwlock(void *arg)
{
	TEST_OPTS *opts;
	WT_SESSION *wt_session;
	WT_SESSION_IMPL *session;
	uint64_t i, counter;
	bool writelock;

	opts = (TEST_OPTS *)arg;
	testutil_check(
	    opts->conn->open_session(opts->conn, NULL, NULL, &wt_session));
	session = (WT_SESSION_IMPL *)wt_session;

	printf("Running rwlock thread\n");
	for (i = 1; i <= opts->nops; ++i) {
		writelock = (i % READS_PER_WRITE == 0);

#ifdef USE_POSIX
		if (writelock)
			testutil_check(pthread_rwlock_wrlock(&p_rwlock));
		else
			testutil_check(pthread_rwlock_rdlock(&p_rwlock));
#else
		if (writelock)
			__wt_writelock(session, &rwlock);
		else
			__wt_readlock(session, &rwlock);
#endif

		/*
		 * Do a tiny amount of work inside the lock so the compiler
		 * can't optimize everything away.
		 */
		(void)__wt_atomic_add64(&counter, 1);

#ifdef CHECK_CORRECTNESS
		if (writelock)
			counter = ++shared_counter;
		else
			counter = shared_counter;

		__wt_yield();

		testutil_assert(counter == shared_counter);
#endif

#ifdef USE_POSIX
		testutil_check(pthread_rwlock_unlock(&p_rwlock));
#else
		if (writelock)
			__wt_writeunlock(session, &rwlock);
		else
			__wt_readunlock(session, &rwlock);
#endif

		if (i % 10000 == 0) {
			printf("%s", session->id == 20 ? ".\n" : ".");
			fflush(stdout);
		}
	}

	opts->running = false;

	return (NULL);
}

void *
thread_dump(void *arg) {
	WT_UNUSED(arg);

	while (running) {
		sleep(1);
		printf("\n"
		    "rwlock { current %" PRIu8 ", next %" PRIu8
		    ", reader %" PRIu8 ", readers_active %" PRIu16
		    ", readers_queued %" PRIu16 " }\n",
		    rwlock.u.s.current,
		    rwlock.u.s.next,
		    rwlock.u.s.reader,
		    rwlock.u.s.readers_active,
		    rwlock.u.s.readers_queued);
	}

	return (NULL);
}
