Author: Remi Meier <remi.me...@gmail.com> Branch: Changeset: r2005:3af462fd449b Date: 2017-02-09 09:28 +0100 http://bitbucket.org/pypy/stmgc/changeset/3af462fd449b/
Log: merge new finalizer API diff --git a/c8/stm/core.c b/c8/stm/core.c --- a/c8/stm/core.c +++ b/c8/stm/core.c @@ -1047,8 +1047,7 @@ assert(tree_is_cleared(STM_PSEGMENT->nursery_objects_shadows)); assert(tree_is_cleared(STM_PSEGMENT->callbacks_on_commit_and_abort[0])); assert(tree_is_cleared(STM_PSEGMENT->callbacks_on_commit_and_abort[1])); - assert(list_is_empty(STM_PSEGMENT->young_objects_with_light_finalizers)); - assert(STM_PSEGMENT->finalizers == NULL); + assert(list_is_empty(STM_PSEGMENT->young_objects_with_destructors)); assert(STM_PSEGMENT->active_queues == NULL); #ifndef NDEBUG /* this should not be used when objects_pointing_to_nursery == NULL */ diff --git a/c8/stm/core.h b/c8/stm/core.h --- a/c8/stm/core.h +++ b/c8/stm/core.h @@ -13,6 +13,8 @@ #include <errno.h> #include <pthread.h> #include <signal.h> +#include <stdbool.h> +#include "list.h" /************************************************************/ @@ -144,9 +146,9 @@ pthread_t running_pthread; #endif - /* light finalizers */ - struct list_s *young_objects_with_light_finalizers; - struct list_s *old_objects_with_light_finalizers; + /* destructors */ + struct list_s *young_objects_with_destructors; + struct list_s *old_objects_with_destructors; /* regular finalizers (objs from the current transaction only) */ struct finalizers_s *finalizers; diff --git a/c8/stm/finalizer.c b/c8/stm/finalizer.c --- a/c8/stm/finalizer.c +++ b/c8/stm/finalizer.c @@ -2,31 +2,65 @@ # error "must be compiled via stmgc.c" # include "core.h" // silence flymake #endif +#include "finalizer.h" +#include "fprintcolor.h" +#include "nursery.h" +#include "gcpage.h" /* callbacks */ -void (*stmcb_light_finalizer)(object_t *); +void (*stmcb_destructor)(object_t *); void (*stmcb_finalizer)(object_t *); static void init_finalizers(struct finalizers_s *f) { f->objects_with_finalizers = list_create(); - f->count_non_young = 0; - f->run_finalizers = NULL; - f->running_next = NULL; + f->probably_young_objects_with_finalizers = list_create(); + f->run_finalizers = list_create(); + f->lock = 0; + f->running_trigger_now = NULL; } static void setup_finalizer(void) { init_finalizers(&g_finalizers); + + for (long j = 1; j < NB_SEGMENTS; j++) { + struct stm_priv_segment_info_s *pseg = get_priv_segment(j); + + assert(pseg->finalizers == NULL); + struct finalizers_s *f = malloc(sizeof(struct finalizers_s)); + if (f == NULL) + stm_fatalerror("out of memory in create_finalizers"); /* XXX */ + init_finalizers(f); + pseg->finalizers = f; + } } -static void teardown_finalizer(void) +void stm_setup_finalizer_queues(int number, stm_finalizer_trigger_fn *triggers) { - if (g_finalizers.run_finalizers != NULL) - list_free(g_finalizers.run_finalizers); - list_free(g_finalizers.objects_with_finalizers); + assert(g_finalizer_triggers.count == 0); + assert(g_finalizer_triggers.triggers == NULL); + + g_finalizer_triggers.count = number; + g_finalizer_triggers.triggers = (stm_finalizer_trigger_fn *) + malloc(number * sizeof(stm_finalizer_trigger_fn)); + + for (int qindex = 0; qindex < number; qindex++) { + g_finalizer_triggers.triggers[qindex] = triggers[qindex]; + dprintf(("setup_finalizer_queue(qindex=%d,fun=%p)\n", qindex, triggers[qindex])); + } +} + +static void teardown_finalizer(void) { + LIST_FREE(g_finalizers.run_finalizers); + LIST_FREE(g_finalizers.objects_with_finalizers); + LIST_FREE(g_finalizers.probably_young_objects_with_finalizers); memset(&g_finalizers, 0, sizeof(g_finalizers)); + + if (g_finalizer_triggers.triggers) + free(g_finalizer_triggers.triggers); + memset(&g_finalizer_triggers, 0, sizeof(g_finalizer_triggers)); } static void _commit_finalizers(void) @@ -36,36 +70,31 @@ stm_spin_loop(); } - if (STM_PSEGMENT->finalizers->run_finalizers != NULL) { + struct finalizers_s *local_fs = STM_PSEGMENT->finalizers; + if (!list_is_empty(local_fs->run_finalizers)) { /* copy 'STM_PSEGMENT->finalizers->run_finalizers' into 'g_finalizers.run_finalizers', dropping any initial NULLs (finalizers already called) */ - struct list_s *src = STM_PSEGMENT->finalizers->run_finalizers; - uintptr_t frm = 0; - if (STM_PSEGMENT->finalizers->running_next != NULL) { - frm = *STM_PSEGMENT->finalizers->running_next; - assert(frm <= list_count(src)); - *STM_PSEGMENT->finalizers->running_next = (uintptr_t)-1; - } - if (frm < list_count(src)) { - if (g_finalizers.run_finalizers == NULL) - g_finalizers.run_finalizers = list_create(); + struct list_s *src = local_fs->run_finalizers; + if (list_count(src)) { g_finalizers.run_finalizers = list_extend( g_finalizers.run_finalizers, - src, frm); + src, 0); } - list_free(src); } + LIST_FREE(local_fs->run_finalizers); /* copy the whole 'STM_PSEGMENT->finalizers->objects_with_finalizers' into 'g_finalizers.objects_with_finalizers' */ g_finalizers.objects_with_finalizers = list_extend( g_finalizers.objects_with_finalizers, - STM_PSEGMENT->finalizers->objects_with_finalizers, 0); - list_free(STM_PSEGMENT->finalizers->objects_with_finalizers); + local_fs->objects_with_finalizers, 0); + LIST_FREE(local_fs->objects_with_finalizers); + assert(list_is_empty(local_fs->probably_young_objects_with_finalizers)); + LIST_FREE(local_fs->probably_young_objects_with_finalizers); - free(STM_PSEGMENT->finalizers); - STM_PSEGMENT->finalizers = NULL; + // re-init + init_finalizers(local_fs); __sync_lock_release(&g_finalizers.lock); } @@ -74,24 +103,22 @@ { /* like _commit_finalizers(), but forget everything from the current transaction */ - if (pseg->finalizers != NULL) { - if (pseg->finalizers->run_finalizers != NULL) { - if (pseg->finalizers->running_next != NULL) { - *pseg->finalizers->running_next = (uintptr_t)-1; - } - list_free(pseg->finalizers->run_finalizers); - } - list_free(pseg->finalizers->objects_with_finalizers); - free(pseg->finalizers); - pseg->finalizers = NULL; - } + LIST_FREE(pseg->finalizers->run_finalizers); + LIST_FREE(pseg->finalizers->objects_with_finalizers); + LIST_FREE(pseg->finalizers->probably_young_objects_with_finalizers); + // re-init + init_finalizers(pseg->finalizers); + + // if we were running triggers, release the lock: + if (g_finalizers.running_trigger_now == pseg) + g_finalizers.running_trigger_now = NULL; /* call the light finalizers for objects that are about to be forgotten from the current transaction */ char *old_gs_register = STM_SEGMENT->segment_base; bool must_fix_gs = old_gs_register != pseg->pub.segment_base; - struct list_s *lst = pseg->young_objects_with_light_finalizers; + struct list_s *lst = pseg->young_objects_with_destructors; long i, count = list_count(lst); if (lst > 0) { for (i = 0; i < count; i++) { @@ -101,15 +128,15 @@ set_gs_register(pseg->pub.segment_base); must_fix_gs = false; } - stmcb_light_finalizer(obj); + stmcb_destructor(obj); } list_clear(lst); } /* also deals with overflow objects: they are at the tail of - old_objects_with_light_finalizers (this list is kept in order + old_objects_with_destructors (this list is kept in order and we cannot add any already-committed object) */ - lst = pseg->old_objects_with_light_finalizers; + lst = pseg->old_objects_with_destructors; count = list_count(lst); while (count > 0) { object_t *obj = (object_t *)list_item(lst, --count); @@ -120,7 +147,7 @@ set_gs_register(pseg->pub.segment_base); must_fix_gs = false; } - stmcb_light_finalizer(obj); + stmcb_destructor(obj); } if (STM_SEGMENT->segment_base != old_gs_register) @@ -128,44 +155,42 @@ } -void stm_enable_light_finalizer(object_t *obj) +void stm_enable_destructor(object_t *obj) { if (_is_young(obj)) { - LIST_APPEND(STM_PSEGMENT->young_objects_with_light_finalizers, obj); + LIST_APPEND(STM_PSEGMENT->young_objects_with_destructors, obj); } else { assert(_is_from_same_transaction(obj)); - LIST_APPEND(STM_PSEGMENT->old_objects_with_light_finalizers, obj); + LIST_APPEND(STM_PSEGMENT->old_objects_with_destructors, obj); } } -object_t *stm_allocate_with_finalizer(ssize_t size_rounded_up) + +void stm_enable_finalizer(int queue_index, object_t *obj) { - object_t *obj = _stm_allocate_external(size_rounded_up); - - if (STM_PSEGMENT->finalizers == NULL) { - struct finalizers_s *f = malloc(sizeof(struct finalizers_s)); - if (f == NULL) - stm_fatalerror("out of memory in create_finalizers"); /* XXX */ - init_finalizers(f); - STM_PSEGMENT->finalizers = f; + if (_is_young(obj)) { + LIST_APPEND(STM_PSEGMENT->finalizers->probably_young_objects_with_finalizers, obj); + LIST_APPEND(STM_PSEGMENT->finalizers->probably_young_objects_with_finalizers, queue_index); } - assert(STM_PSEGMENT->finalizers->count_non_young - <= list_count(STM_PSEGMENT->finalizers->objects_with_finalizers)); - LIST_APPEND(STM_PSEGMENT->finalizers->objects_with_finalizers, obj); - return obj; + else { + assert(_is_from_same_transaction(obj)); + LIST_APPEND(STM_PSEGMENT->finalizers->objects_with_finalizers, obj); + LIST_APPEND(STM_PSEGMENT->finalizers->objects_with_finalizers, queue_index); + } } + /************************************************************/ -/* Light finalizers +/* Destructors */ -static void deal_with_young_objects_with_finalizers(void) +static void deal_with_young_objects_with_destructors(void) { - /* for light finalizers: executes finalizers for objs that don't survive + /* for destructors: executes destructors for objs that don't survive this minor gc */ - struct list_s *lst = STM_PSEGMENT->young_objects_with_light_finalizers; + struct list_s *lst = STM_PSEGMENT->young_objects_with_destructors; long i, count = list_count(lst); for (i = 0; i < count; i++) { object_t *obj = (object_t *)list_item(lst, i); @@ -174,28 +199,29 @@ object_t *TLPREFIX *pforwarded_array = (object_t *TLPREFIX *)obj; if (pforwarded_array[0] != GCWORD_MOVED) { /* not moved: the object dies */ - stmcb_light_finalizer(obj); + stmcb_destructor(obj); } else { obj = pforwarded_array[1]; /* moved location */ assert(!_is_young(obj)); - LIST_APPEND(STM_PSEGMENT->old_objects_with_light_finalizers, obj); + LIST_APPEND(STM_PSEGMENT->old_objects_with_destructors, obj); } } list_clear(lst); } -static void deal_with_old_objects_with_finalizers(void) +static void deal_with_old_objects_with_destructors(void) { - /* for light finalizers */ + /* for destructors */ int old_gs_register = STM_SEGMENT->segment_num; int current_gs_register = old_gs_register; long j; - assert(list_is_empty(get_priv_segment(0)->old_objects_with_light_finalizers)); + assert(list_is_empty(get_priv_segment(0)->old_objects_with_destructors)); for (j = 1; j < NB_SEGMENTS; j++) { struct stm_priv_segment_info_s *pseg = get_priv_segment(j); - struct list_s *lst = pseg->old_objects_with_light_finalizers; + assert(list_is_empty(pseg->young_objects_with_destructors)); + struct list_s *lst = pseg->old_objects_with_destructors; long i, count = list_count(lst); lst->count = 0; for (i = 0; i < count; i++) { @@ -217,7 +243,7 @@ set_gs_register(get_segment_base(j)); current_gs_register = j; } - stmcb_light_finalizer(obj); + stmcb_destructor(obj); } else { /* object survives */ @@ -230,6 +256,7 @@ } + /************************************************************/ /* Algorithm for regular (non-light) finalizers. Follows closely pypy/doc/discussion/finalizer-order.rst @@ -328,20 +355,23 @@ struct list_s *marked = list_create(); + assert(list_is_empty(f->probably_young_objects_with_finalizers)); struct list_s *lst = f->objects_with_finalizers; long i, count = list_count(lst); lst->count = 0; - f->count_non_young = 0; - for (i = 0; i < count; i++) { + for (i = 0; i < count; i += 2) { object_t *x = (object_t *)list_item(lst, i); + uintptr_t qindex = list_item(lst, i + 1); assert(_finalization_state(x) != 1); if (_finalization_state(x) >= 2) { list_set_item(lst, lst->count++, (uintptr_t)x); + list_set_item(lst, lst->count++, qindex); continue; } LIST_APPEND(marked, x); + LIST_APPEND(marked, qindex); struct list_s *pending = _finalizer_pending; LIST_APPEND(pending, x); @@ -373,27 +403,29 @@ struct list_s *run_finalizers = f->run_finalizers; long i, count = list_count(marked); - for (i = 0; i < count; i++) { + for (i = 0; i < count; i += 2) { object_t *x = (object_t *)list_item(marked, i); + uintptr_t qindex = list_item(marked, i + 1); int state = _finalization_state(x); assert(state >= 2); if (state == 2) { - if (run_finalizers == NULL) - run_finalizers = list_create(); LIST_APPEND(run_finalizers, x); + LIST_APPEND(run_finalizers, qindex); _recursively_bump_finalization_state_from_2_to_3(pseg, x); } else { struct list_s *lst = f->objects_with_finalizers; list_set_item(lst, lst->count++, (uintptr_t)x); + list_set_item(lst, lst->count++, qindex); } } - list_free(marked); + LIST_FREE(marked); f->run_finalizers = run_finalizers; } + static void deal_with_objects_with_finalizers(void) { /* for non-light finalizers */ @@ -436,11 +468,10 @@ static void mark_visit_from_finalizer1( struct stm_priv_segment_info_s *pseg, struct finalizers_s *f) { - if (f != NULL && f->run_finalizers != NULL) { - LIST_FOREACH_R(f->run_finalizers, object_t * /*item*/, - ({ - mark_visit_possibly_overflow_object(item, pseg); - })); + long i, count = list_count(f->run_finalizers); + for (i = 0; i < count; i += 2) { + object_t *x = (object_t *)list_item(f->run_finalizers, i); + mark_visit_possibly_overflow_object(x, pseg); } } @@ -454,40 +485,6 @@ mark_visit_from_finalizer1(get_priv_segment(0), &g_finalizers); } -static void _execute_finalizers(struct finalizers_s *f) -{ - if (f->run_finalizers == NULL) - return; /* nothing to do */ - - restart: - if (f->running_next != NULL) - return; /* in a nested invocation of execute_finalizers() */ - - uintptr_t next = 0, total = list_count(f->run_finalizers); - f->running_next = &next; - - while (next < total) { - object_t *obj = (object_t *)list_item(f->run_finalizers, next); - list_set_item(f->run_finalizers, next, 0); - next++; - - stmcb_finalizer(obj); - } - if (next == (uintptr_t)-1) { - /* transaction committed: the whole 'f' was freed */ - return; - } - f->running_next = NULL; - - if (f->run_finalizers->count > total) { - memmove(f->run_finalizers->items, - f->run_finalizers->items + total, - (f->run_finalizers->count - total) * sizeof(uintptr_t)); - goto restart; - } - - LIST_FREE(f->run_finalizers); -} /* XXX: according to translator.backendopt.finalizer, getfield_gc for primitive types is a safe op in light finalizers. @@ -495,43 +492,185 @@ getfield on *dying obj*). */ +static void _trigger_finalizer_queues(struct finalizers_s *f) +{ + /* runs triggers of finalizer queues that have elements in the queue. May + NOT run outside of a transaction, but triggers never leave the + transactional zone. + + returns true if there are also old-style finalizers to run */ + assert(in_transaction(STM_PSEGMENT->pub.running_thread)); + + bool *to_trigger = (bool*)alloca(g_finalizer_triggers.count * sizeof(bool)); + memset(to_trigger, 0, g_finalizer_triggers.count * sizeof(bool)); + + while (__sync_lock_test_and_set(&f->lock, 1) != 0) { + /* somebody is adding more finalizers (_commit_finalizer()) */ + stm_spin_loop(); + } + + int count = list_count(f->run_finalizers); + for (int i = 0; i < count; i += 2) { + int qindex = (int)list_item(f->run_finalizers, i + 1); + dprintf(("qindex=%d\n", qindex)); + to_trigger[qindex] = true; + } + + __sync_lock_release(&f->lock); + + // trigger now: + for (int i = 0; i < g_finalizer_triggers.count; i++) { + if (to_trigger[i]) { + dprintf(("invoke-finalizer-trigger(qindex=%d)\n", i)); + g_finalizer_triggers.triggers[i](); + } + } +} + +static bool _has_oldstyle_finalizers(struct finalizers_s *f) +{ + int count = list_count(f->run_finalizers); + for (int i = 0; i < count; i += 2) { + int qindex = (int)list_item(f->run_finalizers, i + 1); + if (qindex == -1) + return true; + } + return false; +} + +static void _invoke_local_finalizers() +{ + /* called inside a transaction; invoke local triggers, process old-style + * local finalizers */ + dprintf(("invoke_local_finalizers %lu\n", list_count(STM_PSEGMENT->finalizers->run_finalizers))); + if (list_is_empty(STM_PSEGMENT->finalizers->run_finalizers) + && list_is_empty(g_finalizers.run_finalizers)) + return; + + struct stm_priv_segment_info_s *pseg = get_priv_segment(STM_SEGMENT->segment_num); + //try to run local triggers + if (STM_PSEGMENT->finalizers->running_trigger_now == NULL) { + // we are not recursively running them + STM_PSEGMENT->finalizers->running_trigger_now = pseg; + _trigger_finalizer_queues(STM_PSEGMENT->finalizers); + STM_PSEGMENT->finalizers->running_trigger_now = NULL; + } + + // try to run global triggers + if (__sync_lock_test_and_set(&g_finalizers.running_trigger_now, pseg) == NULL) { + // nobody is already running these triggers (recursively) + _trigger_finalizer_queues(&g_finalizers); + g_finalizers.running_trigger_now = NULL; + } + + if (!_has_oldstyle_finalizers(STM_PSEGMENT->finalizers)) + return; // no oldstyle to run + + object_t *obj; + while ((obj = stm_next_to_finalize(-1)) != NULL) { + stmcb_finalizer(obj); + } +} + static void _invoke_general_finalizers(stm_thread_local_t *tl) { - /* called between transactions */ + /* called between transactions + * triggers not called here, since all should have been called already in _invoke_local_finalizers! + * run old-style finalizers (q_index=-1) + * queues that are not empty. */ + dprintf(("invoke_general_finalizers %lu\n", list_count(g_finalizers.run_finalizers))); + if (list_is_empty(g_finalizers.run_finalizers)) + return; + + if (!_has_oldstyle_finalizers(&g_finalizers)) + return; // no oldstyle to run + + // run old-style finalizers: rewind_jmp_buf rjbuf; stm_rewind_jmp_enterframe(tl, &rjbuf); _stm_start_transaction(tl); - /* XXX: become inevitable, bc. otherwise, we would need to keep - around the original g_finalizers.run_finalizers to restore it - in case of an abort. */ - _stm_become_inevitable(MSG_INEV_DONT_SLEEP); - /* did it work? */ - if (STM_PSEGMENT->transaction_state != TS_INEVITABLE) { /* no */ - /* avoid blocking here, waiting for another INEV transaction. - If we did that, application code could not proceed (start the - next transaction) and it will not be obvious from the profile - why we were WAITing. */ - _stm_commit_transaction(); - stm_rewind_jmp_leaveframe(tl, &rjbuf); - return; + + dprintf(("invoke_oldstyle_finalizers %lu\n", list_count(g_finalizers.run_finalizers))); + object_t *obj; + while ((obj = stm_next_to_finalize(-1)) != NULL) { + assert(STM_PSEGMENT->transaction_state == TS_INEVITABLE); + stmcb_finalizer(obj); } + _stm_commit_transaction(); + stm_rewind_jmp_leaveframe(tl, &rjbuf); +} + +object_t* stm_next_to_finalize(int queue_index) { + assert(STM_PSEGMENT->transaction_state != TS_NONE); + + /* first check local run_finalizers queue, then global */ + if (!list_is_empty(STM_PSEGMENT->finalizers->run_finalizers)) { + struct list_s *lst = STM_PSEGMENT->finalizers->run_finalizers; + int count = list_count(lst); + for (int i = 0; i < count; i += 2) { + int qindex = (int)list_item(lst, i + 1); + if (qindex == queue_index) { + /* no need to become inevitable for local ones */ + /* Remove obj from list and return it. */ + object_t *obj = (object_t*)list_item(lst, i); + int remaining = count - i - 2; + if (remaining > 0) { + memmove(&lst->items[i], + &lst->items[i + 2], + remaining * sizeof(uintptr_t)); + } + lst->count -= 2; + return obj; + } + } + } + + /* no local finalizers found, continue in global list */ + while (__sync_lock_test_and_set(&g_finalizers.lock, 1) != 0) { /* somebody is adding more finalizers (_commit_finalizer()) */ stm_spin_loop(); } - struct finalizers_s copy = g_finalizers; - assert(copy.running_next == NULL); - g_finalizers.run_finalizers = NULL; + + struct list_s *lst = g_finalizers.run_finalizers; + int count = list_count(lst); + for (int i = 0; i < count; i += 2) { + int qindex = (int)list_item(lst, i + 1); + if (qindex == queue_index) { + /* XXX: become inevitable, bc. otherwise, we would need to keep + around the original g_finalizers.run_finalizers to restore it + in case of an abort. */ + if (STM_PSEGMENT->transaction_state != TS_INEVITABLE) { + _stm_become_inevitable(MSG_INEV_DONT_SLEEP); + /* did it work? */ + if (STM_PSEGMENT->transaction_state != TS_INEVITABLE) { /* no */ + /* avoid blocking here, waiting for another INEV transaction. + If we did that, application code could not proceed (start the + next transaction) and it will not be obvious from the profile + why we were WAITing. XXX: still true? */ + __sync_lock_release(&g_finalizers.lock); + return NULL; + } + } + + /* Remove obj from list and return it. */ + object_t *obj = (object_t*)list_item(lst, i); + int remaining = count - i - 2; + if (remaining > 0) { + memmove(&lst->items[i], + &lst->items[i + 2], + remaining * sizeof(uintptr_t)); + } + lst->count -= 2; + + __sync_lock_release(&g_finalizers.lock); + return obj; + } + } + /* others may add to g_finalizers again: */ __sync_lock_release(&g_finalizers.lock); - if (copy.run_finalizers != NULL) { - _execute_finalizers(©); - } - - _stm_commit_transaction(); - stm_rewind_jmp_leaveframe(tl, &rjbuf); - - LIST_FREE(copy.run_finalizers); + return NULL; } diff --git a/c8/stm/finalizer.h b/c8/stm/finalizer.h --- a/c8/stm/finalizer.h +++ b/c8/stm/finalizer.h @@ -1,16 +1,20 @@ +#ifndef _STM_FINALIZER_H_ +#define _STM_FINALIZER_H_ + +#include <stdint.h> /* see deal_with_objects_with_finalizers() for explanation of these fields */ struct finalizers_s { long lock; + struct stm_priv_segment_info_s * running_trigger_now; /* our PSEG, if we are running triggers */ struct list_s *objects_with_finalizers; - uintptr_t count_non_young; + struct list_s *probably_young_objects_with_finalizers; /* empty on g_finalizers! */ struct list_s *run_finalizers; - uintptr_t *running_next; }; static void mark_visit_from_finalizer_pending(void); -static void deal_with_young_objects_with_finalizers(void); -static void deal_with_old_objects_with_finalizers(void); +static void deal_with_young_objects_with_destructors(void); +static void deal_with_old_objects_with_destructors(void); static void deal_with_objects_with_finalizers(void); static void setup_finalizer(void); @@ -27,19 +31,22 @@ /* regular finalizers (objs from already-committed transactions) */ static struct finalizers_s g_finalizers; +static struct { + int count; + stm_finalizer_trigger_fn *triggers; +} g_finalizer_triggers; + static void _invoke_general_finalizers(stm_thread_local_t *tl); +static void _invoke_local_finalizers(void); #define invoke_general_finalizers(tl) do { \ - if (g_finalizers.run_finalizers != NULL) \ - _invoke_general_finalizers(tl); \ + _invoke_general_finalizers(tl); \ } while (0) -static void _execute_finalizers(struct finalizers_s *f); -#define any_local_finalizers() (STM_PSEGMENT->finalizers != NULL && \ - STM_PSEGMENT->finalizers->run_finalizers != NULL) #define exec_local_finalizers() do { \ - if (any_local_finalizers()) \ - _execute_finalizers(STM_PSEGMENT->finalizers); \ + _invoke_local_finalizers(); \ } while (0) + +#endif diff --git a/c8/stm/fprintcolor.h b/c8/stm/fprintcolor.h --- a/c8/stm/fprintcolor.h +++ b/c8/stm/fprintcolor.h @@ -1,3 +1,7 @@ +#ifndef _FPRINTCOLOR_H +#define _FPRINTCOLOR_H + + /* ------------------------------------------------------------ */ #ifdef STM_DEBUGPRINT /* ------------------------------------------------------------ */ @@ -40,3 +44,5 @@ __attribute__((unused)) static void stm_fatalerror(const char *format, ...) __attribute__((format (printf, 1, 2), noreturn)); + +#endif diff --git a/c8/stm/gcpage.c b/c8/stm/gcpage.c --- a/c8/stm/gcpage.c +++ b/c8/stm/gcpage.c @@ -807,9 +807,9 @@ LIST_FREE(marked_objects_to_trace); - /* weakrefs and execute old light finalizers */ + /* weakrefs and execute old destructors */ stm_visit_old_weakrefs(); - deal_with_old_objects_with_finalizers(); + deal_with_old_objects_with_destructors(); /* cleanup */ clean_up_segment_lists(); diff --git a/c8/stm/gcpage.h b/c8/stm/gcpage.h --- a/c8/stm/gcpage.h +++ b/c8/stm/gcpage.h @@ -1,3 +1,7 @@ +#ifndef _STM_GCPAGE_H_ +#define _STM_GCPAGE_H_ + +#include <stdbool.h> /* Granularity when grabbing more unused pages: take 20 at a time */ #define GCPAGE_NUM_PAGES 20 @@ -22,3 +26,9 @@ static void major_collection_with_mutex(void); static bool largemalloc_keep_object_at(char *data); /* for largemalloc.c */ static bool smallmalloc_keep_object_at(char *data); /* for smallmalloc.c */ + +static inline bool mark_visited_test(object_t *obj); +static bool is_overflow_obj_safe(struct stm_priv_segment_info_s *pseg, object_t *obj); +static void mark_visit_possibly_overflow_object(object_t *obj, struct stm_priv_segment_info_s *pseg); + +#endif diff --git a/c8/stm/list.h b/c8/stm/list.h --- a/c8/stm/list.h +++ b/c8/stm/list.h @@ -1,5 +1,11 @@ +#ifndef _LIST_H +#define _LIST_H + + #include <stdlib.h> #include <stdbool.h> +#include <stdint.h> + /************************************************************/ @@ -11,13 +17,13 @@ static struct list_s *list_create(void) __attribute__((unused)); -static inline void list_free(struct list_s *lst) +static inline void _list_free(struct list_s *lst) { free(lst); } #define LIST_CREATE(lst) ((lst) = list_create()) -#define LIST_FREE(lst) (list_free(lst), (lst) = NULL) +#define LIST_FREE(lst) (_list_free(lst), (lst) = NULL) static struct list_s *_list_grow(struct list_s *, uintptr_t); @@ -245,3 +251,5 @@ TREE_FIND(tree, addr, result, return false); return true; } + +#endif diff --git a/c8/stm/nursery.c b/c8/stm/nursery.c --- a/c8/stm/nursery.c +++ b/c8/stm/nursery.c @@ -3,6 +3,8 @@ # include "core.h" // silence flymake #endif +#include "finalizer.h" + /************************************************************/ #define NURSERY_START (FIRST_NURSERY_PAGE * 4096UL) @@ -56,7 +58,6 @@ /************************************************************/ static object_t *find_existing_shadow(object_t *obj); -#define GCWORD_MOVED ((object_t *) -1) #define FLAG_SYNC_LARGE 0x01 @@ -441,23 +442,26 @@ } } -static void collect_objs_still_young_but_with_finalizers(void) +static void collect_young_objects_with_finalizers(void) { - struct list_s *lst = STM_PSEGMENT->finalizers->objects_with_finalizers; - uintptr_t i, total = list_count(lst); + /* for real finalizers: in a minor collection, all young objs must survive! */ - for (i = STM_PSEGMENT->finalizers->count_non_young; i < total; i++) { + struct list_s *lst = STM_PSEGMENT->finalizers->probably_young_objects_with_finalizers; + long i, count = list_count(lst); + for (i = 0; i < count; i += 2) { + object_t *obj = (object_t *)list_item(lst, i); + uintptr_t qindex = list_item(lst, i + 1); - object_t *o = (object_t *)list_item(lst, i); - minor_trace_if_young(&o); + minor_trace_if_young(&obj); - /* was not actually movable */ - assert(o == (object_t *)list_item(lst, i)); + LIST_APPEND(STM_PSEGMENT->finalizers->objects_with_finalizers, obj); + LIST_APPEND(STM_PSEGMENT->finalizers->objects_with_finalizers, qindex); } - STM_PSEGMENT->finalizers->count_non_young = total; + list_clear(lst); } + static void throw_away_nursery(struct stm_priv_segment_info_s *pseg) { #pragma push_macro("STM_PSEGMENT") @@ -555,8 +559,7 @@ collect_roots_in_nursery(); - if (STM_PSEGMENT->finalizers != NULL) - collect_objs_still_young_but_with_finalizers(); + collect_young_objects_with_finalizers(); if (STM_PSEGMENT->active_queues != NULL) collect_active_queues(); @@ -568,7 +571,7 @@ acquire_privatization_lock(STM_SEGMENT->segment_num); stm_move_young_weakrefs(); release_privatization_lock(STM_SEGMENT->segment_num); - deal_with_young_objects_with_finalizers(); + deal_with_young_objects_with_destructors(); assert(list_is_empty(STM_PSEGMENT->objects_pointing_to_nursery)); diff --git a/c8/stm/nursery.h b/c8/stm/nursery.h --- a/c8/stm/nursery.h +++ b/c8/stm/nursery.h @@ -1,3 +1,8 @@ +#ifndef _STM_NURSERY_H_ +#define _STM_NURSERY_H_ + +#include <stdint.h> +#include <stdbool.h> #define NSE_SIGPAUSE _STM_NSE_SIGNAL_MAX #define NSE_SIGABORT _STM_NSE_SIGNAL_ABORT @@ -44,3 +49,11 @@ #define must_abort() is_abort(STM_SEGMENT->nursery_end) static object_t *find_shadow(object_t *obj); + + +#define GCWORD_MOVED ((object_t *) -1) +static inline bool _is_young(object_t *obj); +static inline struct object_s *mark_loc(object_t *obj); +static inline bool _is_from_same_transaction(object_t *obj); + +#endif diff --git a/c8/stm/setup.c b/c8/stm/setup.c --- a/c8/stm/setup.c +++ b/c8/stm/setup.c @@ -5,6 +5,8 @@ #include <signal.h> #include <fcntl.h> /* For O_* constants */ +#include "signal_handler.h" +#include "fprintcolor.h" static void setup_mmap(char *reason) { @@ -100,8 +102,8 @@ pr->nursery_objects_shadows = tree_create(); pr->callbacks_on_commit_and_abort[0] = tree_create(); pr->callbacks_on_commit_and_abort[1] = tree_create(); - pr->young_objects_with_light_finalizers = list_create(); - pr->old_objects_with_light_finalizers = list_create(); + pr->young_objects_with_destructors = list_create(); + pr->old_objects_with_destructors = list_create(); pr->last_commit_log_entry = &commit_log_root; pr->overflow_number = GCFLAG_OVERFLOW_NUMBER_bit0 * i; @@ -145,19 +147,19 @@ for (i = 0; i < NB_SEGMENTS; i++) { struct stm_priv_segment_info_s *pr = get_priv_segment(i); assert(list_is_empty(pr->objects_pointing_to_nursery)); - list_free(pr->objects_pointing_to_nursery); - list_free(pr->old_objects_with_cards_set); - list_free(pr->modified_old_objects); + LIST_FREE(pr->objects_pointing_to_nursery); + LIST_FREE(pr->old_objects_with_cards_set); + LIST_FREE(pr->modified_old_objects); assert(list_is_empty(pr->large_overflow_objects)); - list_free(pr->large_overflow_objects); - list_free(pr->young_weakrefs); - list_free(pr->old_weakrefs); + LIST_FREE(pr->large_overflow_objects); + LIST_FREE(pr->young_weakrefs); + LIST_FREE(pr->old_weakrefs); tree_free(pr->young_outside_nursery); tree_free(pr->nursery_objects_shadows); tree_free(pr->callbacks_on_commit_and_abort[0]); tree_free(pr->callbacks_on_commit_and_abort[1]); - list_free(pr->young_objects_with_light_finalizers); - list_free(pr->old_objects_with_light_finalizers); + LIST_FREE(pr->young_objects_with_destructors); + LIST_FREE(pr->old_objects_with_destructors); if (pr->active_queues) tree_free(pr->active_queues); } diff --git a/c8/stmgc.h b/c8/stmgc.h --- a/c8/stmgc.h +++ b/c8/stmgc.h @@ -710,13 +710,13 @@ void stm_force_transaction_break(stm_thread_local_t *tl); -/* Support for light finalizers. This is a simple version of +/* Support for destructors. This is a simple version of finalizers that guarantees not to do anything fancy, like not resurrecting objects. */ -extern void (*stmcb_light_finalizer)(object_t *); -void stm_enable_light_finalizer(object_t *); +extern void (*stmcb_destructor)(object_t *); +void stm_enable_destructor(object_t *); -/* Support for regular finalizers. Unreachable objects with +/* XXX: Support for regular finalizers. Unreachable objects with finalizers are kept alive, as well as everything they point to, and stmcb_finalizer() is called after the major GC. If there are several objects with finalizers that reference each other in a @@ -730,8 +730,14 @@ transaction. For older objects, the finalizer is called from a random thread between regular transactions, in a new custom transaction. */ -extern void (*stmcb_finalizer)(object_t *); -object_t *stm_allocate_with_finalizer(ssize_t size_rounded_up); +typedef void (*stm_finalizer_trigger_fn)(void); +void (*stmcb_finalizer)(object_t *); +void stm_setup_finalizer_queues(int number, stm_finalizer_trigger_fn *triggers); +void stm_enable_finalizer(int queue_index, object_t *obj); + +/* Returns the next object that supposedly died and should have its finalizer + called. XXX: This function turns the transaction inevitable. */ +object_t *stm_next_to_finalize(int queue_index); /* dummies for now: */ @@ -742,7 +748,7 @@ 'object_t *'. Note that the type 'stm_hashtable_t' is not an object type at all; you need to allocate and free it explicitly. If you want to embed the hashtable inside an 'object_t' you - probably need a light finalizer to do the freeing. */ + probably need a destructor to do the freeing. */ typedef struct stm_hashtable_s stm_hashtable_t; typedef TLPREFIX struct stm_hashtable_entry_s stm_hashtable_entry_t; @@ -797,8 +803,8 @@ /* Queues. The items you put() and get() back are in random order. Like hashtables, the type 'stm_queue_t' is not an object type at all; you need to allocate and free it explicitly. If you want to - embed the queue inside an 'object_t' you probably need a light - finalizer to do the freeing. */ + embed the queue inside an 'object_t' you probably need a destructor + to do the freeing. */ typedef struct stm_queue_s stm_queue_t; stm_queue_t *stm_queue_create(void); diff --git a/c8/test/support.py b/c8/test/support.py --- a/c8/test/support.py +++ b/c8/test/support.py @@ -52,7 +52,6 @@ /*void stm_write(object_t *obj); use _checked_stm_write() instead */ object_t *stm_allocate(ssize_t size_rounded_up); object_t *stm_allocate_weakref(ssize_t size_rounded_up); -object_t *stm_allocate_with_finalizer(ssize_t size_rounded_up); object_t *stm_allocate_noconflict(ssize_t size_rounded_up); /*void stm_write_card(); use _checked_stm_write_card() instead */ @@ -206,11 +205,14 @@ bool (*_stm_smallmalloc_keep)(char *data); void _stm_smallmalloc_sweep_test(void); +void (*stmcb_destructor)(object_t *); +void stm_enable_destructor(object_t *); -void (*stmcb_light_finalizer)(object_t *); -void stm_enable_light_finalizer(object_t *); - +typedef void (*stm_finalizer_trigger_fn)(void); void (*stmcb_finalizer)(object_t *); +void stm_setup_finalizer_queues(int number, stm_finalizer_trigger_fn *triggers); +void stm_enable_finalizer(int queue_index, object_t *obj); +object_t *stm_next_to_finalize(int queue_index); typedef struct stm_hashtable_s stm_hashtable_t; typedef ... stm_hashtable_entry_t; @@ -749,15 +751,19 @@ return o def stm_allocate_with_finalizer(size): - o = lib.stm_allocate_with_finalizer(size) + # OLD-Style finalizers! + o = lib.stm_allocate(size) tid = 42 + size lib._set_type_id(o, tid) + lib.stm_enable_finalizer(-1, o) return o def stm_allocate_with_finalizer_refs(n): - o = lib.stm_allocate_with_finalizer(HDR + n * WORD) + # OLD-Style finalizers! + o = lib.stm_allocate(HDR + n * WORD) tid = 421420 + n lib._set_type_id(o, tid) + lib.stm_enable_finalizer(-1, o) return o SIZEOF_HASHTABLE_OBJ = 16 + lib.SIZEOF_MYOBJ diff --git a/c8/test/test_finalizer.py b/c8/test/test_finalizer.py --- a/c8/test/test_finalizer.py +++ b/c8/test/test_finalizer.py @@ -2,13 +2,22 @@ import py -class TestLightFinalizer(BaseTest): +class TestDestructors(BaseTest): def setup_method(self, meth): BaseTest.setup_method(self, meth) # + @ffi.callback("stm_finalizer_trigger_fn") + def trigger(): + # triggers not needed for destructors + assert False + self._trigger_keepalive = trigger + triggers = ffi.new("stm_finalizer_trigger_fn*", trigger) + lib.stm_setup_finalizer_queues(1, triggers) + triggers = None + # @ffi.callback("void(object_t *)") - def light_finalizer(obj): + def destructor(obj): assert stm_get_obj_size(obj) == 48 segnum = lib.current_segment_num() tlnum = '?' @@ -17,21 +26,21 @@ if tl.last_associated_segment_num == segnum: tlnum = n break - self.light_finalizers_called.append((obj, tlnum)) - self.light_finalizers_called = [] - lib.stmcb_light_finalizer = light_finalizer - self._light_finalizer_keepalive = light_finalizer + self.destructors_called.append((obj, tlnum)) + self.destructors_called = [] + lib.stmcb_destructor = destructor + self._destructor_keepalive = destructor def teardown_method(self, meth): - lib.stmcb_light_finalizer = ffi.NULL + lib.stmcb_destructor = ffi.NULL BaseTest.teardown_method(self, meth) def expect_finalized(self, objs, from_tlnum=None): - assert [obj for (obj, tlnum) in self.light_finalizers_called] == objs + assert [obj for (obj, tlnum) in self.destructors_called] == objs if from_tlnum is not None: - for obj, tlnum in self.light_finalizers_called: + for obj, tlnum in self.destructors_called: assert tlnum == from_tlnum - self.light_finalizers_called = [] + self.destructors_called = [] def test_no_finalizer(self): self.start_transaction() @@ -39,45 +48,45 @@ self.commit_transaction() self.expect_finalized([]) - def test_young_light_finalizer(self): + def test_young_destructor(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.expect_finalized([]) self.commit_transaction() self.expect_finalized([lp1], from_tlnum=0) - def test_young_light_finalizer_survives(self): + def test_young_destructor_survives(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.push_root(lp1) # stays alive self.commit_transaction() self.expect_finalized([]) - def test_young_light_finalizer_aborts(self): + def test_young_destructor_aborts(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.expect_finalized([]) self.abort_transaction() self.start_transaction() self.expect_finalized([lp1], from_tlnum=0) - def test_old_light_finalizer(self): + def test_old_destructor(self): self.start_transaction() lp1 = stm_allocate(48) self.push_root(lp1) stm_minor_collect() lp1 = self.pop_root() - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.commit_transaction() self.expect_finalized([]) - def test_old_light_finalizer_2(self): + def test_old_destructor_2(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.push_root(lp1) stm_minor_collect() lp1 = self.pop_root() @@ -86,10 +95,10 @@ self.expect_finalized([lp1]) self.commit_transaction() - def test_old_light_finalizer_survives(self): + def test_old_destructor_survives(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.push_root(lp1) stm_minor_collect() lp1 = self.pop_root() @@ -98,13 +107,13 @@ self.commit_transaction() self.expect_finalized([]) - def test_old_light_finalizer_segment(self): + def test_old_destructor_segment(self): self.start_transaction() # self.switch(1) self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.push_root(lp1) stm_minor_collect() lp1 = self.pop_root() @@ -114,10 +123,10 @@ stm_major_collect() self.expect_finalized([lp1], from_tlnum=1) - def test_old_light_finalizer_aborts(self): + def test_old_destructor_aborts(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.push_root(lp1) self.commit_transaction() # @@ -126,10 +135,10 @@ self.abort_transaction() self.expect_finalized([]) - def test_overflow_light_finalizer_aborts(self): + def test_overflow_destructor_aborts(self): self.start_transaction() lp1 = stm_allocate(48) - lib.stm_enable_light_finalizer(lp1) + lib.stm_enable_destructor(lp1) self.push_root(lp1) stm_minor_collect() lp1 = self.pop_root() @@ -139,14 +148,212 @@ self.expect_finalized([lp1], from_tlnum=0) +class TestRegularFinalizerQueues(BaseTest): + expect_content_character = None + run_major_collect_in_trigger = False -class TestRegularFinalizer(BaseTest): + def setup_method(self, meth): + BaseTest.setup_method(self, meth) + # + @ffi.callback("stm_finalizer_trigger_fn") + def trigger0(): + self.queues_triggered.append(0) + if self.run_major_collect_in_trigger: + stm_major_collect() + @ffi.callback("stm_finalizer_trigger_fn") + def trigger1(): + self.queues_triggered.append(1) + if self.run_major_collect_in_trigger: + stm_major_collect() + self._trigger_keepalive = [trigger0, trigger1] + triggers = ffi.new("stm_finalizer_trigger_fn[]", self._trigger_keepalive) + lib.stm_setup_finalizer_queues(2, triggers) + # + self.queues_triggered = [] + + def get_queues_triggered(self): + res = set(self.queues_triggered) + self.queues_triggered = [] + return res + + def get_to_finalize(self, qindex): + obj = lib.stm_next_to_finalize(qindex) + res = [] + while obj: + res.append(obj) + obj = lib.stm_next_to_finalize(qindex) + return res + + + + def test_no_finalizer(self): + self.start_transaction() + lp1 = stm_allocate(48) + stm_major_collect() + assert not self.get_queues_triggered() + + def test_no_finalizer_in_minor_collection(self): + self.start_transaction() + lp1 = stm_allocate(48) + lib.stm_enable_finalizer(0, lp1) + stm_minor_collect() + assert not self.get_queues_triggered() + + def test_finalizer_in_major_collection(self): + self.start_transaction() + for repeat in range(4): + lp1 = stm_allocate(48) + lp2 = stm_allocate(48) + lp3 = stm_allocate(48) + if repeat % 2 == 0: # register on young + lib.stm_enable_finalizer(0, lp1) + lib.stm_enable_finalizer(1, lp2) + lib.stm_enable_finalizer(1, lp3) + assert not self.get_queues_triggered() + self.push_roots([lp1, lp2, lp3]) + stm_minor_collect() + lp1, lp2, lp3 = self.pop_roots() + if repeat % 2 == 1: # register on old + lib.stm_enable_finalizer(0, lp1) + lib.stm_enable_finalizer(1, lp2) + lib.stm_enable_finalizer(1, lp3) + print repeat, lp1, lp2, lp3 + stm_major_collect() + assert {0, 1} == self.get_queues_triggered() + assert [lp1] == self.get_to_finalize(0) + assert [lp2, lp3] == self.get_to_finalize(1) + + def test_finalizer_from_other_thread(self): + self.start_transaction() + lp1 = stm_allocate(48) + lib.stm_enable_finalizer(0, lp1) + stm_set_char(lp1, 'H') + self.expect_content_character = 'H' + self.push_root(lp1) + stm_minor_collect() + lp1 = self.pop_root() + print lp1 + # + self.switch(1) + self.start_transaction() + stm_major_collect() + assert not self.get_queues_triggered() + # + self.switch(0) + assert {0} == self.get_queues_triggered() + assert [lp1] == self.get_to_finalize(0) + + def test_finalizer_ordering(self): + self.start_transaction() + lp1 = stm_allocate_refs(1) + lp2 = stm_allocate_refs(1) + lp3 = stm_allocate_refs(1) + lib.stm_enable_finalizer(1, lp1) + lib.stm_enable_finalizer(1, lp2) + lib.stm_enable_finalizer(1, lp3) + + print lp1, lp2, lp3 + stm_set_ref(lp3, 0, lp1) + stm_set_ref(lp1, 0, lp2) + + self.push_roots([lp1, lp2, lp3]) + stm_minor_collect() + lp1, lp2, lp3 = self.pop_roots() + + stm_major_collect() + assert {1} == self.get_queues_triggered() + assert [lp3] == self.get_to_finalize(1) + + def test_finalizer_extra_transaction(self): + self.start_transaction() + lp1 = stm_allocate(32) + lib.stm_enable_finalizer(0, lp1) + self.push_root(lp1) + self.commit_transaction() + + self.start_transaction() + lp1b = self.pop_root() + # assert lp1b == lp1 <- lp1 can be in nursery now + assert not self.get_queues_triggered() + self.commit_transaction() # finalizer-obj moved to global queue + assert not self.get_queues_triggered() + + self.start_transaction() + stm_major_collect() + assert {0} == self.get_queues_triggered() + self.commit_transaction() # invoke finalizers (triggers again...) + self.start_transaction() + assert {0} == self.get_queues_triggered() + assert [lp1b] == self.get_to_finalize(0) + + def test_run_cb_for_all_threads(self): + self.start_transaction() + lp1 = stm_allocate(48) + lib.stm_enable_finalizer(0, lp1) + print lp1 + # + self.switch(1) + self.start_transaction() + lp2 = stm_allocate(56) + lib.stm_enable_finalizer(1, lp2) + print lp2 + + assert not self.get_queues_triggered() + stm_major_collect() + assert len(self.get_to_finalize(1)) == 1 + self.switch(0) + assert self.get_queues_triggered() == {0, 1} + assert len(self.get_to_finalize(0)) == 1 + + def test_run_major_collect_in_trigger(self): + self.run_major_collect_in_trigger = True + self.start_transaction() + lp1 = stm_allocate(32) + lp2 = stm_allocate(32) + lp3 = stm_allocate(32) + lib.stm_enable_finalizer(0, lp1) + lib.stm_enable_finalizer(1, lp2) + lib.stm_enable_finalizer(1, lp3) + print lp1, lp2, lp3 + stm_major_collect() + assert 1 == len(self.get_to_finalize(0)) + assert 2 == len(self.get_to_finalize(1)) + + def test_new_objects_w_finalizers(self): + self.switch(2) + self.start_transaction() + lp1 = stm_allocate_refs(3) + lp2 = stm_allocate_refs(3) + lib.stm_enable_finalizer(0, lp1) + lib.stm_enable_finalizer(0, lp2) + stm_set_ref(lp1, 0, lp2) + + self.push_root(lp1) + stm_minor_collect() + lp1 = self.pop_root() + lp2 = stm_get_ref(lp1, 0) + # lp1, lp2 have WB_EXECUTED objs + + assert not self.get_queues_triggered() + stm_major_collect() + assert [lp1] == self.get_to_finalize(0) + + +class TestOldStyleRegularFinalizer(BaseTest): expect_content_character = None run_major_collect_in_finalizer = False def setup_method(self, meth): BaseTest.setup_method(self, meth) # + @ffi.callback("stm_finalizer_trigger_fn") + def trigger(): + # triggers not needed for oldstyle-finalizer tests + assert False + self._trigger_keepalive = trigger + triggers = ffi.new("stm_finalizer_trigger_fn*", trigger) + lib.stm_setup_finalizer_queues(1, triggers) + # @ffi.callback("void(object_t *)") def finalizer(obj): print "finalizing!", obj @@ -161,7 +368,12 @@ self._finalizer_keepalive = finalizer def expect_finalized(self, objs): - assert self.finalizers_called == objs + if isinstance(objs, int): + assert len(self.finalizers_called) == objs + else: + print objs + print self.finalizers_called + assert self.finalizers_called == objs self.finalizers_called = [] def test_no_finalizer(self): @@ -182,8 +394,11 @@ lp1 = stm_allocate_with_finalizer(48) lp2 = stm_allocate_with_finalizer(48) lp3 = stm_allocate_with_finalizer(48) + self.expect_finalized([]) + self.push_roots([lp1, lp2, lp3]) + stm_minor_collect() + lp1, lp2, lp3 = self.pop_roots() print repeat, lp1, lp2, lp3 - self.expect_finalized([]) stm_major_collect() self.expect_finalized([lp1, lp2, lp3]) @@ -192,6 +407,9 @@ lp1 = stm_allocate_with_finalizer(48) stm_set_char(lp1, 'H') self.expect_content_character = 'H' + self.push_root(lp1) + stm_minor_collect() + lp1 = self.pop_root() print lp1 # self.switch(1) @@ -210,6 +428,11 @@ print lp1, lp2, lp3 stm_set_ref(lp3, 0, lp1) stm_set_ref(lp1, 0, lp2) + + self.push_roots([lp1, lp2, lp3]) + stm_minor_collect() + lp1, lp2, lp3 = self.pop_roots() + stm_major_collect() self.expect_finalized([lp3]) @@ -222,16 +445,16 @@ self.start_transaction() lp1b = self.pop_root() - assert lp1b == lp1 + # assert lp1b == lp1 <- lp1 can be in nursery now self.expect_finalized([]) - self.commit_transaction() + self.commit_transaction() # finalizer-obj moved to global queue self.expect_finalized([]) self.start_transaction() stm_major_collect() self.expect_finalized([]) - self.commit_transaction() - self.expect_finalized([lp1]) + self.commit_transaction() # invoke finalizers + self.expect_finalized([lp1b]) def test_run_cb_for_all_threads(self): self.start_transaction() @@ -246,7 +469,7 @@ self.expect_finalized([]) stm_major_collect() self.switch(0) - self.expect_finalized([lp2, lp1]) + self.expect_finalized(2) def test_run_major_collect_in_finalizer(self): self.run_major_collect_in_finalizer = True diff --git a/c8/test/test_hashtable.py b/c8/test/test_hashtable.py --- a/c8/test/test_hashtable.py +++ b/c8/test/test_hashtable.py @@ -43,8 +43,8 @@ BaseTest.setup_method(self, meth) # @ffi.callback("void(object_t *)") - def light_finalizer(obj): - print 'light_finalizer:', obj + def destructor(obj): + print 'destructor:', obj try: assert lib._get_type_id(obj) == 421419 self.seen_hashtables -= 1 @@ -54,20 +54,20 @@ self.errors.append(sys.exc_info()[2]) raise - lib.stmcb_light_finalizer = light_finalizer - self._light_finalizer_keepalive = light_finalizer + lib.stmcb_destructor = destructor + self._destructor_keepalive = destructor self.seen_hashtables = 0 self.errors = [] def teardown_method(self, meth): BaseTest.teardown_method(self, meth) - lib.stmcb_light_finalizer = ffi.NULL + lib.stmcb_destructor = ffi.NULL assert self.errors == [] assert self.seen_hashtables == 0 def allocate_hashtable(self): h = stm_allocate_hashtable() - lib.stm_enable_light_finalizer(h) + lib.stm_enable_destructor(h) self.seen_hashtables += 1 return h diff --git a/c8/test/test_list.py b/c8/test/test_list.py --- a/c8/test/test_list.py +++ b/c8/test/test_list.py @@ -6,7 +6,7 @@ ffi = cffi.FFI() ffi.cdef(""" struct list_s *list_create(void); -void list_free(struct list_s *lst); +void _list_free(struct list_s *lst); struct list_s *list_append(struct list_s *lst, uintptr_t item); uintptr_t list_count(struct list_s *lst); uintptr_t list_item(struct list_s *lst, uintptr_t index); @@ -144,5 +144,5 @@ assert lib.list_count(a) == 17 + 7 for i, expected in enumerate(range(103, 120) + range(113, 120)): assert lib.list_item(a, i) == expected - lib.list_free(b) - lib.list_free(a) + lib._list_free(b) + lib._list_free(a) diff --git a/c8/test/test_queue.py b/c8/test/test_queue.py --- a/c8/test/test_queue.py +++ b/c8/test/test_queue.py @@ -13,8 +13,8 @@ BaseTest.setup_method(self, meth) # @ffi.callback("void(object_t *)") - def light_finalizer(obj): - print 'light_finalizer:', obj + def destructor(obj): + print 'destructor:', obj try: assert lib._get_type_id(obj) == 421417 self.seen_queues -= 1 @@ -24,20 +24,20 @@ self.errors.append(sys.exc_info()[2]) raise - lib.stmcb_light_finalizer = light_finalizer - self._light_finalizer_keepalive = light_finalizer + lib.stmcb_destructor = destructor + self._destructor_keepalive = destructor self.seen_queues = 0 self.errors = [] def teardown_method(self, meth): BaseTest.teardown_method(self, meth) - lib.stmcb_light_finalizer = ffi.NULL + lib.stmcb_destructor = ffi.NULL assert self.errors == [] assert self.seen_queues == 0 def allocate_queue(self): q = stm_allocate_queue() - lib.stm_enable_light_finalizer(q) + lib.stm_enable_destructor(q) self.seen_queues += 1 return q diff --git a/c8/test/test_zrandom.py b/c8/test/test_zrandom.py --- a/c8/test/test_zrandom.py +++ b/c8/test/test_zrandom.py @@ -411,13 +411,20 @@ global_state.add_noconfl_obj(r) def op_allocate_ref(ex, global_state, thread_state): - num = str(global_state.rnd.randrange(1, 1000)) + g_rnd = global_state.rnd + num = str(g_rnd.randrange(1, 1000)) r = global_state.get_new_root_name(True, num) thread_state.push_roots(ex) ex.do('%s = stm_allocate_refs(%s)' % (r, num)) ex.do('# 0x%x' % (int(ffi.cast("uintptr_t", ex.content[r])))) thread_state.transaction_state.add_root(r, "ffi.NULL", True) + if g_rnd.randrange(10) == 0: + # XXX: should empty the queues sometimes! but calling next_to_finalize + # *may* turn inevitable (but only if there is something in the queue). + # so that's a bit involved... + ex.do('lib.stm_enable_finalizer(%s, %s)' % (g_rnd.randrange(2), r)) + thread_state.pop_roots(ex) thread_state.reload_roots(ex) thread_state.register_root(r) @@ -585,6 +592,25 @@ N_THREADS = self.NB_THREADS ex = Exec(self) ex.do("################################################################\n"*10) + ex.do(""" + +@ffi.callback("stm_finalizer_trigger_fn") +def trigger0(): + pass +@ffi.callback("stm_finalizer_trigger_fn") +def trigger1(): + pass +self._trigger_keepalive = [trigger0, trigger1] +triggers = ffi.new("stm_finalizer_trigger_fn[]", self._trigger_keepalive) +lib.stm_setup_finalizer_queues(2, triggers) +# +@ffi.callback("void(object_t *)") +def finalizer(obj): + pass +lib.stmcb_finalizer = finalizer +self._finalizer_keepalive = finalizer + """) + ex.do('# initialization') global_state = GlobalState(ex, rnd) @@ -624,7 +650,7 @@ ] possible_actions = [item for sublist in possible_actions for item in sublist] print possible_actions - for _ in range(2000): + for _ in range(3000): # make sure we are in a transaction: curr_thread = op_switch_thread(ex, global_state, curr_thread) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit