// // 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.