| ... | @@ -29,23 +29,7 @@ join_requested: bool = false, | ... | @@ -29,23 +29,7 @@ join_requested: bool = false, |
| 29 | stack_size: usize, | 29 | stack_size: usize, |
| 30 | /// All threads are spawned detached; this is how we wait until they all exit. | 30 | /// All threads are spawned detached; this is how we wait until they all exit. |
| 31 | wait_group: std.Thread.WaitGroup = .{}, | 31 | wait_group: std.Thread.WaitGroup = .{}, |
| 32 | /// Maximum thread pool size (excluding main thread) when dispatching async | | |
| 33 | /// tasks. Until this limit, calls to `Io.async` when all threads are busy will | | |
| 34 | /// cause a new thread to be spawned and permanently added to the pool. After | | |
| 35 | /// this limit, calls to `Io.async` when all threads are busy run the task | | |
| 36 | /// immediately. | | |
| 37 | /// | | |
| 38 | /// Defaults to a number equal to logical CPU cores. | | |
| 39 | /// | | |
| 40 | /// Protected by `mutex` once the I/O instance is already in use. See | | |
| 41 | /// `setAsyncLimit`. | | |
| 42 | async_limit: Io.Limit, | 32 | async_limit: Io.Limit, |
| 43 | /// Maximum thread pool size (excluding main thread) for dispatching concurrent | | |
| 44 | /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread | | |
| 45 | /// pool size. | | |
| 46 | /// | | |
| 47 | /// concurrent tasks. After this number, calls to `Io.concurrent` return | | |
| 48 | /// `error.ConcurrencyUnavailable`. | | |
| 49 | concurrent_limit: Io.Limit = .unlimited, | 33 | concurrent_limit: Io.Limit = .unlimited, |
| 50 | /// Error from calling `std.Thread.getCpuCount` in `init`. | 34 | /// Error from calling `std.Thread.getCpuCount` in `init`. |
| 51 | cpu_count_error: ?std.Thread.CpuCountError, | 35 | cpu_count_error: ?std.Thread.CpuCountError, |
| ... | @@ -55,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError, | ... | @@ -55,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError, |
| 55 | busy_count: usize = 0, | 39 | busy_count: usize = 0, |
| 56 | main_thread: Thread, | 40 | main_thread: Thread, |
| 57 | pid: Pid = .unknown, | 41 | pid: Pid = .unknown, |
| 58 | /// When a cancel request is made, blocking syscalls can be unblocked by | 42 | robust_cancel: RobustCancel, |
| 59 | /// issuing a signal. However, if the signal arrives after the check and before | | |
| 60 | /// the syscall instruction, it is missed. | | |
| 61 | /// | | |
| 62 | /// This option solves the race condition by retrying the signal delivery | | |
| 63 | /// until it is acknowledged, with an exponential backoff. | | |
| 64 | /// | | |
| 65 | /// Unfortunately, trying again until the cancellation request is acknowledged | | |
| 66 | /// has been observed to be relatively slow, and usually strong cancellation | | |
| 67 | /// guarantees are not needed, so this defaults to off. | | |
| 68 | robust_cancel: RobustCancel = .disabled, | | |
| 69 | | 43 | |
| 70 | wsa: if (is_windows) Wsa else struct {} = .{}, | 44 | wsa: if (is_windows) Wsa else struct {} = .{}, |
| 71 | | 45 | |
| ... | @@ -86,6 +60,32 @@ stderr_writer: File.Writer = .{ | ... | @@ -86,6 +60,32 @@ stderr_writer: File.Writer = .{ |
| 86 | stderr_mode: Io.Terminal.Mode = .no_color, | 60 | stderr_mode: Io.Terminal.Mode = .no_color, |
| 87 | stderr_writer_initialized: bool = false, | 61 | stderr_writer_initialized: bool = false, |
| 88 | | 62 | |
| | 63 | environ: Environ, |
| | 64 | args: Args, |
| | 65 | |
| | 66 | pub const Environ = switch (native_os) { |
| | 67 | .openbsd, .haiku => struct { |
| | 68 | PATH: ?[]const u8, |
| | 69 | |
| | 70 | pub const empty: @This() = .{ |
| | 71 | .PATH = null, |
| | 72 | }; |
| | 73 | }, |
| | 74 | else => struct { |
| | 75 | pub const empty: @This() = .{}; |
| | 76 | }, |
| | 77 | }; |
| | 78 | |
| | 79 | pub const Args = switch (native_os) { |
| | 80 | .openbsd, .haiku => struct { |
| | 81 | list: []const []const u8, |
| | 82 | pub const empty: @This() = .{ .list = &.{} }; |
| | 83 | }, |
| | 84 | else => struct { |
| | 85 | pub const empty: @This() = .{}; |
| | 86 | }, |
| | 87 | }; |
| | 88 | |
| 89 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { | 89 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { |
| 90 | enabled, | 90 | enabled, |
| 91 | disabled, | 91 | disabled, |
| ... | @@ -557,6 +557,54 @@ const Closure = struct { | ... | @@ -557,6 +557,54 @@ const Closure = struct { |
| 557 | } | 557 | } |
| 558 | }; | 558 | }; |
| 559 | | 559 | |
| | 560 | pub const InitOptions = struct { |
| | 561 | /// Affects how many bytes are memory-mapped for threads. |
| | 562 | stack_size: usize = std.Thread.SpawnConfig.default_stack_size, |
| | 563 | /// Maximum thread pool size (excluding main thread) when dispatching async |
| | 564 | /// tasks. Until this limit, calls to `Io.async` when all threads are busy will |
| | 565 | /// cause a new thread to be spawned and permanently added to the pool. After |
| | 566 | /// this limit, calls to `Io.async` when all threads are busy run the task |
| | 567 | /// immediately. |
| | 568 | /// |
| | 569 | /// Defaults to a number equal to logical CPU cores. |
| | 570 | /// |
| | 571 | /// Protected by `Threaded.mutex` once the I/O instance is already in use. See |
| | 572 | /// `setAsyncLimit`. |
| | 573 | async_limit: ?Io.Limit = null, |
| | 574 | /// Maximum thread pool size (excluding main thread) for dispatching concurrent |
| | 575 | /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread |
| | 576 | /// pool size. |
| | 577 | /// |
| | 578 | /// concurrent tasks. After this number, calls to `Io.concurrent` return |
| | 579 | /// `error.ConcurrencyUnavailable`. |
| | 580 | concurrent_limit: Io.Limit = .unlimited, |
| | 581 | /// When a cancel request is made, blocking syscalls can be unblocked by |
| | 582 | /// issuing a signal. However, if the signal arrives after the check and before |
| | 583 | /// the syscall instruction, it is missed. |
| | 584 | /// |
| | 585 | /// This option solves the race condition by retrying the signal delivery |
| | 586 | /// until it is acknowledged, with an exponential backoff. |
| | 587 | /// |
| | 588 | /// Unfortunately, trying again until the cancellation request is acknowledged |
| | 589 | /// has been observed to be relatively slow, and usually strong cancellation |
| | 590 | /// guarantees are not needed, so this defaults to off. |
| | 591 | robust_cancel: RobustCancel = .disabled, |
| | 592 | /// Affects the following operations: |
| | 593 | /// * `processExecutablePath` on OpenBSD and Haiku. |
| | 594 | /// |
| | 595 | /// The default value causes this to be a compile error on systems that need to |
| | 596 | /// initialize this field. `Environ.empty` can be used to omit this field on |
| | 597 | /// all targets. |
| | 598 | environ: Environ = .{}, |
| | 599 | /// Affects the following operations: |
| | 600 | /// * `processExecutablePath` on OpenBSD and Haiku. |
| | 601 | /// |
| | 602 | /// The default value causes this to be a compile error on systems that need to |
| | 603 | /// initialize this field. `Args.empty` can be used to omit this field on all |
| | 604 | /// targets. |
| | 605 | args: Args = .{}, |
| | 606 | }; |
| | 607 | |
| 560 | /// Related: | 608 | /// Related: |
| 561 | /// * `init_single_threaded` | 609 | /// * `init_single_threaded` |
| 562 | pub fn init( | 610 | pub fn init( |
| ... | @@ -568,6 +616,7 @@ pub fn init( | ... | @@ -568,6 +616,7 @@ pub fn init( |
| 568 | /// If these functions are avoided, then `Allocator.failing` may be passed | 616 | /// If these functions are avoided, then `Allocator.failing` may be passed |
| 569 | /// here. | 617 | /// here. |
| 570 | gpa: Allocator, | 618 | gpa: Allocator, |
| | 619 | options: InitOptions, |
| 571 | ) Threaded { | 620 | ) Threaded { |
| 572 | if (builtin.single_threaded) return .init_single_threaded; | 621 | if (builtin.single_threaded) return .init_single_threaded; |
| 573 | | 622 | |
| ... | @@ -575,8 +624,9 @@ pub fn init( | ... | @@ -575,8 +624,9 @@ pub fn init( |
| 575 | | 624 | |
| 576 | var t: Threaded = .{ | 625 | var t: Threaded = .{ |
| 577 | .allocator = gpa, | 626 | .allocator = gpa, |
| 578 | .stack_size = std.Thread.SpawnConfig.default_stack_size, | 627 | .stack_size = options.stack_size, |
| 579 | .async_limit = if (cpu_count) |n| .limited(n - 1) else |_| .nothing, | 628 | .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing, |
| | 629 | .concurrent_limit = options.concurrent_limit, |
| 580 | .cpu_count_error = if (cpu_count) |_| null else |e| e, | 630 | .cpu_count_error = if (cpu_count) |_| null else |e| e, |
| 581 | .old_sig_io = undefined, | 631 | .old_sig_io = undefined, |
| 582 | .old_sig_pipe = undefined, | 632 | .old_sig_pipe = undefined, |
| ... | @@ -586,6 +636,9 @@ pub fn init( | ... | @@ -586,6 +636,9 @@ pub fn init( |
| 586 | .current_closure = null, | 636 | .current_closure = null, |
| 587 | .cancel_protection = undefined, | 637 | .cancel_protection = undefined, |
| 588 | }, | 638 | }, |
| | 639 | .environ = options.environ, |
| | 640 | .args = options.args, |
| | 641 | .robust_cancel = options.robust_cancel, |
| 589 | }; | 642 | }; |
| 590 | | 643 | |
| 591 | if (posix.Sigaction != void) { | 644 | if (posix.Sigaction != void) { |
| ... | @@ -624,6 +677,9 @@ pub const init_single_threaded: Threaded = .{ | ... | @@ -624,6 +677,9 @@ pub const init_single_threaded: Threaded = .{ |
| 624 | .current_closure = null, | 677 | .current_closure = null, |
| 625 | .cancel_protection = undefined, | 678 | .cancel_protection = undefined, |
| 626 | }, | 679 | }, |
| | 680 | .robust_cancel = .disabled, |
| | 681 | .environ = .empty, |
| | 682 | .args = .empty, |
| 627 | }; | 683 | }; |
| 628 | | 684 | |
| 629 | var global_single_threaded_instance: Threaded = .init_single_threaded; | 685 | var global_single_threaded_instance: Threaded = .init_single_threaded; |
| ... | @@ -7186,18 +7242,17 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex | ... | @@ -7186,18 +7242,17 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex |
| 7186 | } | 7242 | } |
| 7187 | }, | 7243 | }, |
| 7188 | .openbsd, .haiku => { | 7244 | .openbsd, .haiku => { |
| 7189 | // OpenBSD doesn't support getting the path of a running process, so try to guess it | 7245 | // The best we can do on these operating systems is check based on CLI args. |
| 7190 | if (std.os.argv.len == 0) | 7246 | const argv = t.args.list; |
| 7191 | return error.FileNotFound; | 7247 | if (argv.len == 0) return error.OperationUnsupported; |
| 7192 | | 7248 | const argv0 = argv[0]; |
| 7193 | const argv0 = std.mem.span(std.os.argv[0]); | | |
| 7194 | if (std.mem.findScalar(u8, argv0, '/') != null) { | 7249 | if (std.mem.findScalar(u8, argv0, '/') != null) { |
| 7195 | // argv[0] is a path (relative or absolute): use realpath(3) directly | 7250 | // argv[0] is a path (relative or absolute): use realpath(3) directly |
| 7196 | const current_thread = Thread.getCurrent(t); | 7251 | const current_thread = Thread.getCurrent(t); |
| 7197 | var resolved_buf: [std.c.PATH_MAX]u8 = undefined; | 7252 | var resolved_buf: [std.c.PATH_MAX]u8 = undefined; |
| 7198 | try current_thread.beginSyscall(); | 7253 | try current_thread.beginSyscall(); |
| 7199 | while (true) { | 7254 | while (true) { |
| 7200 | if (std.c.realpath(std.os.argv[0], &resolved_buf)) |p| { | 7255 | if (std.c.realpath(argv[0], &resolved_buf)) |p| { |
| 7201 | assert(p == &resolved_buf); | 7256 | assert(p == &resolved_buf); |
| 7202 | break current_thread.endSyscall(); | 7257 | break current_thread.endSyscall(); |
| 7203 | } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { | 7258 | } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { |
| ... | @@ -7229,12 +7284,12 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex | ... | @@ -7229,12 +7284,12 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex |
| 7229 | } else if (argv0.len != 0) { | 7284 | } else if (argv0.len != 0) { |
| 7230 | // argv[0] is not empty (and not a path): search PATH | 7285 | // argv[0] is not empty (and not a path): search PATH |
| 7231 | const current_thread = Thread.getCurrent(t); | 7286 | const current_thread = Thread.getCurrent(t); |
| 7232 | const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; | 7287 | const PATH = t.environ.PATH orelse return error.FileNotFound; |
| 7233 | var it = std.mem.tokenizeScalar(u8, PATH, ':'); | 7288 | var it = std.mem.tokenizeScalar(u8, PATH, ':'); |
| 7234 | it: while (it.next()) |dir| { | 7289 | it: while (it.next()) |dir| { |
| 7235 | var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; | 7290 | var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; |
| 7236 | const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ | 7291 | const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ |
| 7237 | dir, std.os.argv[0], | 7292 | dir, argv[0], |
| 7238 | }, 0) catch continue; | 7293 | }, 0) catch continue; |
| 7239 | | 7294 | |
| 7240 | var resolved_buf: [std.c.PATH_MAX]u8 = undefined; | 7295 | var resolved_buf: [std.c.PATH_MAX]u8 = undefined; |