Skip to content

Commit

Permalink
Simplify our dealings with the thread list.
Browse files Browse the repository at this point in the history
Just take a lock for the cases where we need to update or snapshot it.
This eliminates a low-benefit, and diffult to reason about, lock-free
appraoch in the GC orchestration, making for far simpler code there.
It also disentangles it a bit from the GC start flag handling, which
will be getting a refactor shortly to try to improve its resource
usage.
  • Loading branch information
jnthn committed Jul 25, 2017
1 parent 0c7b132 commit 6ff2b3c
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 61 deletions.
5 changes: 3 additions & 2 deletions src/core/instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ struct MVMInstance {
/* The ID to allocate the next-created thread. */
AO_t next_user_thread_id;

/* MVMThreads completed starting, running, and/or exited. */
/* note: used atomically */
/* MVMThreads completed starting, running, and/or exited. Modifications
* and walks that need an accurate picture of it protected by mutex. */
MVMThread *threads;
uv_mutex_t mutex_threads;

/************************************************************************
* Garbage collection and memory management
Expand Down
53 changes: 38 additions & 15 deletions src/core/threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,33 +101,56 @@ static void start_thread(void *data) {
/* Begins execution of a thread. */
void MVM_thread_run(MVMThreadContext *tc, MVMObject *thread_obj) {
MVMThread *child = (MVMThread *)thread_obj;
int status;
int status, added;
ThreadStart *ts;

if (REPR(child)->ID == MVM_REPR_ID_MVMThread) {
MVMThread * volatile *threads;
MVMThreadContext *child_tc = child->body.tc;

/* Move thread to starting stage. */
child->body.stage = MVM_thread_stage_starting;

/* Mark thread as GC blocked until the thread actually starts. */
MVM_gc_mark_thread_blocked(child_tc);

/* Create thread state, to pass to the thread start callback. */
ts = MVM_malloc(sizeof(ThreadStart));
ts->tc = child_tc;
ts->thread_obj = thread_obj;

/* Push this to the *child* tc's temp roots. */
MVM_gc_root_temp_push(child_tc, (MVMCollectable **)&ts->thread_obj);

/* Mark thread as GC blocked until the thread actually starts. */
MVM_gc_mark_thread_blocked(child_tc);

/* Push to starting threads list */
threads = &tc->instance->threads;
do {
MVMThread *curr = *threads;
MVM_ASSIGN_REF(tc, &(child->common.header), child->body.next, curr);
} while (MVM_casptr(threads, child->body.next, child) != child->body.next);
/* Push to starting threads list. We may need to retry this if we are
* asked to join a GC run at this point (since the GC would already
* have taken a snapshot of the thread list, so it's not safe to add
* another at this point). */
added = 0;
while (!added) {
uv_mutex_lock(&tc->instance->mutex_threads);
if (MVM_load(&tc->gc_status) == MVMGCStatus_NONE) {
/* Insert into list. */
MVM_ASSIGN_REF(tc, &(child->common.header), child->body.next,
tc->instance->threads);
tc->instance->threads = child;

/* Store the thread object in the thread start information and
* keep it alive by putting it in the *child* tc's temp roots. */
ts->thread_obj = thread_obj;
MVM_gc_root_temp_push(child_tc, (MVMCollectable **)&ts->thread_obj);

/* Mark us done and unlock the mutex; any GC run will now have
* a consistent view of the thread list and can safely run. */
added = 1;
uv_mutex_unlock(&tc->instance->mutex_threads);
}
else {
/* Another thread decided we'll GC now. Release mutex, and
* do the GC, making sure thread_obj and child are marked. */
uv_mutex_unlock(&tc->instance->mutex_threads);
MVMROOT(tc, thread_obj, {
MVMROOT(tc, child, {
GC_SYNC_POINT(tc);
});
});
}
}

/* Do the actual thread creation. */
status = uv_thread_create(&child->body.thread, start_thread, ts);
Expand Down
57 changes: 20 additions & 37 deletions src/gc/orchestrate.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,17 @@ static MVMuint32 signal_one_thread(MVMThreadContext *tc, MVMThreadContext *to_si
}
}
}
static MVMuint32 signal_all_but(MVMThreadContext *tc, MVMThread *t, MVMThread *tail) {
static MVMuint32 signal_all(MVMThreadContext *tc, MVMThread *threads) {
MVMThread *t = threads;
MVMuint32 count = 0;
MVMThread *next;
if (!t) {
return 0;
}
do {
next = t->body.next;
while (t) {
switch (MVM_load(&t->body.stage)) {
case MVM_thread_stage_starting:
case MVM_thread_stage_waiting:
case MVM_thread_stage_started:
if (t->body.tc != tc) {
/* Don't signal ourself. */
if (t->body.tc != tc)
count += signal_one_thread(tc, t->body.tc);
}
break;
case MVM_thread_stage_exited:
GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : queueing to clear nursery of thread %d\n", t->body.tc->thread_id);
Expand All @@ -91,10 +87,8 @@ static MVMuint32 signal_all_but(MVMThreadContext *tc, MVMThread *t, MVMThread *t
default:
MVM_panic(MVM_exitcode_gcorch, "Corrupted MVMThread or running threads list: invalid thread stage %"MVM_PRSz"", MVM_load(&t->body.stage));
}
} while (next && (t = next));
if (tail)
MVM_gc_write_barrier(tc, (MVMCollectable *)t, (MVMCollectable *)tail);
t->body.next = tail;
t = t->body.next;
}
return count;
}

Expand Down Expand Up @@ -412,34 +406,23 @@ void MVM_gc_enter_from_allocator(MVMThreadContext *tc) {
add_work(tc, tc);

/* Find other threads, and signal or steal. */
do {
MVMThread *threads = (MVMThread *)MVM_load(&tc->instance->threads);
if (threads && threads != last_starter) {
MVMThread *head = threads;
MVMuint32 add;
while ((threads = (MVMThread *)MVM_casptr(&tc->instance->threads, head, NULL)) != head) {
head = threads;
}
uv_mutex_lock(&tc->instance->mutex_threads);
num_threads = signal_all(tc, tc->instance->threads);
MVM_add(&tc->instance->gc_start, num_threads);
uv_mutex_unlock(&tc->instance->mutex_threads);

add = signal_all_but(tc, head, last_starter);
last_starter = head;
if (add) {
GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Found %d other threads\n", add);
MVM_add(&tc->instance->gc_start, add);
num_threads += add;
}
}
/* If there's an event loop thread, wake it up to participate. */
if (tc->instance->event_loop_wakeup)
uv_async_send(tc->instance->event_loop_wakeup);

/* If there's an event loop thread, wake it up to participate. */
if (tc->instance->event_loop_wakeup)
uv_async_send(tc->instance->event_loop_wakeup);
} while (MVM_load(&tc->instance->gc_start) > 1);
/* Wait for other threads to be ready. */
while (MVM_load(&tc->instance->gc_start) > 1)
MVM_platform_thread_yield();

/* Sanity checks. */
if (!MVM_trycas(&tc->instance->threads, NULL, last_starter))
MVM_panic(MVM_exitcode_gcorch, "threads list corrupted\n");
/* Sanity checks finish votes. */
if (MVM_load(&tc->instance->gc_finish) != 0)
MVM_panic(MVM_exitcode_gcorch, "Finish votes was %"MVM_PRSz"\n", MVM_load(&tc->instance->gc_finish));
MVM_panic(MVM_exitcode_gcorch, "Finish votes was %"MVM_PRSz"\n",
MVM_load(&tc->instance->gc_finish));

/* gc_ack gets an extra so the final acknowledger
* can also free the STables. */
Expand Down
14 changes: 7 additions & 7 deletions src/moar.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ MVMInstance * MVM_vm_create_instance(void) {

/* Create the main thread's ThreadContext and stash it. */
instance->main_thread = MVM_tc_create(NULL, instance);

instance->main_thread->thread_id = 1;

/* Next thread to be created gets ID 2 (the main thread got ID 1). */
Expand Down Expand Up @@ -144,15 +143,15 @@ MVMInstance * MVM_vm_create_instance(void) {
init_mutex(instance->mutex_event_loop_start, "event loop thread start");

/* Create main thread object, and also make it the start of the all threads
* linked list. */
MVM_store(&instance->threads,
(instance->main_thread->thread_obj = (MVMThread *)
REPR(instance->boot_types.BOOTThread)->allocate(
instance->main_thread, STABLE(instance->boot_types.BOOTThread))));
* linked list. Set up the mutex to protect it. */
instance->threads = instance->main_thread->thread_obj = (MVMThread *)
REPR(instance->boot_types.BOOTThread)->allocate(
instance->main_thread, STABLE(instance->boot_types.BOOTThread));
instance->threads->body.stage = MVM_thread_stage_started;
instance->threads->body.tc = instance->main_thread;
instance->threads->body.native_thread_id = MVM_platform_thread_id();
instance->threads->body.thread_id = instance->main_thread->thread_id;
init_mutex(instance->mutex_threads, "threads list");

/* Create compiler registry */
instance->compiler_registry = MVM_repr_alloc_init(instance->main_thread, instance->boot_types.BOOTHash);
Expand Down Expand Up @@ -502,8 +501,9 @@ void MVM_vm_destroy_instance(MVMInstance *instance) {
/* Clean up event loop starting mutex. */
uv_mutex_destroy(&instance->mutex_event_loop_start);

/* Destroy main thread contexts. */
/* Destroy main thread contexts and thread list mutex. */
MVM_tc_destroy(instance->main_thread);
uv_mutex_destroy(&instance->mutex_threads);

/* Clear up VM instance memory. */
MVM_free(instance);
Expand Down

0 comments on commit 6ff2b3c

Please sign in to comment.