diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-02-16 13:25:30 -0500 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2020-02-16 13:25:30 -0500 |
| commit | 4b02a39aa93b0043f05de0d90443051c019643ab (patch) | |
| tree | 12632d2e43bc6e13911d8e88d1f91e84b11106f6 /lib/std/event | |
| parent | 5e37fc0746a75ed319fc57ae62d8cc966382c592 (diff) | |
| download | zig-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.zig | 139 | ||||
| -rw-r--r-- | lib/std/event/group.zig | 5 |
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, |
