From 81d424b938c7d47506006796c3e6466520de15d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Mon, 20 Jul 2026 11:40:05 +0200 Subject: [PATCH 1/4] Use getTimeoutRelative() in timedWaitVMWaitLock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timedWaitVMWaitLock macro duplicates the deadline calculation already implemented by getTimeoutRelative(). Make the macro defer to the function instead, so relative timeout deadlines are computed in a single place. Signed-off-by: Guillermo Rodríguez --- src/thread.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/thread.h b/src/thread.h index 4c1d77b..1bd272e 100644 --- a/src/thread.h +++ b/src/thread.h @@ -213,15 +213,8 @@ typedef pthread_mutex_t VMLock; } #define timedWaitVMWaitLock(wait_lock, self, ms) { \ - struct timeval tv; \ struct timespec ts; \ - gettimeofday(&tv, 0); \ - ts.tv_sec = tv.tv_sec + ms/1000; \ - ts.tv_nsec = (tv.tv_usec + ((ms%1000)*1000))*1000; \ - if(ts.tv_nsec > 999999999L) { \ - ts.tv_sec++; \ - ts.tv_nsec -= 1000000000L; \ - } \ + getTimeoutRelative(&ts, ms, 0); \ classlibSetThreadState(self, TIMED_WAITING); \ pthread_cond_timedwait(&wait_lock.cv, &wait_lock.lock, &ts); \ classlibSetThreadState(self, RUNNING); \ From 2ee84cc94b540382701318410becbe139bfd75f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Mon, 6 Jul 2026 10:56:58 +0200 Subject: [PATCH 2/4] Move monotonic clock check to common code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenJDK class library code probes CLOCK_MONOTONIC availability at runtime for use by JVM_NanoTime. Move the probe to common code (time.c), run it early in the VM initialisation sequence, and expose it as haveMonotonicClock(). Signed-off-by: Guillermo Rodríguez --- src/classlib/openjdk/jvm.c | 13 +------------ src/init.c | 1 + src/jam.h | 2 ++ src/time.c | 19 +++++++++++++++++++ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/classlib/openjdk/jvm.c b/src/classlib/openjdk/jvm.c index cd31b4d..27c5029 100644 --- a/src/classlib/openjdk/jvm.c +++ b/src/classlib/openjdk/jvm.c @@ -57,12 +57,6 @@ #define JVM_INTERFACE_VERSION 4 -/* We use the monotonic clock if it is available. As the clock_id may be - present but not actually supported, we check it on startup */ -#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) -static int have_monotonic_clock; -#endif - static Class *cloneable_class, *constant_pool_class; static Class *exception_class, *runtime_excp_class; @@ -72,11 +66,6 @@ static int constant_pool_oop_offset; int initialiseJVMInterface() { Class *pae_class; -#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) - struct timespec ts; - have_monotonic_clock = (clock_gettime(CLOCK_MONOTONIC, &ts) != -1); -#endif - cloneable_class = findSystemClass0(SYMBOL(java_lang_Cloneable)); constant_pool_class = findSystemClass0(SYMBOL(sun_reflect_ConstantPool)); exception_class = findSystemClass0(SYMBOL(java_lang_Exception)); @@ -176,7 +165,7 @@ jlong JVM_NanoTime(JNIEnv *env, jclass ignored) { TRACE("JVM_NanoTime(env=%p, ignored=%p)", env, ignored); #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) - if(have_monotonic_clock) { + if(haveMonotonicClock()) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); diff --git a/src/init.c b/src/init.c index 38ad54b..fdc46c8 100644 --- a/src/init.c +++ b/src/init.c @@ -99,6 +99,7 @@ int initVM(InitArgs *args) { status = initialiseHooks(args) && initialiseProperties(args) && + initialiseTime() && initialiseAlloc(args) && initialiseThreadStage1(args) && initialiseUtf8() && diff --git a/src/jam.h b/src/jam.h index ce73df6..4e2b7ad 100644 --- a/src/jam.h +++ b/src/jam.h @@ -1300,6 +1300,8 @@ extern int initialiseSymbol(); /* time */ +extern int initialiseTime(); +extern int haveMonotonicClock(); extern void getTimeoutAbsolute(struct timespec *ts, long long millis, long long nanos); extern void getTimeoutRelative(struct timespec *ts, long long millis, diff --git a/src/time.c b/src/time.c index 8537e7a..ef018f0 100644 --- a/src/time.c +++ b/src/time.c @@ -22,6 +22,25 @@ #include #include +#include "jam.h" + +/* We use the monotonic clock if it is available. As the clock_id may be + present but not actually supported, we check it on startup */ +static int have_monotonic_clock = FALSE; + +int initialiseTime() { +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + struct timespec ts; + have_monotonic_clock = (clock_gettime(CLOCK_MONOTONIC, &ts) != -1); +#endif + + return TRUE; +} + +int haveMonotonicClock() { + return have_monotonic_clock; +} + void getTimeoutAbsolute(struct timespec *ts, long long millis, long long nanos) { From 1f299a8b64a5f58c8ef1d6f34766a5ce88eb9dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Mon, 6 Jul 2026 11:06:48 +0200 Subject: [PATCH 3/4] Prepare timed waits for monotonic clock support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce initReltimeCondVar() for initialising condition variables used with relative timeouts, and use it for all such condvars (the per-thread wait and park condvars, and VM-internal wait locks). A timed park (threadPark) can take either a relative timeout or an absolute wall-clock deadline, but currently both waits share the per-thread park condvar, and a condvar can only be bound to one clock. Split it in two: park_cv (relative and untimed parks) and park_cv_abs (absolute deadlines), choosing the right one when parking. Unparking signals both; signalling a condvar nobody waits on is harmless and cheap. All condvars are still bound to the default realtime clock, so no change in behaviour; this puts the infrastructure in place for switching relative timeouts to the monotonic clock. Signed-off-by: Guillermo Rodríguez --- src/jam.h | 2 ++ src/thread.c | 32 ++++++++++++++++++++++---------- src/thread.h | 3 ++- src/time.c | 6 ++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/jam.h b/src/jam.h index 4e2b7ad..8e8af0d 100644 --- a/src/jam.h +++ b/src/jam.h @@ -24,6 +24,7 @@ #include #include #include +#include /* Configure options */ #include "config.h" @@ -1306,6 +1307,7 @@ extern void getTimeoutAbsolute(struct timespec *ts, long long millis, long long nanos); extern void getTimeoutRelative(struct timespec *ts, long long millis, long long nanos); +extern int initReltimeCondVar(pthread_cond_t *cv); /* sig */ diff --git a/src/thread.c b/src/thread.c index 8ca1fbc..56ae35e 100644 --- a/src/thread.c +++ b/src/thread.c @@ -248,15 +248,19 @@ void threadPark(Thread *self, int absolute, long long time) { disableSuspend(self); if(time) { + pthread_cond_t *cv; struct timespec ts; - if(absolute) + if(absolute) { getTimeoutAbsolute(&ts, time, 0); - else + cv = &self->park_cv_abs; + } else { getTimeoutRelative(&ts, 0, time); + cv = &self->park_cv; + } classlibSetThreadState(self, TIMED_PARKED); - pthread_cond_timedwait(&self->park_cv, &self->park_lock, &ts); + pthread_cond_timedwait(cv, &self->park_lock, &ts); /* On Linux/i386 systems using LinuxThreads, pthread_cond_timedwait is implemented using sigjmp/longjmp. This resets the fpu @@ -265,6 +269,7 @@ void threadPark(Thread *self, int absolute, long long time) { FPU_HACK; } else { + /* Arbitrary choice of condvar when not timed */ classlibSetThreadState(self, PARKED); pthread_cond_wait(&self->park_cv, &self->park_lock); } @@ -294,11 +299,16 @@ void threadUnpark(Thread *thread) { /* If another thread has given a permit while we were waiting for the lock do nothing. Else increase the state by one (BLOCKED -> RUNNING, RUNNING -> PERMIT) - and if the thread was blocked signal it */ + and if the thread was blocked signal it. The thread + waits on park_cv or park_cv_abs depending on the type + of timeout; signalling an un-waited condvar is harmless + and cheap, so simply signal both */ if(thread->park_state != PARK_PERMIT && - thread->park_state++ == PARK_BLOCKED) + thread->park_state++ == PARK_BLOCKED) { pthread_cond_signal(&thread->park_cv); + pthread_cond_signal(&thread->park_cv_abs); + } pthread_mutex_unlock(&thread->park_lock); } @@ -483,12 +493,13 @@ void initThread(Thread *thread, char is_daemon, void *stack_base) { /* Initialise wait condvar (the condvar is per-thread, not per-monitor) */ - pthread_cond_init(&thread->wait_cv, NULL); + initReltimeCondVar(&thread->wait_cv); - /* Initialise per-thread lock/condvar used for parking + /* Initialise per-thread lock/condvars used for parking and set initial park state */ thread->park_state = PARK_RUNNING; - pthread_cond_init(&thread->park_cv, NULL); + initReltimeCondVar(&thread->park_cv); + pthread_cond_init(&thread->park_cv_abs, NULL); pthread_mutex_init(&thread->park_lock, NULL); /* Record the thread's stack base */ @@ -1307,10 +1318,11 @@ int initialiseThreadStage1(InitArgs *args) { initialiseJavaStack(&main_ee); setThreadSelf(&main_thread); - pthread_cond_init(&main_thread.wait_cv, NULL); + initReltimeCondVar(&main_thread.wait_cv); main_thread.park_state = PARK_RUNNING; - pthread_cond_init(&main_thread.park_cv, NULL); + initReltimeCondVar(&main_thread.park_cv); + pthread_cond_init(&main_thread.park_cv_abs, NULL); pthread_mutex_init(&main_thread.park_lock, NULL); return TRUE; diff --git a/src/thread.h b/src/thread.h index 1bd272e..5544802 100644 --- a/src/thread.h +++ b/src/thread.h @@ -103,6 +103,7 @@ struct thread { Thread *wait_next; pthread_cond_t wait_cv; pthread_cond_t park_cv; + pthread_cond_t park_cv_abs; pthread_mutex_t park_lock; long long blocked_count; long long waited_count; @@ -189,7 +190,7 @@ typedef pthread_mutex_t VMLock; #define initVMLock(lock) pthread_mutex_init(&lock, NULL) #define initVMWaitLock(wait_lock) { \ pthread_mutex_init(&wait_lock.lock, NULL); \ - pthread_cond_init(&wait_lock.cv, NULL); \ + initReltimeCondVar(&wait_lock.cv); \ } #define lockVMLock(lock, self) { \ diff --git a/src/time.c b/src/time.c index ef018f0..f1846aa 100644 --- a/src/time.c +++ b/src/time.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "jam.h" @@ -41,6 +42,11 @@ int haveMonotonicClock() { return have_monotonic_clock; } +/* Initialise a condition variable to be used for relative timed waits */ +int initReltimeCondVar(pthread_cond_t *cv) { + return pthread_cond_init(cv, NULL); +} + void getTimeoutAbsolute(struct timespec *ts, long long millis, long long nanos) { From a6d0f6a4acb6fe6bf0918d55f18cc5a1206b04af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Mon, 6 Jul 2026 11:46:46 +0200 Subject: [PATCH 4/4] Use monotonic clock for relative timed waits when available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All relative timeouts were computed against the wall clock, so stepping the system time backward stretched them and stepping it forward truncated them. Fix this by binding the condvars used for relative timed waits to CLOCK_MONOTONIC, if available, and make getTimeoutRelative() use the same clock when computing the deadline. If the monotonic clock cannot be used, behaviour is unchanged and a warning is printed at startup. Signed-off-by: Guillermo Rodríguez --- configure.ac | 1 + src/time.c | 69 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index 6129b0f..802a5a8 100644 --- a/configure.ac +++ b/configure.ac @@ -292,6 +292,7 @@ AC_CHECK_LIB(thr,pthread_self,,[ AC_CHECK_LIB(rt,clock_gettime,,) AC_CHECK_FUNCS([clock_gettime]) +AC_CHECK_FUNCS([pthread_condattr_setclock]) AC_CHECK_LIB(m,fmod,,AC_MSG_ERROR(libm is missing)) diff --git a/src/time.c b/src/time.c index f1846aa..a9b7f43 100644 --- a/src/time.c +++ b/src/time.c @@ -25,16 +25,44 @@ #include "jam.h" +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) && \ + defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) +#define MONOTONIC_CONDWAIT +#endif + /* We use the monotonic clock if it is available. As the clock_id may be present but not actually supported, we check it on startup */ static int have_monotonic_clock = FALSE; +/* For relative timeouts, additionally check that condvars can be bound + to the monotonic clock */ +static int have_monotonic_condwait = FALSE; + +#ifdef MONOTONIC_CONDWAIT +static pthread_condattr_t monotonic_condattr; +#endif + int initialiseTime() { #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) struct timespec ts; have_monotonic_clock = (clock_gettime(CLOCK_MONOTONIC, &ts) != -1); + +#ifdef MONOTONIC_CONDWAIT + if(have_monotonic_clock && + pthread_condattr_init(&monotonic_condattr) == 0) { + if(pthread_condattr_setclock(&monotonic_condattr, + CLOCK_MONOTONIC) == 0) + have_monotonic_condwait = TRUE; + else + pthread_condattr_destroy(&monotonic_condattr); + } +#endif #endif + if(!have_monotonic_condwait) + jam_fprintf(stderr, "Monotonic clock not available. Changes to " + "the current date/time may affect scheduling.\n"); + return TRUE; } @@ -44,6 +72,10 @@ int haveMonotonicClock() { /* Initialise a condition variable to be used for relative timed waits */ int initReltimeCondVar(pthread_cond_t *cv) { +#ifdef MONOTONIC_CONDWAIT + if(have_monotonic_condwait) + return pthread_cond_init(cv, &monotonic_condattr); +#endif return pthread_cond_init(cv, NULL); } @@ -71,18 +103,36 @@ void getTimeoutAbsolute(struct timespec *ts, long long millis, void getTimeoutRelative(struct timespec *ts, long long millis, long long nanos) { - struct timeval tv; long long seconds; - /* Get the current time */ - gettimeofday(&tv, NULL); +#ifdef MONOTONIC_CONDWAIT + if(have_monotonic_condwait) { + struct timespec now; - /* Calculate seconds (long long prevents overflow) */ - seconds = tv.tv_sec + millis / 1000 + nanos / 1000000000; + /* Get the current time */ + clock_gettime(CLOCK_MONOTONIC, &now); - /* Calculate nanoseconds */ - nanos %= 1000000000; - nanos += (tv.tv_usec + ((millis % 1000) * 1000)) * 1000; + /* Calculate seconds (long long prevents overflow) */ + seconds = now.tv_sec + millis / 1000 + nanos / 1000000000; + + /* Calculate nanoseconds */ + nanos %= 1000000000; + nanos += now.tv_nsec + (millis % 1000) * 1000000; + } else +#endif + { + struct timeval tv; + + /* Get the current time */ + gettimeofday(&tv, NULL); + + /* Calculate seconds (long long prevents overflow) */ + seconds = tv.tv_sec + millis / 1000 + nanos / 1000000000; + + /* Calculate nanoseconds */ + nanos %= 1000000000; + nanos += (tv.tv_usec + ((millis % 1000) * 1000)) * 1000; + } /* Adjust values so that nanos is less than 1 second. This also prevents overflowing the timespec, as the @@ -91,7 +141,8 @@ void getTimeoutRelative(struct timespec *ts, long long millis, nanos %= 1000000000; /* If seconds is too big to fit into the timespec use the - maximum value (year 2038) */ + maximum value (for 32-bit time_t and realtime clock, + year 2038) */ ts->tv_sec = seconds > LONG_MAX ? LONG_MAX : seconds; ts->tv_nsec = nanos; }