/* -*- linux-c -*- 
 * Context Runtime Functions
 * Copyright (C) 2014 Red Hat Inc.
 *
 * This file is part of systemtap, and is free software.  You can
 * redistribute it and/or modify it under the terms of the GNU General
 * Public License (GPL); either version 2, or (at your option) any
 * later version.
 */

#ifndef _STAPDYN_RUNTIME_CONTEXT_H_
#define _STAPDYN_RUNTIME_CONTEXT_H_

#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <asm/unistd.h>

/* Defined after stap_probes[] by translate.cxx */
static const char* stp_probe_point(size_t index);

/* Defined later in common_session_state.h */
static inline struct context* stp_session_context(size_t index);

static int _stp_runtime_num_contexts;

/* Locally-cached context pointer -- see _stp_runtime_entryfn_get_context()
 * and _stp_runtime_entryfn_put_context().  */
static __thread struct context *tls_context;

static int _stp_runtime_contexts_init(void)
{
    _stp_runtime_num_contexts = sysconf(_SC_NPROCESSORS_ONLN);
    if (_stp_runtime_num_contexts < 1)
	_stp_runtime_num_contexts = 1;
    return 0;
}

static int _stp_runtime_contexts_alloc(void)
{
    int i;

    /* The allocation was already done in stp_session_init;
     * we just need to initialize the context data.  */
    for (i = 0; i < _stp_runtime_num_contexts; i++) {
	int rc;
	struct context *c = stp_session_context(i);
	c->data_index = i;
	rc = stp_pthread_mutex_init_shared(&c->lock);
	if (rc != 0) {
	    _stp_error("context mutex initialization failed");
	    return rc;
	}
    }
    return 0;
}

/* Free the context resources.
 *
 * NB: This should *not* be called by every process which has mmaped the shared
 * memory.  Only the main process which created shm and originally called
 * _stp_runtime_contexts_alloc should be the one to free it.
 */
static void _stp_runtime_contexts_free(void)
{
    int i;

    /* The context memory is managed elsewhere;
     * we just need to teardown the context locks. */
    for (i = 0; i < _stp_runtime_num_contexts; i++) {
	struct context *c = stp_session_context(i);
	(void)pthread_mutex_destroy(&c->lock);
    }
}

static int _stp_runtime_get_data_index(void)
{
    int data_index;

    /* If this thread has already gotten a context structure,
     * return the data index from it. */
    if (tls_context != NULL)
	return tls_context->data_index;

    /* This shouldn't happen. */
    /* FIXME: assert? */
    return 0;
}


/* Figure out with cpu we're on, which is our default data_index.
 * Make sure the returned data index number is within the range of
 * [0.._stp_runtime_num_contexts]. Be sure to handle a sched_getcpu()
 * failure (it will return -1). */
static int _stp_context_index(void)
{
    /* The current cpu is the preferred index, because it will usually be
     * different for every concurrent thread.  (It is possible to be the same
     * though, if kernel scheduling is unkind to us.)  */
    int index = _stp_sched_getcpu();

    /* Failing cpu#, use the tid as a somewhat-random index.  */
    if (index < 0)
        index = syscall(SYS_gettid);

    return index % _stp_runtime_num_contexts;
}

static struct context * _stp_runtime_entryfn_get_context(void)
{
    struct context *c;
    int i, index, rc, data_index;

    /* If 'tls_context' (which is thread-local storage) is already set
     * for this thread, we are re-entrant, so just quit. */
    if (tls_context != NULL)
	return NULL;

    data_index = _stp_context_index();
    if (unlikely(data_index < 0))
	data_index = 0;

    /* Try to find a free context structure. */
    index = data_index;
    for (i = 0; i < _stp_runtime_num_contexts; i++, index++) {
	if (index >= _stp_runtime_num_contexts)
	    index = 0;
	c = stp_session_context(index);
	if (pthread_mutex_trylock(&c->lock) == 0) {
	    /* We found a free context structure. Now that it is
	     * locked, set the TLS pointer and return the context. */
	    tls_context = c;
	    return tls_context;
	}
    }

    /* If we're here, we couldn't find a free context structure. Wait
     * on one. */
    c = stp_session_context(data_index);
    rc = pthread_mutex_lock(&c->lock);
    if (rc == 0) {
	tls_context = c;
	return tls_context;
    }
    return NULL;
}

static void _stp_runtime_entryfn_put_context(struct context *c)
{
    if (c && c == tls_context) {
	tls_context = NULL;
	pthread_mutex_unlock(&c->lock);
    }
    /* else, warn about bad state? */
    return;
}

static struct context *_stp_runtime_get_context(void)
{
    /* Note we don't call _stp_runtime_entryfn_get_context()
     * here. This function is called after
     * _stp_runtime_entryfn_get_context() and has no corresponding
     * "put" function. */
    return tls_context;
}

static void _stp_runtime_context_wait(void)
{
    struct timespec hold_start;
    int hold_index;
    int holdon;

    (void)clock_gettime(CLOCK_MONOTONIC_RAW, &hold_start);
    hold_index = -1;
    do {
	int i;
	holdon = 0;
	struct timespec now, elapsed;
		
	for (i = 0; i < _stp_runtime_num_contexts; i++) {
	    struct context *c = stp_session_context(i);
	    int ret = pthread_mutex_trylock(&c->lock);
	    if (ret == 0)
		pthread_mutex_unlock(&c->lock);
	    else if (ret == EBUSY) {
		holdon = 1;

		/* Just In case things are really stuck, let's print
		 * some diagnostics. */
		(void)clock_gettime(CLOCK_MONOTONIC_RAW, &now);
		_stp_timespec_sub(&now, &hold_start, &elapsed);

		/* If its been > 1 second since we started and we
		 * haven't already printed a message for this stuck
		 * context, print one. */
		if (elapsed.tv_sec > 0 && (i > hold_index)) {
		    /* NB: c->probe_point is local memory, invalid across processes,
		     * so read it indirectly through c->probe_index instead.  */
		    const char* pp = stp_probe_point(c->probe_index);
		    if (pp)
			_stp_error("context[%d] stuck: %s", i, pp);
		    else
			_stp_error("context[%d] stuck in unknown probe %zu",
				   i, c->probe_index);
		    hold_index = i;
		}
	    }
	    /* else other pthread error == broken? what then? */
	}

#ifdef STAP_OVERRIDE_STUCK_CONTEXT
	/* In case things are really really stuck, we are going to
	 * pretend/assume/hope everything is OK, and let the cleanup
	 * finish. */
	(void)clock_gettime(CLOCK_MONOTONIC_RAW, &now);
	_stp_timespec_sub(&now, &hold_start, &elapsed);
	if (elapsed.tv_sec > 10) {
	    _stp_warn("overriding stuck context to allow shutdown.");
	    holdon = 0;			/* allow loop to exit */
	}
#endif

	if (holdon) {
	    sched_yield();
	}
    } while (holdon);
}

#endif /* _STAPDYN_RUNTIME_CONTEXT_H_ */
