aboutsummaryrefslogtreecommitdiff
path: root/lib/std/event
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-02-16 13:25:30 -0500
committerAndrew Kelley <andrew@ziglang.org>2020-02-16 13:25:30 -0500
commit4b02a39aa93b0043f05de0d90443051c019643ab (patch)
tree12632d2e43bc6e13911d8e88d1f91e84b11106f6 /lib/std/event
parent5e37fc0746a75ed319fc57ae62d8cc966382c592 (diff)
downloadzig-4b02a39aa93b0043f05de0d90443051c019643ab.tar.gz
zig-4b02a39aa93b0043f05de0d90443051c019643ab.zip
self-hosted libc detection
* libc_installation.cpp is deleted. src-self-hosted/libc_installation.zig is now used for both stage1 and stage2 compilers. * (breaking) move `std.fs.File.access` to `std.fs.Dir.access`. The API now encourages use with an open directory handle. * Add `std.os.faccessat` and related functions. * Deprecate the "C" suffix naming convention for null-terminated parameters. "C" should be used when it is related to libc. However null-terminated parameters often have to do with the native system ABI rather than libc. "Z" suffix is the new convention. For example, `std.os.openC` is deprecated in favor of `std.os.openZ`. * Add `std.mem.dupeZ` for using an allocator to copy memory and add a null terminator. * Remove dead struct field `std.ChildProcess.llnode`. * Introduce `std.event.Batch`. This API allows expressing concurrency without forcing code to be async. It requires no Allocator and does not introduce any failure conditions. However it is not thread-safe. * There is now an ongoing experiment to transition away from `std.event.Group` in favor of `std.event.Batch`. * `std.os.execvpeC` calls `getenvZ` rather than `getenv`. This is slightly more efficient on most systems, and works around a limitation of `getenv` lack of integration with libc. * (breaking) `std.os.AccessError` gains `FileBusy`, `SymLinkLoop`, and `ReadOnlyFileSystem`. Previously these error codes were all reported as `PermissionDenied`. * Add `std.Target.isDragonFlyBSD`. * stage2: access to the windows_sdk functions is done with a manually maintained .zig binding file instead of `@cImport`. * Update src-self-hosted/libc_installation.zig with all the improvements that stage1 has seen to src/libc_installation.cpp until now. In addition, it now takes advantage of Batch so that evented I/O mode takes advantage of concurrency, but it still works in blocking I/O mode, which is how it is used in stage1.
Diffstat (limited to 'lib/std/event')
-rw-r--r--lib/std/event/batch.zig139
-rw-r--r--lib/std/event/group.zig5
2 files changed, 144 insertions, 0 deletions
diff --git a/lib/std/event/batch.zig b/lib/std/event/batch.zig
new file mode 100644
index 0000000000..82dadb91be
--- /dev/null
+++ b/lib/std/event/batch.zig
@@ -0,0 +1,139 @@
+const std = @import("../std.zig");
+const testing = std.testing;
+
+/// Performs multiple async functions in parallel, without heap allocation.
+/// Async function frames are managed externally to this abstraction, and
+/// passed in via the `add` function. Once all the jobs are added, call `wait`.
+/// This API is *not* thread-safe. The object must be accessed from one thread at
+/// a time, however, it need not be the same thread.
+pub fn Batch(
+ /// The return value for each job.
+ /// If a job slot was re-used due to maxed out concurrency, then its result
+ /// value will be overwritten. The values can be accessed with the `results` field.
+ comptime Result: type,
+ /// How many jobs to run in parallel.
+ comptime max_jobs: comptime_int,
+ /// Controls whether the `add` and `wait` functions will be async functions.
+ comptime async_behavior: enum {
+ /// Observe the value of `std.io.is_async` to decide whether `add`
+ /// and `wait` will be async functions. Asserts that the jobs do not suspend when
+ /// `std.io.mode == .blocking`. This is a generally safe assumption, and the
+ /// usual recommended option for this parameter.
+ auto_async,
+
+ /// Always uses the `noasync` keyword when using `await` on the jobs,
+ /// making `add` and `wait` non-async functions. Asserts that the jobs do not suspend.
+ never_async,
+
+ /// `add` and `wait` use regular `await` keyword, making them async functions.
+ always_async,
+ },
+) type {
+ return struct {
+ jobs: [max_jobs]Job,
+ next_job_index: usize,
+ collected_result: CollectedResult,
+
+ const Job = struct {
+ frame: ?anyframe->Result,
+ result: Result,
+ };
+
+ const Self = @This();
+
+ const CollectedResult = switch (@typeInfo(Result)) {
+ .ErrorUnion => Result,
+ else => void,
+ };
+
+ const async_ok = switch (async_behavior) {
+ .auto_async => std.io.is_async,
+ .never_async => false,
+ .always_async => true,
+ };
+
+ pub fn init() Self {
+ return Self{
+ .jobs = [1]Job{
+ .{
+ .frame = null,
+ .result = undefined,
+ },
+ } ** max_jobs,
+ .next_job_index = 0,
+ .collected_result = {},
+ };
+ }
+
+ /// Add a frame to the Batch. If all jobs are in-flight, then this function
+ /// waits until one completes.
+ /// This function is *not* thread-safe. It must be called from one thread at
+ /// a time, however, it need not be the same thread.
+ /// TODO: "select" language feature to use the next available slot, rather than
+ /// awaiting the next index.
+ pub fn add(self: *Self, frame: anyframe->Result) void {
+ const job = &self.jobs[self.next_job_index];
+ self.next_job_index = (self.next_job_index + 1) % max_jobs;
+ if (job.frame) |existing| {
+ job.result = if (async_ok) await existing else noasync await existing;
+ if (CollectedResult != void) {
+ job.result catch |err| {
+ self.collected_result = err;
+ };
+ }
+ }
+ job.frame = frame;
+ }
+
+ /// Wait for all the jobs to complete.
+ /// Safe to call any number of times.
+ /// If `Result` is an error union, this function returns the last error that occurred, if any.
+ /// Unlike the `results` field, the return value of `wait` will report any error that occurred;
+ /// hitting max parallelism will not compromise the result.
+ /// This function is *not* thread-safe. It must be called from one thread at
+ /// a time, however, it need not be the same thread.
+ pub fn wait(self: *Self) CollectedResult {
+ for (self.jobs) |*job| if (job.frame) |f| {
+ job.result = if (async_ok) await f else noasync await f;
+ if (CollectedResult != void) {
+ job.result catch |err| {
+ self.collected_result = err;
+ };
+ }
+ job.frame = null;
+ };
+ return self.collected_result;
+ }
+ };
+}
+
+test "std.event.Batch" {
+ var count: usize = 0;
+ var batch = Batch(void, 2).init();
+ batch.add(&async sleepALittle(&count));
+ batch.add(&async increaseByTen(&count));
+ batch.wait();
+ testing.expect(count == 11);
+
+ var another = Batch(anyerror!void, 2).init();
+ another.add(&async somethingElse());
+ another.add(&async doSomethingThatFails());
+ testing.expectError(error.ItBroke, another.wait());
+}
+
+fn sleepALittle(count: *usize) void {
+ std.time.sleep(1 * std.time.millisecond);
+ _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
+}
+
+fn increaseByTen(count: *usize) void {
+ var i: usize = 0;
+ while (i < 10) : (i += 1) {
+ _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
+ }
+}
+
+fn doSomethingThatFails() anyerror!void {}
+fn somethingElse() anyerror!void {
+ return error.ItBroke;
+}
diff --git a/lib/std/event/group.zig b/lib/std/event/group.zig
index 98ebdbd1f8..ac1bf68245 100644
--- a/lib/std/event/group.zig
+++ b/lib/std/event/group.zig
@@ -5,6 +5,11 @@ const testing = std.testing;
const Allocator = std.mem.Allocator;
/// ReturnType must be `void` or `E!void`
+/// TODO This API was created back with the old design of async/await, when calling any
+/// async function required an allocator. There is an ongoing experiment to transition
+/// all uses of this API to the simpler and more resource-aware `std.event.Batch` API.
+/// If the transition goes well, all usages of `Group` will be gone, and this API
+/// will be deleted.
pub fn Group(comptime ReturnType: type) type {
return struct {
frame_stack: Stack,