threads.c
1 #include <string.h>
2 
3 #include <kuroko/kuroko.h>
4 
5 #ifndef KRK_DISABLE_THREADS
6 #include <kuroko/util.h>
7 #include <kuroko/threads.h>
8 
9 #include <unistd.h>
10 #include <pthread.h>
11 
12 #if defined(__linux__)
13 # include <sys/syscall.h>
14 # define gettid() syscall(SYS_gettid)
15 #elif defined(__toaru__)
16 # include <pthread.h>
17 #elif defined(_WIN32)
18 # define gettid() GetCurrentThreadId()
19 #else
20 # define gettid() -1
21 #endif
22 
33 struct Thread {
34  KrkInstance inst;
35  KrkThreadState * threadState;
36  pthread_t nativeRef;
37  pid_t tid;
38  unsigned int started:1;
39  unsigned int alive:1;
40  unsigned int maxrec;
41 };
42 
49 struct Lock {
50  KrkInstance inst;
51  pthread_mutex_t mutex;
52 };
53 
54 KRK_Function(current_thread) {
55  if (&krk_currentThread == vm.threads) return NONE_VAL();
56  return krk_currentThread.stack[0];
57 }
58 
59 #define IS_Thread(o) (krk_isInstanceOf(o, KRK_BASE_CLASS(Thread)))
60 #define AS_Thread(o) ((struct Thread *)AS_OBJECT(o))
61 #define CURRENT_CTYPE struct Thread *
62 #define CURRENT_NAME self
63 
64 static volatile int _threadLock = 0;
65 static void * _startthread(void * _threadObj) {
66  struct Thread * self = _threadObj;
67 #if defined(__APPLE__) && defined(__aarch64__)
68  krk_forceThreadData();
69 #endif
70  memset(&krk_currentThread, 0, sizeof(KrkThreadState));
71  krk_currentThread.maximumCallDepth = self->maxrec;
73  vm.globalFlags |= KRK_GLOBAL_THREADS;
74  _obtain_lock(_threadLock);
75  if (vm.threads->next) {
76  krk_currentThread.next = vm.threads->next;
77  }
78  vm.threads->next = &krk_currentThread;
79  _release_lock(_threadLock);
80 
81  /* Get our run function */
82  self->threadState = &krk_currentThread;
83  self->tid = gettid();
84 
85  KrkValue runMethod = NONE_VAL();
86  KrkClass * ourType = self->inst._class;
87  if (!krk_tableGet(&ourType->methods, OBJECT_VAL(S("run")), &runMethod)) {
88  krk_runtimeError(KRK_EXC(ThreadError), "Thread object has no run() method");
89  } else {
90  krk_push(runMethod);
91  krk_push(OBJECT_VAL(self));
92  krk_callStack(1);
93  }
94 
95  self->alive = 0;
96 
97  /* Remove this thread from the thread pool, its stack is garbage anyway */
98  _obtain_lock(_threadLock);
100  KrkThreadState * previous = vm.threads;
101  while (previous) {
102  if (previous->next == &krk_currentThread) {
103  previous->next = krk_currentThread.next;
104  break;
105  }
106  previous = previous->next;
107  }
108  _release_lock(_threadLock);
109 
110  KRK_FREE_ARRAY(size_t, krk_currentThread.stack, krk_currentThread.stackSize);
112 
113  return NULL;
114 }
115 
116 KRK_Method(Thread,tid) {
117  METHOD_TAKES_NONE(); /* Property, but can not be assigned. */
118  return INTEGER_VAL(self->tid);
119 }
120 
121 KRK_Method(Thread,join) {
122  if (self->threadState == &krk_currentThread)
123  return krk_runtimeError(KRK_EXC(ThreadError), "Thread can not join itself.");
124  if (!self->started)
125  return krk_runtimeError(KRK_EXC(ThreadError), "Thread has not been started.");
126 
127  pthread_join(self->nativeRef, NULL);
128  return NONE_VAL();
129 }
130 
131 KRK_Method(Thread,start) {
132  unsigned int maxrec = krk_currentThread.maximumCallDepth;
133  if (!krk_parseArgs(".|I", (const char*[]){"maxrec"}, &maxrec)) return NONE_VAL();
134 
135  if (self->started)
136  return krk_runtimeError(KRK_EXC(ThreadError), "Thread has already been started.");
137 
138  self->started = 1;
139  self->alive = 1;
140  self->maxrec = maxrec;
141  pthread_create(&self->nativeRef, NULL, _startthread, (void*)self);
142 
143  return argv[0];
144 }
145 
146 KRK_Method(Thread,is_alive) {
147  METHOD_TAKES_NONE();
148  return BOOLEAN_VAL(self->alive);
149 }
150 
151 #undef CURRENT_CTYPE
152 
153 #define IS_Lock(o) (krk_isInstanceOf(o, KRK_BASE_CLASS(Lock)))
154 #define AS_Lock(o) ((struct Lock *)AS_OBJECT(o))
155 #define CURRENT_CTYPE struct Lock *
156 
157 KRK_Method(Lock,__init__) {
158  METHOD_TAKES_NONE(); /* TODO lock options, like recursive or error-checked? */
159  pthread_mutex_init(&self->mutex, NULL);
160  return NONE_VAL();
161 }
162 
163 static inline void _pushLockStatus(struct Lock * self, struct StringBuilder * sb) {
164 #ifdef __GLIBC__
165  {
166  if (self->mutex.__data.__owner) {
167  pushStringBuilderStr(sb, " (locked)", 9);
168  } else {
169  pushStringBuilderStr(sb, " (unlocked)", 11);
170  }
171  }
172 #else
173  (void)self;
174  (void)sb;
175 #endif
176 }
177 
178 KRK_Method(Lock,__repr__) {
179  METHOD_TAKES_NONE();
180  struct StringBuilder sb = {0};
181  pushStringBuilderStr(&sb, "<Lock ", 6);
182 
183  /* Address of lock object */
184  {
185  char tmp[100];
186  size_t len = snprintf(tmp, 100, "%p", (void*)self);
187  pushStringBuilderStr(&sb, tmp, len);
188  }
189 
190  _pushLockStatus(self,&sb);
191 
192  pushStringBuilder(&sb,'>');
193  return finishStringBuilder(&sb);
194 }
195 
196 KRK_Method(Lock,__enter__) {
197  METHOD_TAKES_NONE();
198  pthread_mutex_lock(&self->mutex);
199  return NONE_VAL();
200 }
201 
202 KRK_Method(Lock,__exit__) {
203  pthread_mutex_unlock(&self->mutex);
204  return NONE_VAL();
205 }
206 
213  KrkInstance * threadsModule = krk_newInstance(vm.baseClasses->moduleClass);
214 
215  krk_attachNamedObject(&vm.modules, "threading", (KrkObj*)threadsModule);
216  krk_attachNamedObject(&threadsModule->fields, "__name__", (KrkObj*)S("threading"));
217  krk_attachNamedValue(&threadsModule->fields, "__file__", NONE_VAL());
218  KRK_DOC(threadsModule,
219  "@brief Methods and classes for creating platform threads.");
220 
221  KRK_DOC(BIND_FUNC(threadsModule, current_thread),
222  "@brief Obtain a reference to the current thread.\n"
223  "@arguments \n\n"
224  "Returns the @ref Thread object associated with the calling thread, if one exists.");
225 
226  KrkClass * ThreadError = krk_makeClass(threadsModule, &KRK_EXC(ThreadError), "ThreadError", vm.exceptions->OSError);
227  KRK_DOC(ThreadError,
228  "Raised in various situations when an action on a thread is invalid."
229  );
230  krk_finalizeClass(ThreadError);
231 
232  KrkClass * Thread = krk_makeClass(threadsModule, &KRK_BASE_CLASS(Thread), "Thread", vm.baseClasses->objectClass);
233  KRK_DOC(Thread,
234  "Base class for building threaded execution contexts.\n\n"
235  "The @ref Thread class should be subclassed and the subclass should implement a @c run method."
236  );
237  Thread->allocSize = sizeof(struct Thread);
238  KRK_DOC(BIND_METHOD(Thread,start), "Start the thread. A thread may only be started once.");
239  KRK_DOC(BIND_METHOD(Thread,join), "Join the thread. Does not return until the thread finishes.");
240  KRK_DOC(BIND_METHOD(Thread,is_alive), "Query the status of the thread.");
241  KRK_DOC(BIND_PROP(Thread,tid), "The platform-specific thread identifier, if available. Usually an integer.");
243 
244  KrkClass * Lock = krk_makeClass(threadsModule, &KRK_BASE_CLASS(Lock), "Lock", vm.baseClasses->objectClass);
245  KRK_DOC(Lock,
246  "Represents an atomic mutex.\n\n"
247  "@ref Lock objects allow for exclusive access to a resource and can be used in a @c with block."
248  );
249  Lock->allocSize = sizeof(struct Lock);
250  KRK_DOC(BIND_METHOD(Lock,__init__), "Initialize a system mutex.");
251  KRK_DOC(BIND_METHOD(Lock,__enter__),"Acquire the lock.");
252  KRK_DOC(BIND_METHOD(Lock,__exit__), "Release the lock.");
253  BIND_METHOD(Lock,__repr__);
255 }
256 
257 
258 #endif
KrkValue krk_runtimeError(KrkClass *type, const char *fmt,...)
Produce and raise an exception with a formatted message.
Definition: exceptions.c:460
Top-level header with configuration macros.
Represents a managed call state in a VM thread.
Definition: vm.h:44
Type object.
Definition: object.h:215
KrkClass * krk_makeClass(KrkInstance *module, KrkClass **_class, const char *name, KrkClass *base)
Convenience function for creating new types.
Definition: vm.c:164
KrkTable methods
General attributes table.
Definition: object.h:218
void krk_finalizeClass(KrkClass *_class)
Finalize a class by collecting pointers to core methods.
Definition: vm.c:189
struct KrkClass * _class
Metaclass.
Definition: object.h:217
An object of a class.
Definition: object.h:281
KrkInstance * krk_newInstance(KrkClass *_class)
Create a new instance of the given class.
Definition: object.c:343
KrkTable fields
Attributes table.
Definition: object.h:284
The most basic object type.
Definition: object.h:41
int krk_tableGet(KrkTable *table, KrkValue key, KrkValue *value)
Obtain the value associated with a key in a table.
Definition: table.c:211
void krk_attachNamedObject(KrkTable *table, const char name[], KrkObj *obj)
Attach an object to an attribute table.
Definition: vm.c:808
void krk_attachNamedValue(KrkTable *table, const char name[], KrkValue obj)
Attach a value to an attribute table.
Definition: vm.c:794
Execution state of a VM thread.
Definition: vm.h:152
struct KrkThreadState * next
Definition: vm.h:153
size_t stackSize
Definition: vm.h:157
KrkValue * stack
Definition: vm.h:158
unsigned int maximumCallDepth
Definition: vm.h:166
KrkCallFrame * frames
Definition: vm.h:155
Stack reference or primative value.
Simple atomic structure for waiting.
Definition: threads.c:49
Inline flexible string array.
Definition: util.h:162
Object representation of a system thread.
Definition: threads.c:33
Convience header for providing atomic operations to threads.
Utilities for creating native bindings.
#define krk_parseArgs(f, n,...)
Parse arguments to a function while accepting keyword arguments.
Definition: util.h:360
#define KRK_DOC(thing, text)
Attach documentation to a thing of various types.
Definition: util.h:304
krk_threadLocal KrkThreadState krk_currentThread
Thread-local VM state.
void krk_resetStack(void)
Reset the current thread's stack state to the top level.
Definition: vm.c:85
void krk_module_init_threading(void)
Initialize the built-in 'threading' module.
Definition: threads.c:207
KrkValue krk_callStack(int argCount)
Call a callable on the stack with argCount arguments.
Definition: vm.c:732
#define vm
Convenience macro for namespacing.
Definition: vm.h:257
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:118