lib/sys/src/timer.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builtin = @import("builtin");
  3 
  4 const capabilities = @import("capabilities.zig");
  5 const process = @import("process/root.zig");
  6 const signal_mod = @import("signal.zig");
  7 const linux = std.os.linux;
  8 
  9 pub const required_capabilities = capabilities.host(&.{ .process, .signal, .time });
 10 
 11 const supported = builtin.os.tag == .linux;
 12 
 13 pub const TimerId = i32;
 14 pub const Signal = signal_mod.RawSignal;
 15 pub const ThreadId = process.ThreadId;
 16 pub const invalid_timer_id: TimerId = -1;
 17 pub const sigev_thread_id: c_int = 4;
 18 pub const sample_signal: Signal = .PROF;
 19 
 20 pub const SignalValue = extern union {
 21     int: c_int,
 22     ptr: ?*anyopaque,
 23 };
 24 
 25 const sigevent_pad_size = (64 - @sizeOf(SignalValue) - @sizeOf(c_int) - @sizeOf(c_int)) / @sizeOf(c_int);
 26 
 27 pub const SignalEventData = extern union {
 28     pad: [sigevent_pad_size]c_int,
 29     tid: linux.pid_t,
 30 };
 31 
 32 pub const SignalEvent = extern struct {
 33     value: SignalValue,
 34     signo: c_int,
 35     notify: c_int,
 36     data: SignalEventData,
 37 };
 38 
 39 pub const Timer = struct {
 40     id: TimerId = invalid_timer_id,
 41 
 42     pub fn createForCurrentThread(signal: Signal) !Timer {
 43         if (!supportsThreadCpuTimers()) return error.UnsupportedPlatform;
 44         return createForThread(signal, try process.currentThreadId());
 45     }
 46 
 47     pub fn createForThread(signal: Signal, thread_id: ThreadId) !Timer {
 48         var event = threadSignalEvent(signal, thread_id);
 49         var id: TimerId = invalid_timer_id;
 50         try timerCreate(.THREAD_CPUTIME_ID, &event, &id);
 51         return .{ .id = id };
 52     }
 53 
 54     pub fn close(self: *Timer) void {
 55         if (self.id == invalid_timer_id) return;
 56         deleteTimer(self.id);
 57         self.id = invalid_timer_id;
 58     }
 59 
 60     pub fn startInterval(self: Timer, time_ns: u64) !void {
 61         if (self.id == invalid_timer_id) return error.UninitializedTimer;
 62         var spec = intervalSpec(time_ns);
 63         try timerSetTime(self.id, &spec);
 64     }
 65 
 66     pub fn startOneShot(self: Timer, time_ns: u64) !void {
 67         if (self.id == invalid_timer_id) return error.UninitializedTimer;
 68         var spec = oneShotSpec(time_ns);
 69         try timerSetTime(self.id, &spec);
 70     }
 71 
 72     pub fn stop(self: Timer) !void {
 73         if (self.id == invalid_timer_id) return error.UninitializedTimer;
 74         var spec = zeroSpec();
 75         try timerSetTime(self.id, &spec);
 76     }
 77 };
 78 
 79 pub fn supportsThreadCpuTimers() bool {
 80     return supported;
 81 }
 82 
 83 pub fn threadSignalEvent(signal: Signal, thread_id: ThreadId) SignalEvent {
 84     return .{
 85         .value = .{ .int = 0 },
 86         .signo = @intCast(@backingInt(signal)),
 87         .notify = sigev_thread_id,
 88         .data = .{ .tid = thread_id },
 89     };
 90 }
 91 
 92 pub fn intervalSpec(time_ns: u64) linux.itimerspec {
 93     const value = timespecFromNs(time_ns);
 94     return .{ .it_interval = value, .it_value = value };
 95 }
 96 
 97 pub fn oneShotSpec(time_ns: u64) linux.itimerspec {
 98     return .{
 99         .it_interval = .{ .sec = 0, .nsec = 0 },
100         .it_value = timespecFromNs(time_ns),
101     };
102 }
103 
104 fn zeroSpec() linux.itimerspec {
105     return .{
106         .it_interval = .{ .sec = 0, .nsec = 0 },
107         .it_value = .{ .sec = 0, .nsec = 0 },
108     };
109 }
110 
111 fn timespecFromNs(time_ns: u64) linux.timespec {
112     const sec = time_ns / std.time.ns_per_s;
113     const nsec = time_ns % std.time.ns_per_s;
114     return .{
115         .sec = @intCast(@min(sec, @as(u64, @intCast(std.math.maxInt(isize))))),
116         .nsec = @intCast(nsec),
117     };
118 }
119 
120 fn timerCreate(clock_id: linux.clockid_t, event: *const SignalEvent, id: *TimerId) !void {
121     if (!supportsThreadCpuTimers()) return error.UnsupportedPlatform;
122     const rc = linux.syscall3(.timer_create, @backingInt(clock_id), @intFromPtr(event), @intFromPtr(id));
123     switch (linux.errno(rc)) {
124         .SUCCESS => return,
125         .AGAIN => return error.ProcessResources,
126         .INVAL => return error.InvalidTimerConfiguration,
127         .NOMEM => return error.SystemResources,
128         .PERM, .ACCES => return error.PermissionDenied,
129         else => return error.TimerCreateFailed,
130     }
131 }
132 
133 fn timerSetTime(id: TimerId, spec: *const linux.itimerspec) !void {
134     if (!supportsThreadCpuTimers()) return error.UnsupportedPlatform;
135     const rc = linux.syscall4(.timer_settime, @as(usize, @bitCast(@as(isize, id))), 0, @intFromPtr(spec), 0);
136     switch (linux.errno(rc)) {
137         .SUCCESS => return,
138         .INVAL => return error.InvalidTimer,
139         else => return error.TimerSetTimeFailed,
140     }
141 }
142 
143 fn deleteTimer(id: TimerId) void {
144     if (!supportsThreadCpuTimers()) return;
145     switch (linux.errno(linux.syscall1(.timer_delete, @as(usize, @bitCast(@as(isize, id)))))) {
146         .SUCCESS => {},
147         else => unreachable,
148     }
149 }
150 
151 test "sigevent layout matches linux timer syscall payload" {
152     try std.testing.expectEqual(@as(usize, 64), @sizeOf(SignalEvent));
153     try std.testing.expect(@alignOf(SignalEvent) >= @alignOf(SignalValue));
154 }
155 
156 test "thread signal event targets a specific thread" {
157     const event = threadSignalEvent(sample_signal, 1234);
158 
159     try std.testing.expectEqual(@as(c_int, @intCast(@backingInt(sample_signal))), event.signo);
160     try std.testing.expectEqual(sigev_thread_id, event.notify);
161     try std.testing.expectEqual(@as(linux.pid_t, 1234), event.data.tid);
162 }
163 
164 test "interval and oneshot specs convert nanoseconds" {
165     const interval = intervalSpec(1_250_000_007);
166     const oneshot = oneShotSpec(2_000_000_003);
167 
168     try std.testing.expectEqual(@as(isize, 1), interval.it_value.sec);
169     try std.testing.expectEqual(@as(isize, 250_000_007), interval.it_value.nsec);
170     try std.testing.expectEqual(interval.it_value.sec, interval.it_interval.sec);
171     try std.testing.expectEqual(interval.it_value.nsec, interval.it_interval.nsec);
172     try std.testing.expectEqual(@as(isize, 0), oneshot.it_interval.sec);
173     try std.testing.expectEqual(@as(isize, 0), oneshot.it_interval.nsec);
174     try std.testing.expectEqual(@as(isize, 2), oneshot.it_value.sec);
175     try std.testing.expectEqual(@as(isize, 3), oneshot.it_value.nsec);
176 }
177 
178 test "timer close is idempotent without owned kernel timer" {
179     var timer: Timer = .{};
180 
181     timer.close();
182     timer.close();
183 
184     try std.testing.expectEqual(invalid_timer_id, timer.id);
185 }
186 
187 test "timer rejects starting an uninitialized timer" {
188     const timer: Timer = .{};
189 
190     try std.testing.expectEqual(error.UninitializedTimer, timer.startInterval(1));
191     try std.testing.expectEqual(error.UninitializedTimer, timer.startOneShot(1));
192     try std.testing.expectEqual(error.UninitializedTimer, timer.stop());
193 }
194 
195 test "timer creates stops and closes a current-thread CPU timer" {
196     if (!supportsThreadCpuTimers()) return error.SkipZigTest;
197 
198     var timer = Timer.createForCurrentThread(sample_signal) catch |err| switch (err) {
199         error.ProcessResources, error.SystemResources => return error.SkipZigTest,
200         else => return err,
201     };
202     defer timer.close();
203 
204     try timer.startInterval(std.time.ns_per_s * 60);
205     try timer.stop();
206 }