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.
327 lines
10 KiB
C++
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
|
|
}
|