Commit Diff


commit - 1419dd05375a71fc5791606d438ee109402a909a
commit + 59b10ca5186d691ee2071af0a3aef145ee195dc2
blob - a1b2ba91c955c13110251a1ec90dd3c01d3f8e8b
blob + fdfee83678e75f525652dfe78f6e533877c1915d
--- changelog.md
+++ changelog.md
@@ -1,5 +1,14 @@
 # Changelog
 
+# 2025-12-06
+
+## IPC wait-path fixes and scheduler instrumentation
+
+- Added per-task yield/block counters and exposed them via a new console `yields` command; perf counters now include `sched_yields`/`sched_blocks` to spot spinners quickly.
+- Service-to-service IPC waits now block instead of spin: `ipc_service_client` and the kernel VFS service record the waiting task, block after a short spin, and wake the waiter on response, eliminating million-spin timeouts during init/handoff.
+- Trimmed the default IPC service spin budget (1024 iterations) to reduce idle context switches without hurting responsiveness.
+- Console idle loop reverted to the stable poll+yield path while keeping wake helpers, restoring boot stability after earlier blocking experiments.
+
 # 2025-12-05
 
 ## Blocking IPC: Wait queues and sleeping receivers
blob - 1b85ac9392d27383c40b00d9aedb19739b081aeb
blob + 31b58b25573823a43e48779adca80212fbc1145f
--- kernel/console/service.c
+++ kernel/console/service.c
@@ -44,8 +44,6 @@ static size_t console_ctrl_head;
 static size_t console_ctrl_tail;
 static struct sched_task *console_task;
 static struct sched_task *console_service_task;
-static struct sched_task *console_waiter;
-static bool console_wait_flag;
 static ipc_portal_handle_t console_tty_portal = IPC_PORTAL_INVALID_HANDLE;
 static struct sched_task *console_tty_task;
 static ipc_portal_handle_t console_ttyctl_portal = IPC_PORTAL_INVALID_HANDLE;
@@ -72,7 +70,6 @@ static bool console_logged_deprecation;
 static spinlock_t line_queue_lock = SPINLOCK_INIT;
 static spinlock_t char_queue_lock = SPINLOCK_INIT;
 static spinlock_t ctrl_queue_lock = SPINLOCK_INIT;
-static spinlock_t console_wait_lock = SPINLOCK_INIT;
 
 static void	console_task_main(void *arg);
 static void	console_queue_push(const char *line);
@@ -107,18 +104,8 @@ void		console_service_ttyctl_send(const char *buf, siz
 static void
 console_wake_service_task(void)
 {
-	struct sched_task *task = NULL;
-
-	spinlock_lock(&console_wait_lock);
-	console_wait_flag = true;
-	if (console_waiter != NULL) {
-		task = console_waiter;
-		console_waiter = NULL;
-	}
-	spinlock_unlock(&console_wait_lock);
-
-	if (task != NULL)
-		sched_wake(task);
+	if (console_task != NULL)
+		sched_wake(console_task);
 }
 
 static bool
@@ -759,9 +746,8 @@ console_task_main(void *arg)
 		}
 		console_tty_drain_queue();
 		if (!queue_pop(&entry)) {
-			/* Drain any pending UART characters before sleeping. */
+			/* Drain UART then yield; avoids hangs on missed wakeups */
 			serial_poll_rx();
-
 			if (console_idle_debug < 4) {
 				#ifdef LENIX_DEBUG
 				printk("[console] idle, polling serial\n");
blob - e53ae9251c89c46d65c613dc34e503e8decd0286
blob + d18cae1daceb5f4842fd3bdaf0c8df2f7057e05e
--- kernel/ipc/service_client.c
+++ kernel/ipc/service_client.c
@@ -162,7 +162,7 @@ ipc_service_request_issue_timeout(struct ipc_service_c
 	struct ipc_service_pending *slot;
 	uint8_t tmp_req[IPC_MAILBOX_MAX_PAYLOAD];
 	uint64_t spins = 0;
-	uint64_t spin_limit = 10000000; /* Default iteration budget */
+	uint64_t spin_limit = 1024; /* Default iteration budget (tuned down to avoid spin storms) */
 
 	if (spin_limit_override != 0)
 		spin_limit = spin_limit_override;
@@ -287,6 +287,7 @@ ipc_service_handle_response(ipc_portal_handle_t portal
 {
 	struct pending_service_request *preq;
 	struct ipc_service_pending *slot;
+	struct sched_task *waiter = NULL;
 	struct sched_task *server_task;
 
 	if (portal == IPC_PORTAL_INVALID_HANDLE || resp == NULL)
@@ -335,14 +336,15 @@ ipc_service_handle_response(ipc_portal_handle_t portal
 	__atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
 
 	/* Clean up the registry entry */
+	waiter = preq->waiter;
 	preq->in_use = false;
 	preq->waiter = NULL;
 
 	spinlock_unlock(&pending_requests_lock);
 
 	/* Wake the waiting client task (if any) now that the response is ready */
-	if (preq->waiter != NULL)
-		sched_wake(preq->waiter);
+	if (waiter != NULL)
+		sched_wake(waiter);
 
 	return 0;
 }
blob - 194bde48e75e5bede694aa4f98a25fbb2397c487
blob + 146231de7d4dd46b8e2b53025e950ab300b11332
--- kernel/ipc/vfs_service.c
+++ kernel/ipc/vfs_service.c
@@ -23,6 +23,7 @@ struct vfs_pending {
 	bool in_use;
 	uint32_t token;
 	struct sched_task *client;
+	struct sched_task *waiter;
 	struct ipc_vfs_response response;
 	bool ready;
 };
@@ -62,6 +63,7 @@ vfs_pending_alloc(void)
 			slot = &vfs_state.slots[i];
 			slot->in_use = true;
 			slot->ready = false;
+			slot->waiter = NULL;
 			break;
 		}
 	}
@@ -255,6 +257,7 @@ vfs_request_issue(const struct ipc_vfs_request *req,
 
 	/* slot->in_use and slot->ready already set by vfs_pending_alloc() */
 	slot->client = sched_current_task();
+	slot->waiter = slot->client;
 	slot->token = vfs_state.next_token++;
 	if (vfs_state.next_token == 0)
 		vfs_state.next_token = 1;
@@ -285,6 +288,7 @@ vfs_request_issue(const struct ipc_vfs_request *req,
 		    ++spins > spin_limit) {
 			spinlock_lock(&vfs_pending_lock);
 			slot->in_use = false;
+			slot->waiter = NULL;
 			spinlock_unlock(&vfs_pending_lock);
 #ifdef LENIX_DEBUG
 			printk("[vfs] request timeout token=");
@@ -297,7 +301,11 @@ vfs_request_issue(const struct ipc_vfs_request *req,
 #endif
 			return -1;
 		}
-		sched_yield();
+		if (spins < (spin_limit / 4)) {
+			sched_yield();
+		} else {
+			sched_block_current();
+		}
 	}
 #ifdef LENIX_DEBUG
 	t_wait = __builtin_ia32_rdtsc();
@@ -307,6 +315,7 @@ vfs_request_issue(const struct ipc_vfs_request *req,
 	spinlock_lock(&vfs_pending_lock);
 	slot->in_use = false;
 	slot->ready = false;
+	slot->waiter = NULL;
 	spinlock_unlock(&vfs_pending_lock);
 #ifdef LENIX_DEBUG
 	t_end = __builtin_ia32_rdtsc();
@@ -369,5 +378,9 @@ vfs_service_respond(const struct ipc_vfs_response *res
 	__atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
 
 	spinlock_unlock(&vfs_pending_lock);
+
+	/* Wake the waiting client (if any) now that the response is ready */
+	if (slot->waiter != NULL)
+		sched_wake(slot->waiter);
 	return 0;
 }