mailing list of musl libc
 help / color / mirror / code / Atom feed
* First draft of new synccall, seems to be working
@ 2015-01-15  4:33 Rich Felker
  2015-01-15 12:52 ` Rich Felker
  0 siblings, 1 reply; 2+ messages in thread
From: Rich Felker @ 2015-01-15  4:33 UTC (permalink / raw)
  To: musl

[-- Attachment #1: Type: text/plain, Size: 820 bytes --]

I just wrote the first draft of the new __synccall, used for
multithreaded set*id(). All the concepts seem sound and seem to be
working. It's missing the code to block creation of unboundedly more
threads while __synccall is running, but otherwise everything's in
place. Both the /proc/self/task processing logic and the PI futex
logic seem to be working as expected. I haven't yet tested in the
presence of multiple realtime priorities where there's a risk of some
threads never getting a chance to run; I need a framework with a
high-RT-prio supervisor process to kill the test if it runs too long,
because I don't want to be rebooting boxes over and over to test. :)

Attached is the current draft of the code, as a source file rather
than a patch since the whole file is basically rewritten. Comments
welcome.

Rich

[-- Attachment #2: synccall.c --]
[-- Type: text/plain, Size: 3767 bytes --]

#include "pthread_impl.h"
#include <semaphore.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
#include "futex.h"
#include <../dirent/__dirent.h>

static struct chain {
	struct chain *next;
	int tid;
	sem_t start_sem, next_sem, finish_sem;
} *head;

static int synccall_lock[2];
static int data_lock[2];
static int cur_tid;
static void (*callback)(void *), *context;

static void handler(int sig)
{
	struct chain ch;
	int old_errno = errno;

	sem_init(&ch.start_sem, 0, 0);
	sem_init(&ch.next_sem, 0, 0);
	sem_init(&ch.finish_sem, 0, 0);

	LOCK(data_lock);
	ch.tid = __syscall(SYS_gettid);
	ch.next = head;
	head = &ch;
	UNLOCK(data_lock);
	if (a_cas(&cur_tid, ch.tid, 0) == (ch.tid | 0x80000000))
		__syscall(SYS_futex, &cur_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);

	sem_wait(&ch.start_sem);
	callback(context);
	sem_post(&ch.next_sem);
	sem_wait(&ch.finish_sem);

	errno = old_errno;
}


static int is_member(struct chain *p, int tid)
{
	while (p) {
		if (p->tid == tid) return 1;
		p = p->next;
	}
	return 0;
}

void __synccall(void (*func)(void *), void *ctx)
{
	sigset_t oldmask;
	int cs;
	int pid, self;
	int i;
	DIR dir = {0};
	struct dirent *de;
	struct sigaction sa = { .sa_flags = 0, .sa_handler = handler };
	struct chain *cp, *next;
	struct timespec ts;

	__block_all_sigs(&oldmask);
	LOCK(synccall_lock);
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);

	LOCK(data_lock);
	head = 0;
	callback = func;
	context = ctx;
	UNLOCK(data_lock);

	if (!libc.threaded) goto single_threaded;

	/* Block even impl-internal signals */
	memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
	__libc_sigaction(SIGSYNCCALL, &sa, 0);

	pid = __syscall(SYS_getpid);
	self = __syscall(SYS_gettid);

	/* Since opendir is not AS-safe, the DIR needs to be setup manually
	 * in automatic storage. Thankfully this is easy. */
	dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
	if (dir.fd < 0) goto out;

	/* Initially send one signal per counted thread. But since we can't
	 * synchronize with thread creation/exit here, there could be too
	 * few signals. This initial signaling is just an optimization, not
	 * part of the logic. */
	for (i=libc.threads_minus_1; i; i--)
		__syscall(SYS_kill, pid, SIGSYNCCALL);

	/* Loop scanning the kernel-provided thread list until it shows no
	 * threads that have not already replied to the signal. */
	for (;;) {
		int miss_cnt = 0;
		while ((de = readdir(&dir))) {
			if (!isdigit(de->d_name[0])) continue;
			int tid = atoi(de->d_name);
			if (tid == self) continue;
			LOCK(data_lock);
			if (is_member(head, tid)) {
				UNLOCK(data_lock);
				continue;
			}
			UNLOCK(data_lock);

			__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);

			/* The FUTEX_LOCK_PI operation is used to loan priority
			 * to the target thread, which otherwise may be unable
			 * to run. Timeout is necessary because there is a race
			 * condition where the tid may be reused by a different
			 * process. */
			clock_gettime(CLOCK_REALTIME, &ts);
			ts.tv_nsec += 10000000;
			if (ts.tv_nsec >= 1000000000) {
				ts.tv_sec++;
				ts.tv_nsec -= 1000000000;
			}
			a_store(&cur_tid, tid);
			__syscall(SYS_futex, &cur_tid, FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);

			LOCK(data_lock);
			if (cur_tid != self && !is_member(head, tid))
				miss_cnt++;
			UNLOCK(data_lock);
		}
		if (!miss_cnt) break;
		rewinddir(&dir);
	}
	close(dir.fd);

	for (cp=head; cp; cp=next) {
		next = cp->next;
		sem_post(&cp->start_sem);
		sem_wait(&cp->next_sem);
	}

	sa.sa_handler = SIG_IGN;
	__libc_sigaction(SIGSYNCCALL, &sa, 0);

single_threaded:
	func(ctx);

	for (cp=head; cp; cp=next) {
		next = cp->next;
		sem_post(&cp->finish_sem);
	}

out:
	pthread_setcancelstate(cs, 0);
	UNLOCK(synccall_lock);
	__restore_sigs(&oldmask);
}

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: First draft of new synccall, seems to be working
  2015-01-15  4:33 First draft of new synccall, seems to be working Rich Felker
@ 2015-01-15 12:52 ` Rich Felker
  0 siblings, 0 replies; 2+ messages in thread
From: Rich Felker @ 2015-01-15 12:52 UTC (permalink / raw)
  To: musl

[-- Attachment #1: Type: text/plain, Size: 1233 bytes --]

On Wed, Jan 14, 2015 at 11:33:15PM -0500, Rich Felker wrote:
> I just wrote the first draft of the new __synccall, used for
> multithreaded set*id(). All the concepts seem sound and seem to be
> working. It's missing the code to block creation of unboundedly more
> threads while __synccall is running, but otherwise everything's in
> place. Both the /proc/self/task processing logic and the PI futex
> logic seem to be working as expected. I haven't yet tested in the
> presence of multiple realtime priorities where there's a risk of some
> threads never getting a chance to run; I need a framework with a
> high-RT-prio supervisor process to kill the test if it runs too long,
> because I don't want to be rebooting boxes over and over to test. :)
> 
> Attached is the current draft of the code, as a source file rather
> than a patch since the whole file is basically rewritten. Comments
> welcome.

I had some synchronization logic errors (which would have been masked
by the timeout anyway, but still wrong) in the old version which I've
corrected, and I realized that most of the internal locking could be
replaced with atomics, which I've done. I still need to write the
pthread_create blocking.

New version attached.

Rich

[-- Attachment #2: synccall.c --]
[-- Type: text/plain, Size: 4218 bytes --]

#include "pthread_impl.h"
#include <semaphore.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
#include "futex.h"
#include "atomic.h"
#include "../dirent/__dirent.h"

static struct chain {
	struct chain *next;
	int tid;
	sem_t target_sem, caller_sem;
} *head;

static int synccall_lock[2];
static int target_tid;
static void (*callback)(void *), *context;

static void handler(int sig)
{
	struct chain ch;
	int old_errno = errno;

	sem_init(&ch.target_sem, 0, 0);
	sem_init(&ch.caller_sem, 0, 0);

	ch.tid = __syscall(SYS_gettid);

	do ch.next = head;
	while (a_cas_p(&head, ch.next, &ch) != ch.next);

	if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000))
		__syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);

	sem_wait(&ch.target_sem);
	callback(context);
	sem_post(&ch.caller_sem);
	sem_wait(&ch.target_sem);

	errno = old_errno;
}

void __synccall(void (*func)(void *), void *ctx)
{
	sigset_t oldmask;
	int cs, i, r, pid, self;;
	DIR dir = {0};
	struct dirent *de;
	struct sigaction sa = { .sa_flags = 0, .sa_handler = handler };
	struct chain *cp, *next;
	struct timespec ts;

	__block_all_sigs(&oldmask);
	LOCK(synccall_lock);
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);

	head = 0;
	callback = func;
	context = ctx;
	a_barrier();

	if (!libc.threaded) goto single_threaded;

	/* Block even impl-internal signals */
	memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
	__libc_sigaction(SIGSYNCCALL, &sa, 0);

	pid = __syscall(SYS_getpid);
	self = __syscall(SYS_gettid);

	/* Since opendir is not AS-safe, the DIR needs to be setup manually
	 * in automatic storage. Thankfully this is easy. */
	dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
	if (dir.fd < 0) goto out;

	/* Initially send one signal per counted thread. But since we can't
	 * synchronize with thread creation/exit here, there could be too
	 * few signals. This initial signaling is just an optimization, not
	 * part of the logic. */
	for (i=libc.threads_minus_1; i; i--)
		__syscall(SYS_kill, pid, SIGSYNCCALL);

	/* Loop scanning the kernel-provided thread list until it shows no
	 * threads that have not already replied to the signal. */
	for (;;) {
		int miss_cnt = 0;
		while ((de = readdir(&dir))) {
			if (!isdigit(de->d_name[0])) continue;
			int tid = atoi(de->d_name);
			if (tid == self) continue;

			/* Set the target thread as the PI futex owner before
			 * checking if it's in the list of caught threads. If it
			 * adds itself to the list after we check for it, then
			 * it will see its own tid in the PI futex and perform
			 * the unlock operation. */
			a_store(&target_tid, tid);

			a_barrier();
			for (cp = head; cp && cp->tid != tid; cp=cp->next);

			/* Thread-already-caught is a success condition. */
			if (cp) continue;

			r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);

			/* Target thread exit is a success condition. */
			if (r == ESRCH) continue;

			/* The FUTEX_LOCK_PI operation is used to loan priority
			 * to the target thread, which otherwise may be unable
			 * to run. Timeout is necessary because there is a race
			 * condition where the tid may be reused by a different
			 * process. */
			clock_gettime(CLOCK_REALTIME, &ts);
			ts.tv_nsec += 10000000;
			if (ts.tv_nsec >= 1000000000) {
				ts.tv_sec++;
				ts.tv_nsec -= 1000000000;
			}
			r = -__syscall(SYS_futex, &target_tid,
				FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);

			/* Obtaining the lock means the thread responded. ESRCH
			 * means the target thread exited, which is okay too. */
			if (!r || r == ESRCH) continue;

			miss_cnt++;
		}
		if (!miss_cnt) break;
		rewinddir(&dir);
	}
	close(dir.fd);

	/* Serialize execution of callback in caught threads. */
	for (cp=head; cp; cp=cp->next) {
		sem_post(&cp->target_sem);
		sem_wait(&cp->caller_sem);
	}

	sa.sa_handler = SIG_IGN;
	__libc_sigaction(SIGSYNCCALL, &sa, 0);

single_threaded:
	func(ctx);

	/* Only release the caught threads once all threads, including the
	 * caller, have returned from the callback function. */
	for (cp=head; cp; cp=next) {
		next = cp->next;
		sem_post(&cp->target_sem);
	}

out:
	pthread_setcancelstate(cs, 0);
	UNLOCK(synccall_lock);
	__restore_sigs(&oldmask);
}

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2015-01-15 12:52 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-01-15  4:33 First draft of new synccall, seems to be working Rich Felker
2015-01-15 12:52 ` Rich Felker

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).