mirror of
https://codeberg.org/ziglings/exercises.git
synced 2026-06-08 07:50:00 +00:00
187 lines
6.5 KiB
Zig
187 lines
6.5 KiB
Zig
//
|
|
// 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.
|