Coverage Report

Created: 2017-04-15 07:07

/home/travis/build/MoarVM/MoarVM/src/core/threads.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
#include <platform/threads.h>
3
4
/* Temporary structure for passing data to thread start. */
5
typedef struct {
6
    MVMThreadContext *tc;
7
    MVMObject        *thread_obj;
8
} ThreadStart;
9
10
/* Creates a new thread handle with the MVMThread representation. Does not
11
 * actually start execution of the thread, but does give it its unique ID. */
12
0
MVMObject * MVM_thread_new(MVMThreadContext *tc, MVMObject *invokee, MVMint64 app_lifetime) {
13
0
    MVMThread *thread;
14
0
    MVMThreadContext *child_tc;
15
0
16
0
    /* Create the Thread object and stash code to run and lifetime. */
17
0
    MVMROOT(tc, invokee, {
18
0
        thread = (MVMThread *)MVM_repr_alloc_init(tc, tc->instance->Thread);
19
0
    });
20
0
    thread->body.stage = MVM_thread_stage_unstarted;
21
0
    MVM_ASSIGN_REF(tc, &(thread->common.header), thread->body.invokee, invokee);
22
0
    thread->body.app_lifetime = app_lifetime;
23
0
24
0
    /* Try to create the new threadcontext. Can throw if libuv can't
25
0
     * create a loop for it for some reason (i.e. too many open files) */
26
0
    child_tc = MVM_tc_create(tc, tc->instance);
27
0
28
0
    /* Set up the new threadcontext a little. */
29
0
    child_tc->thread_obj = thread;
30
0
    child_tc->thread_id = 1 + MVM_incr(&tc->instance->next_user_thread_id);
31
0
        /* Add one, since MVM_incr returns original. */
32
0
    thread->body.tc = child_tc;
33
0
34
0
    /* Also make a copy of the thread ID in the thread object itself, so it
35
0
     * is available once the thread dies and its ThreadContext is gone. */
36
0
    thread->body.thread_id = child_tc->thread_id;
37
0
38
0
    return (MVMObject *)thread;
39
0
}
40
41
/* This callback is passed to the interpreter code. It takes care of making
42
 * the initial invocation of the thread code. */
43
0
static void thread_initial_invoke(MVMThreadContext *tc, void *data) {
44
0
    /* The passed data is simply the code object to invoke. */
45
0
    ThreadStart *ts = (ThreadStart *)data;
46
0
    MVMThread *thread = (MVMThread *)ts->thread_obj;
47
0
    MVMObject *invokee = thread->body.invokee;
48
0
    thread->body.invokee = NULL;
49
0
50
0
    /* Set up the cached current usecapture CallCapture (done here so
51
0
     * we allocate it on the correct thread, and once the thread is
52
0
     * active). */
53
0
    MVMROOT(tc, invokee, {
54
0
        tc->cur_usecapture = MVM_repr_alloc_init(tc, tc->instance->CallCapture);
55
0
    });
56
0
57
0
    /* Create initial frame, which sets up all of the interpreter state also. */
58
0
    invokee = MVM_frame_find_invokee(tc, invokee, NULL);
59
0
    STABLE(invokee)->invoke(tc, invokee, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_NULL_ARGS), NULL);
60
0
61
0
    /* This frame should be marked as the thread entry frame, so that any
62
0
     * return from it will cause us to drop out of the interpreter and end
63
0
     * the thread. */
64
0
    tc->thread_entry_frame = tc->cur_frame;
65
0
}
66
67
/* This callback handles starting execution of a thread. */
68
0
static void start_thread(void *data) {
69
0
    ThreadStart *ts = (ThreadStart *)data;
70
0
    MVMThreadContext *tc = ts->tc;
71
0
72
0
    /* Stash thread ID. */
73
0
    tc->thread_obj->body.native_thread_id = MVM_platform_thread_id();
74
0
75
0
    /* wait for the GC to finish if it's not finished stealing us. */
76
0
    MVM_gc_mark_thread_unblocked(tc);
77
0
    tc->thread_obj->body.stage = MVM_thread_stage_started;
78
0
79
0
    /* Enter the interpreter, to run code. */
80
0
    MVM_interp_run(tc, thread_initial_invoke, ts);
81
0
82
0
    /* Pop the temp root stack's ts->thread_obj, if it's still there (if we
83
0
     * cleared the temp root stack on exception at some point, it'll already be
84
0
     * gone). */
85
0
    if (tc->num_temproots != 0)
86
0
        MVM_gc_root_temp_pop_n(tc, tc->num_temproots);
87
0
    MVM_free(ts);
88
0
89
0
    /* Mark as exited, so the GC will know to clear our stuff. */
90
0
    tc->thread_obj->body.stage = MVM_thread_stage_exited;
91
0
92
0
    /* Mark ourselves as blocked, so that another thread will take care
93
0
     * of GC-ing our objects and cleaning up our thread context. */
94
0
    MVM_gc_mark_thread_blocked(tc);
95
0
96
0
    /* Exit the thread, now it's completed. */
97
0
    MVM_platform_thread_exit(NULL);
98
0
}
99
100
/* Begins execution of a thread. */
101
0
void MVM_thread_run(MVMThreadContext *tc, MVMObject *thread_obj) {
102
0
    MVMThread *child = (MVMThread *)thread_obj;
103
0
    int status;
104
0
    ThreadStart *ts;
105
0
106
0
    if (REPR(child)->ID == MVM_REPR_ID_MVMThread) {
107
0
        MVMThread * volatile *threads;
108
0
        MVMThreadContext *child_tc = child->body.tc;
109
0
110
0
        /* Move thread to starting stage. */
111
0
        child->body.stage = MVM_thread_stage_starting;
112
0
113
0
        /* Create thread state, to pass to the thread start callback. */
114
0
        ts = MVM_malloc(sizeof(ThreadStart));
115
0
        ts->tc = child_tc;
116
0
        ts->thread_obj = thread_obj;
117
0
118
0
        /* Push this to the *child* tc's temp roots. */
119
0
        MVM_gc_root_temp_push(child_tc, (MVMCollectable **)&ts->thread_obj);
120
0
121
0
        /* Mark thread as GC blocked until the thread actually starts. */
122
0
        MVM_gc_mark_thread_blocked(child_tc);
123
0
124
0
        /* Push to starting threads list */
125
0
        threads = &tc->instance->threads;
126
0
        do {
127
0
            MVMThread *curr = *threads;
128
0
            MVM_ASSIGN_REF(tc, &(child->common.header), child->body.next, curr);
129
0
        } while (MVM_casptr(threads, child->body.next, child) != child->body.next);
130
0
131
0
        /* Do the actual thread creation. */
132
0
        status = uv_thread_create(&child->body.thread, start_thread, ts);
133
0
        if (status < 0)
134
0
            MVM_panic(MVM_exitcode_compunit, "Could not spawn thread: errorcode %d", status);
135
0
    }
136
0
    else {
137
0
        MVM_exception_throw_adhoc(tc,
138
0
            "Thread handle passed to run must have representation MVMThread");
139
0
    }
140
0
}
141
142
/* Waits for a thread to finish. */
143
0
static int try_join(MVMThreadContext *tc, MVMThread *thread) {
144
0
    /* Join the thread, marking ourselves as unable to GC while we wait. */
145
0
    int status;
146
0
    MVM_gc_root_temp_push(tc, (MVMCollectable **)&thread);
147
0
    MVM_gc_mark_thread_blocked(tc);
148
0
    if (thread->body.stage < MVM_thread_stage_exited) {
149
0
        status = uv_thread_join(&thread->body.thread);
150
0
    }
151
0
    else {
152
0
        /* the target already ended */
153
0
        status = 0;
154
0
    }
155
0
    MVM_gc_mark_thread_unblocked(tc);
156
0
    MVM_gc_root_temp_pop(tc);
157
0
158
0
    /* After a thread has been joined, we trigger a GC run to clean up after
159
0
     * it. This avoids problems where a program spawns threads and joins them
160
0
     * in a loop gobbling a load of memory and other resources because we do
161
0
     * not ever trigger a GC run to clean up the thread. */
162
0
    MVM_gc_enter_from_allocator(tc);
163
0
164
0
    return status;
165
0
}
166
0
void MVM_thread_join(MVMThreadContext *tc, MVMObject *thread_obj) {
167
0
    if (REPR(thread_obj)->ID == MVM_REPR_ID_MVMThread) {
168
0
        int status = try_join(tc, (MVMThread *)thread_obj);
169
0
        if (status < 0)
170
0
            MVM_panic(MVM_exitcode_compunit, "Could not join thread: errorcode %d", status);
171
0
    }
172
0
    else {
173
0
        MVM_exception_throw_adhoc(tc,
174
0
            "Thread handle passed to join must have representation MVMThread");
175
0
    }
176
0
}
177
178
/* Gets the (VM-level) ID of a thread. */
179
0
MVMint64 MVM_thread_id(MVMThreadContext *tc, MVMObject *thread_obj) {
180
0
    if (REPR(thread_obj)->ID == MVM_REPR_ID_MVMThread)
181
0
        return ((MVMThread *)thread_obj)->body.thread_id;
182
0
    else
183
0
        MVM_exception_throw_adhoc(tc,
184
0
            "Thread handle passed to threadid must have representation MVMThread");
185
0
}
186
187
/* Gets the native OS ID of a thread. If it's not yet available because
188
 * the thread was not yet started, this will return 0. */
189
0
MVMint64 MVM_thread_native_id(MVMThreadContext *tc, MVMObject *thread_obj) {
190
0
    if (REPR(thread_obj)->ID == MVM_REPR_ID_MVMThread)
191
0
        return ((MVMThread *)thread_obj)->body.native_thread_id;
192
0
    else
193
0
        MVM_exception_throw_adhoc(tc,
194
0
            "Thread handle passed to threadnativeid must have representation MVMThread");
195
0
}
196
197
/* Yields control to another thread. */
198
0
void MVM_thread_yield(MVMThreadContext *tc) {
199
0
    MVM_platform_thread_yield();
200
0
}
201
202
/* Gets the object representing the current thread. */
203
0
MVMObject * MVM_thread_current(MVMThreadContext *tc) {
204
0
    return (MVMObject *)tc->thread_obj;
205
0
}
206
207
0
void MVM_thread_cleanup_threads_list(MVMThreadContext *tc, MVMThread **head) {
208
0
    /* Assumed to be the only thread accessing the list.
209
0
     * must set next on every item. */
210
0
    MVMThread *new_list = NULL, *this = *head, *next;
211
0
    *head = NULL;
212
0
    while (this) {
213
0
        next = this->body.next;
214
0
        switch (this->body.stage) {
215
0
            case MVM_thread_stage_starting:
216
0
            case MVM_thread_stage_waiting:
217
0
            case MVM_thread_stage_started:
218
0
            case MVM_thread_stage_exited:
219
0
            case MVM_thread_stage_clearing_nursery:
220
0
                /* push it to the new starting list */
221
0
                this->body.next = new_list;
222
0
                new_list = this;
223
0
                break;
224
0
            case MVM_thread_stage_destroyed:
225
0
                /* don't put in a list */
226
0
                this->body.next = NULL;
227
0
                break;
228
0
            default:
229
0
                MVM_panic(MVM_exitcode_threads, "Thread in unknown stage: %"MVM_PRSz"\n", this->body.stage);
230
0
        }
231
0
        this = next;
232
0
    }
233
0
    *head = new_list;
234
0
}
235
236
/* Goes through all non-app-lifetime threads and joins them. */
237
130
void MVM_thread_join_foreground(MVMThreadContext *tc) {
238
130
    MVMint64 work = 1;
239
260
    while (work) {
240
130
        MVMThread *cur_thread = tc->instance->threads;
241
130
        work = 0;
242
260
        while (cur_thread) {
243
130
            if (cur_thread->body.tc != tc->instance->main_thread) {
244
0
                if (!cur_thread->body.app_lifetime) {
245
0
                    if (MVM_load(&cur_thread->body.stage) < MVM_thread_stage_exited) {
246
0
                        /* Join may trigger GC and invalidate cur_thread, so we
247
0
                        * just set work to 1 and do another trip around the main
248
0
                        * loop. */
249
0
                        try_join(tc, cur_thread);
250
0
                        work = 1;
251
0
                        break;
252
0
                    }
253
0
                }
254
0
            }
255
130
            cur_thread = cur_thread->body.next;
256
130
        }
257
130
    }
258
130
}