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