Skip to content
Open
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
6 changes: 3 additions & 3 deletions runtime-light/components/confdata/state/instance-state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ InstanceState::confdata_piece::~confdata_piece() {
if (m_storage.is_initialized()) {
m_storage.close();
}
if (const auto released{k2::release_shared_memory(m_memory)}; !released) [[unlikely]] {
if (const auto released{k2::shared_memory_release(m_memory)}; !released) [[unlikely]] {
kphp::log::warning("failed to free confdata shared memory: error -> {}", released.error());
}
}
Expand All @@ -167,7 +167,7 @@ auto InstanceState::confdata_piece::create(confdata_piece_list& owner, size_t me
.m_code = static_cast<int32_t>(std::to_underlying(shared_memory_size.error()))}};
}

const auto shared_memory{k2::alloc_shared_memory(*shared_memory_size, kphp::confdata::storage::memory_alignment())};
const auto shared_memory{k2::shared_memory_alloc(*shared_memory_size, kphp::confdata::storage::memory_alignment())};
if (!shared_memory) [[unlikely]] {
return std::unexpected{confdata_sync_error{.m_stage = confdata_sync_error::stage::shared_memory_allocation, .m_code = shared_memory.error()}};
}
Expand Down Expand Up @@ -429,7 +429,7 @@ auto InstanceState::perform_sync(std::string_view confdata_proxy_actor) noexcept
piece.storage().memory_usage().m_used);
// A lease must keep resolving to its pinned piece even if another sync completes before the reader maps it.
// Reject name collisions rather than replacing an allocation that an outstanding lease may still name.
if (const auto published{k2::publish_shared_memory(piece.shared_memory_name(), piece.storage().memory().data(), 0, true, false)}; !published) [[unlikely]] {
if (const auto published{k2::shared_memory_publish(piece.shared_memory_name(), piece.storage().memory().data(), 0, true, false)}; !published) [[unlikely]] {
co_return std::unexpected{
confdata_sync_error{.m_stage = confdata_sync_error::stage::shared_memory_publication, .m_code = static_cast<int32_t>(published.error())}};
}
Expand Down
34 changes: 22 additions & 12 deletions runtime-light/k2-platform/k2-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ using PollStatus = PollStatus;

using ImageInfo = ImageInfo;

using SharedMemoryInfo = SharedMemoryInfo;

using RpcKind = RpcKind;

using ControlFlags = ControlFlags;
Expand Down Expand Up @@ -129,54 +131,62 @@ inline void free_checked(void* ptr, size_t size, size_t align) noexcept {
k2_free_checked(ptr, size, align);
}

inline std::expected<void*, int32_t> alloc_shared_memory(size_t size, size_t align = k2::details::DEFAULT_MEMORY_ALIGN) noexcept {
inline std::expected<void*, int32_t> shared_memory_alloc(size_t size, size_t align = k2::details::DEFAULT_MEMORY_ALIGN) noexcept {
void* pointer{nullptr};
if (auto error_code{k2_alloc_shared_memory(size, align, std::addressof(pointer))}; error_code != k2::errno_ok) [[unlikely]] {
if (auto error_code{k2_shared_memory_alloc(size, align, std::addressof(pointer))}; error_code != k2::errno_ok) [[unlikely]] {
return std::unexpected{error_code};
}
return {pointer};
}

inline std::expected<void, int32_t> publish_shared_memory(std::string_view name, const void* memory, uint64_t ttl_ms, bool as_mut,
inline std::expected<void, int32_t> shared_memory_publish(std::string_view name, const void* memory, uint64_t ttl_ms, bool as_mut,
bool ignore_if_exist) noexcept {
if (auto error_code{k2_publish_shared_memory(name.data(), name.length(), memory, ttl_ms, as_mut, ignore_if_exist)}; error_code != k2::errno_ok) [[unlikely]] {
if (auto error_code{k2_shared_memory_publish(name.data(), name.length(), memory, ttl_ms, as_mut, ignore_if_exist)}; error_code != k2::errno_ok) [[unlikely]] {
return std::unexpected{error_code};
}
return {};
}

inline std::expected<std::span<const std::byte>, int32_t> get_shared_memory(std::string_view name) noexcept {
inline std::expected<std::span<const std::byte>, int32_t> shared_memory_get(std::string_view name) noexcept {
const void* pointer{nullptr};
size_t size{};
if (auto error_code{k2_get_shared_memory(name.data(), name.length(), std::addressof(pointer), std::addressof(size))}; error_code != k2::errno_ok)
if (auto error_code{k2_shared_memory_get(name.data(), name.length(), std::addressof(pointer), std::addressof(size))}; error_code != k2::errno_ok)
[[unlikely]] {
return std::unexpected{error_code};
}
return {std::span{static_cast<const std::byte*>(pointer), size}};
}

inline std::expected<void, int32_t> republish_shared_memory(std::string_view name, uint64_t ttl) noexcept {
if (auto error_code{k2_republish_shared_memory(name.data(), name.length(), ttl)}; error_code != k2::errno_ok) [[unlikely]] {
inline std::expected<void, int32_t> shared_memory_republish(std::string_view name, uint64_t ttl) noexcept {
if (auto error_code{k2_shared_memory_republish(name.data(), name.length(), ttl)}; error_code != k2::errno_ok) [[unlikely]] {
return std::unexpected{error_code};
}
return {};
}

inline std::expected<void, int32_t> seek_ttl_to_shared_memory(std::string_view name, uint8_t percentile, uint64_t remaining_lifetime_limit) noexcept {
if (auto error_code{k2_seek_ttl_to_shared_memory(name.data(), name.length(), percentile, remaining_lifetime_limit)}; error_code != k2::errno_ok)
inline std::expected<void, int32_t> shared_memory_seek_ttl_to(std::string_view name, uint8_t percentile, uint64_t remaining_lifetime_limit) noexcept {
if (auto error_code{k2_shared_memory_seek_ttl_to(name.data(), name.length(), percentile, remaining_lifetime_limit)}; error_code != k2::errno_ok)
[[unlikely]] {
return std::unexpected{error_code};
}
return {};
}

inline std::expected<void, int32_t> release_shared_memory(const void* ptr) noexcept {
if (auto error_code{k2_release_shared_memory(ptr)}; error_code != k2::errno_ok) [[unlikely]] {
inline std::expected<void, int32_t> shared_memory_release(const void* ptr) noexcept {
if (auto error_code{k2_shared_memory_release(ptr)}; error_code != k2::errno_ok) [[unlikely]] {
return std::unexpected{error_code};
}
return {};
}

inline std::expected<const k2::SharedMemoryInfo, int32_t> shared_memory_info(std::string_view name) noexcept {
k2::SharedMemoryInfo info{};
if (auto error_code{k2_shared_memory_info(name.data(), name.length(), std::addressof(info))}; error_code != k2::errno_ok) [[unlikely]] {
return std::unexpected{error_code};
}
return info;
}

[[noreturn]] inline void exit(int32_t exit_code) noexcept {
k2_exit(exit_code);
}
Expand Down
82 changes: 58 additions & 24 deletions runtime-light/k2-platform/k2-header.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#include <time.h>
#endif

#define K2_PLATFORM_HEADER_H_VERSION 16
#define K2_PLATFORM_HEADER_H_VERSION 17

// Always check that enum value is a valid value!

Expand Down Expand Up @@ -124,6 +124,18 @@ struct ImageInfo {
const struct KeyValuePair* extra_info;
};

// `stored_at` (the moment of the last publish/republish) is always treated as time origin 0,
// so both `now` and `expires_at` are nanosecond offsets from it, not wall-clock timestamps.
struct SharedMemoryInfo {
// Nanoseconds elapsed since `stored_at`.
uint64_t now;
// Nanoseconds from `stored_at` to the expiration moment. `0` means the memory is immortal
// (it was published/republished with a zero TTL) and never expires.
uint64_t expires_at;
// `true` if the memory was last published by this instance.
bool is_own;
};

/**
* Symbols provided by component image
* Every image should provide these symbols
Expand Down Expand Up @@ -199,21 +211,21 @@ void k2_free_checked(void* ptr, size_t size, size_t align);

/**
* Shared memory provides a mechanism for instances to share data.
* To use it, first allocate memory with `k2_alloc_shared_memory`, then publish
* it with a unique name using `k2_publish_shared_memory`. Other instances can
* then retrieve the memory by name with `k2_get_shared_memory`.
* To use it, first allocate memory with `k2_shared_memory_alloc`, then publish
* it with a unique name using `k2_shared_memory_publish`. Other instances can
* then retrieve the memory by name with `k2_shared_memory_get`.
*
* Lifecycle:
* - TTL defines when memory becomes eligible for reclamation
* - Memory is reclaimed lazily after TTL expires AND no references remain
*
* Reference counting:
* - Calling `k2_publish_shared_memory` sets the reference count to one
* - Calling `k2_get_shared_memory` increments the reference count by one if
* - Calling `k2_shared_memory_publish` sets the reference count to one
* - Calling `k2_shared_memory_get` increments the reference count by one if
* this instance does not already hold a reference; repeated calls for the
* same underlying pointer while already held do not increment it further
* - Calling `k2_release_shared_memory` clears the held reference, if `pointer`
* is currently held; unpublished memory from `k2_alloc_shared_memory` is
* - Calling `k2_shared_memory_release` clears the held reference, if `pointer`
* is currently held; unpublished memory from `k2_shared_memory_alloc` is
* just freed
*/

Expand All @@ -235,33 +247,33 @@ void k2_free_checked(void* ptr, size_t size, size_t align);
* `ENOMEM` => Not enough memory to allocate the requested region.
* `ENOSYS` => Shared memory subsystem is unavailable on this host.
*/
int32_t k2_alloc_shared_memory(size_t size, size_t align, void** pointer);
int32_t k2_shared_memory_alloc(size_t size, size_t align, void** pointer);

/**
* Publishes shared memory with a name and TTL, making it discoverable by other instances.
*
* @param `name` Name to associate with the memory region. Must be unique.
* Should be valid UTF-8 and not contain null bytes.
* @param `name_len` Length of the name in bytes. Must be greater than 0.
* @param `memory` Pointer to memory previously allocated via `k2_alloc_shared_memory`.
* @param `memory` Pointer to memory previously allocated via `k2_shared_memory_alloc`.
* @param `ttl` Time-to-live in milliseconds. Memory becomes eligible for
* reclamation after this duration, but only when the reference
* count reaches zero. Zero TTL means infinite life.
* @param `as_mut` Controls whether write protection is disabled. If true, the memory is made writable.
* @param `ignore_if_exist` If true, allows re-publishing memory previously allocated via
* `k2_alloc_shared_memory` even if a live published memory with the same name already exists.
* `k2_shared_memory_alloc` even if a live published memory with the same name already exists.
*
* @return `0` on success. libc-like `errno` on error.
*
* Possible `errno`:
* `EINVAL` => `name` is NULL, `name_len` is 0, `memory` is NULL, `name` is
* not valid UTF-8, or `ttl` is too small (non-zero and less than
* 100 ms).
* `ENOENT` => `memory` was not allocated by `k2_alloc_shared_memory`.
* `ENOENT` => `memory` was not allocated by `k2_shared_memory_alloc`.
* `EEXIST` => Memory with this name already exists.
* `ENOSYS` => Shared memory subsystem is unavailable on this host.
*/
int32_t k2_publish_shared_memory(const char* name, size_t name_len, const void* memory, uint64_t ttl, bool as_mut, bool ignore_if_exist);
int32_t k2_shared_memory_publish(const char* name, size_t name_len, const void* memory, uint64_t ttl, bool as_mut, bool ignore_if_exist);

/**
* Retrieves shared memory by name and, unless already held by this
Expand All @@ -278,10 +290,11 @@ int32_t k2_publish_shared_memory(const char* name, size_t name_len, const void*
* Possible `errno`:
* `EINVAL` => `name` is NULL, `name_len` is 0, `pointer` is NULL, or `name`
* is not valid UTF-8.
* `ENOENT` => No memory found with the given name (or TTL expired and memory was freed).
* `ENOENT` => No memory found with the given name (memory was freed).
* `EKEYEXPIRED` => TTL for this name has already expired.
* `ENOSYS` => Shared memory subsystem is unavailable on this host.
*/
int32_t k2_get_shared_memory(const char* name, size_t name_len, const void** pointer, size_t* size);
int32_t k2_shared_memory_get(const char* name, size_t name_len, const void** pointer, size_t* size);

/**
* Republishes shared memory, resetting its TTL and restarting the age counter from this call.
Expand All @@ -299,7 +312,7 @@ int32_t k2_get_shared_memory(const char* name, size_t name_len, const void** poi
* `ENOENT` => No memory found with the given name (or TTL expired and memory was freed).
* `ENOSYS` => Shared memory subsystem is unavailable on this host.
*/
int32_t k2_republish_shared_memory(const char* name, size_t name_len, uint64_t ttl);
int32_t k2_shared_memory_republish(const char* name, size_t name_len, uint64_t ttl);

/**
* Fast-forwards the expiration of published shared memory ahead of its TTL, by name.
Expand Down Expand Up @@ -334,21 +347,21 @@ int32_t k2_republish_shared_memory(const char* name, size_t name_len, uint64_t t
* `ENOENT` => No memory found with the given name (or TTL expired and memory was freed).
* `ENOSYS` => Shared memory subsystem is unavailable on this host.
*/
int32_t k2_seek_ttl_to_shared_memory(const char* name, size_t name_len, uint8_t percentile, uint64_t remaining_lifetime_limit);
int32_t k2_shared_memory_seek_ttl_to(const char* name, size_t name_len, uint8_t percentile, uint64_t remaining_lifetime_limit);

/**
* Frees shared memory allocated via `k2_alloc_shared_memory` that was never
* Frees shared memory allocated via `k2_shared_memory_alloc` that was never
* published, or releases the reference to shared memory held by this
* instance, acquired via `k2_publish_shared_memory` or
* `k2_get_shared_memory` (see the reference counting notes above).
* instance, acquired via `k2_shared_memory_publish` or
* `k2_shared_memory_get` (see the reference counting notes above).
*
* Releasing a reference does not free the underlying memory immediately: the
* memory is only physically reclaimed once its TTL has expired AND the
* reference count has reached zero (see the lifecycle notes above).
*
* @param `pointer` Pointer to shared memory, as returned by
* `k2_alloc_shared_memory` or `k2_get_shared_memory`, or
* passed as the `memory` argument to `k2_publish_shared_memory`.
* `k2_shared_memory_alloc` or `k2_shared_memory_get`, or
* passed as the `memory` argument to `k2_shared_memory_publish`.
*
* @return `0` on success: either the unpublished allocation was freed, or the
* reference held by this instance was released. libc-like `errno`
Expand All @@ -357,10 +370,31 @@ int32_t k2_seek_ttl_to_shared_memory(const char* name, size_t name_len, uint8_t
* Possible `errno`:
* `EINVAL` => `pointer` is NULL.
* `ENOENT` => `pointer` does not correspond to memory allocated by this
* instance via `k2_alloc_shared_memory`, nor to a reference
* instance via `k2_shared_memory_alloc`, nor to a reference
* currently held by this instance.
*/
int32_t k2_release_shared_memory(const void* pointer);
int32_t k2_shared_memory_release(const void* pointer);

/**
* Retrieves timing and ownership info about published shared memory, by name, without
* taking a reference to it.
*
* @param `name` Name of the shared memory to query.
* @param `name_len` Length of the name in bytes. Must be greater than 0.
* @param `info` Out parameter, filled with the current info on success. `stored_at` (the
* moment of the last publish/republish) is always treated as time origin 0, so
* `info->now` and `info->expires_at` are nanosecond offsets from it, not
* wall-clock timestamps. `info->expires_at == 0` means the memory is immortal
* (published/republished with a zero TTL) and never expires.
*
* @return `0` on success. libc-like `errno` on error.
*
* Possible `errno`:
* `EINVAL` => `name` is NULL, `name_len` is 0, `info` is NULL, or `name` is not valid UTF-8.
* `ENOENT` => No memory found with the given name (memory was freed).
* `EKEYEXPIRED` => TTL for this name has already expired.
*/
int32_t k2_shared_memory_info(const char* name, size_t name_len, struct SharedMemoryInfo* info);

/**
* Immediately abort component execution.
Expand Down
2 changes: 1 addition & 1 deletion runtime-light/stdlib/confdata/confdata-reader-lease.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class reader_lease final {
storage::sample_id m_sample_id{storage::INVALID_SAMPLE_ID};
/** Number of meaningful bytes in `m_shared_memory_name`. */
uint32_t m_shared_memory_name_size{};
/** Name passed to `k2_get_shared_memory`; it is not null-terminated. */
/** Name passed to `k2_shared_memory_get`; it is not null-terminated. */
std::array<char, MAX_SHARED_MEMORY_NAME_SIZE> m_shared_memory_name{};

public:
Expand Down
2 changes: 1 addition & 1 deletion runtime-light/stdlib/confdata/confdata-state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ auto instance_state::init() noexcept -> kphp::coro::task<> {
co_return kphp::log::warning("failed to acquire a valid confdata reader lease");
}

const auto shared_memory{k2::get_shared_memory(lease.shared_memory_name())};
const auto shared_memory{k2::shared_memory_get(lease.shared_memory_name())};
if (!shared_memory) {
co_return kphp::log::warning("failed to get confdata shared memory: error -> {}", shared_memory.error());
}
Expand Down
Loading
Loading