Skip to content

Commit c168e47

Browse files
committed
Fix problem of deemon (appearing to) brieflyx hang during exit
1 parent 0815e76 commit c168e47

File tree

5 files changed

+201
-50
lines changed

5 files changed

+201
-50
lines changed

include/deemon/thread.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,12 @@ typedef struct Dee_thread_object {
362362
#define DeeThread_WasInterrupted(self) (__hybrid_atomic_load(&(self)->t_state, __ATOMIC_ACQUIRE) & Dee_THREAD_STATE_INTERRUPTED)
363363
#define DeeThread_WasDetached(self) (!(__hybrid_atomic_load(&(self)->t_state, __ATOMIC_ACQUIRE) & Dee_THREAD_STATE_HASTHREAD))
364364
#define DeeThread_HasCrashed(self) ((DeeThread_HasTerminated(self) || (self) == DeeThread_Self()) && (self)->t_exceptsz > 0)
365+
/* Check if there's work to-be done that can be handled by `DeeThread_CheckInterruptNoInt()' */
366+
#define DeeThread_WasInterruptedNoInt(self) \
367+
((__hybrid_atomic_load(&(self)->t_state, __ATOMIC_ACQUIRE) & \
368+
(Dee_THREAD_STATE_SUSPENDING | Dee_THREAD_STATE_SUSPENDED | \
369+
Dee_THREAD_STATE_INTERRUPTED)) == \
370+
(Dee_THREAD_STATE_SUSPENDING | Dee_THREAD_STATE_INTERRUPTED))
365371
#endif /* !CONFIG_NO_THREADS */
366372

367373
#define _DeeThread_AcquireInterrupt(self) _DeeThread__AcquireStateLock(self, Dee_THREAD_STATE_INTERRUPTING)
@@ -473,22 +479,37 @@ DeeThread_Trace(/*Thread*/ DeeObject *__restrict self);
473479
* only those that jump backwards in code (aka. is guarantied to be checked
474480
* periodically during execution of any kind of infinite-loop). */
475481
DFUNDEF WUNUSED int (DCALL DeeThread_CheckInterrupt)(void);
482+
/* Same as `DeeThread_CheckInterrupt()', but only handle interrupts that
483+
* will not invoke user-code or throw errors. (e.g.: this can be used to
484+
* handle thread suspend requests) */
485+
DFUNDEF void (DCALL DeeThread_CheckInterruptNoInt)(void);
476486

477487
#ifdef CONFIG_BUILDING_DEEMON
478488
/* Same as `DeeThread_CheckInterrupt()', but faster
479489
* if the caller already knows their own thread object. */
480490
INTDEF WUNUSED NONNULL((1)) int
481-
(DCALL DeeThread_CheckInterruptSelf)(DeeThreadObject *__restrict thread_self);
491+
(DCALL DeeThread_CheckInterruptSelf)(DeeThreadObject *__restrict self);
492+
INTDEF NONNULL((1)) void
493+
(DCALL DeeThread_CheckInterruptNoIntSelf)(DeeThreadObject *__restrict self);
482494
#ifndef __OPTIMIZE_SIZE__
483495
/* Since `DeeThread_Self()' is marked as ATTR_CONST, in many cases where the calling function
484496
* already knows about its current thread from a previous call to `DeeThread_Self()', the compiler
485497
* will be able to optimize the secondary lookup away, making the code slightly faster. */
486-
#define DeeThread_CheckInterrupt() DeeThread_CheckInterruptSelf(DeeThread_Self())
498+
#define DeeThread_CheckInterrupt() DeeThread_CheckInterruptSelf(DeeThread_Self())
499+
#define DeeThread_CheckInterruptNoInt() DeeThread_CheckInterruptNoIntSelf(DeeThread_Self())
487500
#endif /* !__OPTIMIZE_SIZE__ */
488501
#else /* CONFIG_BUILDING_DEEMON */
489-
#define DeeThread_CheckInterruptSelf(self) DeeThread_CheckInterrupt()
502+
#define DeeThread_CheckInterruptSelf(self) DeeThread_CheckInterrupt()
503+
#define DeeThread_CheckInterruptNoIntSelf(self) DeeThread_CheckInterruptNoInt()
490504
#endif /* !CONFIG_BUILDING_DEEMON */
491505

506+
#ifdef CONFIG_NO_THREADS
507+
#undef DeeThread_CheckInterruptNoInt
508+
#undef DeeThread_CheckInterruptNoIntSelf
509+
#define DeeThread_CheckInterruptNoInt() (void)0
510+
#define DeeThread_CheckInterruptNoIntSelf(self) (void)0
511+
#endif /* CONFIG_NO_THREADS */
512+
492513
/* Suspend/resume execution of the given thread.
493514
* WARNING: Do _NOT_ expose these functions to user-code.
494515
* WARNING: Do not attempt to suspend more than a single thread at once using this

src/deemon/runtime/gc-inspect.c.inl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ GCSet_GetMarker(GCSet *self, DeeObject *ob) {
220220
/* @return: 1 : Already present
221221
* @return: 0 : Success, or already present
222222
* @return: -1: OOM failure (no error was thrown) */
223-
PRIVATE WUNUSED NONNULL((1, 2)) int DCALL
223+
PRIVATE NONNULL((1, 2)) int DCALL
224224
GCSet_SetMarker(GCSet *self, DeeObject *ob, bool marker) {
225225
Dee_hash_t i, perturb, hash = DeeObject_HashGeneric(ob);
226226
ASSERT((uintptr_t)marker == 0 || (uintptr_t)marker == 1);

src/deemon/runtime/gc.c

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <deemon/arg.h> /* DeeArg_Unpack*, UNPxSIZ */
2828
#include <deemon/asm.h> /* ASM_RET_NONE, instruction_t */
2929
#include <deemon/bool.h> /* return_bool, return_false, return_true */
30+
#include <deemon/system.h> /* return_bool, return_false, return_true */
3031
#include <deemon/class.h> /* instance_clear, instance_tclear */
3132
#include <deemon/code.h> /* DeeCodeObject, DeeCode_NAME, DeeCode_Type, DeeFunctionObject, DeeFunction_*, Dee_CODE_FFINALLY, instruction_t */
3233
#include <deemon/computed-operators.h> /* DEFIMPL, DEFIMPL_UNSUPPORTED */
@@ -73,6 +74,17 @@
7374
#define DBG_memset(dst, byte, n_bytes) (void)0
7475
#endif /* NDEBUG */
7576

77+
#if !defined(NDEBUG) && 0
78+
#if 0
79+
#define GC_TRACE(...) fprintf(stderr, __VA_ARGS__)
80+
#else
81+
#define GC_TRACE(...) Dee_DPRINTF(__VA_ARGS__)
82+
#endif
83+
#else
84+
#define GC_TRACE(...) (void)0
85+
#define GC_TRACE_IS_NOOP
86+
#endif
87+
7688
DECL_BEGIN
7789

7890
#ifndef NDEBUG
@@ -211,10 +223,16 @@ PRIVATE Dee_atomic_lock_t gc_lock = Dee_ATOMIC_LOCK_INIT;
211223
#define gc_lock_available() Dee_atomic_lock_available(&gc_lock)
212224
#define gc_lock_acquired() Dee_atomic_lock_acquired(&gc_lock)
213225
#define gc_lock_tryacquire() Dee_atomic_lock_tryacquire(&gc_lock)
214-
#define gc_lock_acquire() Dee_atomic_lock_acquire(&gc_lock)
215226
#define gc_lock_waitfor() Dee_atomic_lock_waitfor(&gc_lock)
216227
#define _gc_lock_release() Dee_atomic_lock_release(&gc_lock)
217228

229+
PRIVATE void DCALL gc_lock_acquire(void) {
230+
while (!gc_lock_tryacquire()) {
231+
DeeThread_CheckInterruptNoInt();
232+
SCHED_YIELD();
233+
}
234+
}
235+
218236
/* Called to (try to) collect GC objects after "gg_collect_on" of some
219237
* GC generation was reached. This function is must be called without
220238
* the calling thread holding any (internal) locks, include locks
@@ -445,11 +463,6 @@ PRIVATE void DCALL gc_lock_reap(unsigned int flags) {
445463
gc_lock_reap_and_release(flags);
446464
}
447465

448-
PRIVATE bool DCALL gc_lock_force_reap(unsigned int flags) {
449-
gc_lock_acquire();
450-
return gc_lock_reap_and_release(flags);
451-
}
452-
453466
PRIVATE void DCALL gc_lock_acquire_and_reap(unsigned int flags) {
454467
unsigned int status;
455468
again:
@@ -479,6 +492,14 @@ DeeGC_Track(DREF DeeObject *__restrict ob) {
479492
bool must_collect;
480493
struct Dee_gc_head *head = DeeGC_Head(ob);
481494
ASSERT(!((uintptr_t)&head->gc_next & Dee_GC_FLAG_MASK));
495+
#ifndef GC_TRACE_IS_NOOP
496+
if (atomic_read(&DeeThread_Self()->t_state) & Dee_THREAD_STATE_SHUTDOWNINTR) {
497+
GC_TRACE("%llu: %p: DeeGC_Track(%s, %p)\n",
498+
DeeSystem_GetWalltime(), DeeThread_Self(),
499+
ob->ob_type->tp_name, ob);
500+
}
501+
#endif /* !GC_TRACE_IS_NOOP */
502+
482503
if (!gc_lock_tryacquire()) {
483504
/* Setup as a pending insert */
484505
DeeObject *next;
@@ -788,19 +809,30 @@ PRIVATE WUNUSED bool DCALL
788809
gc_collect_acquire(bool allow_interrupts_and_errors) {
789810
DeeObject *pending_remove;
790811
DeeThreadObject *threads;
812+
GC_TRACE("%llu: %p: %s(%d) : HERE%s\n", DeeSystem_GetWalltime(), DeeThread_Self(), __FILE__, __LINE__,
813+
DeeThread_WasInterrupted(DeeThread_Self()) ? " [INTERRUPT]" : "");
791814
again:
792815
/* Wait for "gc_lock" and "gc_remove_modifying" to settle.
793816
*
794817
* During this part, also try to make it so there's a low
795818
* probability of the GC needing to be reaped once we've
796819
* suspended other threads below! */
797820
for (;;) {
798-
while (!gc_lock_available() || atomic_read(&gc_remove_modifying))
821+
while (!gc_lock_available() || atomic_read(&gc_remove_modifying)) {
822+
if (allow_interrupts_and_errors) {
823+
if (DeeThread_CheckInterrupt())
824+
goto err;
825+
} else {
826+
DeeThread_CheckInterruptNoInt();
827+
}
799828
SCHED_YIELD();
829+
}
800830
if (!atomic_read(&gc_mustreap))
801831
break;
802-
if (!gc_lock_force_reap(DeeGC_TRACK_F_NOCOLLECT))
803-
break;
832+
if (!gc_lock_tryacquire())
833+
continue;
834+
gc_lock_reap_and_release(DeeGC_TRACK_F_NOCOLLECT);
835+
break;
804836
}
805837

806838
/* Suspend all other threads (or at least *try* to) */
@@ -861,11 +893,15 @@ gc_collect_acquire(bool allow_interrupts_and_errors) {
861893
}
862894

863895
ASSERT(!DeeThread_IsMultiThreaded);
896+
GC_TRACE("%llu: %p: %s(%d) : HERE%s\n", DeeSystem_GetWalltime(), DeeThread_Self(), __FILE__, __LINE__,
897+
DeeThread_WasInterrupted(DeeThread_Self()) ? " [INTERRUPT]" : "");
864898
return true;
865899
resume_threads_and_try_again:
866900
DeeThread_ResumeAll();
867901
goto again;
868902
err:
903+
GC_TRACE("%llu: %p: %s(%d) : HERE%s\n", DeeSystem_GetWalltime(), DeeThread_Self(), __FILE__, __LINE__,
904+
DeeThread_WasInterrupted(DeeThread_Self()) ? " [INTERRUPT]" : "");
869905
return false;
870906
}
871907

@@ -1260,6 +1296,7 @@ continue_with_next_generation:;
12601296

12611297
/* Collect memory in GC generations */
12621298
status = gc_collect_threshold_generations_r(&gc_gen0);
1299+
GC_TRACE("%llu: %p: %s(%d) : COLLECTED: %u\n", DeeSystem_GetWalltime(), DeeThread_Self(), __FILE__, __LINE__, status);
12631300
switch (status) {
12641301
case GC_GENERATION_COLLECT_OR_UNLOCK__NOTHING:
12651302
/* Nothing could be collected (must objects were moved as they should) */
@@ -1316,10 +1353,11 @@ PRIVATE ATTR_NOINLINE WUNUSED bool DCALL gc_trycollect_gen0(void) {
13161353
flags |= GC_GENERATION_COLLECT_OR_UNLOCK__F_MOVE_REACHABLE;
13171354
ASSERT(!DeeThread_IsMultiThreaded);
13181355
status = gc_generation_collect_or_unlock(iter, &count, flags);
1356+
GC_TRACE("%llu: %p: %s(%d) : COLLECTED: %u: %lu\n", DeeSystem_GetWalltime(), DeeThread_Self(), __FILE__, __LINE__, status, (unsigned long)count);
13191357
} else {
1320-
/* Stop once all overflowing objects have been settled into appropriate generations */
1321-
status = GC_GENERATION_COLLECT_OR_UNLOCK__SUCCESS;
1358+
/* All done! */
13221359
gc_collect_release();
1360+
return true;
13231361
}
13241362
switch (status) {
13251363
case GC_GENERATION_COLLECT_OR_UNLOCK__NOTHING:

src/deemon/runtime/thread.c

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,6 +2266,52 @@ INTERN WUNUSED NONNULL((1)) int
22662266
return -1;
22672267
}
22682268

2269+
INTERN NONNULL((1)) void
2270+
(DCALL DeeThread_CheckInterruptNoIntSelf)(DeeThreadObject *__restrict self) {
2271+
#ifdef DeeThread_USE_SINGLE_THREADED
2272+
(void)self;
2273+
#else /* DeeThread_USE_SINGLE_THREADED */
2274+
uint32_t state;
2275+
again_read_state:
2276+
state = atomic_read(&self->t_state);
2277+
2278+
/* Check if we're supposed to become suspended. */
2279+
if ((state & (Dee_THREAD_STATE_SUSPENDING |
2280+
Dee_THREAD_STATE_SUSPENDED |
2281+
Dee_THREAD_STATE_INTERRUPTED)) ==
2282+
(Dee_THREAD_STATE_SUSPENDING | Dee_THREAD_STATE_INTERRUPTED)) {
2283+
atomic_inc(&self->t_int_vers); /* Indicate that we're checking for interrupts */
2284+
2285+
/* Enter the suspend-state */
2286+
atomic_or(&self->t_state, Dee_THREAD_STATE_SUSPENDED);
2287+
if (!atomic_cmpxch_weak(&self->t_state, state,
2288+
(state & ~Dee_THREAD_STATE_INTERRUPTED) |
2289+
Dee_THREAD_STATE_SUSPENDED))
2290+
goto again_read_state;
2291+
/* Preserve system errors across interrupt checks */
2292+
DeeSystemError_Push();
2293+
DeeFutex_WakeAll(&self->t_state);
2294+
2295+
/* Wait until the suspend-state ends */
2296+
for (;;) {
2297+
state = atomic_read(&self->t_state);
2298+
if (!(state & Dee_THREAD_STATE_SUSPENDING))
2299+
break;
2300+
DeeFutex_Wait32NoInt(&self->t_state, state);
2301+
}
2302+
2303+
/* Leave the suspend state */
2304+
atomic_and(&self->t_state, ~Dee_THREAD_STATE_SUSPENDED);
2305+
DeeFutex_WakeAll(&self->t_state);
2306+
2307+
/* Restore the system error context */
2308+
DeeSystemError_Pop();
2309+
goto again_read_state;
2310+
}
2311+
#endif /* !DeeThread_USE_SINGLE_THREADED */
2312+
}
2313+
2314+
22692315
/* Check for an interrupt exception and throw it if there is one.
22702316
* This function should be called before any blocking system call and is
22712317
* invoked by the interpreter before execution of any JMP-instruction, or
@@ -2275,6 +2321,15 @@ PUBLIC WUNUSED int (DCALL DeeThread_CheckInterrupt)(void) {
22752321
return DeeThread_CheckInterruptSelf(DeeThread_Self());
22762322
}
22772323

2324+
/* Same as `DeeThread_CheckInterrupt()', but only handle interrupts that
2325+
* will not invoke user-code or throw errors. (e.g.: this can be used to
2326+
* handle thread suspend requests) */
2327+
PUBLIC void (DCALL DeeThread_CheckInterruptNoInt)(void) {
2328+
#ifndef DeeThread_USE_SINGLE_THREADED
2329+
DeeThread_CheckInterruptNoIntSelf(DeeThread_Self());
2330+
#endif /* !DeeThread_USE_SINGLE_THREADED */
2331+
}
2332+
22782333

22792334
#ifndef DeeThread_USE_SINGLE_THREADED
22802335

@@ -2340,7 +2395,7 @@ forward_appexit_to_main_thread(/*inherit(always)*/ struct Dee_thread_interrupt *
23402395
atomic_or(&DeeThread_Main.ot_thread.t_state, Dee_THREAD_STATE_WAITING);
23412396
state |= Dee_THREAD_STATE_WAITING;
23422397
}
2343-
2398+
23442399
/* Keep waking the thread in case it just went inside of a blocking system call. */
23452400
DeeFutex_Wait32NoIntTimed(&DeeThread_Main.ot_thread.t_state, state, THREAD_WAKE_DELAY);
23462401
if (version != atomic_read(&DeeThread_Main.ot_thread.t_int_vers))
@@ -2552,7 +2607,7 @@ PRIVATE int DeeThread_Entry_func(void *arg)
25522607
DeeError_Handled(ERROR_HANDLED_INTERRUPT);
25532608
while (DeeError_Catch(&DeeError_Interrupt))
25542609
;
2555-
2610+
25562611
/* If no further exceptions have occurred, set the thread-exit return value. */
25572612
if (!DeeError_Current()) {
25582613
_DeeThread_AcquireInterrupt(&self->ot_thread);
@@ -3040,7 +3095,7 @@ DeeThread_Interrupt(/*Thread*/ DeeObject *self,
30403095
atomic_or(&me->t_state, Dee_THREAD_STATE_WAITING);
30413096
state |= Dee_THREAD_STATE_WAITING;
30423097
}
3043-
3098+
30443099
/* Keep waking the thread in case it just went inside of a blocking system call. */
30453100
DeeFutex_Wait32NoIntTimed(&me->t_state, state, THREAD_WAKE_DELAY);
30463101
if (version != atomic_read(&me->t_int_vers))

0 commit comments

Comments
 (0)