Ruby 3.5.0dev (2025-01-10 revision 5fab31b15e32622c4b71d1d347a41937e9f9c212)
Context.c
1/*
2 * This file is part of the "Coroutine" project and released under the MIT License.
3 *
4 * Created by Samuel Williams on 24/6/2021.
5 * Copyright, 2021, by Samuel Williams.
6*/
7
8#include "Context.h"
9#include <stdlib.h>
10#include <stdio.h>
11#include <errno.h>
12
13static const int DEBUG = 0;
14
15static
16int check(const char * message, int result) {
17 if (result) {
18 switch (result) {
19 case EDEADLK:
20 if (DEBUG) fprintf(stderr, "deadlock detected result=%d errno=%d\n", result, errno);
21 break;
22 default:
23 if (DEBUG) fprintf(stderr, "error detected result=%d errno=%d\n", result, errno);
24 perror(message);
25 }
26 }
27
28 assert(result == 0);
29
30 return result;
31}
32
33void coroutine_initialize_main(struct coroutine_context * context) {
34 context->id = pthread_self();
35
36 check("coroutine_initialize_main:pthread_cond_init",
37 pthread_cond_init(&context->schedule, NULL)
38 );
39
40 context->shared = (struct coroutine_shared*)malloc(sizeof(struct coroutine_shared));
41 assert(context->shared);
42
43 context->shared->main = context;
44 context->shared->count = 1;
45
46 if (DEBUG) {
47 pthread_mutexattr_t attr;
48 pthread_mutexattr_init(&attr);
49 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
50
51 check("coroutine_initialize_main:pthread_mutex_init",
52 pthread_mutex_init(&context->shared->guard, &attr)
53 );
54 } else {
55 check("coroutine_initialize_main:pthread_mutex_init",
56 pthread_mutex_init(&context->shared->guard, NULL)
57 );
58 }
59}
60
61static
62void coroutine_release(struct coroutine_context *context) {
63 if (context->shared) {
64 size_t count = (context->shared->count -= 1);
65
66 if (count == 0) {
67 if (DEBUG) fprintf(stderr, "coroutine_release:pthread_mutex_destroy(%p)\n", &context->shared->guard);
68 pthread_mutex_destroy(&context->shared->guard);
69 free(context->shared);
70 }
71
72 context->shared = NULL;
73
74 if (DEBUG) fprintf(stderr, "coroutine_release:pthread_cond_destroy(%p)\n", &context->schedule);
75 pthread_cond_destroy(&context->schedule);
76 }
77}
78
79void coroutine_initialize(
80 struct coroutine_context *context,
81 coroutine_start start,
82 void *stack,
83 size_t size
84) {
85 assert(start && stack && size >= 1024);
86
87 // We will create the thread when we first transfer, but save the details now:
88 context->shared = NULL;
89 context->start = start;
90 context->stack = stack;
91 context->size = size;
92}
93
94static
95int is_locked(pthread_mutex_t * mutex) {
96 int result = pthread_mutex_trylock(mutex);
97
98 // If we could successfully lock the mutex:
99 if (result == 0) {
100 pthread_mutex_unlock(mutex);
101 // We could lock the mutex, so it wasn't locked:
102 return 0;
103 } else {
104 // Otherwise we couldn't lock it because it's already locked:
105 return 1;
106 }
107}
108
109static
110void coroutine_guard_unlock(void * _context)
111{
112 struct coroutine_context * context = _context;
113
114 if (DEBUG) fprintf(stderr, "coroutine_guard_unlock:pthread_mutex_unlock\n");
115
116 check("coroutine_guard_unlock:pthread_mutex_unlock",
117 pthread_mutex_unlock(&context->shared->guard)
118 );
119}
120
121static
122void coroutine_wait(struct coroutine_context *context)
123{
124 if (DEBUG) fprintf(stderr, "coroutine_wait:pthread_mutex_lock(guard=%p is_locked=%d)\n", &context->shared->guard, is_locked(&context->shared->guard));
125 check("coroutine_wait:pthread_mutex_lock",
126 pthread_mutex_lock(&context->shared->guard)
127 );
128
129 if (DEBUG) fprintf(stderr, "coroutine_wait:pthread_mutex_unlock(guard)\n");
130 pthread_mutex_unlock(&context->shared->guard);
131}
132
133static
134void coroutine_trampoline_cleanup(void *_context) {
135 struct coroutine_context * context = _context;
136 coroutine_release(context);
137}
138
139void * coroutine_trampoline(void * _context)
140{
141 struct coroutine_context * context = _context;
142 assert(context->shared);
143
144 pthread_cleanup_push(coroutine_trampoline_cleanup, context);
145
146 coroutine_wait(context);
147
148 context->start(context->from, context);
149
150 pthread_cleanup_pop(1);
151
152 return NULL;
153}
154
155static
156int coroutine_create_thread(struct coroutine_context *context)
157{
158 int result;
159
160 pthread_attr_t attr;
161 result = pthread_attr_init(&attr);
162 if (result != 0) {
163 return result;
164 }
165
166 result = pthread_attr_setstack(&attr, context->stack, (size_t)context->size);
167 if (result != 0) {
168 pthread_attr_destroy(&attr);
169 return result;
170 }
171
172 result = pthread_cond_init(&context->schedule, NULL);
173 if (result != 0) {
174 pthread_attr_destroy(&attr);
175 return result;
176 }
177
178 result = pthread_create(&context->id, &attr, coroutine_trampoline, context);
179 if (result != 0) {
180 pthread_attr_destroy(&attr);
181 if (DEBUG) fprintf(stderr, "coroutine_create_thread:pthread_cond_destroy(%p)\n", &context->schedule);
182 pthread_cond_destroy(&context->schedule);
183 return result;
184 }
185
186 context->shared->count += 1;
187
188 return result;
189}
190
191struct coroutine_context * coroutine_transfer(struct coroutine_context * current, struct coroutine_context * target)
192{
193 assert(current->shared);
194
195 struct coroutine_context * previous = target->from;
196 target->from = current;
197
198 if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_mutex_lock(guard=%p is_locked=%d)\n", &current->shared->guard, is_locked(&current->shared->guard));
199 pthread_mutex_lock(&current->shared->guard);
200 pthread_cleanup_push(coroutine_guard_unlock, current);
201
202 // First transfer:
203 if (target->shared == NULL) {
204 target->shared = current->shared;
205
206 if (DEBUG) fprintf(stderr, "coroutine_transfer:coroutine_create_thread...\n");
207 if (coroutine_create_thread(target)) {
208 if (DEBUG) fprintf(stderr, "coroutine_transfer:coroutine_create_thread failed\n");
209 target->shared = NULL;
210 target->from = previous;
211 return NULL;
212 }
213 } else {
214 if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_cond_signal(target)\n");
215 pthread_cond_signal(&target->schedule);
216 }
217
218 // A side effect of acting upon a cancellation request while in a condition wait is that the mutex is (in effect) re-acquired before calling the first cancellation cleanup handler. If cancelled, pthread_cond_wait immediately invokes cleanup handlers.
219 if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_cond_wait(schedule=%p, guard=%p, is_locked=%d)\n", &current->schedule, &current->shared->guard, is_locked(&current->shared->guard));
220 check("coroutine_transfer:pthread_cond_wait",
221 pthread_cond_wait(&current->schedule, &current->shared->guard)
222 );
223
224 if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_cleanup_pop\n");
225 pthread_cleanup_pop(1);
226
227#ifdef __FreeBSD__
228 // Apparently required for FreeBSD:
229 pthread_testcancel();
230#endif
231
232 target->from = previous;
233
234 return target;
235}
236
237static
238void coroutine_join(struct coroutine_context * context) {
239 if (DEBUG) fprintf(stderr, "coroutine_join:pthread_cancel\n");
240 int result = pthread_cancel(context->id);
241 if (result == -1 && errno == ESRCH) {
242 // The thread may be dead due to fork, so it cannot be joined and this doesn't represent a real error:
243 return;
244 }
245
246 check("coroutine_join:pthread_cancel", result);
247
248 if (DEBUG) fprintf(stderr, "coroutine_join:pthread_join\n");
249 check("coroutine_join:pthread_join",
250 pthread_join(context->id, NULL)
251 );
252
253 if (DEBUG) fprintf(stderr, "coroutine_join:pthread_join done\n");
254}
255
256void coroutine_destroy(struct coroutine_context * context)
257{
258 if (DEBUG) fprintf(stderr, "coroutine_destroy\n");
259
260 assert(context);
261
262 // We are already destroyed or never created:
263 if (context->shared == NULL) return;
264
265 if (context == context->shared->main) {
266 context->shared->main = NULL;
267 coroutine_release(context);
268 } else {
269 coroutine_join(context);
270 assert(context->shared == NULL);
271 }
272}
#define errno
Ractor-aware version of errno.
Definition ruby.h:388