From 99121d6ff2b02f3d16b791eb103bb9f9e8b96475 Mon Sep 17 00:00:00 2001 From: Tetralux Date: Sat, 26 Oct 2019 22:35:36 +0000 Subject: Implement core:thread and core:sync on Unix using pthreads Also do some cleanup and refactoring of the thread, sync and time APIs. - remove 'semaphore_release' because 'post' and 'wait' is easier to understand - change 'semaphore_wait' to '*_wait_for' to match Condition - pthreads can be given a stack, but doing so requires the user to set up the guard pages manually. BE WARNED. The alignment requirements of the stack are also platform-dependant; it may need to be page size aligned on some systems. Unclear which systems, however. See 'os.get_page_size', and 'mem.make_aligned'. HOWEVER: I was unable to get custom stacks with guard pages working reliably, so while you can do it, the API does not support it. - add 'os.get_page_size', 'mem.make_aligned', and 'mem.new_aligned'. - removed thread return values because windows and linux are not consistent; windows returns 'i32' and pthreads return 'void*'; besides which, if you really wanted to communicate how the thread exited, you probably wouldn't do it with the thread's exit code. - fixed 'thread.is_done' on Windows; it didn't report true immediately after calling 'thread.join'. - moved time related stuff out of 'core:os' to 'core:time'. - add 'mem.align_backward' - fixed default allocator alignment The heap on Windows, and calloc on Linux, both have no facility to request alignment. It's a bit of hack, but the heap_allocator now overallocates; `size + alignment` bytes, and aligns things to at least 2. It does both of these things to ensure that there is at least two bytes before the payload, which it uses to store how much padding it needed to insert in order to fulfil the alignment requested. - make conditions more sane by matching the Windows behaviour. The fact that they were signalled now lingers until a thread tries to wait, causing them to just pass by uninterrupted, without sleeping or locking the underlying mutex, as it would otherwise need to do. This means that a thread no longer has to be waiting in order to be signalled, which avoids timing bugs that causes deadlocks that are hard to debug and fix. See the comment on the `sync.Condition.flag` field. - add thread priority: `thread.create(worker_proc, .High)` --- core/time/time_darwin.odin | 56 ------------------------------- core/time/time_linux.odin | 44 ------------------------- core/time/time_unix.odin | 80 +++++++++++++++++++++++++++++++++++++++++++++ core/time/time_windows.odin | 2 -- 4 files changed, 80 insertions(+), 102 deletions(-) delete mode 100644 core/time/time_darwin.odin delete mode 100644 core/time/time_linux.odin create mode 100644 core/time/time_unix.odin (limited to 'core/time') diff --git a/core/time/time_darwin.odin b/core/time/time_darwin.odin deleted file mode 100644 index 91acd8ae7..000000000 --- a/core/time/time_darwin.odin +++ /dev/null @@ -1,56 +0,0 @@ -package time - -foreign import libc "system:c" - -TimeSpec :: struct { - tv_sec : i64, /* seconds */ - tv_nsec : i64, /* nanoseconds */ -}; - -CLOCK_SYSTEM :: 0; -CLOCK_CALENDAR :: 1; - -IS_SUPPORTED :: true; - -foreign libc { - @(link_name="clock_gettime") _clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) ---; - @(link_name="nanosleep") _nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> int ---; - @(link_name="sleep") _sleep :: proc(seconds: u64) -> int ---; -} - -clock_gettime :: proc(clock_id: u64) -> TimeSpec { - ts : TimeSpec; - _clock_gettime(clock_id, &ts); - return ts; -} - -now :: proc() -> Time { - - time_spec_now := clock_gettime(CLOCK_SYSTEM); - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec; - return Time{_nsec=ns}; -} - -seconds_since_boot :: proc() -> f64 { - - ts_boottime := clock_gettime(CLOCK_SYSTEM); - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9; -} - -sleep :: proc(d: Duration) { - - ds := duration_seconds(d); - seconds := u64(ds); - nanoseconds := i64((ds - f64(seconds)) * 1e9); - - if seconds > 0 do _sleep(seconds); - if nanoseconds > 0 do nanosleep(nanoseconds); -} - -nanosleep :: proc(nanoseconds: i64) -> int { - assert(nanoseconds <= 999999999); - requested, remaining : TimeSpec; - requested = TimeSpec{tv_nsec = nanoseconds}; - - return _nanosleep(&requested, &remaining); -} \ No newline at end of file diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin deleted file mode 100644 index d83d719fb..000000000 --- a/core/time/time_linux.odin +++ /dev/null @@ -1,44 +0,0 @@ -package time - -import "core:os"; - -// NOTE(Jeroen): The times returned are in UTC -IS_SUPPORTED :: true; - -now :: proc() -> Time { - - time_spec_now := os.clock_gettime(os.CLOCK_REALTIME); - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec; - return Time{_nsec=ns}; -} - -boot_time :: proc() -> Time { - - ts_now := os.clock_gettime(os.CLOCK_REALTIME); - ts_boottime := os.clock_gettime(os.CLOCK_BOOTTIME); - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec; - return Time{_nsec=ns}; -} - -seconds_since_boot :: proc() -> f64 { - - ts_boottime := os.clock_gettime(os.CLOCK_BOOTTIME); - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9; -} - -sleep :: proc(d: Duration) { - - ds := duration_seconds(d); - seconds := u64(ds); - nanoseconds := i64((ds - f64(seconds)) * 1e9); - - if seconds > 0 do os.sleep(seconds); - if nanoseconds > 0 do os.nanosleep(nanoseconds); -} - -nanosleep :: proc(d: Duration) { - // NOTE(Jeroen): os.nanosleep returns -1 on failure, 0 on success - // duration needs to be [0, 999999999] nanoseconds. - os.nanosleep(i64(d)); -} diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin new file mode 100644 index 000000000..28a6d6a9f --- /dev/null +++ b/core/time/time_unix.odin @@ -0,0 +1,80 @@ +//+build linux, darwin +package time + +IS_SUPPORTED :: true; // NOTE: Times on Darwin are UTC. + +foreign import libc "system:c" + +@(default_calling_convention="c") +foreign libc { + @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) -> i32 ---; + @(link_name="sleep") _unix_sleep :: proc(seconds: u32) -> i32 ---; + @(link_name="nanosleep") _unix_nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 ---; +} + +TimeSpec :: struct { + tv_sec : i64, /* seconds */ + tv_nsec : i64, /* nanoseconds */ +}; + +CLOCK_REALTIME :: 0; // NOTE(tetra): May jump in time, when user changes the system time. +CLOCK_MONOTONIC :: 1; // NOTE(tetra): May stand still while system is asleep. +CLOCK_PROCESS_CPUTIME_ID :: 2; +CLOCK_THREAD_CPUTIME_ID :: 3; +CLOCK_MONOTONIC_RAW :: 4; // NOTE(tetra): "RAW" means: Not adjusted by NTP. +CLOCK_REALTIME_COARSE :: 5; // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." +CLOCK_MONOTONIC_COARSE :: 6; +CLOCK_BOOTTIME :: 7; // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. +CLOCK_REALTIME_ALARM :: 8; +CLOCK_BOOTTIME_ALARM :: 9; + +// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. +// I do not know if Darwin programmers are used to the existance of these constants or not, so +// I'm leaving aliases to them for now. +CLOCK_SYSTEM :: CLOCK_REALTIME; +CLOCK_CALENDAR :: CLOCK_MONOTONIC; + + +clock_gettime :: proc(clock_id: u64) -> TimeSpec { + ts : TimeSpec; // NOTE(tetra): Do we need to initialize this? + _unix_clock_gettime(clock_id, &ts); + return ts; +} + +now :: proc() -> Time { + time_spec_now := clock_gettime(CLOCK_REALTIME); + ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec; + return Time{_nsec=ns}; +} + +boot_time :: proc() -> Time { + ts_now := clock_gettime(CLOCK_REALTIME); + ts_boottime := clock_gettime(CLOCK_BOOTTIME); + + ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec; + return Time{_nsec=ns}; +} + +seconds_since_boot :: proc() -> f64 { + ts_boottime := clock_gettime(CLOCK_BOOTTIME); + return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9; +} + + +sleep :: proc(d: Duration) { + ds := duration_seconds(d); + seconds := u32(ds); + nanoseconds := i64((ds - f64(seconds)) * 1e9); + + if seconds > 0 do _unix_sleep(seconds); + if nanoseconds > 0 do nanosleep(nanoseconds); +} + +nanosleep :: proc(nanoseconds: i64) -> int { + // NOTE(tetra): Should we remove this assert? We are measuring nanoseconds after all... + assert(nanoseconds <= 999999999); + + requested := TimeSpec{tv_nsec = nanoseconds}; + remaining: TimeSpec; // NOTE(tetra): Do we need to initialize this? + return int(_unix_nanosleep(&requested, &remaining)); +} \ No newline at end of file diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index f6bfdd678..c1d766aee 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -17,8 +17,6 @@ now :: proc() -> Time { return Time{_nsec=ns}; } - - sleep :: proc(d: Duration) { win32.sleep(u32(d/Millisecond)); } -- cgit v1.2.3