diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index b6d771302c..545cab6083 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -556,7 +556,7 @@ pub fn main() !void { try run.thread_pool.init(thread_pool_options); defer run.thread_pool.deinit(); - const now = Io.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err}); + const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err}); run.web_server = if (webui_listen) |listen_address| ws: { if (builtin.single_threaded) unreachable; // `fatal` above diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 8202c4dd15..8f88d840b8 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -21,7 +21,7 @@ io: Io, manifest_dir: fs.Dir, hash: HashHelper = .{}, /// This value is accessed from multiple threads, protected by mutex. -recent_problematic_timestamp: i128 = 0, +recent_problematic_timestamp: Io.Timestamp = .zero, mutex: std.Thread.Mutex = .{}, /// A set of strings such as the zig library directory or project source root, which @@ -155,7 +155,7 @@ pub const File = struct { pub const Stat = struct { inode: fs.File.INode, size: u64, - mtime: i128, + mtime: Io.Timestamp, pub fn fromFs(fs_stat: fs.File.Stat) Stat { return .{ @@ -330,7 +330,7 @@ pub const Manifest = struct { diagnostic: Diagnostic = .none, /// Keeps track of the last time we performed a file system write to observe /// what time the file system thinks it is, according to its own granularity. - recent_problematic_timestamp: i128 = 0, + recent_problematic_timestamp: Io.Timestamp = .zero, pub const Diagnostic = union(enum) { none, @@ -728,7 +728,7 @@ pub const Manifest = struct { file.stat = .{ .size = stat_size, .inode = stat_inode, - .mtime = stat_mtime, + .mtime = .{ .nanoseconds = stat_mtime }, }; file.bin_digest = file_bin_digest; break :f file; @@ -747,7 +747,7 @@ pub const Manifest = struct { .stat = .{ .size = stat_size, .inode = stat_inode, - .mtime = stat_mtime, + .mtime = .{ .nanoseconds = stat_mtime }, }, .bin_digest = file_bin_digest, }; @@ -780,7 +780,7 @@ pub const Manifest = struct { return error.CacheCheckFailed; }; const size_match = actual_stat.size == cache_hash_file.stat.size; - const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime; + const mtime_match = actual_stat.mtime.nanoseconds == cache_hash_file.stat.mtime.nanoseconds; const inode_match = actual_stat.inode == cache_hash_file.stat.inode; if (!size_match or !mtime_match or !inode_match) { @@ -792,7 +792,7 @@ pub const Manifest = struct { if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { // The actual file has an unreliable timestamp, force it to be hashed - cache_hash_file.stat.mtime = 0; + cache_hash_file.stat.mtime = .zero; cache_hash_file.stat.inode = 0; } @@ -848,10 +848,10 @@ pub const Manifest = struct { } } - fn isProblematicTimestamp(man: *Manifest, file_time: i128) bool { + fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) bool { // If the file_time is prior to the most recent problematic timestamp // then we don't need to access the filesystem. - if (file_time < man.recent_problematic_timestamp) + if (timestamp.nanoseconds < man.recent_problematic_timestamp.nanoseconds) return false; // Next we will check the globally shared Cache timestamp, which is accessed @@ -861,7 +861,7 @@ pub const Manifest = struct { // Save the global one to our local one to avoid locking next time. man.recent_problematic_timestamp = man.cache.recent_problematic_timestamp; - if (file_time < man.recent_problematic_timestamp) + if (timestamp.nanoseconds < man.recent_problematic_timestamp.nanoseconds) return false; // This flag prevents multiple filesystem writes for the same hit() call. @@ -879,7 +879,7 @@ pub const Manifest = struct { man.cache.recent_problematic_timestamp = man.recent_problematic_timestamp; } - return file_time >= man.recent_problematic_timestamp; + return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds; } fn populateFileHash(self: *Manifest, ch_file: *File) !void { @@ -904,7 +904,7 @@ pub const Manifest = struct { if (self.isProblematicTimestamp(ch_file.stat.mtime)) { // The actual file has an unreliable timestamp, force it to be hashed - ch_file.stat.mtime = 0; + ch_file.stat.mtime = .zero; ch_file.stat.inode = 0; } @@ -1040,7 +1040,7 @@ pub const Manifest = struct { if (self.isProblematicTimestamp(new_file.stat.mtime)) { // The actual file has an unreliable timestamp, force it to be hashed - new_file.stat.mtime = 0; + new_file.stat.mtime = .zero; new_file.stat.inode = 0; } diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 95338c9f08..50e304c950 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -11,7 +11,7 @@ tcp_server: ?net.Server, serve_thread: ?std.Thread, /// Uses `Io.Clock.awake`. -base_timestamp: i96, +base_timestamp: Io.Timestamp, /// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`. step_names_trailing: []u8, @@ -43,6 +43,8 @@ runner_request: ?RunnerRequest, /// on a fixed interval of this many milliseconds. const default_update_interval_ms = 500; +pub const base_clock: Io.Clock = .awake; + /// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`. pub fn notifyUpdate(ws: *WebServer) void { _ = ws.update_id.rmw(.Add, 1, .release); @@ -58,13 +60,13 @@ pub const Options = struct { root_prog_node: std.Progress.Node, watch: bool, listen_address: net.IpAddress, - base_timestamp: Io.Timestamp, + base_timestamp: Io.Clock.Timestamp, }; pub fn init(opts: Options) WebServer { // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent` // instead of threads, so that the web server can function in single-threaded builds. comptime assert(!builtin.single_threaded); - assert(opts.base_timestamp.clock == .awake); + assert(opts.base_timestamp.clock == base_clock); const all_steps = opts.all_steps; @@ -109,7 +111,7 @@ pub fn init(opts: Options) WebServer { .tcp_server = null, .serve_thread = null, - .base_timestamp = opts.base_timestamp.nanoseconds, + .base_timestamp = opts.base_timestamp.raw, .step_names_trailing = step_names_trailing, .step_status_bits = step_status_bits, @@ -248,9 +250,8 @@ pub fn finishBuild(ws: *WebServer, opts: struct { pub fn now(s: *const WebServer) i64 { const io = s.graph.io; - const base: Io.Timestamp = .{ .nanoseconds = s.base_timestamp, .clock = .awake }; - const ts = Io.Timestamp.now(io, base.clock) catch base; - return @intCast(base.durationTo(ts).toNanoseconds()); + const ts = base_clock.now(io) catch s.base_timestamp; + return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds()); } fn accept(ws: *WebServer, stream: net.Stream) void { @@ -519,7 +520,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa); break :cwd cached_cwd_path.?; }; - try archiver.writeFile(path.sub_path, &file_reader, stat.mtime); + try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); } // intentionally not calling `archiver.finishPedantically` diff --git a/lib/std/Io.zig b/lib/std/Io.zig index e569c4ec94..8d061ad022 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -669,7 +669,7 @@ pub const VTable = struct { fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void, - now: *const fn (?*anyopaque, Timestamp.Clock) Timestamp.Error!i96, + now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server, @@ -705,118 +705,178 @@ pub const UnexpectedError = error{ pub const Dir = @import("Io/Dir.zig"); pub const File = @import("Io/File.zig"); -pub const Timestamp = struct { - nanoseconds: i96, - clock: Clock, - - pub const Clock = enum { - /// A settable system-wide clock that measures real (i.e. wall-clock) - /// time. This clock is affected by discontinuous jumps in the system - /// time (e.g., if the system administrator manually changes the - /// clock), and by frequency adjust‐ ments performed by NTP and similar - /// applications. - /// - /// This clock normally counts the number of seconds since 1970-01-01 - /// 00:00:00 Coordinated Universal Time (UTC) except that it ignores - /// leap seconds; near a leap second it is typically adjusted by NTP to - /// stay roughly in sync with UTC. - /// - /// The epoch is implementation-defined. For example NTFS/Windows uses - /// 1601-01-01. - real, - /// A nonsettable system-wide clock that represents time since some - /// unspecified point in the past. - /// - /// Monotonic: Guarantees that the time returned by consecutive calls - /// will not go backwards, but successive calls may return identical - /// (not-increased) time values. - /// - /// Not affected by discontinuous jumps in the system time (e.g., if - /// the system administrator manually changes the clock), but may be - /// affected by frequency adjustments. - /// - /// This clock expresses intent to **exclude time that the system is - /// suspended**. However, implementations may be unable to satisify - /// this, and may include that time. - /// - /// * On Linux, corresponds `CLOCK_MONOTONIC`. - /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`. - awake, - /// Identical to `awake` except it expresses intent to **include time - /// that the system is suspended**, however, due to limitations it may - /// behave identically to `awake`. - /// - /// * On Linux, corresponds `CLOCK_BOOTTIME`. - /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`. - boot, - /// Tracks the amount of CPU in user or kernel mode used by the calling - /// process. - cpu_process, - /// Tracks the amount of CPU in user or kernel mode used by the calling - /// thread. - cpu_thread, - }; - - pub fn durationTo(from: Timestamp, to: Timestamp) Duration { - assert(from.clock == to.clock); - return .{ .nanoseconds = to.nanoseconds - from.nanoseconds }; - } - - pub fn addDuration(from: Timestamp, duration: Duration) Timestamp { - return .{ - .nanoseconds = from.nanoseconds + duration.nanoseconds, - .clock = from.clock, - }; - } +pub const Clock = enum { + /// A settable system-wide clock that measures real (i.e. wall-clock) + /// time. This clock is affected by discontinuous jumps in the system + /// time (e.g., if the system administrator manually changes the + /// clock), and by frequency adjust‐ ments performed by NTP and similar + /// applications. + /// + /// This clock normally counts the number of seconds since 1970-01-01 + /// 00:00:00 Coordinated Universal Time (UTC) except that it ignores + /// leap seconds; near a leap second it is typically adjusted by NTP to + /// stay roughly in sync with UTC. + /// + /// The epoch is implementation-defined. For example NTFS/Windows uses + /// 1601-01-01. + real, + /// A nonsettable system-wide clock that represents time since some + /// unspecified point in the past. + /// + /// Monotonic: Guarantees that the time returned by consecutive calls + /// will not go backwards, but successive calls may return identical + /// (not-increased) time values. + /// + /// Not affected by discontinuous jumps in the system time (e.g., if + /// the system administrator manually changes the clock), but may be + /// affected by frequency adjustments. + /// + /// This clock expresses intent to **exclude time that the system is + /// suspended**. However, implementations may be unable to satisify + /// this, and may include that time. + /// + /// * On Linux, corresponds `CLOCK_MONOTONIC`. + /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`. + awake, + /// Identical to `awake` except it expresses intent to **include time + /// that the system is suspended**, however, due to limitations it may + /// behave identically to `awake`. + /// + /// * On Linux, corresponds `CLOCK_BOOTTIME`. + /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`. + boot, + /// Tracks the amount of CPU in user or kernel mode used by the calling + /// process. + cpu_process, + /// Tracks the amount of CPU in user or kernel mode used by the calling + /// thread. + cpu_thread, pub const Error = error{UnsupportedClock} || UnexpectedError; /// This function is not cancelable because first of all it does not block, /// but more importantly, the cancelation logic itself may want to check /// the time. - pub fn now(io: Io, clock: Clock) Error!Timestamp { - return .{ - .nanoseconds = try io.vtable.now(io.userdata, clock), - .clock = clock, - }; + pub fn now(clock: Clock, io: Io) Error!Io.Timestamp { + return io.vtable.now(io.userdata, clock); } - pub fn fromNow(io: Io, clock: Clock, duration: Duration) Error!Timestamp { - const now_ts = try now(io, clock); - return addDuration(now_ts, duration); + pub const Timestamp = struct { + raw: Io.Timestamp, + clock: Clock, + + /// This function is not cancelable because first of all it does not block, + /// but more importantly, the cancelation logic itself may want to check + /// the time. + pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp { + return .{ + .raw = try io.vtable.now(io.userdata, clock), + .clock = clock, + }; + } + + pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void { + return io.vtable.sleep(io.userdata, .{ .deadline = t }); + } + + pub fn durationTo(from: Clock.Timestamp, to: Clock.Timestamp) Clock.Duration { + assert(from.clock == to.clock); + return .{ + .raw = from.raw.durationTo(to.raw), + .clock = from.clock, + }; + } + + pub fn addDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp { + assert(from.clock == duration.clock); + return .{ + .raw = from.raw.addDuration(duration.raw), + .clock = from.clock, + }; + } + + pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp { + return .{ + .clock = duration.clock, + .raw = (try duration.clock.now(io)).addDuration(duration.raw), + }; + } + + pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration { + const now_ts = try Clock.Timestamp.now(io, timestamp.clock); + return timestamp.durationTo(now_ts); + } + + pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration { + const now_ts = try timestamp.clock.now(io); + return .{ + .clock = timestamp.clock, + .raw = now_ts.durationTo(timestamp.raw), + }; + } + + pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp { + if (t.clock == clock) return t; + const now_old = try t.clock.now(io); + const now_new = try clock.now(io); + const duration = now_old.durationTo(t); + return .{ + .clock = clock, + .raw = now_new.addDuration(duration), + }; + } + + pub fn compare(lhs: Clock.Timestamp, op: std.math.CompareOperator, rhs: Clock.Timestamp) bool { + assert(lhs.clock == rhs.clock); + return std.math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds); + } + }; + + pub const Duration = struct { + raw: Io.Duration, + clock: Clock, + + pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void { + return io.vtable.sleep(io.userdata, .{ .duration = duration }); + } + }; +}; + +pub const Timestamp = struct { + nanoseconds: i96, + + pub const zero: Timestamp = .{ .nanoseconds = 0 }; + + pub fn durationTo(from: Timestamp, to: Timestamp) Duration { + return .{ .nanoseconds = to.nanoseconds - from.nanoseconds }; } - pub fn untilNow(timestamp: Timestamp, io: Io) Error!Duration { - const now_ts = try Timestamp.now(io, timestamp.clock); - return timestamp.durationTo(now_ts); + pub fn addDuration(from: Timestamp, duration: Duration) Timestamp { + return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds }; } - pub fn durationFromNow(timestamp: Timestamp, io: Io) Error!Duration { - const now_ts = try now(io, timestamp.clock); - return now_ts.durationTo(timestamp); - } - - pub fn toClock(t: Timestamp, io: Io, clock: Clock) Error!Timestamp { - if (t.clock == clock) return t; - const now_old = try now(io, t.clock); - const now_new = try now(io, clock); - const duration = now_old.durationTo(t); - return now_new.addDuration(duration); - } - - pub fn compare(lhs: Timestamp, op: std.math.CompareOperator, rhs: Timestamp) bool { - assert(lhs.clock == rhs.clock); - return std.math.compare(lhs.nanoseconds, op, rhs.nanoseconds); + pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp { + return .{ .nanoseconds = t.nanoseconds, .clock = clock }; } pub fn toSeconds(t: Timestamp) i64 { return @intCast(@divTrunc(t.nanoseconds, std.time.ns_per_s)); } + + pub fn formatNumber(t: Timestamp, w: *std.Io.Writer, n: std.fmt.Number) std.Io.Writer.Error!void { + return w.printInt(t.nanoseconds, n.mode.base() orelse 10, n.case, .{ + .precision = n.precision, + .width = n.width, + .alignment = n.alignment, + .fill = n.fill, + }); + } }; pub const Duration = struct { nanoseconds: i96, + pub const zero: Duration = .{ .nanoseconds = 0 }; pub const max: Duration = .{ .nanoseconds = std.math.maxInt(i96) }; pub fn fromNanoseconds(x: i96) Duration { @@ -842,38 +902,29 @@ pub const Duration = struct { pub fn toNanoseconds(d: Duration) i96 { return d.nanoseconds; } - - pub fn sleep(duration: Duration, io: Io) SleepError!void { - return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .awake } }); - } }; /// Declares under what conditions an operation should return `error.Timeout`. pub const Timeout = union(enum) { none, - duration: ClockAndDuration, - deadline: Timestamp, + duration: Clock.Duration, + deadline: Clock.Timestamp, pub const Error = error{ Timeout, UnsupportedClock }; - pub const ClockAndDuration = struct { - clock: Timestamp.Clock, - duration: Duration, - }; - - pub fn toDeadline(t: Timeout, io: Io) Timestamp.Error!?Timestamp { + pub fn toDeadline(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp { return switch (t) { .none => null, - .duration => |d| try .fromNow(io, d.clock, d.duration), + .duration => |d| try .fromNow(io, d), .deadline => |d| d, }; } - pub fn toDurationFromNow(t: Timeout, io: Io) Timestamp.Error!?ClockAndDuration { + pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration { return switch (t) { .none => null, .duration => |d| d, - .deadline => |d| .{ .clock = d.clock, .duration = try d.durationFromNow(io) }, + .deadline => |d| try d.durationFromNow(io), }; } diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 3c9772076b..8ee450bf34 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -85,7 +85,7 @@ pub fn updateFile( }; if (src_stat.size == dest_stat.size and - src_stat.mtime == dest_stat.mtime and + src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and actual_mode == dest_stat.mode) { return .fresh; diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 018825164e..c4e4e5c9c4 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -45,16 +45,12 @@ pub const Stat = struct { /// This is available on POSIX systems and is always 0 otherwise. mode: Mode, kind: Kind, - /// Last access time in nanoseconds, relative to UTC 1970-01-01. - /// TODO change this to Io.Timestamp except don't waste storage on clock - atime: i128, + atime: Io.Timestamp, /// Last modification time in nanoseconds, relative to UTC 1970-01-01. - /// TODO change this to Io.Timestamp except don't waste storage on clock - mtime: i128, + mtime: Io.Timestamp, /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01. - /// TODO change this to Io.Timestamp except don't waste storage on clock - ctime: i128, + ctime: Io.Timestamp, }; pub fn stdout() File { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 16e134251c..c5f1634f97 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1147,26 +1147,26 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi }; } -fn nowPosix(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!i96 { +fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const pool: *Pool = @ptrCast(@alignCast(userdata)); _ = pool; const clock_id: posix.clockid_t = clockToPosix(clock); var tp: posix.timespec = undefined; switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { - .SUCCESS => return @intCast(@as(i128, tp.sec) * std.time.ns_per_s + tp.nsec), + .SUCCESS => return timestampFromPosix(&tp), .INVAL => return error.UnsupportedClock, else => |err| return posix.unexpectedErrno(err), } } -fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!i96 { +fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const pool: *Pool = @ptrCast(@alignCast(userdata)); _ = pool; switch (clock) { .realtime => { // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds // and uses the NTFS/Windows epoch, which is 1601-01-01. - return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100; + return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 }; }, .monotonic, .uptime => { // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. @@ -1178,7 +1178,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err } } -fn nowWasi(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!i96 { +fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const pool: *Pool = @ptrCast(@alignCast(userdata)); _ = pool; var ns: std.os.wasi.timestamp_t = undefined; @@ -1196,13 +1196,10 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { }); const deadline_nanoseconds: i96 = switch (timeout) { .none => std.math.maxInt(i96), - .duration => |d| d.duration.nanoseconds, - .deadline => |deadline| deadline.nanoseconds, - }; - var timespec: posix.timespec = .{ - .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)), - .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)), + .duration => |duration| duration.raw.nanoseconds, + .deadline => |deadline| deadline.raw.nanoseconds, }; + var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); while (true) { try pool.checkCancel(); switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { @@ -1267,11 +1264,7 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { .sec = std.math.maxInt(sec_type), .nsec = std.math.maxInt(nsec_type), }; - const ns = d.duration.nanoseconds; - break :t .{ - .sec = @intCast(@divFloor(ns, std.time.ns_per_s)), - .nsec = @intCast(@mod(ns, std.time.ns_per_s)), - }; + break :t timestampToPosix(d.duration.nanoseconds); }; while (true) { try pool.checkCancel(); @@ -1879,8 +1872,8 @@ fn netReceive( const max_poll_ms = std.math.maxInt(u31); const timeout_ms: u31 = if (deadline) |d| t: { const duration = d.durationFromNow(pool.io()) catch |err| return .{ err, message_i }; - if (duration.nanoseconds <= 0) return .{ error.Timeout, message_i }; - break :t @intCast(@min(max_poll_ms, duration.toMilliseconds())); + if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i }; + break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); } else max_poll_ms; const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms); @@ -2160,7 +2153,7 @@ fn recoverableOsBugDetected() void { if (builtin.mode == .Debug) unreachable; } -fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t { +fn clockToPosix(clock: Io.Clock) posix.clockid_t { return switch (clock) { .real => posix.CLOCK.REALTIME, .awake => switch (builtin.os.tag) { @@ -2176,7 +2169,7 @@ fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t { }; } -fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t { +fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { return switch (clock) { .realtime => .REALTIME, .awake => .MONOTONIC, @@ -2204,9 +2197,9 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat { std.os.linux.S.IFSOCK => .unix_domain_socket, else => .unknown, }, - .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec, - .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec, - .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec, + .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) }, + .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) }, + .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) }, }; } @@ -2238,9 +2231,9 @@ fn statFromPosix(st: *const std.posix.Stat) Io.File.Stat { break :k .unknown; }, - .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec, - .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec, - .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec, + .atime = timestampFromPosix(&atime), + .mtime = timestampFromPosix(&mtime), + .ctime = timestampFromPosix(&ctime), }; } @@ -2263,3 +2256,14 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat { .ctime = st.ctim, }; } + +fn timestampFromPosix(timespec: *const std.posix.timespec) Io.Timestamp { + return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) }; +} + +fn timestampToPosix(nanoseconds: i96) std.posix.timespec { + return .{ + .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)), + .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)), + }; +} diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 72b5d39038..94c87ab0a4 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -79,7 +79,7 @@ pub const LookupError = error{ NameServerFailure, /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf". DetectingNetworkConfigurationFailed, -} || Io.Timestamp.Error || IpAddress.BindError || Io.Cancelable; +} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable; pub const LookupResult = struct { /// How many `LookupOptions.addresses_buffer` elements are populated. @@ -294,13 +294,14 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio // boot clock is chosen because time the computer is suspended should count // against time spent waiting for external messages to arrive. - var now_ts = try Io.Timestamp.now(io, .boot); + const clock: Io.Clock = .boot; + var now_ts = try clock.now(io); const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); const attempt_duration: Io.Duration = .{ .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, }; - send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boot)) { + send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(io)) { const max_messages = queries_buffer.len * ResolvConf.max_nameservers; { var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; @@ -319,7 +320,10 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio _ = io.vtable.netSend(io.userdata, socket.handle, message_buffer[0..message_i], .{}); } - const timeout: Io.Timeout = .{ .deadline = now_ts.addDuration(attempt_duration) }; + const timeout: Io.Timeout = .{ .deadline = .{ + .raw = now_ts.addDuration(attempt_duration), + .clock = clock, + } }; while (true) { var message_buffer: [max_messages]Io.net.IncomingMessage = undefined; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 191920dc83..b2130bc5da 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -637,23 +637,23 @@ pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError; pub fn updateTimes( self: File, /// access timestamp in nanoseconds - atime: i128, + atime: Io.Timestamp, /// last modification timestamp in nanoseconds - mtime: i128, + mtime: Io.Timestamp, ) UpdateTimesError!void { if (builtin.os.tag == .windows) { - const atime_ft = windows.nanoSecondsToFileTime(atime); - const mtime_ft = windows.nanoSecondsToFileTime(mtime); + const atime_ft = windows.nanoSecondsToFileTime(atime.nanoseconds); + const mtime_ft = windows.nanoSecondsToFileTime(mtime.nanoseconds); return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); } const times = [2]posix.timespec{ posix.timespec{ - .sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize), - .nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize), + .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), + .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), }, posix.timespec{ - .sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize), - .nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize), + .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), + .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize), }, }; try posix.futimens(self.handle, ×); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index dbd547611f..91167a4b0a 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -320,7 +320,7 @@ pub const Connection = struct { const tls: *Tls = @ptrCast(base); var random_buffer: [176]u8 = undefined; std.crypto.random.bytes(&random_buffer); - const now_ts = if (Io.Timestamp.now(io, .real)) |ts| ts.toSeconds() else |_| return error.TlsInitializationFailed; + const now_ts = if (Io.Clock.real.now(io)) |ts| ts.toSeconds() else |_| return error.TlsInitializationFailed; tls.* = .{ .connection = .{ .client = client, diff --git a/lib/std/tar/Writer.zig b/lib/std/tar/Writer.zig index a48e8cc407..129f1e7ede 100644 --- a/lib/std/tar/Writer.zig +++ b/lib/std/tar/Writer.zig @@ -18,7 +18,6 @@ pub const Options = struct { underlying_writer: *Io.Writer, prefix: []const u8 = "", -mtime_now: u64 = 0, const Error = error{ WriteFailed, @@ -44,10 +43,12 @@ pub fn writeFile( w: *Writer, sub_path: []const u8, file_reader: *Io.File.Reader, - stat_mtime: i128, + /// If you want to match the file format's expectations, it wants number of + /// seconds since POSIX epoch. Zero is also a great option here to make + /// generated tarballs more reproducible. + mtime: u64, ) WriteFileError!void { const size = try file_reader.getSize(); - const mtime: u64 = @intCast(@divFloor(stat_mtime, std.time.ns_per_s)); var header: Header = .{}; try w.setPath(&header, sub_path); @@ -238,7 +239,6 @@ pub const Header = extern struct { } // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time. - // mtime == 0 will use current time pub fn setMtime(w: *Header, mtime: u64) error{OctalOverflow}!void { try octal(&w.mtime, mtime); }