Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions builtin-functions/kphp-light/stdlib/confdata-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

function is_confdata_loaded(): bool;

/** @kphp-extern-func-info interruptible */
function confdata_get_value($key ::: string): mixed;

/** @kphp-extern-func-info interruptible */
function confdata_get_values_by_any_wildcard($wildcard ::: string): mixed[];

/** @kphp-extern-func-info interruptible */
function confdata_get_values_by_predefined_wildcard($wildcard ::: string): mixed[];
12 changes: 10 additions & 2 deletions runtime-common/core/allocator/runtime-allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#pragma once

#include <cstddef>
#include <functional>
#include <utility>

#include "common/mixin/not_copyable.h"
#include "runtime-common/core/memory-resource/unsynchronized_pool_resource.h"
Expand All @@ -28,12 +30,18 @@ struct RuntimeAllocator final : vk::not_copyable {
void* realloc_global_memory(void* mem, size_t new_size, size_t old_size) noexcept;
void free_global_memory(void* mem, size_t size) noexcept;

private:
void request_extra_memory(size_t requested_size) noexcept;
[[nodiscard]] std::reference_wrapper<memory_resource::unsynchronized_pool_resource>
replace_script_memory_resource(memory_resource::unsynchronized_pool_resource& replacement) noexcept {
return std::exchange(m_script_memory_resource, std::ref(replacement));
}
memory_resource::unsynchronized_pool_resource& current_script_memory_resource() const noexcept {
return m_script_memory_resource.get();
}

public:
memory_resource::unsynchronized_pool_resource memory_resource;

private:
std::reference_wrapper<memory_resource::unsynchronized_pool_resource> m_script_memory_resource{memory_resource};
size_t m_min_extra_mem_size{0};
};
4 changes: 4 additions & 0 deletions runtime-common/core/memory-resource/resource_allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include <forward_list>
#include <functional>
#include <list>
#include <map>
Expand Down Expand Up @@ -88,6 +89,9 @@ using vector = std::vector<T, resource_allocator<T, Resource>>;
template<class T, class Resource>
using list = std::list<T, resource_allocator<T, Resource>>;

template<class T, class Resource>
using forward_list = std::forward_list<T, resource_allocator<T, Resource>>;

template<class Resource>
using string = std::basic_string<char, std::char_traits<char>, resource_allocator<char, Resource>>;
} // namespace stl
Expand Down
4 changes: 4 additions & 0 deletions runtime-common/core/std/containers.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <deque>
#include <forward_list>
#include <functional>
#include <list>
#include <map>
Expand Down Expand Up @@ -41,6 +42,9 @@ using queue = std::queue<T, std::deque<T, Allocator<T>>>;
template<class T, template<class> class Allocator>
using list = std::list<T, Allocator<T>>;

template<class T, template<class> class Allocator>
using forward_list = std::forward_list<T, Allocator<T>>;

template<class T, template<class> class Allocator>
using vector = std::vector<T, Allocator<T>>;

Expand Down
23 changes: 23 additions & 0 deletions runtime-light/allocator/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@

#include <concepts>
#include <cstddef>
#include <functional>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>

#include "common/containers/final_action.h"
#include "runtime-common/core/allocator/script-allocator-managed.h"
#include "runtime-light/allocator/allocator-state.h"

Expand All @@ -17,6 +23,23 @@ auto make_unique_on_script_memory(Args&&... args) noexcept {
}

namespace kphp::memory {

// Objects allocated by the callback may outlive it, but every later operation
// that can allocate or deallocate their memory must install the same resource.
// Keeping the operation synchronous and non-throwing prevents the replacement
// itself from surviving a suspension or stack unwind.
template<std::invocable callback_type>
requires std::same_as<std::invoke_result_t<callback_type>, void> && std::is_nothrow_invocable_v<callback_type>
void with_script_memory_resource(memory_resource::unsynchronized_pool_resource& resource, callback_type&& callback) noexcept {
auto& allocator{RuntimeAllocator::get()};
const auto previous_resource{allocator.replace_script_memory_resource(resource)};
const auto restore_resource{vk::finally([&allocator, &resource, previous_resource] noexcept {
kphp::log::assertion(std::addressof(allocator.current_script_memory_resource()) == std::addressof(resource));
std::ignore = allocator.replace_script_memory_resource(previous_resource.get());
})};
std::invoke(std::forward<callback_type>(callback));
}

struct libc_alloc_guard final {
libc_alloc_guard() noexcept {
AllocatorState::get_mutable().enable_libc_alloc();
Expand Down
72 changes: 46 additions & 26 deletions runtime-light/allocator/runtime-light-allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,41 @@
#include <bit>
#include <cstddef>
#include <cstring>
#include <memory>

#include "runtime-light/allocator/allocator-state.h"
#include "runtime-light/k2-platform/k2-api.h"
#include "runtime-light/stdlib/diagnostics/logs.h"

namespace {

bool try_extend_default_memory_resource(RuntimeAllocator& allocator, memory_resource::unsynchronized_pool_resource& current_resource, size_t min_extra_mem_size,
size_t requested_size) noexcept {
// Only the allocator-owned resource can be extended with K2 memory. A
// temporarily installed resource is a fixed allocation domain and must
// not silently fall back to request-local memory when it is exhausted.
if (std::addressof(current_resource) != std::addressof(allocator.memory_resource)) {
return false;
}

// Extra mem size have to be greater than max chunk block
const auto min_size{std::max(min_extra_mem_size, memory_resource::unsynchronized_pool_resource::MAX_CHUNK_BLOCK_SIZE)};

size_t extra_mem_size{std::max(min_size, requested_size)};
// Take into account internal layout of `memory_resource::extra_memory_pool`
extra_mem_size += sizeof(memory_resource::extra_memory_pool);
// The smallest power of two that is not smaller than `extra_mem_size`
extra_mem_size = std::bit_ceil(extra_mem_size);

// kphp::log::debug("requested extra memory pool with size {} bytes, will be allocated {} bytes", requested_size, extra_mem_size);

auto* extra_mem{allocator.alloc_global_memory(extra_mem_size)};
allocator.memory_resource.add_extra_memory(new (extra_mem) memory_resource::extra_memory_pool{extra_mem_size});
return true;
}

} // namespace

RuntimeAllocator& RuntimeAllocator::get() noexcept {
return AllocatorState::get_mutable().allocator;
}
Expand Down Expand Up @@ -43,40 +73,46 @@ void RuntimeAllocator::free() {

void* RuntimeAllocator::alloc_script_memory(size_t size) noexcept {
kphp::log::assertion(size != 0);
void* mem{memory_resource.allocate(size)};
auto& current_resource{m_script_memory_resource.get()};
void* mem{current_resource.allocate(size)};
if (mem == nullptr && try_extend_default_memory_resource(*this, current_resource, m_min_extra_mem_size, size)) [[unlikely]] {
mem = current_resource.allocate(size);
}
if (mem == nullptr) [[unlikely]] {
request_extra_memory(size);
mem = memory_resource.allocate(size);
kphp::log::assertion(mem != nullptr);
}
return mem;
}

void* RuntimeAllocator::alloc0_script_memory(size_t size) noexcept {
kphp::log::assertion(size != 0);
void* mem{memory_resource.allocate0(size)};
auto& current_resource{m_script_memory_resource.get()};
void* mem{current_resource.allocate0(size)};
if (mem == nullptr && try_extend_default_memory_resource(*this, current_resource, m_min_extra_mem_size, size)) [[unlikely]] {
mem = current_resource.allocate0(size);
}
if (mem == nullptr) [[unlikely]] {
request_extra_memory(size);
mem = memory_resource.allocate0(size);
kphp::log::assertion(mem != nullptr);
}
return mem;
}

void* RuntimeAllocator::realloc_script_memory(void* old_mem, size_t new_size, size_t old_size) noexcept {
kphp::log::assertion(new_size > old_size);
void* new_mem{memory_resource.reallocate(old_mem, new_size, old_size)};
auto& current_resource{m_script_memory_resource.get()};
void* new_mem{current_resource.reallocate(old_mem, new_size, old_size)};
if (new_mem == nullptr && try_extend_default_memory_resource(*this, current_resource, m_min_extra_mem_size, new_size * 2)) [[unlikely]] {
new_mem = current_resource.reallocate(old_mem, new_size, old_size);
}
if (new_mem == nullptr) [[unlikely]] {
request_extra_memory(new_size * 2);
new_mem = memory_resource.reallocate(old_mem, new_size, old_size);
kphp::log::assertion(new_mem != nullptr);
}
return new_mem;
}

void RuntimeAllocator::free_script_memory(void* mem, size_t size) noexcept {
kphp::log::assertion(size != 0);
memory_resource.deallocate(mem, size);
m_script_memory_resource.get().deallocate(mem, size);
}

void* RuntimeAllocator::alloc_global_memory(size_t size) noexcept {
Expand All @@ -101,19 +137,3 @@ void* RuntimeAllocator::realloc_global_memory(void* old_mem, size_t new_size, si
void RuntimeAllocator::free_global_memory(void* mem, size_t /*unused*/) noexcept {
k2::free(mem);
}

void RuntimeAllocator::request_extra_memory(size_t requested_size) noexcept {
// Extra mem size have to be greater than max chunk block
const auto min_size{std::max(m_min_extra_mem_size, memory_resource::unsynchronized_pool_resource::MAX_CHUNK_BLOCK_SIZE)};

size_t extra_mem_size{std::max(min_size, requested_size)};
// Take into account internal layout of `memory_resource::extra_memory_pool`
extra_mem_size += sizeof(memory_resource::extra_memory_pool);
// The smallest power of two that is not smaller than `extra_mem_size`
extra_mem_size = std::bit_ceil(extra_mem_size);

// kphp::log::debug("requested extra memory pool with size {} bytes, will be allocated {} bytes", requested_size, extra_mem_size);

auto* extra_mem{alloc_global_memory(extra_mem_size)};
memory_resource.add_extra_memory(new (extra_mem) memory_resource::extra_memory_pool{extra_mem_size});
}
2 changes: 1 addition & 1 deletion runtime-light/components/confdata/confdata-component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ VISIBILITY_DEFAULT k2::PollStatus k2_poll() {

VISIBILITY_DEFAULT const ImageInfo* k2_describe() {
static constexpr std::array extra_info{ImageInfo::KeyValuePair{.key = "compiler_version", .value = K2_CONFDATA_COMPILER_VERSION}};
static constexpr ImageInfo image_info{.image_name = kphp::confdata::COMPONENT_NAME.data(),
static constexpr ImageInfo image_info{.image_name = kphp::confdata::IMAGE_NAME.data(),
.is_oneshot = 0,
.build_timestamp = K2_CONFDATA_BUILD_TIMESTAMP,
.header_h_version = K2_PLATFORM_HEADER_H_VERSION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ struct pagination {
bool m_has_synced{};
};

enum class subscribe_error : uint8_t { transport, old_offset, malformed_response, not_synced };
enum class subscribe_error : uint8_t { transport, old_offset, malformed_response, not_synced, storage_busy };

namespace details {

// Performs a single confdata.subscribe round-trip.
// On success, invokes `event_handler(events)` once with the batch of received events and updates `to` pagination.
// If the handler returns false, the batch is rejected with `storage_busy` and `to` is left unchanged so it can be requested again.
// The batch is a view into the response buffer and is only valid for the duration of the call; empty batches are not delivered.
// An empty event value means that the key has been deleted.
template<std::invocable<std::span<const tl::confdata::KeyValuePair>> event_handler_type>
template<std::predicate<std::span<const tl::confdata::KeyValuePair>> event_handler_type>
auto subscribe(std::string_view confdata_proxy_actor, kphp::confdata::pagination& to,
const event_handler_type& event_handler) noexcept -> kphp::coro::task<std::expected<void, kphp::confdata::subscribe_error>> {
// subscribe is a longpoll method, so the timeout must cover the time confdata-proxy may hold the request open
Expand Down Expand Up @@ -91,7 +92,9 @@ auto subscribe(std::string_view confdata_proxy_actor, kphp::confdata::pagination
overloaded{
[&event_handler, &to](const tl::confdata::subscribeResponseOk& response) noexcept -> std::expected<void, kphp::confdata::subscribe_error> {
if (const auto& events{response.events}; events.size() != 0) {
std::invoke(event_handler, std::span<const tl::confdata::KeyValuePair>{events.value});
if (!std::invoke(event_handler, std::span<const tl::confdata::KeyValuePair>{events.value})) {
return std::unexpected{kphp::confdata::subscribe_error::storage_busy};
}
}

to.m_page = response.new_page.value;
Expand All @@ -113,7 +116,7 @@ auto subscribe(std::string_view confdata_proxy_actor, kphp::confdata::pagination
//
// `event_handler` is invoked once per round-trip with a batch of events; the batch is only valid
// for the duration of the call and must be copied if it needs to be retained.
template<std::invocable<std::span<const tl::confdata::KeyValuePair>> event_handler_type>
template<std::predicate<std::span<const tl::confdata::KeyValuePair>> event_handler_type>
auto sync(std::string_view confdata_proxy_actor,
event_handler_type event_handler) noexcept -> kphp::coro::task<std::expected<kphp::confdata::pagination, kphp::confdata::subscribe_error>> {
kphp::confdata::pagination p{};
Expand All @@ -132,7 +135,7 @@ auto sync(std::string_view confdata_proxy_actor,
//
// `event_handler` is invoked once per round-trip with a batch of events; the batch is only valid
// for the duration of the call and must be copied if it needs to be retained.
template<std::invocable<std::span<const tl::confdata::KeyValuePair>> event_handler_type>
template<std::predicate<std::span<const tl::confdata::KeyValuePair>> event_handler_type>
auto update(std::string_view confdata_proxy_actor, kphp::confdata::pagination& from,
event_handler_type event_handler) noexcept -> kphp::coro::task<std::expected<void, kphp::confdata::subscribe_error>> {
// limits the update rate to at most one batch per interval
Expand Down
19 changes: 12 additions & 7 deletions runtime-light/components/confdata/confdata.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ set(K2_CONFDATA_COMPONENT_SRC
${RUNTIME_LIGHT_DIR}/components/confdata/confdata-component.cpp
${RUNTIME_LIGHT_DIR}/components/confdata/bindings/bindings.cpp
${RUNTIME_LIGHT_DIR}/components/confdata/state/component-state.cpp
${RUNTIME_LIGHT_DIR}/components/confdata/state/instance-state.cpp)
${RUNTIME_LIGHT_DIR}/components/confdata/state/instance-state.cpp
${RUNTIME_LIGHT_DIR}/stdlib/confdata/confdata-storage.cpp
${RUNTIME_LIGHT_DIR}/stdlib/confdata/confdata-keys.cpp
${RUNTIME_LIGHT_DIR}/stdlib/confdata/predefined-wildcards.cpp)

set(K2_CONFDATA_TL_SRC
${RUNTIME_LIGHT_DIR}/tl/tl-types.cpp
Expand All @@ -18,18 +21,20 @@ set(K2_CONFDATA_DIAGNOSTICS_SRC
${RUNTIME_LIGHT_DIR}/stdlib/diagnostics/backtrace.cpp
${RUNTIME_LIGHT_DIR}/stdlib/diagnostics/php-assert.cpp)

set(K2_CONFDATA_MEMORY_RESOURCE_SRC
${RUNTIME_COMMON_DIR}/core/memory-resource/unsynchronized_pool_resource.cpp
${RUNTIME_COMMON_DIR}/core/memory-resource/monotonic_buffer_resource.cpp
${RUNTIME_COMMON_DIR}/core/memory-resource/details/memory_chunk_tree.cpp
${RUNTIME_COMMON_DIR}/core/memory-resource/details/memory_ordered_chunk_list.cpp)
set(K2_CONFDATA_SERIALIZATION_SRC
${RUNTIME_COMMON_DIR}/stdlib/serialization/json-functions.cpp
${RUNTIME_COMMON_DIR}/stdlib/serialization/serialize-functions.cpp)

set(K2_CONFDATA_RUNTIME_CORE_SRC ${CORE_SRC})
list(TRANSFORM K2_CONFDATA_RUNTIME_CORE_SRC PREPEND "${RUNTIME_COMMON_DIR}/")

set(K2_CONFDATA_SRC
${K2_CONFDATA_COMPONENT_SRC}
${K2_CONFDATA_TL_SRC}
${K2_CONFDATA_ALLOCATOR_SRC}
${K2_CONFDATA_DIAGNOSTICS_SRC}
${K2_CONFDATA_MEMORY_RESOURCE_SRC}
${K2_CONFDATA_SERIALIZATION_SRC}
${K2_CONFDATA_RUNTIME_CORE_SRC}
# link the alloc-wrapper objects directly (not as an archive) so that
# __wrap_* definitions are always present regardless of link order
$<TARGET_OBJECTS:libc-alloc-wrapper-pic>)
Expand Down
Loading
Loading