Add ConditionVariable.wait_until and ConditionVariable.wait_for (#2302)

* Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for`
* Add "@structlike" for typedefs.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
This commit is contained in:
Christian Buttner
2025-07-19 13:12:14 +02:00
committed by GitHub
parent 448176b0b7
commit 2053f2767b
18 changed files with 104 additions and 36 deletions

View File

@@ -96,6 +96,8 @@ macro anycast(any v, $Type) @builtin
return ($Type*)v.ptr;
}
macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
macro @addr(#val) @builtin
{
$if $defined(&#val):
@@ -104,6 +106,7 @@ macro @addr(#val) @builtin
return &&#val;
$endif
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{
void*[256] buffer;

View File

@@ -20,7 +20,6 @@ macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
macro bool @is_const(#foo) @const @builtin
{
var $v;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module libc;

View File

@@ -4,7 +4,7 @@ import std::time;
<*
Return a "timespec" from a duration.
@require self >= 0
@require self >= time::NANO_DURATION_ZERO
*>
fn TimeSpec NanoDuration.to_timespec(self) @inline
{
@@ -16,7 +16,7 @@ fn TimeSpec NanoDuration.to_timespec(self) @inline
<*
Convert a duration to a timespec.
@require self >= 0
@require self >= time::DURATION_ZERO
*>
fn TimeSpec Duration.to_timespec(self) @inline
{
@@ -24,3 +24,11 @@ fn TimeSpec Duration.to_timespec(self) @inline
Time_t sec = (Time_t)(self / time::SEC);
return { .s = sec, .ns = ns };
}
<*
Convert a timestamp to a timespec.
*>
fn TimeSpec Time.to_timespec(self) @inline
{
return ((Duration)self).to_timespec();
}

View File

@@ -23,7 +23,7 @@ macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
}
}
const Duration POLL_FOREVER = -1;
const Duration POLL_FOREVER = (Duration)-1;
typedef PollSubscribes = ushort;
typedef PollEvents = ushort;

View File

@@ -5,11 +5,11 @@ import std::time, libc;
typedef TcpSocket = inline Socket;
typedef TcpServerSocket = inline Socket;
fn TcpSocket? connect(String host, uint port, Duration timeout = 0, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpSocket? connect(String host, uint port, Duration timeout = time::DURATION_ZERO, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
{
AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_STREAM)!;
defer os::freeaddrinfo(ai);
if (timeout > 0)
if (timeout > time::DURATION_ZERO)
{
return (TcpSocket)net::connect_with_timeout_from_addrinfo(ai, options, timeout)!;
}

View File

@@ -142,12 +142,26 @@ fn void? NativeConditionVariable.wait(&cond, NativeMutex* mtx)
*>
fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
{
TimeSpec now;
if (libc::timespec_get(&now, libc::TIME_UTC) != libc::TIME_UTC) return thread::WAIT_FAILED?;
now.ns += (CLong)((ms % 1000) * 1000_000);
now.s += (Time_t)(ms / 1000 + now.ns / 1000_000_000);
now.ns = now.ns % 1000_000_000;
switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &now))
Time time = time::now() + time::ms(ms);
return cond.wait_until(mtx, time) @inline;
}
<*
@require mtx.is_initialized()
*>
fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, Duration duration)
{
if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT?;
Time time = time::now() + duration;
return cond.wait_until(mtx, time) @inline;
}
<*
@require mtx.is_initialized()
*>
fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time)
{
switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &&time.to_timespec()))
{
case errno::ETIMEDOUT:
return thread::WAIT_TIMEOUT?;
@@ -216,7 +230,7 @@ fn void native_thread_yield()
fn void? native_sleep_nano(NanoDuration nano)
{
if (nano <= 0) return;
if (nano <= time::NANO_DURATION_ZERO) return;
if (libc::nanosleep(&&nano.to_timespec(), null)) return thread::INTERRUPTED?;
}

View File

@@ -198,7 +198,7 @@ fn void? NativeTimedMutex.lock_timeout(&mtx, ulong ms)
NanoDuration duration = time::ms(ms).to_nano();
Clock start = clock::now();
for (NanoDuration remaining = duration; remaining > 0; remaining = duration - start.to_now())
for (NanoDuration remaining = duration; remaining > time::NANO_DURATION_ZERO; remaining = duration - start.to_now())
{
ulong remaining_ms = remaining.to_ms();
if (remaining_ms > uint.max) remaining_ms = uint.max;
@@ -318,6 +318,26 @@ fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
return timedwait(cond, mtx, (uint)ms) @inline;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, Duration duration) @inline
{
if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT?;
long ms = duration.to_ms();
if (ms > uint.max) ms = uint.max;
return timedwait(cond, mtx, (uint)ms) @inline;
}
<*
@require mtx.initialized : "Mutex was not initialized"
*>
fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time) @inline
{
Duration duration = time - time::now();
return cond.wait_timeout_duration(mtx, duration);
}
fn void? NativeThread.create(&thread, ThreadFn func, void* args)
{
if (!(*thread = (NativeThread)win32::createThread(null, 0, func, args, 0, null))) return thread::INIT_FAILED?;

View File

@@ -63,9 +63,21 @@ macro void? ConditionVariable.wait(&cond, Mutex* mutex)
{
return NativeConditionVariable.wait((NativeConditionVariable*)cond, (NativeMutex*)mutex);
}
macro void? ConditionVariable.wait_timeout(&cond, Mutex* mutex, ulong ms)
<*
@require @assignable_to(#ms_or_duration, Duration) || @assignable_to(#ms_or_duration, ulong)
*>
macro void? ConditionVariable.wait_timeout(&cond, Mutex* mutex, #ms_or_duration) @safemacro
{
return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, ms);
$if @implicit_to(#ms_or_duration):
return NativeConditionVariable.wait_timeout_duration((NativeConditionVariable*)cond, (NativeMutex*)mutex, #ms_or_duration);
$else
return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, #ms_or_duration);
$endif
}
macro void? ConditionVariable.wait_until(&cond, Mutex* mutex, Time time)
{
return NativeConditionVariable.wait_until((NativeConditionVariable*)cond, (NativeMutex*)mutex, time);
}
<*

View File

@@ -12,10 +12,10 @@ fn Clock native_clock()
ulong mult = 0;
if (!freq.quadPart)
{
if (!win32::queryPerformanceFrequency(&freq)) return 0;
if (!win32::queryPerformanceFrequency(&freq)) return (Clock)0;
}
Win32_LARGE_INTEGER counter @noinit;
if (!win32::queryPerformanceCounter(&counter)) return 0;
if (!win32::queryPerformanceCounter(&counter)) return (Clock)0;
return (Clock)counter.quadPart.muldiv(1_000_000_000, freq.quadPart);
}

View File

@@ -1,13 +1,15 @@
module std::time;
import std::io, std::time::os;
typedef Time = long;
typedef Duration = long;
typedef Clock = ulong;
typedef NanoDuration (Printable) = long;
typedef Time @structlike = long;
typedef Duration @structlike = long;
typedef Clock @structlike = ulong;
typedef NanoDuration (Printable) @structlike = long;
const Time FAR_FUTURE = long.max;
const Time FAR_PAST = long.min;
const NanoDuration NANO_DURATION_ZERO = 0;
const Duration US = 1;
const Duration MS = 1_000;
const Duration SEC = 1_000_000;
@@ -18,6 +20,7 @@ const Duration WEEK = 7 * DAY;
const Duration MONTH = 30 * DAY;
const Duration YEAR = 36525 * DAY / 100;
const Duration FOREVER = long.max;
const Duration DURATION_ZERO = 0;
fn Duration us(long l) @inline => l * US;
fn Duration ms(long l) @inline => l * MS;
@@ -78,7 +81,7 @@ fn Time now()
$if $defined(native_timestamp):
return os::native_timestamp();
$else
return 0;
return (Time)0;
$endif
}
@@ -114,31 +117,31 @@ macro Duration Duration.mult(#td, long #val) @operator_s(*) @safemacro => (Durat
fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic
{
NanoDuration nd = *self;
if (nd == 0)
if (nd == NANO_DURATION_ZERO)
{
return formatter.printf("0s")!;
}
bool neg = nd < 0;
bool neg = nd < NANO_DURATION_ZERO;
if (neg) nd = -nd;
DString str = dstring::temp_with_capacity(64);
if (nd < 1_000_000_000)
if (nd < (NanoDuration)1_000_000_000)
{
// Less than 1s: print milliseconds, microseconds and nanoseconds.
NanoDuration ms = nd / 1_000_000;
if (ms > 0)
if (ms > NANO_DURATION_ZERO)
{
str.appendf("%dms", ms);
nd -= ms * 1_000_000;
}
NanoDuration us = nd / 1000;
if (us > 0)
if (us > NANO_DURATION_ZERO)
{
str.appendf("%dµs", us);
nd -= us * 1000;
}
if (nd > 0)
if (nd > NANO_DURATION_ZERO)
{
str.appendf("%dns", nd);
}
@@ -150,19 +153,19 @@ fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic
nd /= 1_000_000_000;
NanoDuration hour = nd / 3600;
if (hour > 0)
if (hour > NANO_DURATION_ZERO)
{
str.appendf("%dh", hour);
nd -= hour * 3600;
}
NanoDuration min = nd / 60;
if (min > 0)
if (min > NANO_DURATION_ZERO)
{
str.appendf("%dm", min);
nd -= min * 60;
}
NanoDuration sec = nd;
if (ms > 0)
if (ms > NANO_DURATION_ZERO)
{
// Ignore trailing zeroes.
while (ms / 10 * 10 == ms) ms /= 10;