From 1c6487c1e79cbe0d59a39b483af8ec44b59c586e Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Fri, 3 Apr 2026 18:11:00 +0200 Subject: [PATCH] added async-io quiz --- build.zig | 46 +++-- ...{095_interfaces.zig => 084_interfaces.zig} | 0 exercises/{084_async.zig => 085_async.zig} | 0 exercises/{085_async2.zig => 086_async2.zig} | 0 exercises/{086_async3.zig => 087_async3.zig} | 0 exercises/{087_async4.zig => 088_async4.zig} | 0 exercises/{088_async5.zig => 089_async5.zig} | 0 exercises/{089_async6.zig => 090_async6.zig} | 0 exercises/{090_async7.zig => 091_async7.zig} | 0 exercises/{091_async8.zig => 092_async8.zig} | 0 exercises/{092_async9.zig => 093_async9.zig} | 0 .../{093_async10.zig => 094_async10.zig} | 0 exercises/095_quiz_async.zig | 186 ++++++++++++++++++ patches/patches/084_interfaces.patch | 11 ++ patches/patches/085_async.patch | 11 ++ patches/patches/086_async2.patch | 14 ++ patches/patches/087_async3.patch | 18 ++ patches/patches/088_async4.patch | 11 ++ patches/patches/089_async5.patch | 11 ++ patches/patches/090_async6.patch | 11 ++ patches/patches/091_async7.patch | 13 ++ patches/patches/092_async8.patch | 11 ++ patches/patches/093_async9.patch | 11 ++ patches/patches/094_async10.patch | 13 ++ patches/patches/095_quiz_async.patch | 52 +++++ 25 files changed, 399 insertions(+), 20 deletions(-) rename exercises/{095_interfaces.zig => 084_interfaces.zig} (100%) rename exercises/{084_async.zig => 085_async.zig} (100%) rename exercises/{085_async2.zig => 086_async2.zig} (100%) rename exercises/{086_async3.zig => 087_async3.zig} (100%) rename exercises/{087_async4.zig => 088_async4.zig} (100%) rename exercises/{088_async5.zig => 089_async5.zig} (100%) rename exercises/{089_async6.zig => 090_async6.zig} (100%) rename exercises/{090_async7.zig => 091_async7.zig} (100%) rename exercises/{091_async8.zig => 092_async8.zig} (100%) rename exercises/{092_async9.zig => 093_async9.zig} (100%) rename exercises/{093_async10.zig => 094_async10.zig} (100%) create mode 100644 exercises/095_quiz_async.zig create mode 100644 patches/patches/084_interfaces.patch create mode 100644 patches/patches/085_async.patch create mode 100644 patches/patches/086_async2.patch create mode 100644 patches/patches/087_async3.patch create mode 100644 patches/patches/088_async4.patch create mode 100644 patches/patches/089_async5.patch create mode 100644 patches/patches/090_async6.patch create mode 100644 patches/patches/091_async7.patch create mode 100644 patches/patches/092_async8.patch create mode 100644 patches/patches/093_async9.patch create mode 100644 patches/patches/094_async10.patch create mode 100644 patches/patches/095_quiz_async.patch diff --git a/build.zig b/build.zig index b9aaac3..beb0d72 100644 --- a/build.zig +++ b/build.zig @@ -1109,20 +1109,29 @@ const exercises = [_]Exercise{ .main_file = "083_anonymous_lists.zig", .output = "I say hello!", }, + .{ + .main_file = "084_interfaces.zig", + .output = + \\Daily Insect Report: + \\Ant is alive. + \\Bee visited 17 flowers. + \\Grasshopper hopped 32 meters. + , // pay attention to the comma + }, // Skipped because of https://github.com/ratfactor/ziglings/issues/163 // direct link: https://github.com/ziglang/zig/issues/6025 .{ - .main_file = "084_async.zig", + .main_file = "085_async.zig", .output = "Current time: s since epoch", .timestamp = true, }, .{ - .main_file = "085_async2.zig", + .main_file = "086_async2.zig", .output = "Computing... the answer is: 42", }, .{ - .main_file = "086_async3.zig", + .main_file = "087_async3.zig", .output = \\1 + 2 = 3 \\6 * 7 = 42 @@ -1130,7 +1139,7 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "087_async4.zig", + .main_file = "088_async4.zig", .output = \\Task 1 done. \\Task 2 done. @@ -1139,7 +1148,7 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "088_async5.zig", + .main_file = "089_async5.zig", .output = \\Starting long computation... \\Canceling slow task... @@ -1148,19 +1157,19 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "089_async6.zig", + .main_file = "090_async6.zig", .output = "Hare: I'm fast!", }, .{ - .main_file = "090_async7.zig", + .main_file = "091_async7.zig", .output = "Counter: 400", }, .{ - .main_file = "091_async8.zig", + .main_file = "092_async8.zig", .output = "Sum of 1..10 = 55", }, .{ - .main_file = "092_async9.zig", + .main_file = "093_async9.zig", .output = \\Main thread continues... \\Computing on a separate thread! @@ -1169,7 +1178,7 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "093_async10.zig", + .main_file = "094_async10.zig", .output = \\Starting critical section... \\Critical section completed safely. @@ -1177,17 +1186,14 @@ const exercises = [_]Exercise{ , // pay attention to the comma }, .{ - .main_file = "094_async_quiz.zig", - .output = "", - .skip = true, - }, - .{ - .main_file = "095_interfaces.zig", + .main_file = "095_quiz_async.zig", .output = - \\Daily Insect Report: - \\Ant is alive. - \\Bee visited 17 flowers. - \\Grasshopper hopped 32 meters. + \\=== Doctor Zoraptera's Garden Report === + \\Temperature : 23C + \\Humidity : 63% + \\Wind : 13 km/h + \\Readings : 9 + \\Bee-friendly conditions! Expect high pollination. , // pay attention to the comma }, .{ diff --git a/exercises/095_interfaces.zig b/exercises/084_interfaces.zig similarity index 100% rename from exercises/095_interfaces.zig rename to exercises/084_interfaces.zig diff --git a/exercises/084_async.zig b/exercises/085_async.zig similarity index 100% rename from exercises/084_async.zig rename to exercises/085_async.zig diff --git a/exercises/085_async2.zig b/exercises/086_async2.zig similarity index 100% rename from exercises/085_async2.zig rename to exercises/086_async2.zig diff --git a/exercises/086_async3.zig b/exercises/087_async3.zig similarity index 100% rename from exercises/086_async3.zig rename to exercises/087_async3.zig diff --git a/exercises/087_async4.zig b/exercises/088_async4.zig similarity index 100% rename from exercises/087_async4.zig rename to exercises/088_async4.zig diff --git a/exercises/088_async5.zig b/exercises/089_async5.zig similarity index 100% rename from exercises/088_async5.zig rename to exercises/089_async5.zig diff --git a/exercises/089_async6.zig b/exercises/090_async6.zig similarity index 100% rename from exercises/089_async6.zig rename to exercises/090_async6.zig diff --git a/exercises/090_async7.zig b/exercises/091_async7.zig similarity index 100% rename from exercises/090_async7.zig rename to exercises/091_async7.zig diff --git a/exercises/091_async8.zig b/exercises/092_async8.zig similarity index 100% rename from exercises/091_async8.zig rename to exercises/092_async8.zig diff --git a/exercises/092_async9.zig b/exercises/093_async9.zig similarity index 100% rename from exercises/092_async9.zig rename to exercises/093_async9.zig diff --git a/exercises/093_async10.zig b/exercises/094_async10.zig similarity index 100% rename from exercises/093_async10.zig rename to exercises/094_async10.zig diff --git a/exercises/095_quiz_async.zig b/exercises/095_quiz_async.zig new file mode 100644 index 0000000..fb78e7b --- /dev/null +++ b/exercises/095_quiz_async.zig @@ -0,0 +1,186 @@ +// +// Quiz Time — Async I/O! +// +// Doctor Zoraptera's insect simulation is going well, but she +// realized that her virtual garden needs weather data! Insects +// behave differently depending on temperature, humidity, and +// wind conditions. +// +// She has set up three weather sensors around the garden that +// measure conditions in parallel and report their readings +// through a shared data channel. A collector task gathers the +// readings, and after all sensors have reported, a garden +// report is printed. +// +// But Doctor Z rushed through the code (she was being chased +// by a grasshopper) and left several bugs. Can you fix them? +// +// Here's what the program should do: +// 1. Three sensor tasks run concurrently, each sending +// exactly 3 readings through a Queue +// 2. A collector task receives readings, protected by a Mutex +// 3. After all sensors finish, the queue is closed +// 4. The final report is written in a cancel-protected section +// +// ************************************************************* +// * A NOTE ABOUT THIS EXERCISE * +// * * +// * This quiz uses concepts from exercises 084-093. * +// * There are 6 bugs to fix — look for the ???s! * +// * * +// ************************************************************* +// +const std = @import("std"); +const print = std.debug.print; + +const SensorType = enum { thermometer, hygrometer, anemometer }; + +const Reading = struct { + sensor_type: SensorType, + value: i32, +}; + +const GardenWeather = struct { + temperature: i32 = 0, + humidity: i32 = 0, + wind: i32 = 0, + readings_count: u32 = 0, + mutex: std.Io.Mutex = .init, + + fn addReading(self: *GardenWeather, io: std.Io, reading: Reading) void { + // Bug 1: The collector needs to lock before modifying + // shared state. What Mutex method acquires the lock? + self.mutex.lock(io) catch return; + self.mutex.???(io) catch return; + + switch (reading.sensor_type) { + .thermometer => self.temperature = reading.value, + .hygrometer => self.humidity = reading.value, + .anemometer => self.wind = reading.value, + } + self.readings_count += 1; + } +}; + +pub fn main(init: std.process.Init) !void { + const io = init.io; + + var weather = GardenWeather{}; + + var reading_buf: [8]Reading = undefined; + var queue: std.Io.Queue(Reading) = .init(&reading_buf); + + // Sensor group: runs all three sensors to completion. + var sensors: std.Io.Group = .init; + + // Start three sensor tasks. They need GUARANTEED concurrency + // since they each simulate real-time measurement. + // + // Bug 2: io.async doesn't guarantee a separate thread. + // Which Io method guarantees true concurrency? + // (Don't forget: it can fail, so you need 'try'!) + try sensors.???(io, sensor, .{ io, &queue, .thermometer, 20 }); + try sensors.???(io, sensor, .{ io, &queue, .hygrometer, 60 }); + try sensors.???(io, sensor, .{ io, &queue, .anemometer, 10 }); + + // Collector group: processes readings from the queue. + var collectors: std.Io.Group = .init; + collectors.async(io, collector, .{ io, &queue, &weather }); + + // Bug 3: Wait for ALL sensors to finish sending their readings. + // What Group method blocks until all tasks complete? + try sensors.await(io); + // try sensors.???(io); + + // All sensors done — close the queue so the collector knows + // there's no more data coming. + queue.close(io); + + // Wait for the collector to drain the queue. + try collectors.await(io); + + // Now write the garden report. This is critical — it must + // NOT be interrupted, even if something tries to cancel us! + // + // Bug 4: Protect this section from cancellation. + // What Io method swaps the cancel protection state? + const old_protection = io.???(.blocked); + defer _ = io.???(old_protection); + + printGardenReport(&weather); +} + +fn sensor( + io: std.Io, + queue: *std.Io.Queue(Reading), + sensor_type: SensorType, + base_value: i32, +) void { + // Each sensor takes exactly 3 measurements. + for (1..4) |i| { + io.sleep(std.Io.Duration.fromMilliseconds(100), .awake) catch return; + + const reading = Reading{ + .sensor_type = sensor_type, + .value = base_value + @as(i32, @intCast(i)), + }; + + // Bug 5: Send the reading into the queue. + // What Queue method sends a single element? + queue.???(io, reading) catch return; + } +} + +fn collector( + io: std.Io, + queue: *std.Io.Queue(Reading), + weather: *GardenWeather, +) void { + while (true) { + const reading = queue.getOne(io) catch |err| switch (err) { + error.Closed => break, + error.Canceled => return, + }; + weather.addReading(io, reading); + } +} + +fn printGardenReport(weather: *GardenWeather) void { + print("=== Doctor Zoraptera's Garden Report ===\n", .{}); + print("Temperature : {}C\n", .{weather.temperature}); + print("Humidity : {}%\n", .{weather.humidity}); + print("Wind : {} km/h\n", .{weather.wind}); + print("Readings : {}\n", .{weather.readings_count}); + + if (weather.temperature > 20 and weather.wind < 15) { + print("Bee-friendly conditions! Expect high pollination.\n", .{}); + } else { + print("Grasshoppers will be grumpy today.\n", .{}); + } +} + +// Further reading for the curious: +// +// This quiz covered the main async I/O primitives: +// io.async() - launch a task (may run inline) +// io.concurrent() - launch with guaranteed parallelism +// Group.concurrent() - concurrent tasks in a group +// Future.await/cancel - collect or cancel a single task +// Group.async/await/cancel - manage fire-and-forget tasks +// Select.async/await - race tasks, act on first completion +// Queue - bounded channel between tasks +// Mutex - protect shared state +// CancelProtection - shield critical sections +// +// There are more synchronization primitives we didn't cover: +// Condition - wait for a condition to become true +// RwLock - multiple readers OR one writer +// Semaphore - limit concurrent access to a resource +// Futex - low-level wait/wake on a memory address +// Batch - submit multiple I/O operations at once +// +// The key insight: all of these work through the Io VTable, +// so your code is portable across backends (Threaded, Uring, +// Kqueue, Dispatch) without any changes! +// +// Doctor Zoraptera approves. diff --git a/patches/patches/084_interfaces.patch b/patches/patches/084_interfaces.patch new file mode 100644 index 0000000..a1d0628 --- /dev/null +++ b/patches/patches/084_interfaces.patch @@ -0,0 +1,11 @@ +--- exercises/084_interfaces.zig 2025-08-15 15:17:57.839348063 +0200 ++++ answers/084_interfaces.zig 2026-04-03 14:27:32.670756488 +0200 +@@ -106,7 +106,7 @@ + for (my_insects) |insect| { + // Almost done! We want to print() each insect with a + // single method call here. +- ??? ++ insect.print(); + } + } + diff --git a/patches/patches/085_async.patch b/patches/patches/085_async.patch new file mode 100644 index 0000000..ca8b102 --- /dev/null +++ b/patches/patches/085_async.patch @@ -0,0 +1,11 @@ +--- exercises/085_async.zig 2026-04-01 20:40:08.904999609 +0200 ++++ answers/085_async.zig 2026-04-01 20:40:05.641933231 +0200 +@@ -37,7 +37,7 @@ + const std = @import("std"); + + pub fn main(init: std.process.Init) !void { +- const io = init.???; ++ const io = init.io; + + // Get the current wall-clock time using the Io interface. + // Hint: Timestamp.now() takes an Io and a Clock type (.real = wall clock). diff --git a/patches/patches/086_async2.patch b/patches/patches/086_async2.patch new file mode 100644 index 0000000..7506a69 --- /dev/null +++ b/patches/patches/086_async2.patch @@ -0,0 +1,14 @@ +--- exercises/086_async2.zig 2026-04-01 19:22:50.017227542 +0200 ++++ answers/086_async2.zig 2026-04-01 19:21:57.569158481 +0200 +@@ -38,9 +38,9 @@ + + // Now collect the result. What method on Future gives us + // the value, blocking if it isn't ready yet? +- const answer = future.???(io); ++ const answer = future.await(io); + +- std.debug.print("The answer is: {}\n", .{answer}); ++ std.debug.print("the answer is: {}\n", .{answer}); + } + + fn computeAnswer(a: u32, b: u32) u32 { diff --git a/patches/patches/087_async3.patch b/patches/patches/087_async3.patch new file mode 100644 index 0000000..8365e7a --- /dev/null +++ b/patches/patches/087_async3.patch @@ -0,0 +1,18 @@ +--- exercises/087_async3.zig 2026-04-01 22:51:05.540094851 +0200 ++++ answers/087_async3.zig 2026-04-01 22:50:44.579669189 +0200 +@@ -29,12 +29,12 @@ + const io = init.io; + + // Launch both tasks asynchronously. +- var future_a = io.async(slowAdd, .{ 10, 20 }); +- var future_b = ???(slowMul, .{ 6, 7 }); ++ var future_a = io.async(slowAdd, .{ 1, 2 }); ++ var future_b = io.async(slowMul, .{ 6, 7 }); + + // Await both results. + const sum = future_a.await(io); +- const product = future_b.???(io); ++ const product = future_b.await(io); + + print("{} + {} = {}\n", .{ 1, 2, sum }); + print("{} * {} = {}\n", .{ 6, 7, product }); diff --git a/patches/patches/088_async4.patch b/patches/patches/088_async4.patch new file mode 100644 index 0000000..1faf30e --- /dev/null +++ b/patches/patches/088_async4.patch @@ -0,0 +1,11 @@ +--- exercises/088_async4.zig 2026-04-01 23:17:31.066443941 +0200 ++++ answers/088_async4.zig 2026-04-01 23:17:39.251612131 +0200 +@@ -38,7 +38,7 @@ + + // Wait for all tasks to finish. + // What Group method blocks until all tasks complete? +- try group.??? ++ try group.await(io); + + print("All tasks finished!\n", .{}); + } diff --git a/patches/patches/089_async5.patch b/patches/patches/089_async5.patch new file mode 100644 index 0000000..d2baa96 --- /dev/null +++ b/patches/patches/089_async5.patch @@ -0,0 +1,11 @@ +--- exercises/089_async5.zig 2026-04-01 23:40:40.505855238 +0200 ++++ answers/089_async5.zig 2026-04-01 23:40:10.176236971 +0200 +@@ -40,7 +40,7 @@ + + // We don't want to wait 10 seconds! + // Which Future method requests cancellation AND returns the result? +- const result = ???; ++ const result = future.cancel(io); + + print("Task returned: {}\n", .{result}); + } diff --git a/patches/patches/090_async6.patch b/patches/patches/090_async6.patch new file mode 100644 index 0000000..5ac777b --- /dev/null +++ b/patches/patches/090_async6.patch @@ -0,0 +1,11 @@ +--- exercises/090_async6.zig 2026-04-02 10:25:34.016616118 +0200 ++++ answers/090_async6.zig 2026-04-02 10:27:48.827144051 +0200 +@@ -47,7 +47,7 @@ + + // Wait for the first finisher. + // What Select method returns the first completed result? +- const winner = ???; ++ const winner = try sel.await(); + + switch (winner) { + .hare => |msg| print("Hare: {s}\n", .{msg}), diff --git a/patches/patches/091_async7.patch b/patches/patches/091_async7.patch new file mode 100644 index 0000000..b4bab9b --- /dev/null +++ b/patches/patches/091_async7.patch @@ -0,0 +1,13 @@ +--- exercises/091_async7.zig 2026-04-02 10:50:08.142508099 +0200 ++++ answers/091_async7.zig 2026-04-02 10:49:59.629341593 +0200 +@@ -49,8 +49,8 @@ + for (0..times) |_| { + // Acquire the lock before modifying shared state. + // What Mutex method blocks until the lock is acquired? +- state.mutex.??? catch return; +- defer state.mutex.unlock(); // <-- what's missing here? ++ state.mutex.lock(io) catch return; ++ defer state.mutex.unlock(io); + + state.counter += 1; + } diff --git a/patches/patches/092_async8.patch b/patches/patches/092_async8.patch new file mode 100644 index 0000000..0ec9116 --- /dev/null +++ b/patches/patches/092_async8.patch @@ -0,0 +1,11 @@ +--- exercises/092_async8.zig 2026-04-02 10:49:27.925721496 +0200 ++++ answers/092_async8.zig 2026-04-02 10:49:31.694795212 +0200 +@@ -43,7 +43,7 @@ + // Send numbers 1 through 10 into the queue. + for (1..11) |i| { + // What Queue method sends a single element, blocking if full? +- queue.???(io, @intCast(i)) catch return; ++ queue.putOne(io, @intCast(i)) catch return; + } + // Signal that we're done sending. + queue.close(io); diff --git a/patches/patches/093_async9.patch b/patches/patches/093_async9.patch new file mode 100644 index 0000000..f759921 --- /dev/null +++ b/patches/patches/093_async9.patch @@ -0,0 +1,11 @@ +--- exercises/093_async9.zig 2026-04-03 13:44:50.526780809 +0200 ++++ answers/093_async9.zig 2026-04-03 13:44:54.957870294 +0200 +@@ -36,7 +36,7 @@ + // Launch with a guaranteed separate thread. + // Which Io method guarantees true concurrency? + // (Hint: unlike io.async, this one can fail!) +- var future = try io.???(compute, .{io}); ++ var future = try io.concurrent(compute, .{io}); + + print("Main thread continues...\n", .{}); + diff --git a/patches/patches/094_async10.patch b/patches/patches/094_async10.patch new file mode 100644 index 0000000..ae0d26d --- /dev/null +++ b/patches/patches/094_async10.patch @@ -0,0 +1,13 @@ +--- exercises/094_async10.zig 2026-04-03 14:25:16.600025924 +0200 ++++ answers/094_async10.zig 2026-04-03 14:24:56.192615893 +0200 +@@ -50,8 +50,8 @@ + + // Protect this section from cancellation. + // What method swaps the cancel protection state? +- const old = io.???(. blocked); +- defer _ = io.???(old); ++ const old = io.swapCancelProtection(.blocked); ++ defer _ = io.swapCancelProtection(old); + + // This sleep will NOT return error.Canceled even though + // we get canceled during it — protection is active! diff --git a/patches/patches/095_quiz_async.patch b/patches/patches/095_quiz_async.patch new file mode 100644 index 0000000..dbaae07 --- /dev/null +++ b/patches/patches/095_quiz_async.patch @@ -0,0 +1,52 @@ +--- exercises/095_quiz_async.zig 2026-04-03 18:04:53.577391455 +0200 ++++ answers/095_quiz_async.zig 2026-04-03 18:05:42.570392172 +0200 +@@ -51,7 +51,7 @@ + // Bug 1: The collector needs to lock before modifying + // shared state. What Mutex method acquires the lock? + self.mutex.lock(io) catch return; +- self.mutex.???(io) catch return; ++ defer self.mutex.unlock(io); + + switch (reading.sensor_type) { + .thermometer => self.temperature = reading.value, +@@ -79,9 +79,9 @@ + // Bug 2: io.async doesn't guarantee a separate thread. + // Which Io method guarantees true concurrency? + // (Don't forget: it can fail, so you need 'try'!) +- try sensors.???(io, sensor, .{ io, &queue, .thermometer, 20 }); +- try sensors.???(io, sensor, .{ io, &queue, .hygrometer, 60 }); +- try sensors.???(io, sensor, .{ io, &queue, .anemometer, 10 }); ++ try sensors.concurrent(io, sensor, .{ io, &queue, .thermometer, 20 }); ++ try sensors.concurrent(io, sensor, .{ io, &queue, .hygrometer, 60 }); ++ try sensors.concurrent(io, sensor, .{ io, &queue, .anemometer, 10 }); + + // Collector group: processes readings from the queue. + var collectors: std.Io.Group = .init; +@@ -90,7 +90,6 @@ + // Bug 3: Wait for ALL sensors to finish sending their readings. + // What Group method blocks until all tasks complete? + try sensors.await(io); +- // try sensors.???(io); + + // All sensors done — close the queue so the collector knows + // there's no more data coming. +@@ -104,8 +103,8 @@ + // + // Bug 4: Protect this section from cancellation. + // What Io method swaps the cancel protection state? +- const old_protection = io.???(.blocked); +- defer _ = io.???(old_protection); ++ const old_protection = io.swapCancelProtection(.blocked); ++ defer _ = io.swapCancelProtection(old_protection); + + printGardenReport(&weather); + } +@@ -127,7 +126,7 @@ + + // Bug 5: Send the reading into the queue. + // What Queue method sends a single element? +- queue.???(io, reading) catch return; ++ queue.putOne(io, reading) catch return; + } + } +