lean4-htt/src/runtime/uv/timer.cpp
Eric Wieser 6819c805b9
fix: propagate memory errors when allocating for libuv (#13546)
This PR prevents memory exhaustion turning into segfaults when using
Lean functions which call into libuv

`malloc` can return `NULL`, in which case this code would previously go
on to dereference a null pointer.
Instead, it now returns a suitable `IO.Error`.

Calling `lean_internal_panic_out_of_memory` would also be an option
here, since the adjacent `lean_promise_new` calls would fail in this
way.
2026-04-28 15:29:25 +00:00

327 lines
10 KiB
C++

/*
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Sofia Rodrigues, Henrik Böving
*/
#include "runtime/uv/timer.h"
namespace lean {
#ifndef LEAN_EMSCRIPTEN
using namespace std;
// The finalizer of the `Timer`.
void lean_uv_timer_finalizer(void* ptr) {
lean_uv_timer_object * timer = (lean_uv_timer_object*) ptr;
/// The timer can be null in two states, it has not started and it got cancelled.
if (timer->m_promise != NULL) {
lean_dec(timer->m_promise);
}
event_loop_lock(&global_ev);
uv_close((uv_handle_t*)timer->m_uv_timer, [](uv_handle_t* handle) {
free(handle);
});
event_loop_unlock(&global_ev);
free(timer);
}
void initialize_libuv_timer() {
g_uv_timer_external_class = lean_register_external_class(lean_uv_timer_finalizer, [](void* obj, lean_object* f) {
if (((lean_uv_timer_object*)obj)->m_promise != NULL) {
lean_inc(f);
lean_apply_1(f, ((lean_uv_timer_object*)obj)->m_promise);
}
});
}
static bool timer_promise_is_finished(lean_uv_timer_object * timer) {
return lean_io_get_task_state_core((lean_object *)lean_to_promise(timer->m_promise)->m_result) == 2;
}
void handle_timer_event(uv_timer_t* handle) {
lean_object * obj = (lean_object*)handle->data;
lean_uv_timer_object * timer = lean_to_uv_timer(obj);
// handle_timer_event may only be called while the timer is running. The promise can be NULL
// if the last promise was cancelled.
lean_assert(timer->m_state == TIMER_STATE_RUNNING);
if (timer->m_repeating) {
// For repeating timers, only resolves if the promise exists and is not finished
if (timer->m_promise != NULL && !timer_promise_is_finished(timer)) {
lean_object* res = lean_io_promise_resolve(lean_box(0), timer->m_promise);
lean_dec(res);
}
} else {
// For non-repeating timers, resolves if the promise exists
if (timer->m_promise != NULL) {
lean_assert(!timer_promise_is_finished(timer));
lean_object* res = lean_io_promise_resolve(lean_box(0), timer->m_promise);
lean_dec(res);
}
uv_timer_stop(timer->m_uv_timer);
timer->m_state = TIMER_STATE_FINISHED;
// The loop does not need to keep the timer alive anymore.
lean_dec(obj);
}
}
/* Std.Internal.UV.Timer.mk (timeout : UInt64) (repeating : Bool) : IO Timer */
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_mk(uint64_t timeout, uint8_t repeating) {
lean_uv_timer_object * timer = (lean_uv_timer_object*)malloc(sizeof(lean_uv_timer_object));
if (timer == nullptr) {
return lean_io_result_mk_error(decode_io_error(ENOMEM, nullptr));
}
timer->m_timeout = timeout;
timer->m_repeating = repeating;
timer->m_state = TIMER_STATE_INITIAL;
timer->m_promise = NULL;
uv_timer_t * uv_timer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
if (uv_timer == nullptr) {
free(timer);
return lean_io_result_mk_error(decode_io_error(ENOMEM, nullptr));
}
event_loop_lock(&global_ev);
int result = uv_timer_init(global_ev.loop, uv_timer);
event_loop_unlock(&global_ev);
if (result != 0) {
free(uv_timer);
free(timer);
return lean_io_result_mk_error(lean_decode_uv_error(result, NULL));
}
timer->m_uv_timer = uv_timer;
lean_object * obj = lean_uv_timer_new(timer);
lean_mark_mt(obj);
timer->m_uv_timer->data = obj;
return lean_io_result_mk_ok(obj);
}
/* Std.Internal.UV.Timer.next (timer : @& Timer) : IO (IO.Promise Unit) */
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_next(b_obj_arg obj) {
lean_uv_timer_object * timer = lean_to_uv_timer(obj);
auto create_promise = []() {
return lean_io_promise_new();
};
auto setup_timer = [create_promise, obj, timer]() {
lean_assert(timer->m_promise == NULL);
lean_object* promise = create_promise();
timer->m_promise = promise;
timer->m_state = TIMER_STATE_RUNNING;
// The event loop must keep the timer alive for the duration of the run time.
lean_inc(obj);
lean_inc(promise);
int result = uv_timer_start(
timer->m_uv_timer,
handle_timer_event,
timer->m_repeating ? 0 : timer->m_timeout,
timer->m_repeating ? timer->m_timeout : 0
);
if (result != 0) {
lean_dec(obj);
event_loop_unlock(&global_ev);
return lean_io_result_mk_error(lean_decode_uv_error(result, NULL));
}
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(promise);
};
event_loop_lock(&global_ev);
if (timer->m_repeating) {
switch (timer->m_state) {
case TIMER_STATE_INITIAL:
{
return setup_timer();
}
case TIMER_STATE_RUNNING:
{
if (timer->m_promise == NULL || timer_promise_is_finished(timer)) {
if (timer->m_promise != NULL) {
lean_dec(timer->m_promise);
}
timer->m_promise = create_promise();
}
lean_inc(timer->m_promise);
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(timer->m_promise);
}
case TIMER_STATE_FINISHED:
{
if (timer->m_promise != NULL) {
lean_inc(timer->m_promise);
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(timer->m_promise);
} else {
// Creates a resolved promise
lean_object* finished_promise = create_promise();
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(finished_promise);
}
}
}
} else {
if (timer->m_state == TIMER_STATE_INITIAL) {
return setup_timer();
} else if (timer->m_promise != NULL) {
lean_inc(timer->m_promise);
lean_object* promise = timer->m_promise;
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(promise);
} else {
event_loop_unlock(&global_ev);
// Creates a resolved promise
lean_object* finished_promise = create_promise();
return lean_io_result_mk_ok(finished_promise);
}
}
}
/* Std.Internal.UV.Timer.reset (timer : @& Timer) : IO Unit */
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_reset(b_obj_arg obj) {
lean_uv_timer_object * timer = lean_to_uv_timer(obj);
// Locking to access the state in order to avoid data-race
event_loop_lock(&global_ev);
if (timer->m_state == TIMER_STATE_RUNNING) {
uv_timer_stop(timer->m_uv_timer);
int result = uv_timer_start(
timer->m_uv_timer,
handle_timer_event,
timer->m_timeout,
timer->m_repeating ? timer->m_timeout : 0
);
event_loop_unlock(&global_ev);
if (result != 0) {
return lean_io_result_mk_error(lean_decode_uv_error(result, NULL));
} else {
return lean_io_result_mk_ok(lean_box(0));
}
} else {
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(lean_box(0));
}
}
/* Std.Internal.UV.Timer.stop (timer : @& Timer) : IO Unit */
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_stop(b_obj_arg obj) {
lean_uv_timer_object * timer = lean_to_uv_timer(obj);
// Locking to access the state in order to avoid data-race
event_loop_lock(&global_ev);
if (timer->m_promise != NULL) {
lean_dec(timer->m_promise);
timer->m_promise = NULL;
}
if (timer->m_state == TIMER_STATE_RUNNING) {
uv_timer_stop(timer->m_uv_timer);
event_loop_unlock(&global_ev);
timer->m_state = TIMER_STATE_FINISHED;
// The loop does not need to keep the timer alive anymore.
lean_dec(obj);
return lean_io_result_mk_ok(lean_box(0));
}
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(lean_box(0));
}
/* Std.Internal.UV.Timer.cancel (timer : @& Timer) : IO Unit */
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_cancel(b_obj_arg obj) {
lean_uv_timer_object * timer = lean_to_uv_timer(obj);
// It's locking here to avoid changing the state during other operations.
event_loop_lock(&global_ev);
if (timer->m_state == TIMER_STATE_RUNNING && timer->m_promise != NULL) {
if (timer->m_repeating) {
lean_dec(timer->m_promise);
timer->m_promise = NULL;
} else {
uv_timer_stop(timer->m_uv_timer);
lean_dec(timer->m_promise);
timer->m_promise = NULL;
timer->m_state = TIMER_STATE_INITIAL;
// The loop does not need to keep the timer alive anymore.
lean_dec(obj);
}
}
event_loop_unlock(&global_ev);
return lean_io_result_mk_ok(lean_box(0));
}
#else
void lean_uv_timer_finalizer(void* ptr);
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_mk(uint64_t timeout, uint8_t repeating) {
lean_always_assert(
false && ("Please build a version of Lean4 with libuv to invoke this.")
);
}
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_next(b_obj_arg timer) {
lean_always_assert(
false && ("Please build a version of Lean4 with libuv to invoke this.")
);
}
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_reset(b_obj_arg timer) {
lean_always_assert(
false && ("Please build a version of Lean4 with libuv to invoke this.")
);
}
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_stop(b_obj_arg timer) {
lean_always_assert(
false && ("Please build a version of Lean4 with libuv to invoke this.")
);
}
extern "C" LEAN_EXPORT lean_obj_res lean_uv_timer_cancel(b_obj_arg obj) {
lean_always_assert(
false && ("Please build a version of Lean4 with libuv to invoke this.")
);
}
#endif
}