/* Copyright 2000-2001 ActiveState
 */

/* #define NO_PERL_LOCK */
/* #define LOCK_DEBUG */

#if !defined(NO_PERL_LOCK) && defined(WITH_THREAD)
 #define DO_THREAD /* a simpler macro to test for in files that include this */
#endif

#ifdef LOCK_DEBUG

#ifdef DO_THREAD
  /* XXX these test are not valid in a multi-thread environment.
   * Should test if the current thread has the given lock.
   */
  #error "LOCK_DEBUG with real threads doesn't work"
  extern int perl_lock_held();
  #define PERL_LOCK_HELD   perl_lock_held()
  #define PYTHON_LOCK_HELD (last_py_tstate == NULL)
#else /* DO_THREAD */
  #define PERL_LOCK_HELD   perl_lock_held
  #define PYTHON_LOCK_HELD python_lock_held
#endif /* DO_THREAD */

extern void Lock_Fatal_Error(const char*, const char*, int);
#define LOCK_FATAL_ERROR(msg)  Lock_Fatal_Error(msg, __FILE__, __LINE__)

/* Poor mans Eiffel :-) */

#define ASSERT_LOCK_PERL \
          do { \
              if (!PERL_LOCK_HELD || PYTHON_LOCK_HELD) \
		LOCK_FATAL_ERROR("Only perl lock should be held"); \
          } while (0)

#define ASSERT_LOCK_PYTHON \
          do { \
              if (PERL_LOCK_HELD || !PYTHON_LOCK_HELD) \
		LOCK_FATAL_ERROR("Only python lock should be held"); \
          } while (0)

#define ASSERT_LOCK_BOTH \
          do { \
              if (!PERL_LOCK_HELD || !PYTHON_LOCK_HELD) \
		LOCK_FATAL_ERROR("Both perl and python lock should be held"); \
          } while (0)

#else /* LOCK_DEBUG */

/* All assertions expand to nothing */
#define ASSERT_LOCK_PERL
#define ASSERT_LOCK_PYTHON
#define ASSERT_LOCK_BOTH

extern void lang_lock_init(void);

#endif /* LOCK_DEBUG */




#ifdef DO_THREAD

#ifdef MULTI_PERL

extern perl_key last_py_tstate;

/* These assume that the thread specific 'ctx' pointer is
 * in scope.
 */
#define ENTER_PERL   do { ctx->last_py_state = PyEval_SaveThread(); \
                     } while (0)
#define ENTER_PYTHON do { PyEval_RestoreThread(ctx->last_py_state); \
                     } while (0)
#define PERL_LOCK    /* nothing */
#define PERL_UNLOCK  /* nothing */
#define PYTHON_LOCK   ENTER_PYTHON
#define PYTHON_UNLOCK ENTER_PERL

#else /* MULTI_PERL */

/* This stuff is similar to the threading macros you find in _tkinter.c,
 * but not exactly the same :-)
 */

#include "pythread.h"
extern PyThread_type_lock perl_lock;
extern PyThreadState *last_py_tstate;


/* The locking rules are:

     1) All code should be protected by one (or more) locks.
     2) When we execute python code we should have the python lock and
        *not* have the perl lock
     3) When we execute perl code we should have the perl lock and *not*
        the python lock
     4) You can have both locks while executing API functions that does
        not risk invoking arbitrary perl/python code.
     5) Lot of API functions might invoke perl/python callbacks through
        hooks, overloading or tying.  These must only be invoked with
        the correct single lock.
*/

/* This one should only be called when you are sure you have the python lock,
   and not the perl lock.  The result is that you have perl lock.
 */
#define ENTER_PERL   do { PyThreadState *tstate;               \
                          ASSERT_LOCK_PYTHON;                  \
                          tstate = PyEval_SaveThread();        \
                          PyThread_acquire_lock(perl_lock, 1); \
                          last_py_tstate = tstate;             \
                     } while (0)

/* This one should only be called when you are sure you have the perl lock,
   and not the python lock.  The result is that you have the python lock.
 */
#define ENTER_PYTHON do { PyThreadState *tstate;               \
                          ASSERT_LOCK_PERL;                    \
                          tstate = last_py_tstate;             \
                          last_py_tstate = NULL;               \
                          PyThread_release_lock(perl_lock);    \
                          PyEval_RestoreThread(tstate);        \
                     } while (0)

/* These can only be called while you have the python lock */
#define PERL_LOCK    do { ASSERT_LOCK_PYTHON; \
                          while (!PyThread_acquire_lock(perl_lock, 0)) { \
			      ENTER_PERL; \
			      ENTER_PYTHON; \
                          } \
                     } while (0)
#define PERL_UNLOCK  do { ASSERT_LOCK_BOTH; \
                          PyThread_release_lock(perl_lock); \
                     } while (0)

/* These can only be called while you have the perl lock */
#define PYTHON_LOCK do { ENTER_PYTHON; PERL_LOCK; } while (0)
#define PYTHON_UNLOCK \
     do { ASSERT_LOCK_BOTH; \
          if (last_py_tstate != NULL) \
             Py_FatalError("PYTHON_UNLOCK: non-NULL tstate"); \
          last_py_tstate = PyEval_SaveThread(); \
     } while (0)

/* The reason PYTHON_LOCK is defined as it is, is that we should always
 * obtain locks in the same sequence in order to avoid potential deadlock.
 */
#endif /* MULTI_PERL */

#else /* DO_THREAD */

#ifdef LOCK_DEBUG

extern int perl_lock_held;
extern int python_lock_held;

#define ENTER_PERL \
          do { \
	    ASSERT_LOCK_PYTHON; \
	    python_lock_held = 0; \
	    perl_lock_held = 1; \
          } while (0) \

#define ENTER_PYTHON \
          do { \
	    ASSERT_LOCK_PERL; \
	    python_lock_held = 1; \
	    perl_lock_held = 0; \
          } while (0)

#define PERL_LOCK \
          do { \
	    ASSERT_LOCK_PYTHON; \
	    perl_lock_held = 1; \
          } while (0)

#define PERL_UNLOCK \
          do { \
	    ASSERT_LOCK_BOTH; \
	    perl_lock_held = 0; \
          } while (0)

#define PYTHON_LOCK \
          do { \
	    ASSERT_LOCK_PERL; \
	    python_lock_held = 1; \
          } while (0)

#define PYTHON_UNLOCK \
          do { \
	    ASSERT_LOCK_BOTH; \
	    python_lock_held = 0; \
          } while (0)

#else /* LOCK_DEBUG */

/* No threads and no debug, all macros expand to nothing */

#define ENTER_PERL
#define ENTER_PYTHON
#define PERL_LOCK
#define PERL_UNLOCK
#define PYTHON_UNLOCK
#define PYTHON_LOCK

#endif /* LOCK_DEBUG */

#endif /* DO_THREAD */
