bug-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] Port leak when using clisp


From: Flávio Cruz
Subject: Re: [PATCH] Port leak when using clisp
Date: Wed, 26 Aug 2015 22:26:13 +0000

Em qua, 26 de ago de 2015 às 12:18, Justus Winter <4winter@informatik.uni-hamburg.de> escreveu:

> After looking around and using KDB, I've figured out that the following loop in
> kern/exceptions.c/exception_raise_continue_slow was the culprit:

Could you elaborate a little on your method?

I changed the code to chain all the allocated ports together in a linked list. Then, I added new fields to ipc_port to track send-only rights and to understand where those rights were being created and where the port was being manipulated. Finally, I added a new KDB command to show all the inactive ports. It was then easy to track exactly how the port was handled inside exception_raise_continue_slow.


> while (mr == MACH_RCV_INTERRUPTED) {
> /*
> * Somebody is trying to force this thread
> * to a clean point.  We must cooperate
> * and then resume the receive.
> */
>
> while (thread_should_halt(self)) {
> /* don't terminate while holding a reference */
> if (self->ast & AST_TERMINATE)
> ipc_port_release(reply_port);
> thread_halt_self();
> }
>
> ip_lock(reply_port);
> ....
> }
>
> The reply port of the target thread (from CLISP?) is not being released since
> it enters the while(thread_should_halt(self)) loop and the thread terminates
> inside thread_halt_self but the previous condition does not hold, which fails
> to release the port. I also think that once the code enters thread_halt_self,
> it never comes back since the stack is discarded (for both if cases inside the
> function).

While I agree that the function thread_halt_self does not return, it
only terminates the thread if the AST_TERMINATE flag is set.
Otherwise, it returns to userspace.

Yes, it returns to userspace using thread_exception_return, but notice that a reference must be released anyway (check first statement after the while loop). For the else case in thread_halt_self, the thread may throw away the current stack and run thread_exception_return in thread_block, which is also executed in the case of success in exception_raise_continue_slow.


> I've changed the code to make sure the port is released when thread_should_halt
> is true. So far, the kernel works great and I was finally able to complete the
> second bootstrapping stage for SBCL. Please see the attached patch and let me
> know what you think.

Fwiw, I prefer inlined patches so I can reply easier ;)

OK ;)
 

> diff --git a/kern/exception.c b/kern/exception.c
> index 6cb3bfb..c68c5cb 100644
> --- a/kern/exception.c
> +++ b/kern/exception.c
> @@ -875,12 +875,10 @@ exception_raise_continue_slow(
>                *      and then resume the receive.
>                */
>
> -             while (thread_should_halt(self)) {
> -                     /* don't terminate while holding a reference */
> -                     if (self->ast & AST_TERMINATE)

Unless I'm missing something, you're basically removing this
conditional.  For me, it is not obvious that this is correct.  B/c now
we've given up our reference, return to userspace, and userspace is
likely re-trying the same again.  So when it does, isn't the reference
missing now?

See above. Furthermore, the port has 2 references and 1 must always be released (see line 387, where those ports are created).
 

> -                             ipc_port_release(reply_port);
> +             if (thread_should_halt(self))
> +                     ipc_port_release(reply_port);
> +             while (thread_should_halt(self))
>                       thread_halt_self();
> -             }

I find the use of `while' dodgy in the first place b/c if
thread_halt_self doesn't return, an if would do the trick as well and
don't imply the chance of that code looping...

I think there might be some situations where it will loop around depending on the scheduling order (see line 798 on sched_prim.c).

I've added a continuation argument to thread_halt_self so that the port is released if the thread resumes using the continuation (followed by  thread_exception_return). Otherwise, it may loop and it will eventually also release the reference and call thread_exception_return.

I've also made some cosmetic changes to the code.

diff --git a/kern/ast.c b/kern/ast.c
index 4b9d63d..2772ed3 100644
--- a/kern/ast.c
+++ b/kern/ast.c
@@ -96,7 +96,7 @@ ast_taken(void)
  if (self != current_processor()->idle_thread) {
 #ifndef MIGRATING_THREADS
  while (thread_should_halt(self))
- thread_halt_self();
+ thread_halt_self(thread_exception_return);
 #endif
 
  /*
diff --git a/kern/exception.c b/kern/exception.c
index 6cb3bfb..9ad1c42 100644
--- a/kern/exception.c
+++ b/kern/exception.c
@@ -231,7 +231,7 @@ exception_no_server(void)
  */
 
  while (thread_should_halt(self))
- thread_halt_self();
+ thread_halt_self(thread_exception_return);
 
 
 #if 0
@@ -257,7 +257,7 @@ exception_no_server(void)
  */
 
  (void) task_terminate(self->task);
- thread_halt_self();
+ thread_halt_self(thread_exception_return);
  panic("terminating the task didn't kill us");
  /*NOTREACHED*/
 }
@@ -848,6 +848,26 @@ exception_raise_continue(void)
 }
 
 /*
+ * Routine: thread_release_and_exception_return
+ * Purpose:
+ * Continue after thread was halted.
+ * Conditions:
+ * Nothing locked.  We are running on a new kernel stack and
+ * control goes back to thread_exception_return.
+ * Returns:
+ * Doesn't return.
+ */
+static void
+thread_release_and_exception_return(void)
+{
+ ipc_thread_t self = current_thread();
+ /* reply port must be released */
+ ipc_port_release(self->ith_port);
+ thread_exception_return();
+ /*NOTREACHED*/
+}
+
+/*
  * Routine: exception_raise_continue_slow
  * Purpose:
  * Continue after finishing an ipc_mqueue_receive
@@ -876,10 +896,14 @@ exception_raise_continue_slow(
  */
 
  while (thread_should_halt(self)) {
- /* don't terminate while holding a reference */
+ /* if thread is about to terminate, release the port */
  if (self->ast & AST_TERMINATE)
  ipc_port_release(reply_port);
- thread_halt_self();
+ /*
+ * Use the continuation to release the port in
+ * case the thread is about to halt.
+ */
+ thread_halt_self(thread_release_and_exception_return);
  }
 
  ip_lock(reply_port);
diff --git a/kern/machine.c b/kern/machine.c
index eced768..3f7a7f7 100644
--- a/kern/machine.c
+++ b/kern/machine.c
@@ -270,7 +270,7 @@ Retry:
  assert_wait((event_t) processor, TRUE);
  processor_unlock(processor);
  splx(s);
- thread_block((void(*)()) 0);
+ thread_block(thread_no_continuation);
  goto Retry;
     }
   
@@ -299,7 +299,7 @@ Retry:
  assert_wait((event_t)processor, TRUE);
  processor_unlock(processor);
  splx(s);
- thread_block((void (*)()) 0);
+ thread_block(thread_no_continuation);
  s = splsched();
  processor_lock(processor);
  }
@@ -415,7 +415,7 @@ void processor_doaction(processor_t processor)
  */
  this_thread = current_thread();
  thread_bind(this_thread, processor);
- thread_block((void (*)()) 0);
+ thread_block(thread_no_continuation);
 
  pset = processor->processor_set;
 #if MACH_HOST
@@ -572,7 +572,7 @@ Restart_pset:
  thread_deallocate(prev_thread);
     thread_bind(this_thread, PROCESSOR_NULL);
 
-    thread_block((void (*)()) 0);
+    thread_block(thread_no_continuation);
     return;
  }
 
diff --git a/kern/profile.c b/kern/profile.c
index 5510721..1381b1a 100644
--- a/kern/profile.c
+++ b/kern/profile.c
@@ -172,7 +172,7 @@ printf("profile_thread: mach_msg failed returned %x\n",(int)mr);
  sizeof(struct buf_to_send));
  }
 
- thread_halt_self();
+ thread_halt_self(thread_exception_return);
 }
 
 
@@ -213,7 +213,7 @@ thread_t th;
  thread_wakeup((event_t) profile_thread);
  assert_wait((event_t) &buf_entry->wakeme, TRUE);
  splx(s);
- thread_block((void (*)()) 0);
+ thread_block(thread_no_continuation);
  } else {
  splx(s);
  kmem_free(kernel_map, vm_buf_entry, sizeof(struct buf_to_send));
diff --git a/kern/sched_prim.c b/kern/sched_prim.c
index e8f260e..580ca43 100644
--- a/kern/sched_prim.c
+++ b/kern/sched_prim.c
@@ -454,7 +454,7 @@ void thread_sleep(
 {
  assert_wait(event, interruptible); /* assert event */
  simple_unlock(lock); /* release the lock */
- thread_block((void (*)()) 0); /* block ourselves */
+ thread_block(thread_no_continuation); /* block ourselves */
 }
 
 /*
@@ -617,7 +617,7 @@ boolean_t thread_invoke(
     thread_unlock(new_thread);
     thread_wakeup(TH_EV_STATE(new_thread));
 
-    if (continuation != (void (*)()) 0) {
+    if (continuation != thread_no_continuation) {
  (void) spl0();
  call_continuation(continuation);
  /*NOTREACHED*/
@@ -630,7 +630,7 @@ boolean_t thread_invoke(
  */
  thread_lock(new_thread);
  if ((old_thread->stack_privilege != current_stack()) &&
-    (continuation != (void (*)()) 0))
+    (continuation != thread_no_continuation))
  {
     switch (new_thread->state & TH_SWAP_STATE) {
  case TH_SWAPPED:
@@ -915,7 +915,7 @@ void thread_dispatch(
 
  thread_lock(thread);
 
- if (thread->swap_func != (void (*)()) 0) {
+ if (thread->swap_func != thread_no_continuation) {
  assert((thread->state & TH_SWAP_STATE) == 0);
  thread->state |= TH_SWAPPED;
  stack_free(thread);
diff --git a/kern/sched_prim.h b/kern/sched_prim.h
index 62698dc..bb1865c 100644
--- a/kern/sched_prim.h
+++ b/kern/sched_prim.h
@@ -52,6 +52,8 @@ typedef void *event_t; /* wait event */
 
 typedef void (*continuation_t)(void); /* continuation */
 
+#define thread_no_continuation ((continuation_t) 0) /* no continuation */
+
 /*
  * Exported interface to sched_prim.c.
  */
diff --git a/kern/task.c b/kern/task.c
index 9a3d848..e9e6ba2 100644
--- a/kern/task.c
+++ b/kern/task.c
@@ -377,7 +377,7 @@ kern_return_t task_terminate(
                 task_unlock(task);
                 thread_force_terminate(thread);
                 thread_deallocate(thread);
-                thread_block((void (*)()) 0);
+                thread_block(thread_no_continuation);
                 task_lock(task);
         }
         task_unlock(task);
@@ -893,7 +893,7 @@ task_assign(
  task->assign_active = TRUE;
  assert_wait((event_t)&task->assign_active, TRUE);
  task_unlock(task);
- thread_block((void (*)()) 0);
+ thread_block(thread_no_continuation);
  task_lock(task);
  }
 
diff --git a/kern/thread.c b/kern/thread.c
index 865a1cc..3e90079 100644
--- a/kern/thread.c
+++ b/kern/thread.c
@@ -1132,7 +1132,7 @@ void __attribute__((noreturn)) walking_zombie(void)
  * Thread calls this routine on exit from the kernel when it
  * notices a halt request.
  */
-void thread_halt_self(void)
+void thread_halt_self(continuation_t continuation)
 {
  thread_t thread = current_thread();
  spl_t s;
@@ -1173,7 +1173,7 @@ void thread_halt_self(void)
  thread_unlock(thread);
  splx(s);
  counter(c_thread_halt_self_block++);
- thread_block(thread_exception_return);
+ thread_block(continuation);
  /*
  * thread_release resets TH_HALTED.
  */
@@ -1348,7 +1348,7 @@ kern_return_t thread_suspend(
  while (thread->state & TH_UNINT) {
  assert_wait(TH_EV_STATE(thread), TRUE);
  thread_unlock(thread);
- thread_block(NULL);
+ thread_block(thread_no_continuation);
  thread_lock(thread);
  }
  if (thread->user_stop_count++ == 0) {
diff --git a/kern/thread.h b/kern/thread.h
index 0e85d8c..7106fd2 100644
--- a/kern/thread.h
+++ b/kern/thread.h
@@ -100,7 +100,7 @@ struct thread {
  vm_offset_t stack_privilege;/* reserved kernel stack */
 
  /* Swapping information */
- void (*swap_func)(); /* start here after swapin */
+ continuation_t swap_func; /* start here after swapin */
 
  /* Blocking information */
  event_t wait_event; /* event we are waiting on */
@@ -362,7 +362,7 @@ extern void thread_release(thread_t);
 extern kern_return_t thread_halt(
  thread_t thread,
  boolean_t must_halt);
-extern void thread_halt_self(void);
+extern void thread_halt_self(continuation_t);
 extern void thread_force_terminate(thread_t);
 extern thread_t kernel_thread(
  task_t task,


reply via email to

[Prev in Thread] Current Thread [Next in Thread]