Skip to content

Commit dec3f85

Browse files
author
Ewan Crawford
committed
Refactor L0 command-buffer cleanup.
Coverity has reported that there is the possibility of the command-buffer destructor throwing an exception from its calls to `CleanupCompletedEvent`. Where there is an underlying `throw` when a `dynamic_cast` doesn't behave as expected. This PR address this in two ways: 1. Change the `throw` error handling to an assert. Since uncaught exceptions aren't a standard method of error reporting in the handler change this to an assert. An assert was chosen rather than providing a mechanism to return an error since it seems like this case would arise due to programmer error rather than something a user would do. 2. Move the calls to `CleanupCompletedEvent()` outside of the destructor. This allows us us to report an error from command-buffer release by doing cleanup prior to calling the destructor.
1 parent 8cc2b87 commit dec3f85

File tree

3 files changed

+63
-33
lines changed

3 files changed

+63
-33
lines changed

source/adapters/level_zero/command_buffer.cpp

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,59 @@ ur_exp_command_buffer_handle_t_::ur_exp_command_buffer_handle_t_(
449449
urDeviceRetain(Device);
450450
}
451451

452+
ur_result_t ur_exp_command_buffer_handle_t_::cleanupCommandBufferResources() {
453+
ur_result_t RetResult = UR_RESULT_SUCCESS;
454+
if (SignalEvent) {
455+
ur_result_t Result = CleanupCompletedEvent(SignalEvent, false);
456+
RetResult = (Result != UR_RESULT_SUCCESS) ? Result : RetResult;
457+
}
458+
if (WaitEvent) {
459+
ur_result_t Result = CleanupCompletedEvent(WaitEvent, false);
460+
RetResult = (Result != UR_RESULT_SUCCESS) ? Result : RetResult;
461+
}
462+
if (AllResetEvent) {
463+
ur_result_t Result = CleanupCompletedEvent(AllResetEvent, false);
464+
RetResult = (Result != UR_RESULT_SUCCESS) ? Result : RetResult;
465+
}
466+
467+
// Release events added to the command_buffer
468+
for (auto &Sync : SyncPoints) {
469+
auto &Event = Sync.second;
470+
ur_result_t Result = CleanupCompletedEvent(Event, false);
471+
RetResult = (Result != UR_RESULT_SUCCESS) ? Result : RetResult;
472+
}
473+
474+
auto ReleaseIndirectMem = [](ur_kernel_handle_t Kernel) {
475+
if (IndirectAccessTrackingEnabled) {
476+
// urKernelRelease is called by CleanupCompletedEvent(Event) as soon as
477+
// kernel execution has finished. This is the place where we need to
478+
// release memory allocations. If kernel is not in use (not submitted by
479+
// some other thread) then release referenced memory allocations. As a
480+
// result, memory can be deallocated and context can be removed from
481+
// container in the platform. That's why we need to lock a mutex here.
482+
ur_platform_handle_t Platform = Kernel->Program->Context->getPlatform();
483+
std::scoped_lock<ur_shared_mutex> ContextsLock(Platform->ContextsMutex);
484+
485+
if (--Kernel->SubmissionsCount == 0) {
486+
// Kernel is not submitted for execution, release referenced memory
487+
// allocations.
488+
for (auto &MemAlloc : Kernel->MemAllocs) {
489+
// std::pair<void *const, MemAllocRecord> *, Hash
490+
USMFreeHelper(MemAlloc->second.Context, MemAlloc->first,
491+
MemAlloc->second.OwnNativeHandle);
492+
}
493+
Kernel->MemAllocs.clear();
494+
}
495+
}
496+
};
497+
498+
for (auto &AssociatedKernel : KernelsList) {
499+
ReleaseIndirectMem(AssociatedKernel);
500+
}
501+
502+
return RetResult;
503+
}
504+
452505
// The ur_exp_command_buffer_handle_t_ destructor releases all the memory
453506
// objects allocated for command_buffer management.
454507
ur_exp_command_buffer_handle_t_::~ur_exp_command_buffer_handle_t_() {
@@ -475,22 +528,18 @@ ur_exp_command_buffer_handle_t_::~ur_exp_command_buffer_handle_t_() {
475528

476529
// Release additional signal and wait events used by command_buffer
477530
if (SignalEvent) {
478-
CleanupCompletedEvent(SignalEvent, false);
479531
urEventReleaseInternal(SignalEvent);
480532
}
481533
if (WaitEvent) {
482-
CleanupCompletedEvent(WaitEvent, false);
483534
urEventReleaseInternal(WaitEvent);
484535
}
485536
if (AllResetEvent) {
486-
CleanupCompletedEvent(AllResetEvent, false);
487537
urEventReleaseInternal(AllResetEvent);
488538
}
489539

490540
// Release events added to the command_buffer
491541
for (auto &Sync : SyncPoints) {
492542
auto &Event = Sync.second;
493-
CleanupCompletedEvent(Event, false);
494543
urEventReleaseInternal(Event);
495544
}
496545

@@ -500,32 +549,7 @@ ur_exp_command_buffer_handle_t_::~ur_exp_command_buffer_handle_t_() {
500549
ZE_CALL_NOCHECK(zeFenceDestroy, (ZeFence));
501550
}
502551

503-
auto ReleaseIndirectMem = [](ur_kernel_handle_t Kernel) {
504-
if (IndirectAccessTrackingEnabled) {
505-
// urKernelRelease is called by CleanupCompletedEvent(Event) as soon as
506-
// kernel execution has finished. This is the place where we need to
507-
// release memory allocations. If kernel is not in use (not submitted by
508-
// some other thread) then release referenced memory allocations. As a
509-
// result, memory can be deallocated and context can be removed from
510-
// container in the platform. That's why we need to lock a mutex here.
511-
ur_platform_handle_t Platform = Kernel->Program->Context->getPlatform();
512-
std::scoped_lock<ur_shared_mutex> ContextsLock(Platform->ContextsMutex);
513-
514-
if (--Kernel->SubmissionsCount == 0) {
515-
// Kernel is not submitted for execution, release referenced memory
516-
// allocations.
517-
for (auto &MemAlloc : Kernel->MemAllocs) {
518-
// std::pair<void *const, MemAllocRecord> *, Hash
519-
USMFreeHelper(MemAlloc->second.Context, MemAlloc->first,
520-
MemAlloc->second.OwnNativeHandle);
521-
}
522-
Kernel->MemAllocs.clear();
523-
}
524-
}
525-
};
526-
527552
for (auto &AssociatedKernel : KernelsList) {
528-
ReleaseIndirectMem(AssociatedKernel);
529553
urKernelRelease(AssociatedKernel);
530554
}
531555
}
@@ -727,8 +751,9 @@ urCommandBufferReleaseExp(ur_exp_command_buffer_handle_t CommandBuffer) {
727751
if (!CommandBuffer->RefCount.decrementAndTest())
728752
return UR_RESULT_SUCCESS;
729753

754+
ur_result_t Result = CommandBuffer->cleanupCommandBufferResources();
730755
delete CommandBuffer;
731-
return UR_RESULT_SUCCESS;
756+
return Result;
732757
}
733758

734759
UR_APIEXPORT ur_result_t UR_APICALL

source/adapters/level_zero/command_buffer.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ struct ur_exp_command_buffer_handle_t_ : public _ur_object {
6565
*/
6666
ze_command_list_handle_t chooseCommandList(bool PreferCopyEngine);
6767

68+
/**
69+
* Frees the resources associated with the command-buffer.
70+
* @return UR_RESULT_SUCCESS or an error if a resources wasn't freed
71+
* successfully.
72+
*/
73+
ur_result_t cleanupCommandBufferResources();
74+
6875
// UR context associated with this command-buffer
6976
ur_context_handle_t Context;
7077
// Device associated with this command buffer

source/adapters/level_zero/queue.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -886,9 +886,7 @@ template <typename QueueT> QueueT GetQueue(ur_queue_handle_t Queue) {
886886
if (!Queue)
887887
return nullptr;
888888
auto *Q = dynamic_cast<QueueT>(Queue);
889-
if (!Q) {
890-
throw UR_RESULT_ERROR_INVALID_QUEUE;
891-
}
889+
assert(Q && "Dynamic cast of queue handle failed");
892890
return Q;
893891
}
894892

0 commit comments

Comments
 (0)