Skip to content

Commit 5676911

Browse files
committed
ze: tear down our context-bound state in zeContextDestroy prologue
The tracer was leaving its own L0 objects (event pools/events used as the recycle pool, shadow command lists keyed by (ctx, device)) alive inside the user's context. When the user app destroyed the context before destroying a command list that had our injected events baked into it (the Intel libomptarget L0 plugin does this at _dl_fini), the following zeCommandListDestroy segfaulted inside libze_intel_gpu.so. Add a zeContextDestroy prologue that, while the context is still valid, walks _ze_cls / _ze_shadow_cls / _ze_event_pools and frees everything we own keyed to the dying context.
1 parent 03c24fa commit 5676911

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

backends/ze/tracer_ze_helpers.include.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,84 @@ static void _on_destroy_command_list(ze_command_list_handle_t command_list) {
868868
free(cl_data);
869869
}
870870

871+
/* zeContextDestroy prologue. The user contract is that the device is no
872+
* longer referencing the context, so all cls/events bound to it are
873+
* conceptually dead from the user's perspective. Our job here is solely
874+
* to avoid leaking our own L0 objects that live inside this context:
875+
*
876+
* 1) cls registered against this ctx: free their slot/slab/chunk state
877+
* (drop tracer-owned events to L0 without re-pooling — the pool is
878+
* about to die anyway).
879+
* 2) per-(ctx, device) shadow cls: zeCommandListDestroy them.
880+
* 3) per-ctx event-pool freelist: zeEventDestroy + zeEventPoolDestroy
881+
* each wrapper, recycle the wrapper structs.
882+
*
883+
* Forwards no calls about the user's own cls/events to the driver — the
884+
* user takes care of those (or accepts the contract). */
885+
static void _on_destroy_context(ze_context_handle_t hContext) {
886+
/* 1) Drop cls bound to this ctx. */
887+
pthread_mutex_lock(&_ze_cls_mutex);
888+
struct _ze_command_list_obj_data *cl_data = NULL, *cl_tmp = NULL;
889+
HASH_ITER(hh, _ze_cls, cl_data, cl_tmp) {
890+
if (cl_data->cached_context != hContext) continue;
891+
HASH_DEL(_ze_cls, cl_data);
892+
pthread_mutex_lock(&cl_data->mtx);
893+
struct _ze_slab_chunk *c, *ctmp;
894+
DL_FOREACH_SAFE(cl_data->chunks, c, ctmp) {
895+
for (uint32_t i = 0; i < c->n_used; ++i) {
896+
struct _ze_slot *s = &c->slots[i];
897+
/* Recycle our event wrappers but DON'T return them to the per-ctx
898+
* pool — the pool entry will be wiped in step 3, and we want the
899+
* underlying L0 event/pool destroyed there too, not here. The
900+
* wrappers themselves are context-agnostic, so reuse them. */
901+
if (s->inj) PUT_ZE_EVENT_WRAPPER(s->inj);
902+
if (s->shadow_done) PUT_ZE_EVENT_WRAPPER(s->shadow_done);
903+
free(s->waits);
904+
free(s->preds);
905+
_latest_clear_if(s->attr, s);
906+
}
907+
DL_DELETE(cl_data->chunks, c);
908+
/* Skip zeMemFree on the slab — the ctx is being destroyed; the
909+
* driver will reclaim the device allocation. Calling zeMemFree
910+
* on a doomed ctx is at best racy. */
911+
free(c);
912+
}
913+
pthread_mutex_unlock(&cl_data->mtx);
914+
pthread_mutex_destroy(&cl_data->mtx);
915+
free(cl_data);
916+
}
917+
pthread_mutex_unlock(&_ze_cls_mutex);
918+
919+
/* 2) Shadow cls keyed by (ctx, device). */
920+
pthread_mutex_lock(&_ze_shadow_cls_mutex);
921+
struct _ze_shadow_cl *sh = NULL, *sh_tmp = NULL;
922+
HASH_ITER(hh, _ze_shadow_cls, sh, sh_tmp) {
923+
if (sh->key.context != hContext) continue;
924+
HASH_DEL(_ze_shadow_cls, sh);
925+
if (sh->cl) ZE_COMMAND_LIST_DESTROY_PTR(sh->cl);
926+
pthread_mutex_destroy(&sh->mtx);
927+
free(sh);
928+
}
929+
pthread_mutex_unlock(&_ze_shadow_cls_mutex);
930+
931+
/* 3) Per-ctx event pool freelist. */
932+
pthread_mutex_lock(&_ze_event_pools_mutex);
933+
struct _ze_event_pool_entry *pe = NULL;
934+
HASH_FIND_PTR(_ze_event_pools, &hContext, pe);
935+
if (pe) {
936+
HASH_DEL(_ze_event_pools, pe);
937+
struct _ze_event_h *w, *w_tmp;
938+
DL_FOREACH_SAFE(pe->events, w, w_tmp) {
939+
if (w->event) ZE_EVENT_DESTROY_PTR(w->event);
940+
if (w->event_pool) ZE_EVENT_POOL_DESTROY_PTR(w->event_pool);
941+
DL_DELETE(pe->events, w);
942+
PUT_ZE_EVENT_WRAPPER(w);
943+
}
944+
free(pe);
945+
}
946+
pthread_mutex_unlock(&_ze_event_pools_mutex);
947+
}
948+
871949
/* Drain every cl whose in_flight_q matches. */
872950
static void _on_sync_drain_queue(ze_command_queue_handle_t hQueue) {
873951
pthread_mutex_lock(&_ze_cls_mutex);

backends/ze/ze_model.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ def upper_snake_case(str)
170170
_on_destroy_command_list(hCommandList);
171171
EOF
172172

173+
# zeContextDestroy prologue: tear down our own L0 objects that live
174+
# inside this context (shadow cls, per-ctx event pools/events) BEFORE the
175+
# user destroys the context. The L0 spec says the user has ensured the
176+
# device is no longer referencing the context, so all user-side cls/events
177+
# are already done — we just need to not leak our allocations.
178+
register_prologue 'zeContextDestroy', <<EOF
179+
if (_do_profile && hContext)
180+
_on_destroy_context(hContext);
181+
EOF
182+
173183
# Epilogue runs after L0's actual submission has returned. ALL the
174184
# tracer's bookkeeping for Execute happens here (no prologue) so that
175185
# concurrent Executes / Syncs from other threads observe in_flight_q

0 commit comments

Comments
 (0)