summaryrefslogtreecommitdiff
path: root/kern/sleepq.c
diff options
context:
space:
mode:
Diffstat (limited to 'kern/sleepq.c')
-rw-r--r--kern/sleepq.c63
1 files changed, 28 insertions, 35 deletions
diff --git a/kern/sleepq.c b/kern/sleepq.c
index 0d04c6e..44ab53b 100644
--- a/kern/sleepq.c
+++ b/kern/sleepq.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017 Richard Braun.
+ * Copyright (c) 2017-2018 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -431,6 +431,16 @@ sleepq_remove_waiter(struct sleepq *sleepq, struct sleepq_waiter *waiter)
list_remove(&waiter->node);
}
+static struct sleepq_waiter *
+sleepq_get_last_waiter(struct sleepq *sleepq)
+{
+ if (list_empty(&sleepq->waiters)) {
+ return NULL;
+ }
+
+ return list_last_entry(&sleepq->waiters, struct sleepq_waiter, node);
+}
+
bool
sleepq_empty(const struct sleepq *sleepq)
{
@@ -441,7 +451,7 @@ static int
sleepq_wait_common(struct sleepq *sleepq, const char *wchan,
bool timed, uint64_t ticks)
{
- struct sleepq_waiter waiter;
+ struct sleepq_waiter waiter, *next;
struct thread *thread;
int error;
@@ -469,6 +479,17 @@ sleepq_wait_common(struct sleepq *sleepq, const char *wchan,
sleepq_remove_waiter(sleepq, &waiter);
+ /*
+ * Chain wake-ups here to prevent broadacasting from walking a list
+ * with preemption disabled. Note that this doesn't guard against
+ * the thundering herd effect for condition variables.
+ */
+ next = sleepq_get_last_waiter(sleepq);
+
+ if (next) {
+ sleepq_waiter_wakeup(next);
+ }
+
return error;
}
@@ -503,46 +524,18 @@ sleepq_signal(struct sleepq *sleepq)
sleepq_waiter_wakeup(waiter);
}
-static void
-sleepq_wakeup_common(struct sleepq *sleepq)
-{
- struct sleepq_waiter *waiter;
-
- assert(!list_empty(&sleepq->waiters));
-
- waiter = list_last_entry(&sleepq->waiters, struct sleepq_waiter, node);
- sleepq_waiter_wakeup(waiter);
-}
-
void
sleepq_broadcast(struct sleepq *sleepq)
{
struct sleepq_waiter *waiter;
- if (sleepq->oldest_waiter == NULL) {
- goto out;
- }
-
- list_for_each_entry(&sleepq->waiters, waiter, node) {
- sleepq_waiter_set_pending_wakeup(waiter);
-
- if (waiter == sleepq->oldest_waiter) {
- break;
- }
- }
-
- sleepq->oldest_waiter = NULL;
-
-out:
- sleepq_wakeup_common(sleepq);
-}
+ waiter = sleepq->oldest_waiter;
-void
-sleepq_wakeup(struct sleepq *sleepq)
-{
- if (list_empty(&sleepq->waiters)) {
+ if (!waiter) {
return;
}
- sleepq_wakeup_common(sleepq);
+ sleepq->oldest_waiter = NULL;
+ sleepq_waiter_set_pending_wakeup(waiter);
+ sleepq_waiter_wakeup(waiter);
}