255 lines
6.9 KiB
C
255 lines
6.9 KiB
C
/* multiple hak instances in one process.
|
|
*
|
|
* lib/std.c keeps process-wide state that no single hak instance owns: the
|
|
* g_hak chain that links every live instance, and g_sig_state[] holding the
|
|
* signal dispositions saved before hak installed its own. An embedder may
|
|
* hold several instances at once, so that state is shared and is guarded by
|
|
* a global lock rather than by any per-instance one.
|
|
*
|
|
* Run with no arguments, as make check does, this covers the sequential case,
|
|
* which is deterministic and is what regressions are caught by. Pass a thread
|
|
* count for the concurrent stress mode:
|
|
*
|
|
* ./t-002 8
|
|
*
|
|
* The stress mode is kept out of make check because its runtime scales with
|
|
* the thread count and it is timing dependent, so it is a poor gate even
|
|
* though it passes. Reach for it when touching chain(), unchain() or the
|
|
* signal handler bookkeeping. */
|
|
|
|
#include <hak.h>
|
|
#include "tap.h"
|
|
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
|
|
#if !defined(__DOS__) && !defined(EMSCRIPTEN) && defined(HAVE_PTHREAD) && defined(HAVE_STRERROR_R)
|
|
# define USE_THREAD
|
|
# include <pthread.h>
|
|
#endif
|
|
|
|
#define NINST 4
|
|
|
|
#if defined(HAVE_SIGACTION)
|
|
static int disposition_of (int sig, void** handler)
|
|
{
|
|
struct sigaction sa;
|
|
if (sigaction(sig, (struct sigaction*)0, &sa) <= -1) return -1;
|
|
*handler = (sa.sa_flags & SA_SIGINFO)? (void*)sa.sa_sigaction: (void*)sa.sa_handler;
|
|
return 0;
|
|
}
|
|
#else
|
|
static int disposition_of (int sig, void** handler) { *handler = (void*)0; return -1; }
|
|
#endif
|
|
|
|
/* open NINST instances, then close them in an order that leaves the one being
|
|
* unchained with a live neighbour on each side, which the plain open-then-close
|
|
* pattern never produces. */
|
|
static void interleaved (void)
|
|
{
|
|
hak_t* inst[NINST];
|
|
int i;
|
|
static const int order[NINST] = { 1, 3, 0, 2 }; /* middles first */
|
|
|
|
for (i = 0; i < NINST; i++)
|
|
{
|
|
inst[i] = hak_openstd(0, HAK_NULL);
|
|
OK (inst[i] != HAK_NULL, "instantiation with siblings already open");
|
|
}
|
|
|
|
for (i = 0; i < NINST; i++)
|
|
{
|
|
if (inst[order[i]]) hak_close(inst[order[i]]);
|
|
inst[order[i]] = HAK_NULL;
|
|
}
|
|
|
|
/* the chain must still be usable once emptied out of order */
|
|
inst[0] = hak_openstd(0, HAK_NULL);
|
|
OK (inst[0] != HAK_NULL, "instantiation after out-of-order teardown");
|
|
if (inst[0]) hak_close(inst[0]);
|
|
}
|
|
|
|
/* a surviving instance must still work after its neighbours are gone */
|
|
static void survivor (void)
|
|
{
|
|
hak_t* keep;
|
|
hak_t* tmp;
|
|
int i, n;
|
|
|
|
keep = hak_openstd(0, HAK_NULL);
|
|
OK (keep != HAK_NULL, "instantiation of the survivor");
|
|
if (!keep) return;
|
|
|
|
for (i = 0; i < NINST; i++)
|
|
{
|
|
tmp = hak_openstd(0, HAK_NULL);
|
|
if (tmp) hak_close(tmp);
|
|
}
|
|
|
|
n = hak_ignite(keep, 0);
|
|
OK (n == 0, "survivor ignites after its neighbours are closed");
|
|
n = hak_addbuiltinprims(keep);
|
|
OK (n == 0, "survivor registers builtin primitives");
|
|
|
|
hak_close(keep);
|
|
}
|
|
|
|
#if defined(USE_THREAD)
|
|
/* Concurrent open/close, hammering the global lock that guards g_hak and
|
|
* g_sig_state. Removing either GLOBAL_LOCK() in lib/std.c makes this fault
|
|
* within a couple of runs at eight threads. */
|
|
#define ROUNDS 150
|
|
static void* worker (void* arg)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ROUNDS; i++)
|
|
{
|
|
hak_t* h = hak_openstd(0, HAK_NULL);
|
|
if (h) hak_close(h);
|
|
}
|
|
return (void*)0;
|
|
}
|
|
|
|
static int stress (int nthr)
|
|
{
|
|
pthread_t t[64];
|
|
int i;
|
|
|
|
if (nthr < 1) nthr = 1;
|
|
if (nthr > 64) nthr = 64;
|
|
|
|
for (i = 0; i < nthr; i++)
|
|
{
|
|
if (pthread_create(&t[i], (pthread_attr_t*)0, worker, (void*)0) != 0) break;
|
|
}
|
|
nthr = i;
|
|
for (i = 0; i < nthr; i++) pthread_join(t[i], (void**)0);
|
|
|
|
printf("# %d threads x %d instances completed\n", nthr, ROUNDS);
|
|
return 0;
|
|
}
|
|
#else
|
|
static int stress (int nthr)
|
|
{
|
|
printf("# built without thread support - stress mode unavailable\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* Signal dispositions are process-wide, so the library must not touch them
|
|
* behind the host's back. Neutralising SIGPIPE is the application's call -
|
|
* bin/hak.c makes it - and a program that would rather die quietly on a broken
|
|
* pipe is entitled to that. Opening and closing an instance must therefore
|
|
* leave every disposition exactly as it found it. */
|
|
static const int WATCHED[] = {
|
|
#if defined(SIGPIPE)
|
|
SIGPIPE,
|
|
#endif
|
|
SIGINT, SIGTERM
|
|
};
|
|
#define NWATCHED ((int)(sizeof(WATCHED) / sizeof(WATCHED[0])))
|
|
|
|
static void host_signals_untouched (void)
|
|
{
|
|
void* before[NWATCHED];
|
|
void* during[NWATCHED];
|
|
void* after[NWATCHED];
|
|
hak_t* h;
|
|
int i, probed = 0;
|
|
|
|
for (i = 0; i < NWATCHED; i++)
|
|
{
|
|
if (disposition_of(WATCHED[i], &before[i]) <= -1) return; /* no sigaction */
|
|
probed = 1;
|
|
}
|
|
if (!probed) return;
|
|
|
|
h = hak_openstd(0, HAK_NULL);
|
|
OK (h != HAK_NULL, "instantiation failure");
|
|
if (!h) return;
|
|
|
|
for (i = 0; i < NWATCHED; i++) disposition_of(WATCHED[i], &during[i]);
|
|
hak_close(h);
|
|
for (i = 0; i < NWATCHED; i++) disposition_of(WATCHED[i], &after[i]);
|
|
|
|
for (i = 0; i < NWATCHED; i++)
|
|
{
|
|
OK (during[i] == before[i], "an open instance leaves the host disposition alone");
|
|
OK (after[i] == before[i], "a closed instance leaves the host disposition alone");
|
|
}
|
|
}
|
|
|
|
/* hak_catch_termreq() is the sanctioned way to hand hak the termination
|
|
* signals, and hak_uncatch_termreq() must put the host's dispositions back
|
|
* exactly as it found them. Nothing in the tree calls either, so this is the
|
|
* only exercise they get - and the only coverage of the restore path in
|
|
* unset_signal_handler(). */
|
|
static const int TERMREQ[] = {
|
|
SIGTERM,
|
|
SIGINT
|
|
#if defined(SIGHUP)
|
|
, SIGHUP
|
|
#endif
|
|
};
|
|
#define NTERMREQ ((int)(sizeof(TERMREQ) / sizeof(TERMREQ[0])))
|
|
|
|
static void termreq_round_trips (void)
|
|
{
|
|
void* before[NTERMREQ];
|
|
void* during[NTERMREQ];
|
|
void* after[NTERMREQ];
|
|
int i;
|
|
#if defined(SIGPIPE)
|
|
/* SIGPIPE is deliberately NOT in the set above. It is not a termination
|
|
* request - it is the opposite, a measure against being terminated - and
|
|
* neutralising it is the application's call, not the library's, because
|
|
* the disposition is process-wide. bin/hak.c makes that call for itself.
|
|
* So termreq must leave it exactly alone. */
|
|
void* pipe_before;
|
|
void* pipe_during;
|
|
int pipe_probed;
|
|
#endif
|
|
|
|
for (i = 0; i < NTERMREQ; i++)
|
|
{
|
|
if (disposition_of(TERMREQ[i], &before[i]) <= -1) return; /* no sigaction */
|
|
}
|
|
|
|
#if defined(SIGPIPE)
|
|
pipe_probed = (disposition_of(SIGPIPE, &pipe_before) >= 0);
|
|
#endif
|
|
|
|
hak_catch_termreq();
|
|
for (i = 0; i < NTERMREQ; i++) disposition_of(TERMREQ[i], &during[i]);
|
|
#if defined(SIGPIPE)
|
|
if (pipe_probed) disposition_of(SIGPIPE, &pipe_during);
|
|
#endif
|
|
|
|
hak_uncatch_termreq();
|
|
for (i = 0; i < NTERMREQ; i++) disposition_of(TERMREQ[i], &after[i]);
|
|
|
|
for (i = 0; i < NTERMREQ; i++)
|
|
{
|
|
OK (during[i] != before[i], "hak_catch_termreq installs a handler");
|
|
OK (after[i] == before[i], "hak_uncatch_termreq restores the original");
|
|
}
|
|
#if defined(SIGPIPE)
|
|
if (pipe_probed) OK (pipe_during == pipe_before, "hak_catch_termreq leaves SIGPIPE to the application");
|
|
#endif
|
|
}
|
|
|
|
int main (int argc, char* argv[])
|
|
{
|
|
if (argc > 1) return stress(atoi(argv[1]));
|
|
|
|
no_plan();
|
|
|
|
host_signals_untouched();
|
|
termreq_round_trips();
|
|
|
|
interleaved();
|
|
survivor();
|
|
|
|
return exit_status();
|
|
}
|