/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 | } |