- int ref_count;
-
- if (semaphore != NULL) {
- ref_count = hw_atomic_sub(&semaphore->ref_count, 1);
-
- if (ref_count == 1) {
- ipc_port_t port = semaphore->port;
-
- if (IP_VALID(port) &&
- OSCompareAndSwapPtr(port, IP_NULL, &semaphore->port)) {
- /*
- * We get to disassociate the port from the sema and
- * drop the port's reference on the sema.
- */
- ipc_port_dealloc_kernel(port);
- ref_count = hw_atomic_sub(&semaphore->ref_count, 1);
- }
- }
- if (ref_count == 0) {
- assert(wait_queue_empty(&semaphore->wait_queue));
- zfree(semaphore_zone, semaphore);
+ uint32_t collisions;
+ spl_t spl_level;
+
+ if (semaphore == NULL) {
+ return;
+ }
+
+ if (os_ref_release(&semaphore->ref_count) > 0) {
+ return;
+ }
+
+ /*
+ * Last ref, clean up the port [if any]
+ * associated with the semaphore, destroy
+ * it (if still active) and then free
+ * the semaphore.
+ */
+ ipc_port_t port = semaphore->port;
+
+ if (IP_VALID(port)) {
+ assert(!port->ip_srights);
+ ipc_port_dealloc_kernel(port);
+ }
+
+ /*
+ * Lock the semaphore to lock in the owner task reference.
+ * Then continue to try to lock the task (inverse order).
+ */
+ spl_level = splsched();
+ semaphore_lock(semaphore);
+ for (collisions = 0; semaphore->active; collisions++) {
+ task_t task = semaphore->owner;
+
+ assert(task != TASK_NULL);
+
+ if (task_lock_try(task)) {
+ semaphore_destroy_internal(task, semaphore);
+ /* semaphore unlocked */
+ splx(spl_level);
+ task_unlock(task);
+ goto out;