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 }