mirror of
https://codeberg.org/ziglings/exercises.git
synced 2026-06-08 07:50:00 +00:00
added async-io quiz
This commit is contained in:
186
exercises/095_quiz_async.zig
Normal file
186
exercises/095_quiz_async.zig
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user