Ruby  3.1.4p223 (2023-03-30 revision HEAD)
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 
13 static const int DEBUG = 0;
14 
15 static
16 int 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 
33 void 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 
61 static
62 void 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 
79 void 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 
94 static
95 int 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 
109 static
110 void 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 
121 static
122 void 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 
133 static
134 void coroutine_trampoline_cleanup(void *_context) {
135  struct coroutine_context * context = _context;
136  coroutine_release(context);
137 }
138 
139 void * 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 
155 static
156 int 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 
191 struct 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 
237 static
238 void 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 
256 void 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 }