From f4aa0194d623aae72d4def408d627622f16f5030 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 20 Nov 2025 19:37:44 -0800 Subject: [PATCH 01/26] realm: proposed interface for a vtable for a network bootstrap --- src/realm/runtime.h | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 0fa3a4929c0..f75d30a86d0 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -57,6 +57,58 @@ namespace Realm { // *argc and *argv contain the application's real command line // (instead of e.g. mpi spawner information) bool network_init(int *argc, char ***argv); + // Some networks prefer to bootstrap via callbacks using a vtable + // A client provides an implementation of the functions in the vtable + // and Realm will invoke them as part of bootstrapping the network. + // Some functions are required in order to successfully bootstrap + // the network while others are optional and provide information + // whenever the state of the network changes. + struct NetworkVtable { + //////////////////////// + // REQUIRED FUNCTIONS // + //////////////////////// + // The "put" function must store a global key value pair in a way that it + // can be retrieved from any other process using a corresponding get call. + // The function is passed a buffer containing a key and buffer containing + // a value. The implementation must copy these values before returning from + // the callback if it needs to persist them as they are not guaranteed to + // live longer than the function call. The function should return true if + // the put succeeds and false if it doesn't. If the call fails then it is + // likely that the network initialization might not succeed. + bool (*put)(const void *key, size_t key_size, const void *value, + size_t value_size) = nullptr; + // The "get" function must retrieve the value associated with the given key + // if it can be found. Realm will call this function with the value buffer + // already allocated with the given value_size. If the key is found and the + // value is the correct size for the value buffer, then the callee should + // copy the value into the value buffer and return true indicating that + // the call succeeded. If the key cannot be found or if the value found by + // the callee is different from the value size, then function should + // return false indicating that the callbcak failed. If the callback fails + // then the network initialization might not succeed. + bool (*get)(const void *key, size_t key_size, void *value, + size_t value_size) = nullptr; + //////////////////////// + // OPTIONAL FUNCTIONS // + //////////////////////// + // You can pass in an optional buffer of data to uniquely identify this + // process to other processes as part of the bootstrap. Realm will make + // a copy of this data if provided during the network_init call so it + // you do not need to keep it alive longer than that. + const void *unique_data = nullptr; + size_t unique_data_size = 0; + // This callback is an optional callback that will be invoked any time a + // process joins the Realm. It will be told of the assigned Realm + // AddressSpace given to the process along with the unique data for the + // process if it was provided (might be null). Note that you will also + // get a callback for yourself upon joining as well. + void (*join)(AddressSpace space, const void *data, size_t size) = nullptr; + // This callback is performed any time a process leaves the Realm. + // It will be told of the AddressSpace for the process as well as the + // unique data associated with the process that is leaving. + void (*leave)(AddressSpace space, const void *data, size_t size) = nullptr; + }; + bool network_init(const NetworkVtable &vtable); void parse_command_line(int argc, char **argv); void parse_command_line(std::vector &cmdline, From dd3183770a4aa262924279553d7ebd0f825cbbb6 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 20 Nov 2025 22:23:19 -0800 Subject: [PATCH 02/26] realm: more documentation for network vtable interface --- src/realm/runtime.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index f75d30a86d0..cdb17809f2e 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -62,7 +62,13 @@ namespace Realm { // and Realm will invoke them as part of bootstrapping the network. // Some functions are required in order to successfully bootstrap // the network while others are optional and provide information - // whenever the state of the network changes. + // whenever the state of the network changes. All callbacks will + // either be performed in an external thread (one not made by Realm + // but has called into Realm) or by a designated Realm thread + // independent of Realm's background worker threads so that clients + // can use non-Realm synchronization primitives in the implementation + // of these functions and not need to worry about blocking or + // impacting forward progress. struct NetworkVtable { //////////////////////// // REQUIRED FUNCTIONS // @@ -101,11 +107,17 @@ namespace Realm { // process joins the Realm. It will be told of the assigned Realm // AddressSpace given to the process along with the unique data for the // process if it was provided (might be null). Note that you will also - // get a callback for yourself upon joining as well. + // get a callback for yourself upon joining as well. This call back is + // only done *AFTER* the joining process has successfully joined meaning + // that this callback marks when it is safe to start referring to the + // new address space and all of the handles associated with it. void (*join)(AddressSpace space, const void *data, size_t size) = nullptr; // This callback is performed any time a process leaves the Realm. // It will be told of the AddressSpace for the process as well as the - // unique data associated with the process that is leaving. + // unique data associated with the process that is leaving. This + // callback is done *BEFORE* the process leaves the Realm. After this + // callback returns it is no longer safe to refer to handles from the + // address space that is leaving in the process where the callback occurs. void (*leave)(AddressSpace space, const void *data, size_t size) = nullptr; }; bool network_init(const NetworkVtable &vtable); From 67673a9c0856758a8672ecc73090bc1dea2a3812 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 21 Nov 2025 03:11:58 -0800 Subject: [PATCH 03/26] realm: fix formatting --- src/realm/runtime.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index cdb17809f2e..5cfec8633bd 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -107,7 +107,7 @@ namespace Realm { // process joins the Realm. It will be told of the assigned Realm // AddressSpace given to the process along with the unique data for the // process if it was provided (might be null). Note that you will also - // get a callback for yourself upon joining as well. This call back is + // get a callback for yourself upon joining as well. This call back is // only done *AFTER* the joining process has successfully joined meaning // that this callback marks when it is safe to start referring to the // new address space and all of the handles associated with it. From 09f1b550e7346810142bab9a8db6ac022a264774 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 24 Nov 2025 13:47:32 -0800 Subject: [PATCH 04/26] realm: more proposed updates to the vtable for bootstrapping callbacks --- src/realm/runtime.h | 82 +++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 5cfec8633bd..aaccde6c72f 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -85,40 +85,56 @@ namespace Realm { size_t value_size) = nullptr; // The "get" function must retrieve the value associated with the given key // if it can be found. Realm will call this function with the value buffer - // already allocated with the given value_size. If the key is found and the - // value is the correct size for the value buffer, then the callee should - // copy the value into the value buffer and return true indicating that - // the call succeeded. If the key cannot be found or if the value found by - // the callee is different from the value size, then function should - // return false indicating that the callbcak failed. If the callback fails - // then the network initialization might not succeed. + // already allocated with the value_size populated with the maximum size + // of the value that can be returned. If the key is found and the value + // size is less than or equal to the value_size passed in by Realm, then + // the value buffer should be populated and the value_size updated with + // the actual size of the value found. If the value is not found or is + // too large for the specified buffer then value_size should be set to + // zero. If for any reason the call fails then return value should be + // false. Not finding a key should still be considered a success as in + // some cases the backend might be able to cope with not finding some + // keys. Returning false should only occur if the function call fails + // in some way that makes it impossible to know if the key exists or + // not. If the callback fails then the network initialization might + // not succeed. bool (*get)(const void *key, size_t key_size, void *value, - size_t value_size) = nullptr; - //////////////////////// - // OPTIONAL FUNCTIONS // - //////////////////////// - // You can pass in an optional buffer of data to uniquely identify this - // process to other processes as part of the bootstrap. Realm will make - // a copy of this data if provided during the network_init call so it - // you do not need to keep it alive longer than that. - const void *unique_data = nullptr; - size_t unique_data_size = 0; - // This callback is an optional callback that will be invoked any time a - // process joins the Realm. It will be told of the assigned Realm - // AddressSpace given to the process along with the unique data for the - // process if it was provided (might be null). Note that you will also - // get a callback for yourself upon joining as well. This call back is - // only done *AFTER* the joining process has successfully joined meaning - // that this callback marks when it is safe to start referring to the - // new address space and all of the handles associated with it. - void (*join)(AddressSpace space, const void *data, size_t size) = nullptr; - // This callback is performed any time a process leaves the Realm. - // It will be told of the AddressSpace for the process as well as the - // unique data associated with the process that is leaving. This - // callback is done *BEFORE* the process leaves the Realm. After this - // callback returns it is no longer safe to refer to handles from the - // address space that is leaving in the process where the callback occurs. - void (*leave)(AddressSpace space, const void *data, size_t size) = nullptr; + size_t *value_size) = nullptr; + ////////////////////////////// + // SYNCRONIZATION FUNCTIONS // + // (PROVIDE EXACTLY ONE) // + ////////////////////////////// + // The "bar" function should be provided in cases of non-elastic + // bootstrap when there is a fixed unverse of processes. The bar + // function must flush all puts and then perform a barrier across + // all the processes in the job. It should return true if the barrier + // succeeds and false if it fails. If the barrier fails then it + // can be expected the Realm bootstrap will also fail. If you + // provide a bar method, then you must also provide support + // in the "get" method for two kinds of special keys. Specifically + // you must provide support for the "realm_rank" key which will + // return a unique integer identifier for this process as well + // as a "realm_ranks" key which will return the total number of + // processes in the job. The integer identifiers for processes + // must start at zero, be contiguous incrementally, and all be + // strictly less than the value of "realm_ranks". + bool (*bar)(void) = nullptr; + // The "cas" function should be provided in cases of elastic + // bootstrap when an arbitrary number of processes can join or + // leave the Realm during its execution. The cas function should + // perform an atomic compare-and-swap operation on a key by + // checking that the key matches a particular value and if it + // does then updating it with the desired value in a single atomic + // operation. If the value of the key does not match the expected + // result, the call should fail, but return the updated expected + // value and size as long as it is less than or equal to the + // original expected size. If the new value size is larger than + // the expected size, then only the expected_size should be updated. + // It is possible for this call to fail and for the bootstrap to + // continue, although a large number of repetitive failures will + // likely lead to a timeout. + bool (*cas)(const void *key, size_t key_size, void *expected, size_t *expected_size, + const void *desired, size_t desired_size) = nullptr; }; bool network_init(const NetworkVtable &vtable); From 79f1ba4b906a058e5c41d3d054b032655675957b Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 24 Nov 2025 13:59:01 -0800 Subject: [PATCH 05/26] realm: more updated documentation for network vtable --- src/realm/runtime.h | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index aaccde6c72f..d8f9b747da2 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -102,22 +102,36 @@ namespace Realm { size_t *value_size) = nullptr; ////////////////////////////// // SYNCRONIZATION FUNCTIONS // - // (PROVIDE EXACTLY ONE) // + // (PROVIDE ONE OR BOTH) // ////////////////////////////// - // The "bar" function should be provided in cases of non-elastic - // bootstrap when there is a fixed unverse of processes. The bar - // function must flush all puts and then perform a barrier across - // all the processes in the job. It should return true if the barrier - // succeeds and false if it fails. If the barrier fails then it - // can be expected the Realm bootstrap will also fail. If you - // provide a bar method, then you must also provide support - // in the "get" method for two kinds of special keys. Specifically - // you must provide support for the "realm_rank" key which will - // return a unique integer identifier for this process as well - // as a "realm_ranks" key which will return the total number of - // processes in the job. The integer identifiers for processes - // must start at zero, be contiguous incrementally, and all be - // strictly less than the value of "realm_ranks". + // For the synchronization callbacks you can provide one or both + // functions. All three combinations correspond to different use cases. + // * Providing only "bar": this is an inelastic job with a fixed + // universe of processes that will never change. + // * Providing only "cas": this is an elastic job with processes + // that will come and go one at a time. + // * Providing both: this is an elastic job with processes that + // will come and go as groups. Groups of processes must both + // join and leave together. + + // The "bar" function should be provided in cases where processes + // are joining and leaving the Realm as a group. It must perform + // a barrier across all the processes in the (implicit) group that + // this process is a part of along with flushing any puts done + // before it. It should return true if the barrier succeeds and + // false if it fails. If the barrier fails then it can be expected + // the Realm bootstrap will also fail. If you provide a bar method, + // then you must also provide support in the "get" method for two + // kinds of special keys. Specifically you must provide support + // for the "realm_rank" key which will return a unique integer + // identifier for this process in its group as well as a "realm_ranks" + // key which will return the total number of processes in the group. + // The integer identifiers for processes must start at zero, be + // contiguous incrementally, and all be strictly less than the + // value of "realm_ranks". Note that each group should have its + // numbering start at zero and grow incrementally. Process numbers + // can be the same across ranks. Realm will generate a unique + // address space for this process as part of the bootstrap. bool (*bar)(void) = nullptr; // The "cas" function should be provided in cases of elastic // bootstrap when an arbitrary number of processes can join or From d5ada1d8f83bc65d751892c26514314cea4a2752 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 24 Nov 2025 14:17:04 -0800 Subject: [PATCH 06/26] realm: fix formatting --- src/realm/runtime.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index d8f9b747da2..97e55f5bd55 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -118,16 +118,16 @@ namespace Realm { // are joining and leaving the Realm as a group. It must perform // a barrier across all the processes in the (implicit) group that // this process is a part of along with flushing any puts done - // before it. It should return true if the barrier succeeds and - // false if it fails. If the barrier fails then it can be expected + // before it. It should return true if the barrier succeeds and + // false if it fails. If the barrier fails then it can be expected // the Realm bootstrap will also fail. If you provide a bar method, // then you must also provide support in the "get" method for two - // kinds of special keys. Specifically you must provide support - // for the "realm_rank" key which will return a unique integer - // identifier for this process in its group as well as a "realm_ranks" - // key which will return the total number of processes in the group. - // The integer identifiers for processes must start at zero, be - // contiguous incrementally, and all be strictly less than the + // kinds of special keys. Specifically you must provide support + // for the "realm_rank" key which will return a unique integer + // identifier for this process in its group as well as a "realm_ranks" + // key which will return the total number of processes in the group. + // The integer identifiers for processes must start at zero, be + // contiguous incrementally, and all be strictly less than the // value of "realm_ranks". Note that each group should have its // numbering start at zero and grow incrementally. Process numbers // can be the same across ranks. Realm will generate a unique From b2590f6618dac376be6736e9f0d61f4705407c9d Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 24 Nov 2025 19:11:23 -0800 Subject: [PATCH 07/26] prealm: small prealm bug fix --- src/realm/prealm/prealm.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/prealm/prealm.inl b/src/realm/prealm/prealm.inl index dca59b80aba..7bf44a41ca6 100644 --- a/src/realm/prealm/prealm.inl +++ b/src/realm/prealm/prealm.inl @@ -1148,7 +1148,7 @@ namespace PRealm { } inline void prealm_time_range(long long start_time_in_ns, const std::string_view &name, - Event external) + Realm::Event external) { ThreadProfiler::get_thread_profiler().record_time_range(start_time_in_ns, name, external); From ae9711903db7a5a743915ce15ecac93bcb2bb67f Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 24 Nov 2025 19:11:46 -0800 Subject: [PATCH 08/26] Revert "prealm: small prealm bug fix" This reverts commit b2590f6618dac376be6736e9f0d61f4705407c9d. --- src/realm/prealm/prealm.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/prealm/prealm.inl b/src/realm/prealm/prealm.inl index 7bf44a41ca6..dca59b80aba 100644 --- a/src/realm/prealm/prealm.inl +++ b/src/realm/prealm/prealm.inl @@ -1148,7 +1148,7 @@ namespace PRealm { } inline void prealm_time_range(long long start_time_in_ns, const std::string_view &name, - Realm::Event external) + Event external) { ThreadProfiler::get_thread_profiler().record_time_range(start_time_in_ns, name, external); From dd9e53e74dd1c51869e50666850f26101a747f8f Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 25 Nov 2025 01:21:20 -0800 Subject: [PATCH 09/26] realm: fix minor typo in vtable interface --- src/realm/runtime.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 97e55f5bd55..1a50d751c3c 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -122,16 +122,16 @@ namespace Realm { // false if it fails. If the barrier fails then it can be expected // the Realm bootstrap will also fail. If you provide a bar method, // then you must also provide support in the "get" method for two - // kinds of special keys. Specifically you must provide support - // for the "realm_rank" key which will return a unique integer - // identifier for this process in its group as well as a "realm_ranks" + // special keys. Specifically you must provide support for the + // "realm_rank" key which will return a unique integer identifier + // for this process in its group as well as a "realm_ranks" // key which will return the total number of processes in the group. // The integer identifiers for processes must start at zero, be // contiguous incrementally, and all be strictly less than the // value of "realm_ranks". Note that each group should have its // numbering start at zero and grow incrementally. Process numbers - // can be the same across ranks. Realm will generate a unique - // address space for this process as part of the bootstrap. + // can be the same across groups. Realm will generate a unique + // address space for each process as part of the bootstrap. bool (*bar)(void) = nullptr; // The "cas" function should be provided in cases of elastic // bootstrap when an arbitrary number of processes can join or From 05edc0b2e93f452402294520468aa64a7c89310c Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 25 Nov 2025 01:39:31 -0800 Subject: [PATCH 10/26] realm: fix formatting --- src/realm/runtime.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 1a50d751c3c..02cb9b4494a 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -122,7 +122,7 @@ namespace Realm { // false if it fails. If the barrier fails then it can be expected // the Realm bootstrap will also fail. If you provide a bar method, // then you must also provide support in the "get" method for two - // special keys. Specifically you must provide support for the + // special keys. Specifically you must provide support for the // "realm_rank" key which will return a unique integer identifier // for this process in its group as well as a "realm_ranks" // key which will return the total number of processes in the group. From fd97d7b5c577ce6447e948b274efa0bf2d9af5cc Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 1 Dec 2025 12:03:21 -0800 Subject: [PATCH 11/26] realm: switch to doxygen comments --- src/realm/runtime.h | 119 +++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 02cb9b4494a..7300547599d 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -73,31 +73,35 @@ namespace Realm { //////////////////////// // REQUIRED FUNCTIONS // //////////////////////// - // The "put" function must store a global key value pair in a way that it - // can be retrieved from any other process using a corresponding get call. - // The function is passed a buffer containing a key and buffer containing - // a value. The implementation must copy these values before returning from - // the callback if it needs to persist them as they are not guaranteed to - // live longer than the function call. The function should return true if - // the put succeeds and false if it doesn't. If the call fails then it is - // likely that the network initialization might not succeed. + /** + * The "put" function must store a global key value pair in a way that it + * can be retrieved from any other process using a corresponding get call. + * The function is passed a buffer containing a key and buffer containing + * a value. The implementation must copy these values before returning from + * the callback if it needs to persist them as they are not guaranteed to + * live longer than the function call. The function should return true if + * the put succeeds and false if it doesn't. If the call fails then it is + * likely that the network initialization might not succeed. + */ bool (*put)(const void *key, size_t key_size, const void *value, size_t value_size) = nullptr; - // The "get" function must retrieve the value associated with the given key - // if it can be found. Realm will call this function with the value buffer - // already allocated with the value_size populated with the maximum size - // of the value that can be returned. If the key is found and the value - // size is less than or equal to the value_size passed in by Realm, then - // the value buffer should be populated and the value_size updated with - // the actual size of the value found. If the value is not found or is - // too large for the specified buffer then value_size should be set to - // zero. If for any reason the call fails then return value should be - // false. Not finding a key should still be considered a success as in - // some cases the backend might be able to cope with not finding some - // keys. Returning false should only occur if the function call fails - // in some way that makes it impossible to know if the key exists or - // not. If the callback fails then the network initialization might - // not succeed. + /** + * The "get" function must retrieve the value associated with the given key + * if it can be found. Realm will call this function with the value buffer + * already allocated with the value_size populated with the maximum size + * of the value that can be returned. If the key is found and the value + * size is less than or equal to the value_size passed in by Realm, then + * the value buffer should be populated and the value_size updated with + * the actual size of the value found. If the value is not found or is + * too large for the specified buffer then value_size should be set to + * zero. If for any reason the call fails then return value should be + * false. Not finding a key should still be considered a success as in + * some cases the backend might be able to cope with not finding some + * keys. Returning false should only occur if the function call fails + * in some way that makes it impossible to know if the key exists or + * not. If the callback fails then the network initialization might + * not succeed. + */ bool (*get)(const void *key, size_t key_size, void *value, size_t *value_size) = nullptr; ////////////////////////////// @@ -113,40 +117,43 @@ namespace Realm { // * Providing both: this is an elastic job with processes that // will come and go as groups. Groups of processes must both // join and leave together. - - // The "bar" function should be provided in cases where processes - // are joining and leaving the Realm as a group. It must perform - // a barrier across all the processes in the (implicit) group that - // this process is a part of along with flushing any puts done - // before it. It should return true if the barrier succeeds and - // false if it fails. If the barrier fails then it can be expected - // the Realm bootstrap will also fail. If you provide a bar method, - // then you must also provide support in the "get" method for two - // special keys. Specifically you must provide support for the - // "realm_rank" key which will return a unique integer identifier - // for this process in its group as well as a "realm_ranks" - // key which will return the total number of processes in the group. - // The integer identifiers for processes must start at zero, be - // contiguous incrementally, and all be strictly less than the - // value of "realm_ranks". Note that each group should have its - // numbering start at zero and grow incrementally. Process numbers - // can be the same across groups. Realm will generate a unique - // address space for each process as part of the bootstrap. + /** + * The "bar" function should be provided in cases where processes + * are joining and leaving the Realm as a group. It must perform + * a barrier across all the processes in the (implicit) group that + * this process is a part of along with flushing any puts done + * before it. It should return true if the barrier succeeds and + * false if it fails. If the barrier fails then it can be expected + * the Realm bootstrap will also fail. If you provide a bar method, + * then you must also provide support in the "get" method for two + * special keys. Specifically you must provide support for the + * "realm_rank" key which will return a unique integer identifier + * for this process in its group as well as a "realm_ranks" + * key which will return the total number of processes in the group. + * The integer identifiers for processes must start at zero, be + * contiguous incrementally, and all be strictly less than the + * value of "realm_ranks". Note that each group should have its + * numbering start at zero and grow incrementally. Process numbers + * can be the same across groups. Realm will generate a unique + * address space for each process as part of the bootstrap. + */ bool (*bar)(void) = nullptr; - // The "cas" function should be provided in cases of elastic - // bootstrap when an arbitrary number of processes can join or - // leave the Realm during its execution. The cas function should - // perform an atomic compare-and-swap operation on a key by - // checking that the key matches a particular value and if it - // does then updating it with the desired value in a single atomic - // operation. If the value of the key does not match the expected - // result, the call should fail, but return the updated expected - // value and size as long as it is less than or equal to the - // original expected size. If the new value size is larger than - // the expected size, then only the expected_size should be updated. - // It is possible for this call to fail and for the bootstrap to - // continue, although a large number of repetitive failures will - // likely lead to a timeout. + /** + * The "cas" function should be provided in cases of elastic + * bootstrap when an arbitrary number of processes can join or + * leave the Realm during its execution. The cas function should + * perform an atomic compare-and-swap operation on a key by + * checking that the key matches a particular value and if it + * does then updating it with the desired value in a single atomic + * operation. If the value of the key does not match the expected + * result, the call should fail, but return the updated expected + * value and size as long as it is less than or equal to the + * original expected size. If the new value size is larger than + * the expected size, then only the expected_size should be updated. + * It is possible for this call to fail and for the bootstrap to + * continue, although a large number of repetitive failures will + * likely lead to a timeout. + */ bool (*cas)(const void *key, size_t key_size, void *expected, size_t *expected_size, const void *desired, size_t desired_size) = nullptr; }; From d9e806696820d975b3b8e6e2d07961621053c4a6 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 5 Dec 2025 13:43:19 -0800 Subject: [PATCH 12/26] realm: initial implementation of general methods for the network vtable in the runtime singleton --- src/realm/realm_c.cc | 3 +- src/realm/runtime.h | 90 ++++++++++------- src/realm/runtime_impl.cc | 196 +++++++++++++++++++++++++++++++------- src/realm/runtime_impl.h | 25 ++++- 4 files changed, 242 insertions(+), 72 deletions(-) diff --git a/src/realm/realm_c.cc b/src/realm/realm_c.cc index bf6042b971a..dcdc1524f50 100644 --- a/src/realm/realm_c.cc +++ b/src/realm/realm_c.cc @@ -307,8 +307,9 @@ realm_status_t realm_runtime_init(realm_runtime_t runtime, int *argc, char ***ar argv = &my_argv; } + Realm::Runtime::NetworkVtable vtable; // TODO: we need to let each of these functions to return a specific error code - if(!runtime_impl->network_init(argc, argv)) { + if(!runtime_impl->network_init(argc, argv, vtable)) { return REALM_ERROR; } if(!runtime_impl->create_configs(*argc, *argv)) { diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 7300547599d..1fea10b38b5 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -59,17 +59,25 @@ namespace Realm { bool network_init(int *argc, char ***argv); // Some networks prefer to bootstrap via callbacks using a vtable // A client provides an implementation of the functions in the vtable - // and Realm will invoke them as part of bootstrapping the network. - // Some functions are required in order to successfully bootstrap - // the network while others are optional and provide information - // whenever the state of the network changes. All callbacks will - // either be performed in an external thread (one not made by Realm - // but has called into Realm) or by a designated Realm thread - // independent of Realm's background worker threads so that clients - // can use non-Realm synchronization primitives in the implementation - // of these functions and not need to worry about blocking or - // impacting forward progress. + // and Realm will invoke them as part of bootstrapping the network and + // providing support for elasticity (when networks support it). + // Some functions are required while others are either/or options. + // All callbacks will either be performed in an external thread + // (one not made by Realm but has called into Realm) or by a designated + // Realm thread independent of Realm's background worker threads so that + // clients can use non-Realm synchronization primitives in the + // implementation of these functions and not need to worry about + // blocking or impacting forward progress. struct NetworkVtable { + /** + * Optional blob of data passed to all the network vtable functions + * when they are invoked by Realm. Realm will not attempt to + * interpret this data at all but will simply pass it through + * to each call. You do not have to pass any data through to + * implement the callbacks, it is purely for your convenience. + */ + const void *vtable_data; + size_t vtable_data_size; //////////////////////// // REQUIRED FUNCTIONS // //////////////////////// @@ -83,8 +91,8 @@ namespace Realm { * the put succeeds and false if it doesn't. If the call fails then it is * likely that the network initialization might not succeed. */ - bool (*put)(const void *key, size_t key_size, const void *value, - size_t value_size) = nullptr; + bool (*put)(const void *key, size_t key_size, const void *value, size_t value_sizem, + const void *vtable_data, size_t vtable_data_size) = nullptr; /** * The "get" function must retrieve the value associated with the given key * if it can be found. Realm will call this function with the value buffer @@ -92,18 +100,20 @@ namespace Realm { * of the value that can be returned. If the key is found and the value * size is less than or equal to the value_size passed in by Realm, then * the value buffer should be populated and the value_size updated with - * the actual size of the value found. If the value is not found or is - * too large for the specified buffer then value_size should be set to - * zero. If for any reason the call fails then return value should be - * false. Not finding a key should still be considered a success as in - * some cases the backend might be able to cope with not finding some - * keys. Returning false should only occur if the function call fails - * in some way that makes it impossible to know if the key exists or - * not. If the callback fails then the network initialization might - * not succeed. + * the actual size of the value found. If the key is found but the + * resulting value is larger than the buffer size, then the function + * should update value_size with the actual size of the buffer but + * does not need to populate the value buffer (sinze it obviously will + * not fit). If the key cannot be found then the value size should be + * set to zero. The function should always return true as long as the + * get call works correctly (even if a key is not found or the buffer + * is not larger enough). Returning false should only occur if the + * function call fails in some way that makes it impossible to know + * if the key exists or not. If the callback fails then the network + * initialization might not succeed. */ - bool (*get)(const void *key, size_t key_size, void *value, - size_t *value_size) = nullptr; + bool (*get)(const void *key, size_t key_size, void *value, size_t *value_size, + const void *vtable_data, size_t vtable_data_size) = nullptr; ////////////////////////////// // SYNCRONIZATION FUNCTIONS // // (PROVIDE ONE OR BOTH) // @@ -125,19 +135,26 @@ namespace Realm { * before it. It should return true if the barrier succeeds and * false if it fails. If the barrier fails then it can be expected * the Realm bootstrap will also fail. If you provide a bar method, - * then you must also provide support in the "get" method for two - * special keys. Specifically you must provide support for the - * "realm_rank" key which will return a unique integer identifier - * for this process in its group as well as a "realm_ranks" - * key which will return the total number of processes in the group. - * The integer identifiers for processes must start at zero, be - * contiguous incrementally, and all be strictly less than the - * value of "realm_ranks". Note that each group should have its - * numbering start at zero and grow incrementally. Process numbers - * can be the same across groups. Realm will generate a unique - * address space for each process as part of the bootstrap. + * then you must also provide support in the "get" method for three + * special keys: + * - "realm_group": the value associated with this key must be + * interpretable as an integer that is the same for all processes + * that cooperate in this barrier. It must be distinct from + * any other group identifier for other processes that are + * joining the same Realm. + * - "realm_ranks": the value associated with this key must be + * interpretable as an integer that indicates how many processes + * are participating in this barrier operation together. + * - "realm_rank": the value associated with this key must be + * interpretable as an integer that uniquely identifies this + * process in its local group. It must be in the range + * [0, realm_ranks). + * Note that each group should have its rank numbering start at zero + * and grow incrementally. Rank numbers are therefore the same + * across groups. Realm will generate a unique address space for + * each process as part of the bootstrap. */ - bool (*bar)(void) = nullptr; + bool (*bar)(const void *vtable_data, size_t vtable_data_size) = nullptr; /** * The "cas" function should be provided in cases of elastic * bootstrap when an arbitrary number of processes can join or @@ -155,7 +172,8 @@ namespace Realm { * likely lead to a timeout. */ bool (*cas)(const void *key, size_t key_size, void *expected, size_t *expected_size, - const void *desired, size_t desired_size) = nullptr; + const void *desired, size_t desired_size, const void *vtable_data, + size_t vtable_data_size) = nullptr; }; bool network_init(const NetworkVtable &vtable); diff --git a/src/realm/runtime_impl.cc b/src/realm/runtime_impl.cc index b68284052eb..1a251de199b 100644 --- a/src/realm/runtime_impl.cc +++ b/src/realm/runtime_impl.cc @@ -442,46 +442,20 @@ namespace Realm { /*static*/ const char *Runtime::get_library_version() { return realm_library_version; } -#if defined(REALM_USE_UCX) || defined(REALM_USE_MPI) || defined(REALM_USE_GASNET1) || \ - defined(REALM_USE_GASNETEX) || defined(REALM_USE_KOKKOS) - // global flag that tells us if a realm runtime has already been - // initialized in this process - some underlying libraries (e.g. mpi, - // gasnet, kokkos) do not permit reinitialization - static bool runtime_initialized = false; -#endif - // performs any network initialization and, critically, makes sure // *argc and *argv contain the application's real command line // (instead of e.g. mpi spawner information) bool Runtime::network_init(int *argc, char ***argv) { -#if defined(REALM_USE_UCX) || defined(REALM_USE_MPI) || defined(REALM_USE_GASNET1) || \ - defined(REALM_USE_GASNETEX) || defined(REALM_USE_KOKKOS) - if(runtime_initialized) { - fprintf(stderr, "ERROR: reinitialization not supported by these Realm components:" -#ifdef REALM_USE_UCX - " ucx" -#endif -#ifdef REALM_USE_MPI - " mpi" -#endif -#ifdef REALM_USE_GASNET1 - " gasnet1" -#endif -#ifdef REALM_USE_GASNETEX - " gasnetex" -#endif -#ifdef REALM_USE_KOKKOS - " kokkos" -#endif - "\n"); - return false; - } - runtime_initialized = true; -#endif + assert(runtime_singleton != 0); + NetworkVtable vtable; + return static_cast(impl)->network_init(argc, argv, vtable); + } + bool Runtime::network_init(const NetworkVtable &vtable) + { assert(runtime_singleton != 0); - return static_cast(impl)->network_init(argc, argv); + return static_cast(impl)->network_init(nullptr, nullptr, vtable); } void Runtime::parse_command_line(int argc, char **argv) @@ -1196,8 +1170,63 @@ namespace Realm { } } - bool RuntimeImpl::network_init(int *argc, char ***argv) + bool RuntimeImpl::network_init(int *argc, char ***argv, + const Runtime::NetworkVtable &vtable) { +#if defined(REALM_USE_UCX) || defined(REALM_USE_MPI) || defined(REALM_USE_GASNET1) || \ + defined(REALM_USE_GASNETEX) || defined(REALM_USE_KOKKOS) + // global flag that tells us if a realm runtime has already been + // initialized in this process - some underlying libraries (e.g. mpi, + // gasnet, kokkos) do not permit reinitialization + static std::atomic runtime_initialized = false; + if(runtime_initialized.exchange(true)) { + fprintf(stderr, "ERROR: reinitialization not supported by these Realm components:" +#ifdef REALM_USE_UCX + " ucx" +#endif +#ifdef REALM_USE_MPI + " mpi" +#endif +#ifdef REALM_USE_GASNET1 + " gasnet1" +#endif +#ifdef REALM_USE_GASNETEX + " gasnetex" +#endif +#ifdef REALM_USE_KOKKOS + " kokkos" +#endif + "\n"); + return false; + } +#endif + // Check the sanity of the network vtable + if((vtable.get != nullptr) || (vtable.put != nullptr) || (vtable.bar != nullptr) || + (vtable.cas != nullptr)) { + if(vtable.get == nullptr) { + fprintf(stderr, + "Detected non-trivial network vtable with missing 'get' callback.\n"); + return false; + } + if(vtable.put == nullptr) { + fprintf(stderr, + "Detected non-trivial network vtable with missing 'put' callback.\n"); + return false; + } + if((vtable.bar == nullptr) && (vtable.cas == nullptr)) { + fprintf(stderr, "Detected non-trivial network vtable with missing 'bar' or 'cas' " + "callback. At least one must be specified.\n"); + return false; + } + // Safe to save the network vtable + network_vtable = vtable; + if(vtable.vtable_data_size > 0) { + network_vtable_data.resize(vtable.vtable_data_size); + uint8_t *data = &network_vtable_data.front(); + std::memcpy(data, vtable.vtable_data, vtable.vtable_data_size); + network_vtable.vtable_data = data; + } + } // if we're given empty or non-existent argc/argv, start from a // dummy command line with a single string (which is supposed to be // the name of the binary) so that the network module and/or the @@ -1306,6 +1335,105 @@ namespace Realm { return true; } + bool RuntimeImpl::has_network_vtable(void) const + { + return (network_vtable.put != nullptr); + } + + bool RuntimeImpl::network_vtable_elastic(void) const + { + return (network_vtable.cas != nullptr); + } + + bool RuntimeImpl::network_vtable_group(void) const + { + return (network_vtable.bar != nullptr); + } + + std::optional RuntimeImpl::network_vtable_local_rank(void) const + { + return network_vtable_get_int("realm_rank"); + } + + std::optional RuntimeImpl::network_vtable_local_ranks(void) const + { + return network_vtable_get_int("realm_ranks"); + } + + std::optional + RuntimeImpl::network_vtable_get_int(const std::string_view &key) const + { + constexpr size_t max_int_size = sizeof(uint64_t); + uint8_t buffer[max_int_size]; + size_t actual_size = max_int_size; + if(!network_vtable_get(key.data(), key.size(), buffer, &actual_size) || + (actual_size == 0)) { + log_runtime.error() << "Unable to find expected key " << key + << " in key-value store for vtable 'get'. This key " + << "must be provided by the vtable implementation in " + << "order for Realm to be able to bootstrap network " + << "communication successfully."; + return std::nullopt; + } + if(actual_size == sizeof(uint8_t)) { + return std::optional(buffer[0]); + } else if(actual_size == sizeof(uint16_t)) { + uint16_t value; + std::memcpy(&value, buffer, actual_size); + return std::optional(value); + } else if(actual_size == sizeof(uint32_t)) { + uint32_t value; + std::memcpy(&value, buffer, actual_size); + return std::optional(value); + } else if(actual_size == sizeof(uint64_t)) { + uint64_t value; + std::memcpy(&value, buffer, actual_size); + return std::optional(value); + } else { + log_runtime.error() << "Expected key " << key << " has an " + << "invalid size for vtable 'get'. This key must be an " + << "integer but the found size was " << actual_size + << ". The size must be 1, 2, 4, or 8 bytes to be " + << "interpreted as an integer."; + return std::nullopt; + } + } + + bool RuntimeImpl::network_vtable_put(const void *key, size_t key_size, + const void *value, size_t value_size) const + { + assert(network_vtable.put != nullptr); + return (*network_vtable.put)(key, key_size, value, value_size, + network_vtable.vtable_data, + network_vtable.vtable_data_size); + } + + bool RuntimeImpl::network_vtable_get(const void *key, size_t key_size, void *value, + size_t *value_size) const + { + assert(network_vtable.get != nullptr); + return (*network_vtable.get)(key, key_size, value, value_size, + network_vtable.vtable_data, + network_vtable.vtable_data_size); + } + + bool RuntimeImpl::network_vtable_bar(void) const + { + assert(network_vtable.bar != nullptr); + return (*network_vtable.bar)(network_vtable.vtable_data, + network_vtable.vtable_data_size); + } + + bool RuntimeImpl::network_vtable_cas(const void *key, size_t key_size, void *expected, + size_t *expected_size, const void *desired, + size_t desired_size) const + { + assert(network_vtable.cas != nullptr); + return (*network_vtable.cas)(key, key_size, expected, expected_size, desired, + desired_size, network_vtable.vtable_data, + network_vtable.vtable_data_size); + } + template static bool serialize_announce(T &serializer, const Machine::ProcessInfo *process_info, NetworkModule *net) diff --git a/src/realm/runtime_impl.h b/src/realm/runtime_impl.h index ee173218d9d..a17a76f6a60 100644 --- a/src/realm/runtime_impl.h +++ b/src/realm/runtime_impl.h @@ -55,6 +55,7 @@ #include "realm/shm.h" #include "realm/hardware_topology.h" +#include #include #include @@ -265,7 +266,26 @@ namespace Realm { RuntimeImpl(void); ~RuntimeImpl(void); - bool network_init(int *argc, char ***argv); + bool network_init(int *argc, char ***argv, const Runtime::NetworkVtable &vtable); + bool has_network_vtable(void) const; + // Is this an elastic Realm + bool network_vtable_elastic(void) const; + // Are we a single process joining by ourself or part of a group + bool network_vtable_group(void) const; + // Our local rank in the group + std::optional network_vtable_local_rank(void) const; + // The total number of ranks in our group + std::optional network_vtable_local_ranks(void) const; + // Helper for getting integers of unknown size + std::optional network_vtable_get_int(const std::string_view &key) const; + bool network_vtable_put(const void *key, size_t key_size, const void *value, + size_t value_size) const; + bool network_vtable_get(const void *key, size_t key_size, void *value, + size_t *value_size) const; + bool network_vtable_bar(void) const; + bool network_vtable_cas(const void *key, size_t key_size, void *expected, + size_t *expected_size, const void *desired, + size_t desired_size) const; void parse_command_line(std::vector &cmdline); @@ -462,6 +482,9 @@ namespace Realm { std::vector network_segments; std::map module_configs; + + Runtime::NetworkVtable network_vtable; + std::vector network_vtable_data; }; extern RuntimeImpl *runtime_singleton; From b79e726c7b3551e00607df252a921b14f7888c8e Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 6 Dec 2025 00:04:39 -0800 Subject: [PATCH 13/26] realm: initial support for using the vtable to bootstrap ucx --- src/realm/runtime.h | 6 +- src/realm/runtime_impl.cc | 5 ++ src/realm/runtime_impl.h | 2 + src/realm/ucx/bootstrap/bootstrap.cc | 80 ++++++++++++++++++++ src/realm/ucx/bootstrap/bootstrap.h | 26 ++++--- src/realm/ucx/bootstrap/bootstrap_internal.h | 3 +- src/realm/ucx/ucp_internal.cc | 63 ++++++++++++--- 7 files changed, 158 insertions(+), 27 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 1fea10b38b5..223c7bb2ced 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -162,8 +162,10 @@ namespace Realm { * perform an atomic compare-and-swap operation on a key by * checking that the key matches a particular value and if it * does then updating it with the desired value in a single atomic - * operation. If the value of the key does not match the expected - * result, the call should fail, but return the updated expected + * operation. If the key does not yet exist then the transaction + * should create the key with the desirecd value. If the value + * associated with the key does not match the expected result, + * the call should fail, but return the updated expected * value and size as long as it is less than or equal to the * original expected size. If the new value size is larger than * the expected size, then only the expected_size should be updated. diff --git a/src/realm/runtime_impl.cc b/src/realm/runtime_impl.cc index 1a251de199b..676c7d1670f 100644 --- a/src/realm/runtime_impl.cc +++ b/src/realm/runtime_impl.cc @@ -1350,6 +1350,11 @@ namespace Realm { return (network_vtable.bar != nullptr); } + std::optional RuntimeImpl::network_vtable_local_group(void) const + { + return network_vtable_get_int("realm_group"); + } + std::optional RuntimeImpl::network_vtable_local_rank(void) const { return network_vtable_get_int("realm_rank"); diff --git a/src/realm/runtime_impl.h b/src/realm/runtime_impl.h index a17a76f6a60..b54397d1fbc 100644 --- a/src/realm/runtime_impl.h +++ b/src/realm/runtime_impl.h @@ -272,6 +272,8 @@ namespace Realm { bool network_vtable_elastic(void) const; // Are we a single process joining by ourself or part of a group bool network_vtable_group(void) const; + // Our local group + std::optional network_vtable_local_group(void) const; // Our local rank in the group std::optional network_vtable_local_rank(void) const; // The total number of ranks in our group diff --git a/src/realm/ucx/bootstrap/bootstrap.cc b/src/realm/ucx/bootstrap/bootstrap.cc index 3bd98a4e8a8..944ccae31aa 100644 --- a/src/realm/ucx/bootstrap/bootstrap.cc +++ b/src/realm/ucx/bootstrap/bootstrap.cc @@ -18,6 +18,7 @@ // UCP network module internals #include "realm/logging.h" +#include "realm/runtime_impl.h" #include "realm/ucx/bootstrap/bootstrap_internal.h" #include "realm/ucx/bootstrap/bootstrap_loader.h" @@ -28,6 +29,63 @@ namespace Realm { namespace UCP { + static int bootstrap_vtable_allgather(const void *sendbuf, void *recvbuf, int bytes, + struct bootstrap_handle *handle) + { + RuntimeImpl *runtime = get_runtime(); + assert(runtime->has_network_vtable()); + if(runtime->network_vtable_group()) { + // Need our group ID too to avoid interfering with other groups + // that might be trying to join at the same time + const std::optional group = runtime->network_vtable_local_group(); + if(!group) + return 1; + // Synthesize our local key + constexpr size_t max_key_size = 1024; + char key[max_key_size]; + size_t key_size = snprintf(key, max_key_size, "realm_bootstrap_key_%ld_%d", + *group, handle->pg_rank); + // < and not <= because we don't care about null terminator + if(max_key_size < key_size) { + log_ucp.error() << "Internal bootstrap error, key too large"; + // Explode since this is our fault + std::abort(); + } + // Put our local value in the key-value store + if(!runtime->network_vtable_put(key, key_size, sendbuf, bytes)) { + log_ucp.error() << "Failed bootstrap 'put' operation, " + << "the UCX bootstrap will not succeed."; + return 1; + } + // Synchronize to make sure everyone is done + if(!runtime->network_vtable_bar()) + return 1; + // Get all the values from everyone else + uint8_t *ptr = (uint8_t *)recvbuf; + for(int rank = 0; rank < handle->pg_size; rank++) { + key_size = + snprintf(key, max_key_size, "realm_boostrap_key_%ld_%d", *group, rank); + if(max_key_size < key_size) { + log_ucp.error() << "Internal bootstrap error, key too large"; + // Explode since this is our fault + std::abort(); + } + size_t actual_size = bytes; + if(!runtime->network_vtable_get(key, key_size, ptr, &actual_size) || + (actual_size != ((size_t)bytes))) { + log_ucp.error() << "Failed bootstrap 'get' operation, " + << "the UCX boostrap will not succeed"; + return 1; + } + ptr += bytes; + } + } else { + // Not a group, we're just a single process so just copy things over + std::memcpy(recvbuf, sendbuf, bytes); + } + return 0; + } + int bootstrap_init(const BootstrapConfig *config, bootstrap_handle_t *handle) { int status = 0; @@ -59,6 +117,25 @@ namespace Realm { log_ucp.error() << "bootstrap_loader_init failed"; } break; + case BOOTSTRAP_VTABLE: + { + RuntimeImpl *runtime = get_runtime(); + assert(runtime->has_network_vtable()); + // We need to get our local process group information here and fill + // in our all-gather implementation + std::optional rank = runtime->network_vtable_local_rank(); + if(!rank) { + return 1; + } + std::optional ranks = runtime->network_vtable_local_ranks(); + if(!ranks) { + return 1; + } + handle->pg_rank = *rank; + handle->pg_size = *ranks; + handle->allgather = bootstrap_vtable_allgather; + break; + } default: status = BOOTSTRAP_ERROR_INTERNAL; log_ucp.error() << ("invalid bootstrap mode"); @@ -69,6 +146,9 @@ namespace Realm { int bootstrap_finalize(bootstrap_handle_t *handle) { + // Need this for the case of vtable where there is no finalize + if(handle->finalize == nullptr) + return 0; int status = bootstrap_loader_finalize(handle); if(status != 0) { log_ucp.error() << "bootstrap_finalize failed"; diff --git a/src/realm/ucx/bootstrap/bootstrap.h b/src/realm/ucx/bootstrap/bootstrap.h index 86bdcc635a2..6b60cf1292d 100644 --- a/src/realm/ucx/bootstrap/bootstrap.h +++ b/src/realm/ucx/bootstrap/bootstrap.h @@ -30,23 +30,25 @@ enum reduction_op }; typedef struct bootstrap_handle { - int pg_rank; - int pg_size; - int *shared_ranks; - int num_shared_ranks; - int (*barrier)(struct bootstrap_handle *handle); - int (*bcast)(void *buf, int bytes, int root, struct bootstrap_handle *handle); + int pg_rank = 0; + int pg_size = 0; + int *shared_ranks = nullptr; + int num_shared_ranks = 0; + // TODO: Do we really need all these?!? As far as I can tell this interface + // only requires the allgather function and nothing else + int (*barrier)(struct bootstrap_handle *handle) = nullptr; + int (*bcast)(void *buf, int bytes, int root, struct bootstrap_handle *handle) = nullptr; int (*gather)(const void *sendbuf, void *recvbuf, int bytes, int root, - struct bootstrap_handle *handle); + struct bootstrap_handle *handle) = nullptr; int (*allgather)(const void *sendbuf, void *recvbuf, int bytes, - struct bootstrap_handle *handle); + struct bootstrap_handle *handle) = nullptr; int (*alltoall)(const void *sendbuf, void *recvbuf, int bytes, - struct bootstrap_handle *handle); + struct bootstrap_handle *handle) = nullptr; int (*allreduce_ull)(const void *sendbuf, void *recvbuf, int count, - enum reduction_op op, struct bootstrap_handle *handle); + enum reduction_op op, struct bootstrap_handle *handle) = nullptr; int (*allgatherv)(const void *sendbuf, void *recvbuf, int *sizes, int *offsets, - struct bootstrap_handle *handle); - int (*finalize)(struct bootstrap_handle *handle); + struct bootstrap_handle *handle) = nullptr; + int (*finalize)(struct bootstrap_handle *handle) = nullptr; } bootstrap_handle_t; #ifdef __cplusplus diff --git a/src/realm/ucx/bootstrap/bootstrap_internal.h b/src/realm/ucx/bootstrap/bootstrap_internal.h index def7fa6a22b..b09d6aecaf5 100644 --- a/src/realm/ucx/bootstrap/bootstrap_internal.h +++ b/src/realm/ucx/bootstrap/bootstrap_internal.h @@ -32,7 +32,8 @@ namespace Realm { { BOOTSTRAP_MPI, BOOTSTRAP_P2P, - BOOTSTRAP_PLUGIN + BOOTSTRAP_PLUGIN, + BOOTSTRAP_VTABLE, }; struct BootstrapConfig { diff --git a/src/realm/ucx/ucp_internal.cc b/src/realm/ucx/ucp_internal.cc index ce5839b747b..259ff6fbc51 100644 --- a/src/realm/ucx/ucp_internal.cc +++ b/src/realm/ucx/ucp_internal.cc @@ -804,9 +804,11 @@ namespace Realm { assert(!initialized_boot && !initialized_ucp); BootstrapConfig boot_config; - const char *bootstrap_mode_str = getenv("REALM_UCP_BOOTSTRAP_MODE"); - if(bootstrap_mode_str == NULL) { + // If we have a direct vtable from the client alaways prefer that + if(runtime->has_network_vtable()) { + boot_config.mode = Realm::UCP::BOOTSTRAP_VTABLE; + } else if(bootstrap_mode_str == NULL) { // use MPI as the default bootstrap boot_config.mode = Realm::UCP::BOOTSTRAP_MPI; } else if(strcmp(bootstrap_mode_str, "mpi") == 0) { @@ -817,12 +819,14 @@ namespace Realm { boot_config.mode = Realm::UCP::BOOTSTRAP_PLUGIN; } else { log_ucp.fatal() << "invalid UCP bootstrap mode %s" << bootstrap_mode_str; - goto err; + return false; } boot_config.plugin_name = getenv("REALM_UCP_BOOTSTRAP_PLUGIN"); - CHKERR_JUMP(bootstrap_init(&boot_config, &boot_handle), "failed to bootstrap ucp", - log_ucp, err); + if(!bootstrap_init(&boot_config, &boot_handle)) { + log_ucp.error() << "failed to bootstrap ucp"; + return false; + } ucc_comm.reset( new ucc::UCCComm(boot_handle.pg_rank, boot_handle.pg_size, &boot_handle)); @@ -830,27 +834,62 @@ namespace Realm { status = ucc_comm->init(); if(UCC_OK != status) { log_ucp.error() << "Failed to initialize ucc collectives\n"; - goto err; + return false; } // Compute shared ranks if(!compute_shared_ranks()) { log_ucp.error() << "Failed to compute shared ranks \n"; - goto err; + return false; } Network::my_node_id = ucc_comm->get_rank(); Network::max_node_id = ucc_comm->get_world_size() - 1; - Network::all_peers.add_range(0, ucc_comm->get_world_size() - 1); - Network::all_peers.remove(ucc_comm->get_rank()); + if(runtime->network_vtable_elastic()) { + // If we're part of an elastic job then we need to do more work here + uint64_t offset = 0; + if(Network::my_node_id == 0) { + // Do the work to do the CAS to bump the number of processes + constexpr std::string_view key("realm_total_spaces"); + size_t offset_size = sizeof(offset); + uint64_t desired = ucc_comm->get_world_size(); + bool success = false; + // Try this up to 100 times, if we don't succeed then + // we'll time out and fail to join + for(unsigned idx = 0; idx < 100; idx++) { + if(runtime->network_vtable_cas(key.data(), key.size(), &offset, &offset_size, + &desired, sizeof(desired))) { + success = true; + break; + } + if(offset_size != sizeof(offset)) { + log_ucp.error() << "UCP failed to join Realm due to CAS failure"; + return false; + } + offset += ucc_comm->get_world_size(); + } + if(!success) { + log_ucp.error() << "UCP timed out trying to join Realm"; + return false; + } + } + if(ucc_comm->UCC_Bcast(&offset, 1, UCC_DT_UINT64, 0) != UCC_OK) { + log_ucp.error() << "Failed ucc broadcast during boostrap"; + return false; + } + Network::my_node_id += offset; + // TODO: this is only instantaneously valid, it could already + // be stale so we ultimately need to remove this from all of Realm + Network::max_node_id += offset; + } + // TODO: what to do about processes that have already left + Network::all_peers.add_range(0, Network::max_node_id); + Network::all_peers.remove(Network::my_node_id); initialized_boot = true; log_ucp.info() << "bootstrapped UCP network module"; return true; - - err: - return false; } bool UCPInternal::compute_shared_ranks() From f488a35885fb3883de4402cfe5b5b7c3589e80f9 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 6 Dec 2025 00:24:32 -0800 Subject: [PATCH 14/26] ucx: apparently the ucx bootstrap handle class must be pure C --- src/realm/ucx/bootstrap/bootstrap.cc | 9 +++++++++ src/realm/ucx/bootstrap/bootstrap.h | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/realm/ucx/bootstrap/bootstrap.cc b/src/realm/ucx/bootstrap/bootstrap.cc index 944ccae31aa..05646b89901 100644 --- a/src/realm/ucx/bootstrap/bootstrap.cc +++ b/src/realm/ucx/bootstrap/bootstrap.cc @@ -133,7 +133,16 @@ namespace Realm { } handle->pg_rank = *rank; handle->pg_size = *ranks; + handle->shared_ranks = nullptr; + handle->num_shared_ranks = 0; + handle->barrier = nullptr; + handle->bcast = nullptr; + handle->gather = nullptr; handle->allgather = bootstrap_vtable_allgather; + handle->alltoall = nullptr; + handle->allreduce_ull = nullptr; + handle->allgatherv = nullptr; + handle->finalize = nullptr; break; } default: diff --git a/src/realm/ucx/bootstrap/bootstrap.h b/src/realm/ucx/bootstrap/bootstrap.h index 6b60cf1292d..3829dc15f5c 100644 --- a/src/realm/ucx/bootstrap/bootstrap.h +++ b/src/realm/ucx/bootstrap/bootstrap.h @@ -30,25 +30,25 @@ enum reduction_op }; typedef struct bootstrap_handle { - int pg_rank = 0; - int pg_size = 0; - int *shared_ranks = nullptr; - int num_shared_ranks = 0; + int pg_rank; + int pg_size; + int *shared_ranks; + int num_shared_ranks; // TODO: Do we really need all these?!? As far as I can tell this interface // only requires the allgather function and nothing else - int (*barrier)(struct bootstrap_handle *handle) = nullptr; - int (*bcast)(void *buf, int bytes, int root, struct bootstrap_handle *handle) = nullptr; + int (*barrier)(struct bootstrap_handle *handle); + int (*bcast)(void *buf, int bytes, int root, struct bootstrap_handle *handle); int (*gather)(const void *sendbuf, void *recvbuf, int bytes, int root, - struct bootstrap_handle *handle) = nullptr; + struct bootstrap_handle *handle); int (*allgather)(const void *sendbuf, void *recvbuf, int bytes, - struct bootstrap_handle *handle) = nullptr; + struct bootstrap_handle *handle); int (*alltoall)(const void *sendbuf, void *recvbuf, int bytes, - struct bootstrap_handle *handle) = nullptr; + struct bootstrap_handle *handle); int (*allreduce_ull)(const void *sendbuf, void *recvbuf, int count, - enum reduction_op op, struct bootstrap_handle *handle) = nullptr; + enum reduction_op op, struct bootstrap_handle *handle); int (*allgatherv)(const void *sendbuf, void *recvbuf, int *sizes, int *offsets, - struct bootstrap_handle *handle) = nullptr; - int (*finalize)(struct bootstrap_handle *handle) = nullptr; + struct bootstrap_handle *handle); + int (*finalize)(struct bootstrap_handle *handle); } bootstrap_handle_t; #ifdef __cplusplus From c87ee6960564d7e272bb479dabb30c9650928482 Mon Sep 17 00:00:00 2001 From: sbahirnv <159593068+sbahirnv@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:30:51 -0800 Subject: [PATCH 15/26] Add MPI based vtable example (#377) Adds a MPI-based NetworkVtable example that demonstrates how to implement the bootstrap interface. The example builds and runs with `mpirun -n 2` successfully. --- examples/CMakeLists.txt | 8 ++ examples/vtable/CMakeLists.txt | 32 +++++ examples/vtable/realm_bootstrap.cc | 180 +++++++++++++++++++++++++++ examples/vtable/realm_bootstrap.h | 14 +++ examples/vtable/vtable.cc | 26 ++++ src/realm/ucx/bootstrap/bootstrap.cc | 2 +- src/realm/ucx/ucp_internal.cc | 2 +- 7 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 examples/vtable/CMakeLists.txt create mode 100644 examples/vtable/realm_bootstrap.cc create mode 100644 examples/vtable/realm_bootstrap.h create mode 100644 examples/vtable/vtable.cc diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 15bce18a57b..900dc08d866 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -79,6 +79,14 @@ add_example_test(saxpy) set(stencil_ARGS -ll:cpu 4) add_example_test(stencil) +add_subdirectory(vtable) +add_test(NAME example_vtable COMMAND $) +set_property(TEST example_vtable PROPERTY RESOURCE_LOCK ${TEST_RESOURCE_LOCK}) +if(REALM_USE_UCX AND TARGET realm_ucp_bootstrap_mpi) + add_dependencies(vtable realm_ucp_bootstrap_mpi) +endif() +list(APPEND _example_list example_vtable) + set_tests_properties( ${_example_list} PROPERTIES TEST_LAUNCHER diff --git a/examples/vtable/CMakeLists.txt b/examples/vtable/CMakeLists.txt new file mode 100644 index 00000000000..c56b9ec8e76 --- /dev/null +++ b/examples/vtable/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.22 FATAL_ERROR) +project(RealmExample_vtable) + +if(NOT TARGET Realm::Realm) + find_package(Realm REQUIRED) +endif() + +find_package(MPI REQUIRED) + +add_executable(vtable vtable.cc realm_bootstrap.cc) +target_link_libraries(vtable Realm::Realm MPI::MPI_CXX) + +find_library(UCP_LIBRARY NAMES ucp PATHS ENV LD_LIBRARY_PATH) +find_library(UCC_LIBRARY NAMES ucc PATHS ENV LD_LIBRARY_PATH) +find_library(UCS_LIBRARY NAMES ucs PATHS ENV LD_LIBRARY_PATH) +find_library(UCT_LIBRARY NAMES uct PATHS ENV LD_LIBRARY_PATH) + +if(UCP_LIBRARY) + target_link_libraries(vtable ${UCP_LIBRARY}) +endif() +if(UCC_LIBRARY) + target_link_libraries(vtable ${UCC_LIBRARY}) +endif() +if(UCS_LIBRARY) + target_link_libraries(vtable ${UCS_LIBRARY}) +endif() +if(UCT_LIBRARY) + target_link_libraries(vtable ${UCT_LIBRARY}) +endif() + +target_include_directories(vtable PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/examples/vtable/realm_bootstrap.cc b/examples/vtable/realm_bootstrap.cc new file mode 100644 index 00000000000..a6dba87bdc0 --- /dev/null +++ b/examples/vtable/realm_bootstrap.cc @@ -0,0 +1,180 @@ +#include "realm_bootstrap.h" +#include +#include +#include +#include +#include +#include + +namespace App { + +// Realm NetworkVtable required keys +static constexpr const char *REALM_KEY_RANK = "realm_rank"; +static constexpr const char *REALM_KEY_RANKS = "realm_ranks"; +static constexpr const char *REALM_KEY_GROUP = "realm_group"; + +struct VtableContext { + std::map> local_kv_store; + std::map> global_kv_store; + bool pending_sync = false; + int mpi_rank = 0; + int mpi_size = 0; +}; + +static bool app_put(const void *key, size_t key_size, const void *value, size_t value_size, + const void *vtable_data, size_t vtable_data_size) +{ + if(!key || !value || key_size == 0 || !vtable_data) return false; + VtableContext *state = *(VtableContext**)vtable_data; + + std::string k(static_cast(key), key_size); + std::vector v(static_cast(value), + static_cast(value) + value_size); + state->local_kv_store[k] = v; + state->pending_sync = true; + // std::cout << "app_put: key='" << k << "' value_size=" << value_size << std::endl; + return true; +} + +static bool app_get(const void *key, size_t key_size, void *value, size_t *value_size, + const void *vtable_data, size_t vtable_data_size) +{ + if(!key || !value || !value_size || key_size == 0 || !vtable_data) return false; + VtableContext *state = *(VtableContext**)vtable_data; + + std::string k(static_cast(key), key_size); + // std::cout << "app_get: key='" << k << "'" << std::endl; + + if(k == REALM_KEY_RANK) { + if(*value_size < sizeof(uint32_t)) { *value_size = 0; return false; } + uint32_t v = static_cast(state->mpi_rank); + memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + if(k == REALM_KEY_RANKS) { + if(*value_size < sizeof(uint32_t)) { *value_size = 0; return false; } + uint32_t v = static_cast(state->mpi_size); + memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + if(k == REALM_KEY_GROUP) { + if(*value_size < sizeof(uint32_t)) { *value_size = 0; return false; } + uint32_t v = 0; + memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + + auto it = state->global_kv_store.find(k); + if(it != state->global_kv_store.end()) { + if(it->second.size() > *value_size) { + *value_size = it->second.size(); + return false; + } + memcpy(value, it->second.data(), it->second.size()); + *value_size = it->second.size(); + return true; + } + + it = state->local_kv_store.find(k); + if(it != state->local_kv_store.end()) { + if(it->second.size() > *value_size) { + *value_size = it->second.size(); + return false; + } + memcpy(value, it->second.data(), it->second.size()); + *value_size = it->second.size(); + return true; + } + + *value_size = 0; + return true; +} + +static bool app_bar(const void *vtable_data, size_t vtable_data_size) +{ + if(!vtable_data) return false; + VtableContext *state = *(VtableContext**)vtable_data; + + // std::cout << "app_bar called" << std::endl; + + // Exchange local KV data. Two stores needed because bootstrap reuses keys across + // rounds with different sizes - only sync new data to avoid clobbering. + if(state->pending_sync && !state->local_kv_store.empty()) { + std::vector sendbuf; + for(const auto &kv : state->local_kv_store) { + uint32_t klen = kv.first.size(); + uint32_t vlen = kv.second.size(); + sendbuf.insert(sendbuf.end(), reinterpret_cast(&klen), + reinterpret_cast(&klen) + sizeof(klen)); + sendbuf.insert(sendbuf.end(), kv.first.begin(), kv.first.end()); + sendbuf.insert(sendbuf.end(), reinterpret_cast(&vlen), + reinterpret_cast(&vlen) + sizeof(vlen)); + sendbuf.insert(sendbuf.end(), kv.second.begin(), kv.second.end()); + } + + int sendsize = sendbuf.size(); + std::vector recvsizes(state->mpi_size); + MPI_Allgather(&sendsize, 1, MPI_INT, recvsizes.data(), 1, MPI_INT, MPI_COMM_WORLD); + + std::vector displs(state->mpi_size); + int total = 0; + for(int i = 0; i < state->mpi_size; i++) { + displs[i] = total; + total += recvsizes[i]; + } + + std::vector recvbuf(total); + MPI_Allgatherv(sendbuf.data(), sendsize, MPI_BYTE, + recvbuf.data(), recvsizes.data(), displs.data(), MPI_BYTE, + MPI_COMM_WORLD); + + size_t off = 0; + while(off < recvbuf.size()) { + uint32_t klen, vlen; + memcpy(&klen, &recvbuf[off], sizeof(klen)); off += sizeof(klen); + std::string k(reinterpret_cast(&recvbuf[off]), klen); off += klen; + memcpy(&vlen, &recvbuf[off], sizeof(vlen)); off += sizeof(vlen); + std::vector v(&recvbuf[off], &recvbuf[off] + vlen); off += vlen; + state->global_kv_store[k] = v; + } + + state->pending_sync = false; + state->local_kv_store.clear(); + } + + return true; +} + +Realm::Runtime::NetworkVtable create_network_vtable() +{ + MPI_Init(NULL, NULL); + + VtableContext *state = new VtableContext(); + MPI_Comm_rank(MPI_COMM_WORLD, &state->mpi_rank); + MPI_Comm_size(MPI_COMM_WORLD, &state->mpi_size); + + Realm::Runtime::NetworkVtable vtable; + vtable.vtable_data = new VtableContext*(state); + vtable.vtable_data_size = sizeof(VtableContext*); + vtable.put = app_put; + vtable.get = app_get; + vtable.bar = app_bar; + vtable.cas = nullptr; + return vtable; +} + +void finalize_network_vtable(const Realm::Runtime::NetworkVtable &vtable) +{ + if(vtable.vtable_data) { + VtableContext *state = *(VtableContext**)vtable.vtable_data; + delete state; + delete (VtableContext**)vtable.vtable_data; + } + MPI_Finalize(); +} + +} + diff --git a/examples/vtable/realm_bootstrap.h b/examples/vtable/realm_bootstrap.h new file mode 100644 index 00000000000..4d8991c38b5 --- /dev/null +++ b/examples/vtable/realm_bootstrap.h @@ -0,0 +1,14 @@ +#ifndef REALM_BOOTSTRAP_H +#define REALM_BOOTSTRAP_H + +#include "realm/runtime.h" + +namespace App { + +Realm::Runtime::NetworkVtable create_network_vtable(); +void finalize_network_vtable(const Realm::Runtime::NetworkVtable &vtable); + +} + +#endif + diff --git a/examples/vtable/vtable.cc b/examples/vtable/vtable.cc new file mode 100644 index 00000000000..cec3ceab8ff --- /dev/null +++ b/examples/vtable/vtable.cc @@ -0,0 +1,26 @@ +#include "realm.h" +#include "realm/network.h" +#include "realm_bootstrap.h" +#include + +using namespace Realm; + +int main(int argc, char **argv) +{ + Runtime::NetworkVtable vtable = App::create_network_vtable(); + Runtime rt; + + if(!rt.network_init(vtable)) return 1; + if(!rt.create_configs(argc, argv)) return 1; + if(!rt.configure_from_command_line(argc, argv)) return 1; + rt.start(); + + std::cout << "node " << Network::my_node_id << " of " + << (Network::max_node_id + 1) << std::endl; + + rt.shutdown(); + int rc = rt.wait_for_shutdown(); + App::finalize_network_vtable(vtable); + return rc; +} + diff --git a/src/realm/ucx/bootstrap/bootstrap.cc b/src/realm/ucx/bootstrap/bootstrap.cc index 05646b89901..7279a863ec1 100644 --- a/src/realm/ucx/bootstrap/bootstrap.cc +++ b/src/realm/ucx/bootstrap/bootstrap.cc @@ -64,7 +64,7 @@ namespace Realm { uint8_t *ptr = (uint8_t *)recvbuf; for(int rank = 0; rank < handle->pg_size; rank++) { key_size = - snprintf(key, max_key_size, "realm_boostrap_key_%ld_%d", *group, rank); + snprintf(key, max_key_size, "realm_bootstrap_key_%ld_%d", *group, rank); if(max_key_size < key_size) { log_ucp.error() << "Internal bootstrap error, key too large"; // Explode since this is our fault diff --git a/src/realm/ucx/ucp_internal.cc b/src/realm/ucx/ucp_internal.cc index 259ff6fbc51..dd5703acafb 100644 --- a/src/realm/ucx/ucp_internal.cc +++ b/src/realm/ucx/ucp_internal.cc @@ -823,7 +823,7 @@ namespace Realm { } boot_config.plugin_name = getenv("REALM_UCP_BOOTSTRAP_PLUGIN"); - if(!bootstrap_init(&boot_config, &boot_handle)) { + if(bootstrap_init(&boot_config, &boot_handle) != 0) { log_ucp.error() << "failed to bootstrap ucp"; return false; } From 2590e7799bae02615a464a30d7ccb861db509661 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 13 Jan 2026 02:04:10 -0800 Subject: [PATCH 16/26] realm: add missing copyright headers for vtable example --- examples/vtable/CMakeLists.txt | 15 +++++++++++++++ examples/vtable/realm_bootstrap.cc | 17 +++++++++++++++++ examples/vtable/realm_bootstrap.h | 17 +++++++++++++++++ examples/vtable/vtable.cc | 17 +++++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/examples/vtable/CMakeLists.txt b/examples/vtable/CMakeLists.txt index c56b9ec8e76..bb1840f2623 100644 --- a/examples/vtable/CMakeLists.txt +++ b/examples/vtable/CMakeLists.txt @@ -1,3 +1,18 @@ +# Copyright 2025 Stanford University, NVIDIA Corporation +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.22 FATAL_ERROR) project(RealmExample_vtable) diff --git a/examples/vtable/realm_bootstrap.cc b/examples/vtable/realm_bootstrap.cc index a6dba87bdc0..556c17ee65e 100644 --- a/examples/vtable/realm_bootstrap.cc +++ b/examples/vtable/realm_bootstrap.cc @@ -1,3 +1,20 @@ +/* + * Copyright 2025 Stanford University, NVIDIA Corporation + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "realm_bootstrap.h" #include #include diff --git a/examples/vtable/realm_bootstrap.h b/examples/vtable/realm_bootstrap.h index 4d8991c38b5..f95cdf75d19 100644 --- a/examples/vtable/realm_bootstrap.h +++ b/examples/vtable/realm_bootstrap.h @@ -1,3 +1,20 @@ +/* + * Copyright 2025 Stanford University, NVIDIA Corporation + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef REALM_BOOTSTRAP_H #define REALM_BOOTSTRAP_H diff --git a/examples/vtable/vtable.cc b/examples/vtable/vtable.cc index cec3ceab8ff..ec4d1055481 100644 --- a/examples/vtable/vtable.cc +++ b/examples/vtable/vtable.cc @@ -1,3 +1,20 @@ +/* + * Copyright 2025 Stanford University, NVIDIA Corporation + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "realm.h" #include "realm/network.h" #include "realm_bootstrap.h" From 9673e98b883b4c1cc09edba27ae6bed03b6b6a58 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 13 Jan 2026 02:39:36 -0800 Subject: [PATCH 17/26] realm: rename vtable types and cleanup --- examples/CMakeLists.txt | 2 +- examples/vtable/CMakeLists.txt | 2 +- examples/vtable/realm_bootstrap.cc | 32 +++---- examples/vtable/realm_bootstrap.h | 7 +- examples/vtable/vtable.cc | 6 +- src/realm/realm_c.cc | 4 +- src/realm/runtime.h | 6 +- src/realm/runtime_impl.cc | 90 ++++++++++---------- src/realm/runtime_impl.h | 39 ++++----- src/realm/ucx/bootstrap/bootstrap.cc | 20 ++--- src/realm/ucx/bootstrap/bootstrap.h | 2 +- src/realm/ucx/bootstrap/bootstrap_internal.h | 2 +- src/realm/ucx/ucp_internal.cc | 10 +-- 13 files changed, 111 insertions(+), 111 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 900dc08d866..137a247702d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2025 Stanford University, NVIDIA Corporation +# Copyright 2026 Stanford University, NVIDIA Corporation # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/vtable/CMakeLists.txt b/examples/vtable/CMakeLists.txt index bb1840f2623..86a616c14b6 100644 --- a/examples/vtable/CMakeLists.txt +++ b/examples/vtable/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2025 Stanford University, NVIDIA Corporation +# Copyright 2026 Stanford University, NVIDIA Corporation # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/vtable/realm_bootstrap.cc b/examples/vtable/realm_bootstrap.cc index 556c17ee65e..f2f61a1ebd1 100644 --- a/examples/vtable/realm_bootstrap.cc +++ b/examples/vtable/realm_bootstrap.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,18 +25,18 @@ namespace App { -// Realm NetworkVtable required keys -static constexpr const char *REALM_KEY_RANK = "realm_rank"; -static constexpr const char *REALM_KEY_RANKS = "realm_ranks"; -static constexpr const char *REALM_KEY_GROUP = "realm_group"; + // Realm KeyValueStoreVtable required keys + static constexpr const char *REALM_KEY_RANK = "realm_rank"; + static constexpr const char *REALM_KEY_RANKS = "realm_ranks"; + static constexpr const char *REALM_KEY_GROUP = "realm_group"; -struct VtableContext { - std::map> local_kv_store; - std::map> global_kv_store; - bool pending_sync = false; - int mpi_rank = 0; - int mpi_size = 0; -}; + struct VtableContext { + std::map> local_kv_store; + std::map> global_kv_store; + bool pending_sync = false; + int mpi_rank = 0; + int mpi_size = 0; + }; static bool app_put(const void *key, size_t key_size, const void *value, size_t value_size, const void *vtable_data, size_t vtable_data_size) @@ -165,15 +165,15 @@ static bool app_bar(const void *vtable_data, size_t vtable_data_size) return true; } -Realm::Runtime::NetworkVtable create_network_vtable() +Realm::Runtime::KeyValueStoreVtable create_key_value_store_vtable() { MPI_Init(NULL, NULL); VtableContext *state = new VtableContext(); MPI_Comm_rank(MPI_COMM_WORLD, &state->mpi_rank); MPI_Comm_size(MPI_COMM_WORLD, &state->mpi_size); - - Realm::Runtime::NetworkVtable vtable; + + Realm::Runtime::KeyValueStoreVtable vtable; vtable.vtable_data = new VtableContext*(state); vtable.vtable_data_size = sizeof(VtableContext*); vtable.put = app_put; @@ -183,7 +183,7 @@ Realm::Runtime::NetworkVtable create_network_vtable() return vtable; } -void finalize_network_vtable(const Realm::Runtime::NetworkVtable &vtable) +void finalize_key_value_store_vtable(const Realm::Runtime::KeyValueStoreVtable &vtable) { if(vtable.vtable_data) { VtableContext *state = *(VtableContext**)vtable.vtable_data; diff --git a/examples/vtable/realm_bootstrap.h b/examples/vtable/realm_bootstrap.h index f95cdf75d19..66a63af515a 100644 --- a/examples/vtable/realm_bootstrap.h +++ b/examples/vtable/realm_bootstrap.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,8 @@ namespace App { -Realm::Runtime::NetworkVtable create_network_vtable(); -void finalize_network_vtable(const Realm::Runtime::NetworkVtable &vtable); - + Realm::Runtime::KeyValueStoreVtable create_key_value_store_vtable(); + void finalize_key_value_store_vtable(const Realm::Runtime::KeyValueStoreVtable &vtable); } #endif diff --git a/examples/vtable/vtable.cc b/examples/vtable/vtable.cc index ec4d1055481..00fa5a26d42 100644 --- a/examples/vtable/vtable.cc +++ b/examples/vtable/vtable.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ using namespace Realm; int main(int argc, char **argv) { - Runtime::NetworkVtable vtable = App::create_network_vtable(); + Runtime::KeyValueStoreVtable vtable = App::create_key_value_store_vtable(); Runtime rt; if(!rt.network_init(vtable)) return 1; @@ -37,7 +37,7 @@ int main(int argc, char **argv) rt.shutdown(); int rc = rt.wait_for_shutdown(); - App::finalize_network_vtable(vtable); + App::finalize_key_value_store_vtable(vtable); return rc; } diff --git a/src/realm/realm_c.cc b/src/realm/realm_c.cc index dcdc1524f50..4497b2662e9 100644 --- a/src/realm/realm_c.cc +++ b/src/realm/realm_c.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -307,7 +307,7 @@ realm_status_t realm_runtime_init(realm_runtime_t runtime, int *argc, char ***ar argv = &my_argv; } - Realm::Runtime::NetworkVtable vtable; + Realm::Runtime::KeyValueStoreVtable vtable; // TODO: we need to let each of these functions to return a specific error code if(!runtime_impl->network_init(argc, argv, vtable)) { return REALM_ERROR; diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 223c7bb2ced..096155cac6b 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -68,7 +68,7 @@ namespace Realm { // clients can use non-Realm synchronization primitives in the // implementation of these functions and not need to worry about // blocking or impacting forward progress. - struct NetworkVtable { + struct KeyValueStoreVtable { /** * Optional blob of data passed to all the network vtable functions * when they are invoked by Realm. Realm will not attempt to @@ -177,7 +177,7 @@ namespace Realm { const void *desired, size_t desired_size, const void *vtable_data, size_t vtable_data_size) = nullptr; }; - bool network_init(const NetworkVtable &vtable); + bool network_init(const KeyValueStoreVtable &vtable); void parse_command_line(int argc, char **argv); void parse_command_line(std::vector &cmdline, diff --git a/src/realm/runtime_impl.cc b/src/realm/runtime_impl.cc index 676c7d1670f..a9c167ac546 100644 --- a/src/realm/runtime_impl.cc +++ b/src/realm/runtime_impl.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -448,11 +448,11 @@ namespace Realm { bool Runtime::network_init(int *argc, char ***argv) { assert(runtime_singleton != 0); - NetworkVtable vtable; + KeyValueStoreVtable vtable; return static_cast(impl)->network_init(argc, argv, vtable); } - bool Runtime::network_init(const NetworkVtable &vtable) + bool Runtime::network_init(const KeyValueStoreVtable &vtable) { assert(runtime_singleton != 0); return static_cast(impl)->network_init(nullptr, nullptr, vtable); @@ -1171,7 +1171,7 @@ namespace Realm { } bool RuntimeImpl::network_init(int *argc, char ***argv, - const Runtime::NetworkVtable &vtable) + const Runtime::KeyValueStoreVtable &vtable) { #if defined(REALM_USE_UCX) || defined(REALM_USE_MPI) || defined(REALM_USE_GASNET1) || \ defined(REALM_USE_GASNETEX) || defined(REALM_USE_KOKKOS) @@ -1219,12 +1219,12 @@ namespace Realm { return false; } // Safe to save the network vtable - network_vtable = vtable; + key_value_store_vtable = vtable; if(vtable.vtable_data_size > 0) { - network_vtable_data.resize(vtable.vtable_data_size); - uint8_t *data = &network_vtable_data.front(); + key_value_store_vtable_data.resize(vtable.vtable_data_size); + uint8_t *data = &key_value_store_vtable_data.front(); std::memcpy(data, vtable.vtable_data, vtable.vtable_data_size); - network_vtable.vtable_data = data; + key_value_store_vtable.vtable_data = data; } } // if we're given empty or non-existent argc/argv, start from a @@ -1335,43 +1335,43 @@ namespace Realm { return true; } - bool RuntimeImpl::has_network_vtable(void) const + bool RuntimeImpl::has_key_value_store(void) const { - return (network_vtable.put != nullptr); + return (key_value_store_vtable.put != nullptr); } - bool RuntimeImpl::network_vtable_elastic(void) const + bool RuntimeImpl::key_value_store_elastic(void) const { - return (network_vtable.cas != nullptr); + return (key_value_store_vtable.cas != nullptr); } - bool RuntimeImpl::network_vtable_group(void) const + bool RuntimeImpl::key_value_store_group(void) const { - return (network_vtable.bar != nullptr); + return (key_value_store_vtable.bar != nullptr); } - std::optional RuntimeImpl::network_vtable_local_group(void) const + std::optional RuntimeImpl::key_value_store_local_group(void) const { - return network_vtable_get_int("realm_group"); + return key_value_store_get_int("realm_group"); } - std::optional RuntimeImpl::network_vtable_local_rank(void) const + std::optional RuntimeImpl::key_value_store_local_rank(void) const { - return network_vtable_get_int("realm_rank"); + return key_value_store_get_int("realm_rank"); } - std::optional RuntimeImpl::network_vtable_local_ranks(void) const + std::optional RuntimeImpl::key_value_store_local_ranks(void) const { - return network_vtable_get_int("realm_ranks"); + return key_value_store_get_int("realm_ranks"); } std::optional - RuntimeImpl::network_vtable_get_int(const std::string_view &key) const + RuntimeImpl::key_value_store_get_int(const std::string_view &key) const { constexpr size_t max_int_size = sizeof(uint64_t); uint8_t buffer[max_int_size]; size_t actual_size = max_int_size; - if(!network_vtable_get(key.data(), key.size(), buffer, &actual_size) || + if(!key_value_store_get(key.data(), key.size(), buffer, &actual_size) || (actual_size == 0)) { log_runtime.error() << "Unable to find expected key " << key << " in key-value store for vtable 'get'. This key " @@ -1404,39 +1404,39 @@ namespace Realm { } } - bool RuntimeImpl::network_vtable_put(const void *key, size_t key_size, - const void *value, size_t value_size) const + bool RuntimeImpl::key_value_store_put(const void *key, size_t key_size, + const void *value, size_t value_size) const { - assert(network_vtable.put != nullptr); - return (*network_vtable.put)(key, key_size, value, value_size, - network_vtable.vtable_data, - network_vtable.vtable_data_size); + assert(key_value_store_vtable.put != nullptr); + return (*key_value_store_vtable.put)(key, key_size, value, value_size, + key_value_store_vtable.vtable_data, + key_value_store_vtable.vtable_data_size); } - bool RuntimeImpl::network_vtable_get(const void *key, size_t key_size, void *value, - size_t *value_size) const + bool RuntimeImpl::key_value_store_get(const void *key, size_t key_size, void *value, + size_t *value_size) const { - assert(network_vtable.get != nullptr); - return (*network_vtable.get)(key, key_size, value, value_size, - network_vtable.vtable_data, - network_vtable.vtable_data_size); + assert(key_value_store_vtable.get != nullptr); + return (*key_value_store_vtable.get)(key, key_size, value, value_size, + key_value_store_vtable.vtable_data, + key_value_store_vtable.vtable_data_size); } - bool RuntimeImpl::network_vtable_bar(void) const + bool RuntimeImpl::key_value_store_bar(void) const { - assert(network_vtable.bar != nullptr); - return (*network_vtable.bar)(network_vtable.vtable_data, - network_vtable.vtable_data_size); + assert(key_value_store_vtable.bar != nullptr); + return (*key_value_store_vtable.bar)(key_value_store_vtable.vtable_data, + key_value_store_vtable.vtable_data_size); } - bool RuntimeImpl::network_vtable_cas(const void *key, size_t key_size, void *expected, - size_t *expected_size, const void *desired, - size_t desired_size) const + bool RuntimeImpl::key_value_store_cas(const void *key, size_t key_size, void *expected, + size_t *expected_size, const void *desired, + size_t desired_size) const { - assert(network_vtable.cas != nullptr); - return (*network_vtable.cas)(key, key_size, expected, expected_size, desired, - desired_size, network_vtable.vtable_data, - network_vtable.vtable_data_size); + assert(key_value_store_vtable.cas != nullptr); + return (*key_value_store_vtable.cas)(key, key_size, expected, expected_size, desired, + desired_size, key_value_store_vtable.vtable_data, + key_value_store_vtable.vtable_data_size); } template diff --git a/src/realm/runtime_impl.h b/src/realm/runtime_impl.h index b54397d1fbc..ce6d3c858e0 100644 --- a/src/realm/runtime_impl.h +++ b/src/realm/runtime_impl.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Stanford University, NVIDIA Corporation + * Copyright 2026 Stanford University, NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -266,28 +266,29 @@ namespace Realm { RuntimeImpl(void); ~RuntimeImpl(void); - bool network_init(int *argc, char ***argv, const Runtime::NetworkVtable &vtable); - bool has_network_vtable(void) const; + bool network_init(int *argc, char ***argv, + const Runtime::KeyValueStoreVtable &vtable); + bool has_key_value_store(void) const; // Is this an elastic Realm - bool network_vtable_elastic(void) const; + bool key_value_store_elastic(void) const; // Are we a single process joining by ourself or part of a group - bool network_vtable_group(void) const; + bool key_value_store_group(void) const; // Our local group - std::optional network_vtable_local_group(void) const; + std::optional key_value_store_local_group(void) const; // Our local rank in the group - std::optional network_vtable_local_rank(void) const; + std::optional key_value_store_local_rank(void) const; // The total number of ranks in our group - std::optional network_vtable_local_ranks(void) const; + std::optional key_value_store_local_ranks(void) const; // Helper for getting integers of unknown size - std::optional network_vtable_get_int(const std::string_view &key) const; - bool network_vtable_put(const void *key, size_t key_size, const void *value, - size_t value_size) const; - bool network_vtable_get(const void *key, size_t key_size, void *value, - size_t *value_size) const; - bool network_vtable_bar(void) const; - bool network_vtable_cas(const void *key, size_t key_size, void *expected, - size_t *expected_size, const void *desired, - size_t desired_size) const; + std::optional key_value_store_get_int(const std::string_view &key) const; + bool key_value_store_put(const void *key, size_t key_size, const void *value, + size_t value_size) const; + bool key_value_store_get(const void *key, size_t key_size, void *value, + size_t *value_size) const; + bool key_value_store_bar(void) const; + bool key_value_store_cas(const void *key, size_t key_size, void *expected, + size_t *expected_size, const void *desired, + size_t desired_size) const; void parse_command_line(std::vector &cmdline); @@ -485,8 +486,8 @@ namespace Realm { std::map module_configs; - Runtime::NetworkVtable network_vtable; - std::vector network_vtable_data; + Runtime::KeyValueStoreVtable key_value_store_vtable; + std::vector key_value_store_vtable_data; }; extern RuntimeImpl *runtime_singleton; diff --git a/src/realm/ucx/bootstrap/bootstrap.cc b/src/realm/ucx/bootstrap/bootstrap.cc index 7279a863ec1..6202fdc38b2 100644 --- a/src/realm/ucx/bootstrap/bootstrap.cc +++ b/src/realm/ucx/bootstrap/bootstrap.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 NVIDIA Corporation + * Copyright 2026 NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,11 +33,11 @@ namespace Realm { struct bootstrap_handle *handle) { RuntimeImpl *runtime = get_runtime(); - assert(runtime->has_network_vtable()); - if(runtime->network_vtable_group()) { + assert(runtime->has_key_value_store()); + if(runtime->key_value_store_group()) { // Need our group ID too to avoid interfering with other groups // that might be trying to join at the same time - const std::optional group = runtime->network_vtable_local_group(); + const std::optional group = runtime->key_value_store_local_group(); if(!group) return 1; // Synthesize our local key @@ -52,13 +52,13 @@ namespace Realm { std::abort(); } // Put our local value in the key-value store - if(!runtime->network_vtable_put(key, key_size, sendbuf, bytes)) { + if(!runtime->key_value_store_put(key, key_size, sendbuf, bytes)) { log_ucp.error() << "Failed bootstrap 'put' operation, " << "the UCX bootstrap will not succeed."; return 1; } // Synchronize to make sure everyone is done - if(!runtime->network_vtable_bar()) + if(!runtime->key_value_store_bar()) return 1; // Get all the values from everyone else uint8_t *ptr = (uint8_t *)recvbuf; @@ -71,7 +71,7 @@ namespace Realm { std::abort(); } size_t actual_size = bytes; - if(!runtime->network_vtable_get(key, key_size, ptr, &actual_size) || + if(!runtime->key_value_store_get(key, key_size, ptr, &actual_size) || (actual_size != ((size_t)bytes))) { log_ucp.error() << "Failed bootstrap 'get' operation, " << "the UCX boostrap will not succeed"; @@ -120,14 +120,14 @@ namespace Realm { case BOOTSTRAP_VTABLE: { RuntimeImpl *runtime = get_runtime(); - assert(runtime->has_network_vtable()); + assert(runtime->has_key_value_store()); // We need to get our local process group information here and fill // in our all-gather implementation - std::optional rank = runtime->network_vtable_local_rank(); + std::optional rank = runtime->key_value_store_local_rank(); if(!rank) { return 1; } - std::optional ranks = runtime->network_vtable_local_ranks(); + std::optional ranks = runtime->key_value_store_local_ranks(); if(!ranks) { return 1; } diff --git a/src/realm/ucx/bootstrap/bootstrap.h b/src/realm/ucx/bootstrap/bootstrap.h index 3829dc15f5c..bfaeb8eed87 100644 --- a/src/realm/ucx/bootstrap/bootstrap.h +++ b/src/realm/ucx/bootstrap/bootstrap.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 NVIDIA Corporation + * Copyright 2026 NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/realm/ucx/bootstrap/bootstrap_internal.h b/src/realm/ucx/bootstrap/bootstrap_internal.h index b09d6aecaf5..fbb4ef45e34 100644 --- a/src/realm/ucx/bootstrap/bootstrap_internal.h +++ b/src/realm/ucx/bootstrap/bootstrap_internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 NVIDIA Corporation + * Copyright 2026 NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/realm/ucx/ucp_internal.cc b/src/realm/ucx/ucp_internal.cc index dd5703acafb..566fc4f83c5 100644 --- a/src/realm/ucx/ucp_internal.cc +++ b/src/realm/ucx/ucp_internal.cc @@ -1,5 +1,5 @@ /* - * Copyright 2025 NVIDIA Corporation + * Copyright 2026 NVIDIA Corporation * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -806,7 +806,7 @@ namespace Realm { BootstrapConfig boot_config; const char *bootstrap_mode_str = getenv("REALM_UCP_BOOTSTRAP_MODE"); // If we have a direct vtable from the client alaways prefer that - if(runtime->has_network_vtable()) { + if(runtime->has_key_value_store()) { boot_config.mode = Realm::UCP::BOOTSTRAP_VTABLE; } else if(bootstrap_mode_str == NULL) { // use MPI as the default bootstrap @@ -845,7 +845,7 @@ namespace Realm { Network::my_node_id = ucc_comm->get_rank(); Network::max_node_id = ucc_comm->get_world_size() - 1; - if(runtime->network_vtable_elastic()) { + if(runtime->key_value_store_elastic()) { // If we're part of an elastic job then we need to do more work here uint64_t offset = 0; if(Network::my_node_id == 0) { @@ -857,8 +857,8 @@ namespace Realm { // Try this up to 100 times, if we don't succeed then // we'll time out and fail to join for(unsigned idx = 0; idx < 100; idx++) { - if(runtime->network_vtable_cas(key.data(), key.size(), &offset, &offset_size, - &desired, sizeof(desired))) { + if(runtime->key_value_store_cas(key.data(), key.size(), &offset, &offset_size, + &desired, sizeof(desired))) { success = true; break; } From 2404ffc9289d413c0ca536db42f58b2ae34b58f3 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 13 Jan 2026 02:43:57 -0800 Subject: [PATCH 18/26] examples: only add vtable example if UCX is supported --- examples/CMakeLists.txt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 137a247702d..8340c1b8152 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -78,14 +78,9 @@ endmacro() add_example_test(saxpy) set(stencil_ARGS -ll:cpu 4) add_example_test(stencil) - -add_subdirectory(vtable) -add_test(NAME example_vtable COMMAND $) -set_property(TEST example_vtable PROPERTY RESOURCE_LOCK ${TEST_RESOURCE_LOCK}) -if(REALM_USE_UCX AND TARGET realm_ucp_bootstrap_mpi) - add_dependencies(vtable realm_ucp_bootstrap_mpi) +if(REALM_USE_UCX) +add_example_test(vtable) endif() -list(APPEND _example_list example_vtable) set_tests_properties( ${_example_list} From a560fa2ff1ee12445535b27e65e4a2ef8094e9d8 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 13 Jan 2026 02:48:10 -0800 Subject: [PATCH 19/26] examples: fix vtable example formatting --- examples/vtable/realm_bootstrap.cc | 302 +++++++++++++++-------------- examples/vtable/realm_bootstrap.h | 3 +- examples/vtable/vtable.cc | 20 +- 3 files changed, 170 insertions(+), 155 deletions(-) diff --git a/examples/vtable/realm_bootstrap.cc b/examples/vtable/realm_bootstrap.cc index f2f61a1ebd1..b7e791d741b 100644 --- a/examples/vtable/realm_bootstrap.cc +++ b/examples/vtable/realm_bootstrap.cc @@ -38,160 +38,174 @@ namespace App { int mpi_size = 0; }; -static bool app_put(const void *key, size_t key_size, const void *value, size_t value_size, - const void *vtable_data, size_t vtable_data_size) -{ - if(!key || !value || key_size == 0 || !vtable_data) return false; - VtableContext *state = *(VtableContext**)vtable_data; - - std::string k(static_cast(key), key_size); - std::vector v(static_cast(value), - static_cast(value) + value_size); - state->local_kv_store[k] = v; - state->pending_sync = true; - // std::cout << "app_put: key='" << k << "' value_size=" << value_size << std::endl; - return true; -} - -static bool app_get(const void *key, size_t key_size, void *value, size_t *value_size, - const void *vtable_data, size_t vtable_data_size) -{ - if(!key || !value || !value_size || key_size == 0 || !vtable_data) return false; - VtableContext *state = *(VtableContext**)vtable_data; - - std::string k(static_cast(key), key_size); - // std::cout << "app_get: key='" << k << "'" << std::endl; - - if(k == REALM_KEY_RANK) { - if(*value_size < sizeof(uint32_t)) { *value_size = 0; return false; } - uint32_t v = static_cast(state->mpi_rank); - memcpy(value, &v, sizeof(v)); - *value_size = sizeof(v); - return true; - } - if(k == REALM_KEY_RANKS) { - if(*value_size < sizeof(uint32_t)) { *value_size = 0; return false; } - uint32_t v = static_cast(state->mpi_size); - memcpy(value, &v, sizeof(v)); - *value_size = sizeof(v); - return true; - } - if(k == REALM_KEY_GROUP) { - if(*value_size < sizeof(uint32_t)) { *value_size = 0; return false; } - uint32_t v = 0; - memcpy(value, &v, sizeof(v)); - *value_size = sizeof(v); + static bool app_put(const void *key, size_t key_size, const void *value, + size_t value_size, const void *vtable_data, size_t vtable_data_size) + { + if(!key || !value || key_size == 0 || !vtable_data) + return false; + VtableContext *state = *(VtableContext **)vtable_data; + + std::string k(static_cast(key), key_size); + std::vector v(static_cast(value), + static_cast(value) + value_size); + state->local_kv_store[k] = v; + state->pending_sync = true; + // std::cout << "app_put: key='" << k << "' value_size=" << value_size << std::endl; return true; } - - auto it = state->global_kv_store.find(k); - if(it != state->global_kv_store.end()) { - if(it->second.size() > *value_size) { - *value_size = it->second.size(); + + static bool app_get(const void *key, size_t key_size, void *value, size_t *value_size, + const void *vtable_data, size_t vtable_data_size) + { + if(!key || !value || !value_size || key_size == 0 || !vtable_data) return false; + VtableContext *state = *(VtableContext **)vtable_data; + + std::string k(static_cast(key), key_size); + // std::cout << "app_get: key='" << k << "'" << std::endl; + + if(k == REALM_KEY_RANK) { + if(*value_size < sizeof(uint32_t)) { + *value_size = 0; + return false; + } + uint32_t v = static_cast(state->mpi_rank); + memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; } - memcpy(value, it->second.data(), it->second.size()); - *value_size = it->second.size(); - return true; - } - - it = state->local_kv_store.find(k); - if(it != state->local_kv_store.end()) { - if(it->second.size() > *value_size) { + if(k == REALM_KEY_RANKS) { + if(*value_size < sizeof(uint32_t)) { + *value_size = 0; + return false; + } + uint32_t v = static_cast(state->mpi_size); + memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + if(k == REALM_KEY_GROUP) { + if(*value_size < sizeof(uint32_t)) { + *value_size = 0; + return false; + } + uint32_t v = 0; + memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + + auto it = state->global_kv_store.find(k); + if(it != state->global_kv_store.end()) { + if(it->second.size() > *value_size) { + *value_size = it->second.size(); + return false; + } + memcpy(value, it->second.data(), it->second.size()); *value_size = it->second.size(); - return false; + return true; + } + + it = state->local_kv_store.find(k); + if(it != state->local_kv_store.end()) { + if(it->second.size() > *value_size) { + *value_size = it->second.size(); + return false; + } + memcpy(value, it->second.data(), it->second.size()); + *value_size = it->second.size(); + return true; } - memcpy(value, it->second.data(), it->second.size()); - *value_size = it->second.size(); + + *value_size = 0; return true; } - - *value_size = 0; - return true; -} - -static bool app_bar(const void *vtable_data, size_t vtable_data_size) -{ - if(!vtable_data) return false; - VtableContext *state = *(VtableContext**)vtable_data; - - // std::cout << "app_bar called" << std::endl; - - // Exchange local KV data. Two stores needed because bootstrap reuses keys across - // rounds with different sizes - only sync new data to avoid clobbering. - if(state->pending_sync && !state->local_kv_store.empty()) { - std::vector sendbuf; - for(const auto &kv : state->local_kv_store) { - uint32_t klen = kv.first.size(); - uint32_t vlen = kv.second.size(); - sendbuf.insert(sendbuf.end(), reinterpret_cast(&klen), - reinterpret_cast(&klen) + sizeof(klen)); - sendbuf.insert(sendbuf.end(), kv.first.begin(), kv.first.end()); - sendbuf.insert(sendbuf.end(), reinterpret_cast(&vlen), - reinterpret_cast(&vlen) + sizeof(vlen)); - sendbuf.insert(sendbuf.end(), kv.second.begin(), kv.second.end()); - } - - int sendsize = sendbuf.size(); - std::vector recvsizes(state->mpi_size); - MPI_Allgather(&sendsize, 1, MPI_INT, recvsizes.data(), 1, MPI_INT, MPI_COMM_WORLD); - - std::vector displs(state->mpi_size); - int total = 0; - for(int i = 0; i < state->mpi_size; i++) { - displs[i] = total; - total += recvsizes[i]; - } - - std::vector recvbuf(total); - MPI_Allgatherv(sendbuf.data(), sendsize, MPI_BYTE, - recvbuf.data(), recvsizes.data(), displs.data(), MPI_BYTE, - MPI_COMM_WORLD); - - size_t off = 0; - while(off < recvbuf.size()) { - uint32_t klen, vlen; - memcpy(&klen, &recvbuf[off], sizeof(klen)); off += sizeof(klen); - std::string k(reinterpret_cast(&recvbuf[off]), klen); off += klen; - memcpy(&vlen, &recvbuf[off], sizeof(vlen)); off += sizeof(vlen); - std::vector v(&recvbuf[off], &recvbuf[off] + vlen); off += vlen; - state->global_kv_store[k] = v; + + static bool app_bar(const void *vtable_data, size_t vtable_data_size) + { + if(!vtable_data) + return false; + VtableContext *state = *(VtableContext **)vtable_data; + + // std::cout << "app_bar called" << std::endl; + + // Exchange local KV data. Two stores needed because bootstrap reuses keys across + // rounds with different sizes - only sync new data to avoid clobbering. + if(state->pending_sync && !state->local_kv_store.empty()) { + std::vector sendbuf; + for(const auto &kv : state->local_kv_store) { + uint32_t klen = kv.first.size(); + uint32_t vlen = kv.second.size(); + sendbuf.insert(sendbuf.end(), reinterpret_cast(&klen), + reinterpret_cast(&klen) + sizeof(klen)); + sendbuf.insert(sendbuf.end(), kv.first.begin(), kv.first.end()); + sendbuf.insert(sendbuf.end(), reinterpret_cast(&vlen), + reinterpret_cast(&vlen) + sizeof(vlen)); + sendbuf.insert(sendbuf.end(), kv.second.begin(), kv.second.end()); + } + + int sendsize = sendbuf.size(); + std::vector recvsizes(state->mpi_size); + MPI_Allgather(&sendsize, 1, MPI_INT, recvsizes.data(), 1, MPI_INT, MPI_COMM_WORLD); + + std::vector displs(state->mpi_size); + int total = 0; + for(int i = 0; i < state->mpi_size; i++) { + displs[i] = total; + total += recvsizes[i]; + } + + std::vector recvbuf(total); + MPI_Allgatherv(sendbuf.data(), sendsize, MPI_BYTE, recvbuf.data(), recvsizes.data(), + displs.data(), MPI_BYTE, MPI_COMM_WORLD); + + size_t off = 0; + while(off < recvbuf.size()) { + uint32_t klen, vlen; + memcpy(&klen, &recvbuf[off], sizeof(klen)); + off += sizeof(klen); + std::string k(reinterpret_cast(&recvbuf[off]), klen); + off += klen; + memcpy(&vlen, &recvbuf[off], sizeof(vlen)); + off += sizeof(vlen); + std::vector v(&recvbuf[off], &recvbuf[off] + vlen); + off += vlen; + state->global_kv_store[k] = v; + } + + state->pending_sync = false; + state->local_kv_store.clear(); } - - state->pending_sync = false; - state->local_kv_store.clear(); + + return true; } - - return true; -} - -Realm::Runtime::KeyValueStoreVtable create_key_value_store_vtable() -{ - MPI_Init(NULL, NULL); - - VtableContext *state = new VtableContext(); - MPI_Comm_rank(MPI_COMM_WORLD, &state->mpi_rank); - MPI_Comm_size(MPI_COMM_WORLD, &state->mpi_size); - - Realm::Runtime::KeyValueStoreVtable vtable; - vtable.vtable_data = new VtableContext*(state); - vtable.vtable_data_size = sizeof(VtableContext*); - vtable.put = app_put; - vtable.get = app_get; - vtable.bar = app_bar; - vtable.cas = nullptr; - return vtable; -} - -void finalize_key_value_store_vtable(const Realm::Runtime::KeyValueStoreVtable &vtable) -{ - if(vtable.vtable_data) { - VtableContext *state = *(VtableContext**)vtable.vtable_data; - delete state; - delete (VtableContext**)vtable.vtable_data; + + Realm::Runtime::KeyValueStoreVtable create_key_value_store_vtable() + { + MPI_Init(NULL, NULL); + + VtableContext *state = new VtableContext(); + MPI_Comm_rank(MPI_COMM_WORLD, &state->mpi_rank); + MPI_Comm_size(MPI_COMM_WORLD, &state->mpi_size); + + Realm::Runtime::KeyValueStoreVtable vtable; + vtable.vtable_data = new VtableContext *(state); + vtable.vtable_data_size = sizeof(VtableContext *); + vtable.put = app_put; + vtable.get = app_get; + vtable.bar = app_bar; + vtable.cas = nullptr; + return vtable; } - MPI_Finalize(); -} -} + void finalize_key_value_store_vtable(const Realm::Runtime::KeyValueStoreVtable &vtable) + { + if(vtable.vtable_data) { + VtableContext *state = *(VtableContext **)vtable.vtable_data; + delete state; + delete(VtableContext **)vtable.vtable_data; + } + MPI_Finalize(); + } +} // namespace App diff --git a/examples/vtable/realm_bootstrap.h b/examples/vtable/realm_bootstrap.h index 66a63af515a..cde6151a633 100644 --- a/examples/vtable/realm_bootstrap.h +++ b/examples/vtable/realm_bootstrap.h @@ -24,7 +24,6 @@ namespace App { Realm::Runtime::KeyValueStoreVtable create_key_value_store_vtable(); void finalize_key_value_store_vtable(const Realm::Runtime::KeyValueStoreVtable &vtable); -} +} // namespace App #endif - diff --git a/examples/vtable/vtable.cc b/examples/vtable/vtable.cc index 00fa5a26d42..bc36be57081 100644 --- a/examples/vtable/vtable.cc +++ b/examples/vtable/vtable.cc @@ -26,18 +26,20 @@ int main(int argc, char **argv) { Runtime::KeyValueStoreVtable vtable = App::create_key_value_store_vtable(); Runtime rt; - - if(!rt.network_init(vtable)) return 1; - if(!rt.create_configs(argc, argv)) return 1; - if(!rt.configure_from_command_line(argc, argv)) return 1; + + if(!rt.network_init(vtable)) + return 1; + if(!rt.create_configs(argc, argv)) + return 1; + if(!rt.configure_from_command_line(argc, argv)) + return 1; rt.start(); - - std::cout << "node " << Network::my_node_id << " of " - << (Network::max_node_id + 1) << std::endl; - + + std::cout << "node " << Network::my_node_id << " of " << (Network::max_node_id + 1) + << std::endl; + rt.shutdown(); int rc = rt.wait_for_shutdown(); App::finalize_key_value_store_vtable(vtable); return rc; } - From 2ff5db29220240ffc595ace9d7c18b9d33d61f7e Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 14 Jan 2026 23:53:16 -0800 Subject: [PATCH 20/26] realm: add key-value store unit tests --- tests/CMakeLists.txt | 1 + tests/unit_tests/key_value_store_test.cc | 654 +++++++++++++++++++++++ 2 files changed, 655 insertions(+) create mode 100644 tests/unit_tests/key_value_store_test.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77bc2d6efba..56ffb146967 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -121,6 +121,7 @@ list( hpp_span_test.cc hpp_runtime_test.cc hpp_event_test.cc + key_value_store_test.cc ) list(TRANSFORM REALM_UNIT_TESTS PREPEND "${REALM_TEST_DIR}/unit_tests/") add_executable(realm_unit_tests ${REALM_UNIT_TESTS}) diff --git a/tests/unit_tests/key_value_store_test.cc b/tests/unit_tests/key_value_store_test.cc new file mode 100644 index 00000000000..632b4c3013b --- /dev/null +++ b/tests/unit_tests/key_value_store_test.cc @@ -0,0 +1,654 @@ +/* + * Copyright 2025 Stanford University, NVIDIA Corporation + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "realm/runtime_impl.h" +#include "realm/runtime.h" + +#include +#include +#include +#include +#include + +using namespace Realm; + +// Mock key-value store context for testing +struct MockKVStoreContext { + std::map> store; + int barrier_call_count = 0; + int put_call_count = 0; + int get_call_count = 0; + int cas_call_count = 0; + bool barrier_should_fail = false; + bool put_should_fail = false; + bool get_should_fail = false; + bool cas_should_fail = false; + + // For group testing + uint64_t rank = 0; + uint64_t ranks = 1; + uint64_t group = 0; +}; + +// Mock vtable callbacks +static bool mock_put(const void *key, size_t key_size, const void *value, + size_t value_size, const void *vtable_data, size_t vtable_data_size) +{ + if(!key || !value || key_size == 0 || !vtable_data) + return false; + + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; + ctx->put_call_count++; + + if(ctx->put_should_fail) + return false; + + std::string k(static_cast(key), key_size); + std::vector v(static_cast(value), + static_cast(value) + value_size); + ctx->store[k] = v; + return true; +} + +static bool mock_get(const void *key, size_t key_size, void *value, size_t *value_size, + const void *vtable_data, size_t vtable_data_size) +{ + if(!key || !value || !value_size || key_size == 0 || !vtable_data) + return false; + + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; + ctx->get_call_count++; + + if(ctx->get_should_fail) + return false; + + std::string k(static_cast(key), key_size); + + // Handle special realm keys + if(k == "realm_rank") { + if(*value_size < sizeof(uint32_t)) { + *value_size = sizeof(uint32_t); + return true; + } + uint32_t v = static_cast(ctx->rank); + std::memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + + if(k == "realm_ranks") { + if(*value_size < sizeof(uint32_t)) { + *value_size = sizeof(uint32_t); + return true; + } + uint32_t v = static_cast(ctx->ranks); + std::memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + + if(k == "realm_group") { + if(*value_size < sizeof(uint32_t)) { + *value_size = sizeof(uint32_t); + return true; + } + uint32_t v = static_cast(ctx->group); + std::memcpy(value, &v, sizeof(v)); + *value_size = sizeof(v); + return true; + } + + auto it = ctx->store.find(k); + if(it != ctx->store.end()) { + if(it->second.size() > *value_size) { + *value_size = it->second.size(); + return true; + } + std::memcpy(value, it->second.data(), it->second.size()); + *value_size = it->second.size(); + return true; + } + + // Key not found + *value_size = 0; + return true; +} + +static bool mock_bar(const void *vtable_data, size_t vtable_data_size) +{ + if(!vtable_data) + return false; + + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; + ctx->barrier_call_count++; + + return !ctx->barrier_should_fail; +} + +static bool mock_cas(const void *key, size_t key_size, void *expected, size_t *expected_size, + const void *desired, size_t desired_size, const void *vtable_data, + size_t vtable_data_size) +{ + if(!key || !expected || !expected_size || !desired || key_size == 0 || !vtable_data) + return false; + + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; + ctx->cas_call_count++; + + if(ctx->cas_should_fail) + return false; + + std::string k(static_cast(key), key_size); + + auto it = ctx->store.find(k); + if(it == ctx->store.end()) { + // Key doesn't exist, create it with desired value + std::vector v(static_cast(desired), + static_cast(desired) + desired_size); + ctx->store[k] = v; + return true; + } + + // Key exists, check if current value matches expected + if(it->second.size() == *expected_size && + std::memcmp(it->second.data(), expected, *expected_size) == 0) { + // Match! Update to desired + std::vector v(static_cast(desired), + static_cast(desired) + desired_size); + ctx->store[k] = v; + return true; + } + + // No match, return current value in expected + if(it->second.size() > *expected_size) { + *expected_size = it->second.size(); + return false; + } + std::memcpy(expected, it->second.data(), it->second.size()); + *expected_size = it->second.size(); + return false; +} + +class KeyValueStoreTestBase : public ::testing::Test { +protected: + void SetUp() override + { + ctx = new MockKVStoreContext(); + ctx_ptr = new MockKVStoreContext *(ctx); + + // Create a minimal runtime for testing + runtime_impl = new RuntimeImpl(); + } + + void TearDown() override + { + delete runtime_impl; + delete ctx; + delete ctx_ptr; + } + + void InitVtable(bool with_put = true, bool with_get = true, + bool with_bar = false, bool with_cas = false) + { + vtable.vtable_data = ctx_ptr; + vtable.vtable_data_size = sizeof(MockKVStoreContext *); + vtable.put = with_put ? mock_put : nullptr; + vtable.get = with_get ? mock_get : nullptr; + vtable.bar = with_bar ? mock_bar : nullptr; + vtable.cas = with_cas ? mock_cas : nullptr; + + // Directly set the vtable in runtime_impl for testing + runtime_impl->key_value_store_vtable = vtable; + } + + MockKVStoreContext *ctx; + MockKVStoreContext **ctx_ptr; + RuntimeImpl *runtime_impl; + Runtime::KeyValueStoreVtable vtable; +}; + +// Test has_key_value_store +TEST_F(KeyValueStoreTestBase, HasKeyValueStore_WithPut) +{ + InitVtable(true, true, false, false); + EXPECT_TRUE(runtime_impl->has_key_value_store()); +} + +TEST_F(KeyValueStoreTestBase, HasKeyValueStore_WithoutPut) +{ + InitVtable(false, true, false, false); + EXPECT_FALSE(runtime_impl->has_key_value_store()); +} + +// Test key_value_store_elastic +TEST_F(KeyValueStoreTestBase, Elastic_WithCas) +{ + InitVtable(true, true, false, true); + EXPECT_TRUE(runtime_impl->key_value_store_elastic()); +} + +TEST_F(KeyValueStoreTestBase, Elastic_WithoutCas) +{ + InitVtable(true, true, false, false); + EXPECT_FALSE(runtime_impl->key_value_store_elastic()); +} + +// Test key_value_store_group +TEST_F(KeyValueStoreTestBase, Group_WithBar) +{ + InitVtable(true, true, true, false); + EXPECT_TRUE(runtime_impl->key_value_store_group()); +} + +TEST_F(KeyValueStoreTestBase, Group_WithoutBar) +{ + InitVtable(true, true, false, false); + EXPECT_FALSE(runtime_impl->key_value_store_group()); +} + +// Test key_value_store_local_group +TEST_F(KeyValueStoreTestBase, LocalGroup_Success) +{ + ctx->group = 42; + InitVtable(true, true, true, false); + + auto result = runtime_impl->key_value_store_local_group(); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), 42); +} + +TEST_F(KeyValueStoreTestBase, LocalGroup_DifferentSizes) +{ + InitVtable(true, true, true, false); + + // Test with uint8_t + ctx->group = 255; + auto result8 = runtime_impl->key_value_store_local_group(); + ASSERT_TRUE(result8.has_value()); + EXPECT_EQ(result8.value(), 255); + + // Test with uint16_t + uint16_t group16 = 1000; + ctx->store["realm_group"] = std::vector( + reinterpret_cast(&group16), + reinterpret_cast(&group16) + sizeof(group16)); + auto result16 = runtime_impl->key_value_store_local_group(); + ASSERT_TRUE(result16.has_value()); + EXPECT_EQ(result16.value(), 1000); + + // Test with uint64_t + uint64_t group64 = 1000000; + ctx->store["realm_group"] = std::vector( + reinterpret_cast(&group64), + reinterpret_cast(&group64) + sizeof(group64)); + auto result64 = runtime_impl->key_value_store_local_group(); + ASSERT_TRUE(result64.has_value()); + EXPECT_EQ(result64.value(), 1000000); +} + +// Test key_value_store_local_rank +TEST_F(KeyValueStoreTestBase, LocalRank_Success) +{ + ctx->rank = 3; + ctx->ranks = 8; + InitVtable(true, true, true, false); + + auto result = runtime_impl->key_value_store_local_rank(); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), 3); +} + +// Test key_value_store_local_ranks +TEST_F(KeyValueStoreTestBase, LocalRanks_Success) +{ + ctx->rank = 3; + ctx->ranks = 8; + InitVtable(true, true, true, false); + + auto result = runtime_impl->key_value_store_local_ranks(); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), 8); +} + +// Test key_value_store_put +TEST_F(KeyValueStoreTestBase, Put_Success) +{ + InitVtable(true, true, false, false); + + const char *key = "test_key"; + const char *value = "test_value"; + + bool result = runtime_impl->key_value_store_put(key, strlen(key), value, strlen(value)); + + EXPECT_TRUE(result); + EXPECT_EQ(ctx->put_call_count, 1); + EXPECT_EQ(ctx->store.size(), 1); + + std::string stored_value(ctx->store["test_key"].begin(), ctx->store["test_key"].end()); + EXPECT_EQ(stored_value, "test_value"); +} + +TEST_F(KeyValueStoreTestBase, Put_BinaryData) +{ + InitVtable(true, true, false, false); + + const char *key = "binary_key"; + uint8_t value[] = {0x01, 0x02, 0x03, 0x04, 0xFF}; + + bool result = + runtime_impl->key_value_store_put(key, strlen(key), value, sizeof(value)); + + EXPECT_TRUE(result); + EXPECT_EQ(ctx->store["binary_key"].size(), 5); + EXPECT_EQ(ctx->store["binary_key"][0], 0x01); + EXPECT_EQ(ctx->store["binary_key"][4], 0xFF); +} + +TEST_F(KeyValueStoreTestBase, Put_Overwrite) +{ + InitVtable(true, true, false, false); + + const char *key = "key"; + + runtime_impl->key_value_store_put(key, strlen(key), "value1", 6); + runtime_impl->key_value_store_put(key, strlen(key), "value2", 6); + + EXPECT_EQ(ctx->put_call_count, 2); + std::string stored_value(ctx->store["key"].begin(), ctx->store["key"].end()); + EXPECT_EQ(stored_value, "value2"); +} + +// Test key_value_store_get +TEST_F(KeyValueStoreTestBase, Get_Success) +{ + InitVtable(true, true, false, false); + + const char *key = "test_key"; + const char *value = "test_value"; + + // First put a value + runtime_impl->key_value_store_put(key, strlen(key), value, strlen(value)); + + // Now get it + char buffer[256]; + size_t buffer_size = sizeof(buffer); + bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); + + EXPECT_TRUE(result); + EXPECT_EQ(ctx->get_call_count, 1); + EXPECT_EQ(buffer_size, strlen(value)); + EXPECT_EQ(std::string(buffer, buffer_size), "test_value"); +} + +TEST_F(KeyValueStoreTestBase, Get_KeyNotFound) +{ + InitVtable(true, true, false, false); + + const char *key = "nonexistent_key"; + char buffer[256]; + size_t buffer_size = sizeof(buffer); + + bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); + + EXPECT_TRUE(result); + EXPECT_EQ(buffer_size, 0); +} + +TEST_F(KeyValueStoreTestBase, Get_BufferTooSmall) +{ + InitVtable(true, true, false, false); + + const char *key = "test_key"; + const char *value = "this_is_a_long_value"; + + runtime_impl->key_value_store_put(key, strlen(key), value, strlen(value)); + + char buffer[5]; + size_t buffer_size = sizeof(buffer); + bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); + + EXPECT_TRUE(result); + EXPECT_EQ(buffer_size, strlen(value)); // Should return actual size +} + +TEST_F(KeyValueStoreTestBase, Get_BinaryData) +{ + InitVtable(true, true, false, false); + + const char *key = "binary_key"; + uint8_t value[] = {0xDE, 0xAD, 0xBE, 0xEF}; + + runtime_impl->key_value_store_put(key, strlen(key), value, sizeof(value)); + + uint8_t buffer[10]; + size_t buffer_size = sizeof(buffer); + bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); + + EXPECT_TRUE(result); + EXPECT_EQ(buffer_size, 4); + EXPECT_EQ(buffer[0], 0xDE); + EXPECT_EQ(buffer[1], 0xAD); + EXPECT_EQ(buffer[2], 0xBE); + EXPECT_EQ(buffer[3], 0xEF); +} + +// Test key_value_store_bar +TEST_F(KeyValueStoreTestBase, Bar_Success) +{ + InitVtable(true, true, true, false); + + bool result = runtime_impl->key_value_store_bar(); + + EXPECT_TRUE(result); + EXPECT_EQ(ctx->barrier_call_count, 1); +} + +TEST_F(KeyValueStoreTestBase, Bar_Failure) +{ + InitVtable(true, true, true, false); + ctx->barrier_should_fail = true; + + bool result = runtime_impl->key_value_store_bar(); + + EXPECT_FALSE(result); + EXPECT_EQ(ctx->barrier_call_count, 1); +} + +TEST_F(KeyValueStoreTestBase, Bar_MultipleCalls) +{ + InitVtable(true, true, true, false); + + runtime_impl->key_value_store_bar(); + runtime_impl->key_value_store_bar(); + runtime_impl->key_value_store_bar(); + + EXPECT_EQ(ctx->barrier_call_count, 3); +} + +// Test key_value_store_cas +TEST_F(KeyValueStoreTestBase, CAS_CreateNew) +{ + InitVtable(true, true, false, true); + + const char *key = "new_key"; + uint32_t expected = 0; + size_t expected_size = sizeof(expected); + uint32_t desired = 42; + + bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, + &expected_size, &desired, sizeof(desired)); + + EXPECT_TRUE(result); + EXPECT_EQ(ctx->cas_call_count, 1); + + // Verify the value was stored + char buffer[256]; + size_t buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); + EXPECT_EQ(buffer_size, sizeof(uint32_t)); + EXPECT_EQ(*reinterpret_cast(buffer), 42); +} + +TEST_F(KeyValueStoreTestBase, CAS_SuccessfulUpdate) +{ + InitVtable(true, true, false, true); + + const char *key = "counter"; + uint32_t initial = 10; + + // Put initial value + runtime_impl->key_value_store_put(key, strlen(key), &initial, sizeof(initial)); + + // CAS update from 10 to 11 + uint32_t expected = 10; + size_t expected_size = sizeof(expected); + uint32_t desired = 11; + + bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, + &expected_size, &desired, sizeof(desired)); + + EXPECT_TRUE(result); + + // Verify the value was updated + uint32_t buffer; + size_t buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get(key, strlen(key), &buffer, &buffer_size); + EXPECT_EQ(buffer, 11); +} + +TEST_F(KeyValueStoreTestBase, CAS_FailedUpdate) +{ + InitVtable(true, true, false, true); + + const char *key = "counter"; + uint32_t initial = 10; + + // Put initial value + runtime_impl->key_value_store_put(key, strlen(key), &initial, sizeof(initial)); + + // Try to CAS with wrong expected value + uint32_t expected = 5; // Wrong! + size_t expected_size = sizeof(expected); + uint32_t desired = 11; + + bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, + &expected_size, &desired, sizeof(desired)); + + EXPECT_FALSE(result); + EXPECT_EQ(expected, 10); // Should return actual value + + // Verify the value was NOT updated + uint32_t buffer; + size_t buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get(key, strlen(key), &buffer, &buffer_size); + EXPECT_EQ(buffer, 10); +} + +TEST_F(KeyValueStoreTestBase, CAS_AtomicCounter) +{ + InitVtable(true, true, false, true); + + const char *key = "atomic_counter"; + uint32_t value = 0; + + // Simulate atomic increment operations + for(int i = 0; i < 5; i++) { + uint32_t expected = value; + size_t expected_size = sizeof(expected); + uint32_t desired = value + 1; + + bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, + &expected_size, &desired, sizeof(desired)); + EXPECT_TRUE(result); + value++; + } + + // Final value should be 5 + uint32_t buffer; + size_t buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get(key, strlen(key), &buffer, &buffer_size); + EXPECT_EQ(buffer, 5); + EXPECT_EQ(ctx->cas_call_count, 5); +} + +// Integration tests combining multiple operations +TEST_F(KeyValueStoreTestBase, Integration_PutGetWorkflow) +{ + InitVtable(true, true, false, false); + + // Put multiple key-value pairs + runtime_impl->key_value_store_put("key1", 4, "value1", 6); + runtime_impl->key_value_store_put("key2", 4, "value2", 6); + runtime_impl->key_value_store_put("key3", 4, "value3", 6); + + // Get them back + char buffer[256]; + size_t buffer_size; + + buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get("key1", 4, buffer, &buffer_size); + EXPECT_EQ(std::string(buffer, buffer_size), "value1"); + + buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get("key2", 4, buffer, &buffer_size); + EXPECT_EQ(std::string(buffer, buffer_size), "value2"); + + buffer_size = sizeof(buffer); + runtime_impl->key_value_store_get("key3", 4, buffer, &buffer_size); + EXPECT_EQ(std::string(buffer, buffer_size), "value3"); +} + +TEST_F(KeyValueStoreTestBase, Integration_GroupWithBarrier) +{ + ctx->rank = 2; + ctx->ranks = 4; + ctx->group = 1; + InitVtable(true, true, true, false); + + EXPECT_TRUE(runtime_impl->key_value_store_group()); + + auto rank = runtime_impl->key_value_store_local_rank(); + auto ranks = runtime_impl->key_value_store_local_ranks(); + auto group = runtime_impl->key_value_store_local_group(); + + ASSERT_TRUE(rank.has_value()); + ASSERT_TRUE(ranks.has_value()); + ASSERT_TRUE(group.has_value()); + + EXPECT_EQ(rank.value(), 2); + EXPECT_EQ(ranks.value(), 4); + EXPECT_EQ(group.value(), 1); + + EXPECT_TRUE(runtime_impl->key_value_store_bar()); +} + +TEST_F(KeyValueStoreTestBase, Integration_ElasticWithCAS) +{ + InitVtable(true, true, false, true); + + EXPECT_TRUE(runtime_impl->key_value_store_elastic()); + + // Simulate elastic node joining + const char *counter_key = "node_counter"; + uint32_t expected = 0; + size_t expected_size = sizeof(expected); + uint32_t desired = 1; + + bool result = runtime_impl->key_value_store_cas(counter_key, strlen(counter_key), &expected, + &expected_size, &desired, sizeof(desired)); + EXPECT_TRUE(result); +} From edfb325f1a8456f84b24fce3b7c38b1f9e19f5ee Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 15 Jan 2026 00:08:32 -0800 Subject: [PATCH 21/26] realm: fixes for key-value store unit tests --- tests/unit_tests/key_value_store_test.cc | 45 ++++++++++++++++-------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/unit_tests/key_value_store_test.cc b/tests/unit_tests/key_value_store_test.cc index 632b4c3013b..2861c7e1017 100644 --- a/tests/unit_tests/key_value_store_test.cc +++ b/tests/unit_tests/key_value_store_test.cc @@ -17,6 +17,7 @@ #include "realm/runtime_impl.h" #include "realm/runtime.h" +#include "test_mock.h" #include #include @@ -26,6 +27,10 @@ using namespace Realm; +namespace Realm { + extern bool enable_unit_tests; +}; + // Mock key-value store context for testing struct MockKVStoreContext { std::map> store; @@ -183,22 +188,34 @@ static bool mock_cas(const void *key, size_t key_size, void *expected, size_t *e return false; } +// Helper class to expose protected members for testing +class TestableRuntimeImpl : public MockRuntimeImpl { +public: + using MockRuntimeImpl::key_value_store_vtable; +}; + class KeyValueStoreTestBase : public ::testing::Test { protected: void SetUp() override { + Realm::enable_unit_tests = true; + ctx = new MockKVStoreContext(); ctx_ptr = new MockKVStoreContext *(ctx); // Create a minimal runtime for testing - runtime_impl = new RuntimeImpl(); + runtime_impl = new TestableRuntimeImpl(); + runtime_impl->init(1); } void TearDown() override { + runtime_impl->finalize(); delete runtime_impl; delete ctx; delete ctx_ptr; + + Realm::enable_unit_tests = false; } void InitVtable(bool with_put = true, bool with_get = true, @@ -211,13 +228,13 @@ class KeyValueStoreTestBase : public ::testing::Test { vtable.bar = with_bar ? mock_bar : nullptr; vtable.cas = with_cas ? mock_cas : nullptr; - // Directly set the vtable in runtime_impl for testing + // Directly set the vtable (now accessible via TestableRuntimeImpl) runtime_impl->key_value_store_vtable = vtable; } MockKVStoreContext *ctx; MockKVStoreContext **ctx_ptr; - RuntimeImpl *runtime_impl; + TestableRuntimeImpl *runtime_impl; Runtime::KeyValueStoreVtable vtable; }; @@ -275,26 +292,26 @@ TEST_F(KeyValueStoreTestBase, LocalGroup_DifferentSizes) { InitVtable(true, true, true, false); - // Test with uint8_t + // Test with uint8_t - use context value ctx->group = 255; auto result8 = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result8.has_value()); EXPECT_EQ(result8.value(), 255); - // Test with uint16_t - uint16_t group16 = 1000; - ctx->store["realm_group"] = std::vector( - reinterpret_cast(&group16), - reinterpret_cast(&group16) + sizeof(group16)); + // Test with uint16_t - update context to test larger value + ctx->group = 1000; auto result16 = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result16.has_value()); EXPECT_EQ(result16.value(), 1000); - // Test with uint64_t - uint64_t group64 = 1000000; - ctx->store["realm_group"] = std::vector( - reinterpret_cast(&group64), - reinterpret_cast(&group64) + sizeof(group64)); + // Test with uint32_t - update context for even larger value + ctx->group = 100000; + auto result32 = runtime_impl->key_value_store_local_group(); + ASSERT_TRUE(result32.has_value()); + EXPECT_EQ(result32.value(), 100000); + + // Test with uint64_t - update context for maximum value + ctx->group = 1000000; auto result64 = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result64.has_value()); EXPECT_EQ(result64.value(), 1000000); From 37654b6347717a21f1b8259a33de7e14c54609d7 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 15 Jan 2026 01:59:46 -0800 Subject: [PATCH 22/26] realm: fix clang format of key-value unit test --- tests/unit_tests/key_value_store_test.cc | 230 +++++++++++------------ 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/tests/unit_tests/key_value_store_test.cc b/tests/unit_tests/key_value_store_test.cc index 2861c7e1017..6dd88512fbb 100644 --- a/tests/unit_tests/key_value_store_test.cc +++ b/tests/unit_tests/key_value_store_test.cc @@ -42,7 +42,7 @@ struct MockKVStoreContext { bool put_should_fail = false; bool get_should_fail = false; bool cas_should_fail = false; - + // For group testing uint64_t rank = 0; uint64_t ranks = 1; @@ -55,13 +55,13 @@ static bool mock_put(const void *key, size_t key_size, const void *value, { if(!key || !value || key_size == 0 || !vtable_data) return false; - + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; ctx->put_call_count++; - + if(ctx->put_should_fail) return false; - + std::string k(static_cast(key), key_size); std::vector v(static_cast(value), static_cast(value) + value_size); @@ -74,15 +74,15 @@ static bool mock_get(const void *key, size_t key_size, void *value, size_t *valu { if(!key || !value || !value_size || key_size == 0 || !vtable_data) return false; - + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; ctx->get_call_count++; - + if(ctx->get_should_fail) return false; - + std::string k(static_cast(key), key_size); - + // Handle special realm keys if(k == "realm_rank") { if(*value_size < sizeof(uint32_t)) { @@ -94,7 +94,7 @@ static bool mock_get(const void *key, size_t key_size, void *value, size_t *valu *value_size = sizeof(v); return true; } - + if(k == "realm_ranks") { if(*value_size < sizeof(uint32_t)) { *value_size = sizeof(uint32_t); @@ -105,7 +105,7 @@ static bool mock_get(const void *key, size_t key_size, void *value, size_t *valu *value_size = sizeof(v); return true; } - + if(k == "realm_group") { if(*value_size < sizeof(uint32_t)) { *value_size = sizeof(uint32_t); @@ -116,7 +116,7 @@ static bool mock_get(const void *key, size_t key_size, void *value, size_t *valu *value_size = sizeof(v); return true; } - + auto it = ctx->store.find(k); if(it != ctx->store.end()) { if(it->second.size() > *value_size) { @@ -127,7 +127,7 @@ static bool mock_get(const void *key, size_t key_size, void *value, size_t *valu *value_size = it->second.size(); return true; } - + // Key not found *value_size = 0; return true; @@ -137,28 +137,28 @@ static bool mock_bar(const void *vtable_data, size_t vtable_data_size) { if(!vtable_data) return false; - + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; ctx->barrier_call_count++; - + return !ctx->barrier_should_fail; } -static bool mock_cas(const void *key, size_t key_size, void *expected, size_t *expected_size, - const void *desired, size_t desired_size, const void *vtable_data, - size_t vtable_data_size) +static bool mock_cas(const void *key, size_t key_size, void *expected, + size_t *expected_size, const void *desired, size_t desired_size, + const void *vtable_data, size_t vtable_data_size) { if(!key || !expected || !expected_size || !desired || key_size == 0 || !vtable_data) return false; - + MockKVStoreContext *ctx = *(MockKVStoreContext **)vtable_data; ctx->cas_call_count++; - + if(ctx->cas_should_fail) return false; - + std::string k(static_cast(key), key_size); - + auto it = ctx->store.find(k); if(it == ctx->store.end()) { // Key doesn't exist, create it with desired value @@ -167,7 +167,7 @@ static bool mock_cas(const void *key, size_t key_size, void *expected, size_t *e ctx->store[k] = v; return true; } - + // Key exists, check if current value matches expected if(it->second.size() == *expected_size && std::memcmp(it->second.data(), expected, *expected_size) == 0) { @@ -177,7 +177,7 @@ static bool mock_cas(const void *key, size_t key_size, void *expected, size_t *e ctx->store[k] = v; return true; } - + // No match, return current value in expected if(it->second.size() > *expected_size) { *expected_size = it->second.size(); @@ -199,27 +199,27 @@ class KeyValueStoreTestBase : public ::testing::Test { void SetUp() override { Realm::enable_unit_tests = true; - + ctx = new MockKVStoreContext(); ctx_ptr = new MockKVStoreContext *(ctx); - + // Create a minimal runtime for testing runtime_impl = new TestableRuntimeImpl(); runtime_impl->init(1); } - + void TearDown() override { runtime_impl->finalize(); delete runtime_impl; delete ctx; delete ctx_ptr; - + Realm::enable_unit_tests = false; } - - void InitVtable(bool with_put = true, bool with_get = true, - bool with_bar = false, bool with_cas = false) + + void InitVtable(bool with_put = true, bool with_get = true, bool with_bar = false, + bool with_cas = false) { vtable.vtable_data = ctx_ptr; vtable.vtable_data_size = sizeof(MockKVStoreContext *); @@ -227,11 +227,11 @@ class KeyValueStoreTestBase : public ::testing::Test { vtable.get = with_get ? mock_get : nullptr; vtable.bar = with_bar ? mock_bar : nullptr; vtable.cas = with_cas ? mock_cas : nullptr; - + // Directly set the vtable (now accessible via TestableRuntimeImpl) runtime_impl->key_value_store_vtable = vtable; } - + MockKVStoreContext *ctx; MockKVStoreContext **ctx_ptr; TestableRuntimeImpl *runtime_impl; @@ -282,7 +282,7 @@ TEST_F(KeyValueStoreTestBase, LocalGroup_Success) { ctx->group = 42; InitVtable(true, true, true, false); - + auto result = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 42); @@ -291,25 +291,25 @@ TEST_F(KeyValueStoreTestBase, LocalGroup_Success) TEST_F(KeyValueStoreTestBase, LocalGroup_DifferentSizes) { InitVtable(true, true, true, false); - + // Test with uint8_t - use context value ctx->group = 255; auto result8 = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result8.has_value()); EXPECT_EQ(result8.value(), 255); - + // Test with uint16_t - update context to test larger value ctx->group = 1000; auto result16 = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result16.has_value()); EXPECT_EQ(result16.value(), 1000); - + // Test with uint32_t - update context for even larger value ctx->group = 100000; auto result32 = runtime_impl->key_value_store_local_group(); ASSERT_TRUE(result32.has_value()); EXPECT_EQ(result32.value(), 100000); - + // Test with uint64_t - update context for maximum value ctx->group = 1000000; auto result64 = runtime_impl->key_value_store_local_group(); @@ -323,7 +323,7 @@ TEST_F(KeyValueStoreTestBase, LocalRank_Success) ctx->rank = 3; ctx->ranks = 8; InitVtable(true, true, true, false); - + auto result = runtime_impl->key_value_store_local_rank(); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 3); @@ -335,7 +335,7 @@ TEST_F(KeyValueStoreTestBase, LocalRanks_Success) ctx->rank = 3; ctx->ranks = 8; InitVtable(true, true, true, false); - + auto result = runtime_impl->key_value_store_local_ranks(); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 8); @@ -345,16 +345,16 @@ TEST_F(KeyValueStoreTestBase, LocalRanks_Success) TEST_F(KeyValueStoreTestBase, Put_Success) { InitVtable(true, true, false, false); - + const char *key = "test_key"; const char *value = "test_value"; - + bool result = runtime_impl->key_value_store_put(key, strlen(key), value, strlen(value)); - + EXPECT_TRUE(result); EXPECT_EQ(ctx->put_call_count, 1); EXPECT_EQ(ctx->store.size(), 1); - + std::string stored_value(ctx->store["test_key"].begin(), ctx->store["test_key"].end()); EXPECT_EQ(stored_value, "test_value"); } @@ -362,13 +362,12 @@ TEST_F(KeyValueStoreTestBase, Put_Success) TEST_F(KeyValueStoreTestBase, Put_BinaryData) { InitVtable(true, true, false, false); - + const char *key = "binary_key"; uint8_t value[] = {0x01, 0x02, 0x03, 0x04, 0xFF}; - - bool result = - runtime_impl->key_value_store_put(key, strlen(key), value, sizeof(value)); - + + bool result = runtime_impl->key_value_store_put(key, strlen(key), value, sizeof(value)); + EXPECT_TRUE(result); EXPECT_EQ(ctx->store["binary_key"].size(), 5); EXPECT_EQ(ctx->store["binary_key"][0], 0x01); @@ -378,12 +377,12 @@ TEST_F(KeyValueStoreTestBase, Put_BinaryData) TEST_F(KeyValueStoreTestBase, Put_Overwrite) { InitVtable(true, true, false, false); - + const char *key = "key"; - + runtime_impl->key_value_store_put(key, strlen(key), "value1", 6); runtime_impl->key_value_store_put(key, strlen(key), "value2", 6); - + EXPECT_EQ(ctx->put_call_count, 2); std::string stored_value(ctx->store["key"].begin(), ctx->store["key"].end()); EXPECT_EQ(stored_value, "value2"); @@ -393,18 +392,18 @@ TEST_F(KeyValueStoreTestBase, Put_Overwrite) TEST_F(KeyValueStoreTestBase, Get_Success) { InitVtable(true, true, false, false); - + const char *key = "test_key"; const char *value = "test_value"; - + // First put a value runtime_impl->key_value_store_put(key, strlen(key), value, strlen(value)); - + // Now get it char buffer[256]; size_t buffer_size = sizeof(buffer); bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); - + EXPECT_TRUE(result); EXPECT_EQ(ctx->get_call_count, 1); EXPECT_EQ(buffer_size, strlen(value)); @@ -414,13 +413,13 @@ TEST_F(KeyValueStoreTestBase, Get_Success) TEST_F(KeyValueStoreTestBase, Get_KeyNotFound) { InitVtable(true, true, false, false); - + const char *key = "nonexistent_key"; char buffer[256]; size_t buffer_size = sizeof(buffer); - + bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); - + EXPECT_TRUE(result); EXPECT_EQ(buffer_size, 0); } @@ -428,16 +427,16 @@ TEST_F(KeyValueStoreTestBase, Get_KeyNotFound) TEST_F(KeyValueStoreTestBase, Get_BufferTooSmall) { InitVtable(true, true, false, false); - + const char *key = "test_key"; const char *value = "this_is_a_long_value"; - + runtime_impl->key_value_store_put(key, strlen(key), value, strlen(value)); - + char buffer[5]; size_t buffer_size = sizeof(buffer); bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); - + EXPECT_TRUE(result); EXPECT_EQ(buffer_size, strlen(value)); // Should return actual size } @@ -445,16 +444,16 @@ TEST_F(KeyValueStoreTestBase, Get_BufferTooSmall) TEST_F(KeyValueStoreTestBase, Get_BinaryData) { InitVtable(true, true, false, false); - + const char *key = "binary_key"; uint8_t value[] = {0xDE, 0xAD, 0xBE, 0xEF}; - + runtime_impl->key_value_store_put(key, strlen(key), value, sizeof(value)); - + uint8_t buffer[10]; size_t buffer_size = sizeof(buffer); bool result = runtime_impl->key_value_store_get(key, strlen(key), buffer, &buffer_size); - + EXPECT_TRUE(result); EXPECT_EQ(buffer_size, 4); EXPECT_EQ(buffer[0], 0xDE); @@ -467,9 +466,9 @@ TEST_F(KeyValueStoreTestBase, Get_BinaryData) TEST_F(KeyValueStoreTestBase, Bar_Success) { InitVtable(true, true, true, false); - + bool result = runtime_impl->key_value_store_bar(); - + EXPECT_TRUE(result); EXPECT_EQ(ctx->barrier_call_count, 1); } @@ -478,9 +477,9 @@ TEST_F(KeyValueStoreTestBase, Bar_Failure) { InitVtable(true, true, true, false); ctx->barrier_should_fail = true; - + bool result = runtime_impl->key_value_store_bar(); - + EXPECT_FALSE(result); EXPECT_EQ(ctx->barrier_call_count, 1); } @@ -488,11 +487,11 @@ TEST_F(KeyValueStoreTestBase, Bar_Failure) TEST_F(KeyValueStoreTestBase, Bar_MultipleCalls) { InitVtable(true, true, true, false); - + runtime_impl->key_value_store_bar(); runtime_impl->key_value_store_bar(); runtime_impl->key_value_store_bar(); - + EXPECT_EQ(ctx->barrier_call_count, 3); } @@ -500,18 +499,18 @@ TEST_F(KeyValueStoreTestBase, Bar_MultipleCalls) TEST_F(KeyValueStoreTestBase, CAS_CreateNew) { InitVtable(true, true, false, true); - + const char *key = "new_key"; uint32_t expected = 0; size_t expected_size = sizeof(expected); uint32_t desired = 42; - - bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, - &expected_size, &desired, sizeof(desired)); - + + bool result = runtime_impl->key_value_store_cas( + key, strlen(key), &expected, &expected_size, &desired, sizeof(desired)); + EXPECT_TRUE(result); EXPECT_EQ(ctx->cas_call_count, 1); - + // Verify the value was stored char buffer[256]; size_t buffer_size = sizeof(buffer); @@ -523,23 +522,23 @@ TEST_F(KeyValueStoreTestBase, CAS_CreateNew) TEST_F(KeyValueStoreTestBase, CAS_SuccessfulUpdate) { InitVtable(true, true, false, true); - + const char *key = "counter"; uint32_t initial = 10; - + // Put initial value runtime_impl->key_value_store_put(key, strlen(key), &initial, sizeof(initial)); - + // CAS update from 10 to 11 uint32_t expected = 10; size_t expected_size = sizeof(expected); uint32_t desired = 11; - - bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, - &expected_size, &desired, sizeof(desired)); - + + bool result = runtime_impl->key_value_store_cas( + key, strlen(key), &expected, &expected_size, &desired, sizeof(desired)); + EXPECT_TRUE(result); - + // Verify the value was updated uint32_t buffer; size_t buffer_size = sizeof(buffer); @@ -550,24 +549,24 @@ TEST_F(KeyValueStoreTestBase, CAS_SuccessfulUpdate) TEST_F(KeyValueStoreTestBase, CAS_FailedUpdate) { InitVtable(true, true, false, true); - + const char *key = "counter"; uint32_t initial = 10; - + // Put initial value runtime_impl->key_value_store_put(key, strlen(key), &initial, sizeof(initial)); - + // Try to CAS with wrong expected value uint32_t expected = 5; // Wrong! size_t expected_size = sizeof(expected); uint32_t desired = 11; - - bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, - &expected_size, &desired, sizeof(desired)); - + + bool result = runtime_impl->key_value_store_cas( + key, strlen(key), &expected, &expected_size, &desired, sizeof(desired)); + EXPECT_FALSE(result); EXPECT_EQ(expected, 10); // Should return actual value - + // Verify the value was NOT updated uint32_t buffer; size_t buffer_size = sizeof(buffer); @@ -578,22 +577,22 @@ TEST_F(KeyValueStoreTestBase, CAS_FailedUpdate) TEST_F(KeyValueStoreTestBase, CAS_AtomicCounter) { InitVtable(true, true, false, true); - + const char *key = "atomic_counter"; uint32_t value = 0; - + // Simulate atomic increment operations for(int i = 0; i < 5; i++) { uint32_t expected = value; size_t expected_size = sizeof(expected); uint32_t desired = value + 1; - - bool result = runtime_impl->key_value_store_cas(key, strlen(key), &expected, - &expected_size, &desired, sizeof(desired)); + + bool result = runtime_impl->key_value_store_cas( + key, strlen(key), &expected, &expected_size, &desired, sizeof(desired)); EXPECT_TRUE(result); value++; } - + // Final value should be 5 uint32_t buffer; size_t buffer_size = sizeof(buffer); @@ -606,24 +605,24 @@ TEST_F(KeyValueStoreTestBase, CAS_AtomicCounter) TEST_F(KeyValueStoreTestBase, Integration_PutGetWorkflow) { InitVtable(true, true, false, false); - + // Put multiple key-value pairs runtime_impl->key_value_store_put("key1", 4, "value1", 6); runtime_impl->key_value_store_put("key2", 4, "value2", 6); runtime_impl->key_value_store_put("key3", 4, "value3", 6); - + // Get them back char buffer[256]; size_t buffer_size; - + buffer_size = sizeof(buffer); runtime_impl->key_value_store_get("key1", 4, buffer, &buffer_size); EXPECT_EQ(std::string(buffer, buffer_size), "value1"); - + buffer_size = sizeof(buffer); runtime_impl->key_value_store_get("key2", 4, buffer, &buffer_size); EXPECT_EQ(std::string(buffer, buffer_size), "value2"); - + buffer_size = sizeof(buffer); runtime_impl->key_value_store_get("key3", 4, buffer, &buffer_size); EXPECT_EQ(std::string(buffer, buffer_size), "value3"); @@ -635,37 +634,38 @@ TEST_F(KeyValueStoreTestBase, Integration_GroupWithBarrier) ctx->ranks = 4; ctx->group = 1; InitVtable(true, true, true, false); - + EXPECT_TRUE(runtime_impl->key_value_store_group()); - + auto rank = runtime_impl->key_value_store_local_rank(); auto ranks = runtime_impl->key_value_store_local_ranks(); auto group = runtime_impl->key_value_store_local_group(); - + ASSERT_TRUE(rank.has_value()); ASSERT_TRUE(ranks.has_value()); ASSERT_TRUE(group.has_value()); - + EXPECT_EQ(rank.value(), 2); EXPECT_EQ(ranks.value(), 4); EXPECT_EQ(group.value(), 1); - + EXPECT_TRUE(runtime_impl->key_value_store_bar()); } TEST_F(KeyValueStoreTestBase, Integration_ElasticWithCAS) { InitVtable(true, true, false, true); - + EXPECT_TRUE(runtime_impl->key_value_store_elastic()); - + // Simulate elastic node joining const char *counter_key = "node_counter"; uint32_t expected = 0; size_t expected_size = sizeof(expected); uint32_t desired = 1; - - bool result = runtime_impl->key_value_store_cas(counter_key, strlen(counter_key), &expected, - &expected_size, &desired, sizeof(desired)); + + bool result = + runtime_impl->key_value_store_cas(counter_key, strlen(counter_key), &expected, + &expected_size, &desired, sizeof(desired)); EXPECT_TRUE(result); } From 620b927767bb98377c903fb7a38d8c3f09c65281 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 15 Jan 2026 02:03:32 -0800 Subject: [PATCH 23/26] realm: rename a few internal key-value store methods --- src/realm/runtime_impl.cc | 4 ++-- src/realm/runtime_impl.h | 4 ++-- src/realm/ucx/bootstrap/bootstrap.cc | 2 +- src/realm/ucx/ucp_internal.cc | 2 +- tests/unit_tests/key_value_store_test.cc | 16 ++++++++-------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/realm/runtime_impl.cc b/src/realm/runtime_impl.cc index fadfb5cc768..687d963e5ad 100644 --- a/src/realm/runtime_impl.cc +++ b/src/realm/runtime_impl.cc @@ -1340,12 +1340,12 @@ namespace Realm { return (key_value_store_vtable.put != nullptr); } - bool RuntimeImpl::key_value_store_elastic(void) const + bool RuntimeImpl::is_key_value_store_elastic(void) const { return (key_value_store_vtable.cas != nullptr); } - bool RuntimeImpl::key_value_store_group(void) const + bool RuntimeImpl::has_key_value_store_group(void) const { return (key_value_store_vtable.bar != nullptr); } diff --git a/src/realm/runtime_impl.h b/src/realm/runtime_impl.h index ce6d3c858e0..bc2d3301490 100644 --- a/src/realm/runtime_impl.h +++ b/src/realm/runtime_impl.h @@ -270,9 +270,9 @@ namespace Realm { const Runtime::KeyValueStoreVtable &vtable); bool has_key_value_store(void) const; // Is this an elastic Realm - bool key_value_store_elastic(void) const; + bool is_key_value_store_elastic(void) const; // Are we a single process joining by ourself or part of a group - bool key_value_store_group(void) const; + bool has_key_value_store_group(void) const; // Our local group std::optional key_value_store_local_group(void) const; // Our local rank in the group diff --git a/src/realm/ucx/bootstrap/bootstrap.cc b/src/realm/ucx/bootstrap/bootstrap.cc index 6202fdc38b2..4e98a2a90c1 100644 --- a/src/realm/ucx/bootstrap/bootstrap.cc +++ b/src/realm/ucx/bootstrap/bootstrap.cc @@ -34,7 +34,7 @@ namespace Realm { { RuntimeImpl *runtime = get_runtime(); assert(runtime->has_key_value_store()); - if(runtime->key_value_store_group()) { + if(runtime->has_key_value_store_group()) { // Need our group ID too to avoid interfering with other groups // that might be trying to join at the same time const std::optional group = runtime->key_value_store_local_group(); diff --git a/src/realm/ucx/ucp_internal.cc b/src/realm/ucx/ucp_internal.cc index 566fc4f83c5..5b37b5aff1d 100644 --- a/src/realm/ucx/ucp_internal.cc +++ b/src/realm/ucx/ucp_internal.cc @@ -845,7 +845,7 @@ namespace Realm { Network::my_node_id = ucc_comm->get_rank(); Network::max_node_id = ucc_comm->get_world_size() - 1; - if(runtime->key_value_store_elastic()) { + if(runtime->is_key_value_store_elastic()) { // If we're part of an elastic job then we need to do more work here uint64_t offset = 0; if(Network::my_node_id == 0) { diff --git a/tests/unit_tests/key_value_store_test.cc b/tests/unit_tests/key_value_store_test.cc index 6dd88512fbb..243078830b7 100644 --- a/tests/unit_tests/key_value_store_test.cc +++ b/tests/unit_tests/key_value_store_test.cc @@ -251,30 +251,30 @@ TEST_F(KeyValueStoreTestBase, HasKeyValueStore_WithoutPut) EXPECT_FALSE(runtime_impl->has_key_value_store()); } -// Test key_value_store_elastic +// Test is_key_value_store_elastic TEST_F(KeyValueStoreTestBase, Elastic_WithCas) { InitVtable(true, true, false, true); - EXPECT_TRUE(runtime_impl->key_value_store_elastic()); + EXPECT_TRUE(runtime_impl->is_key_value_store_elastic()); } TEST_F(KeyValueStoreTestBase, Elastic_WithoutCas) { InitVtable(true, true, false, false); - EXPECT_FALSE(runtime_impl->key_value_store_elastic()); + EXPECT_FALSE(runtime_impl->is_key_value_store_elastic()); } -// Test key_value_store_group +// Test has_key_value_store_group TEST_F(KeyValueStoreTestBase, Group_WithBar) { InitVtable(true, true, true, false); - EXPECT_TRUE(runtime_impl->key_value_store_group()); + EXPECT_TRUE(runtime_impl->has_key_value_store_group()); } TEST_F(KeyValueStoreTestBase, Group_WithoutBar) { InitVtable(true, true, false, false); - EXPECT_FALSE(runtime_impl->key_value_store_group()); + EXPECT_FALSE(runtime_impl->has_key_value_store_group()); } // Test key_value_store_local_group @@ -635,7 +635,7 @@ TEST_F(KeyValueStoreTestBase, Integration_GroupWithBarrier) ctx->group = 1; InitVtable(true, true, true, false); - EXPECT_TRUE(runtime_impl->key_value_store_group()); + EXPECT_TRUE(runtime_impl->has_key_value_store_group()); auto rank = runtime_impl->key_value_store_local_rank(); auto ranks = runtime_impl->key_value_store_local_ranks(); @@ -656,7 +656,7 @@ TEST_F(KeyValueStoreTestBase, Integration_ElasticWithCAS) { InitVtable(true, true, false, true); - EXPECT_TRUE(runtime_impl->key_value_store_elastic()); + EXPECT_TRUE(runtime_impl->is_key_value_store_elastic()); // Simulate elastic node joining const char *counter_key = "node_counter"; From 247c78b9e286e32e478fdf4a4f63f3c0877f7335 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 15 Jan 2026 17:40:02 -0800 Subject: [PATCH 24/26] realm: update comments to use doxygen format --- src/realm/runtime.h | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 096155cac6b..907db25e09f 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -57,17 +57,20 @@ namespace Realm { // *argc and *argv contain the application's real command line // (instead of e.g. mpi spawner information) bool network_init(int *argc, char ***argv); - // Some networks prefer to bootstrap via callbacks using a vtable - // A client provides an implementation of the functions in the vtable - // and Realm will invoke them as part of bootstrapping the network and - // providing support for elasticity (when networks support it). - // Some functions are required while others are either/or options. - // All callbacks will either be performed in an external thread - // (one not made by Realm but has called into Realm) or by a designated - // Realm thread independent of Realm's background worker threads so that - // clients can use non-Realm synchronization primitives in the - // implementation of these functions and not need to worry about - // blocking or impacting forward progress. + /** + * \struct KeyValueStoreVtable + * Some networks prefer to bootstrap via callbacks using a vtable + * A client provides an implementation of the functions in the vtable + * and Realm will invoke them as part of bootstrapping the network and + * providing support for elasticity (when networks support it). + * Some functions are required while others are either/or options. + * All callbacks will either be performed in an external thread + * (one not made by Realm but has called into Realm) or by a designated + * Realm thread independent of Realm's background worker threads so that + * clients can use non-Realm synchronization primitives in the + * implementation of these functions and not need to worry about + * blocking or impacting forward progress. + */ struct KeyValueStoreVtable { /** * Optional blob of data passed to all the network vtable functions From 17895285e8175af3647d3fd6b7fedcb075372b52 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 15 Jan 2026 17:41:21 -0800 Subject: [PATCH 25/26] realm: fix timout for ucp elastic join to wait for a specified number of seconds instead of a certain number of loop iterations --- src/realm/runtime.h | 2 +- src/realm/ucx/ucp_internal.cc | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/realm/runtime.h b/src/realm/runtime.h index 907db25e09f..f10dff8eba9 100644 --- a/src/realm/runtime.h +++ b/src/realm/runtime.h @@ -70,7 +70,7 @@ namespace Realm { * clients can use non-Realm synchronization primitives in the * implementation of these functions and not need to worry about * blocking or impacting forward progress. - */ + */ struct KeyValueStoreVtable { /** * Optional blob of data passed to all the network vtable functions diff --git a/src/realm/ucx/ucp_internal.cc b/src/realm/ucx/ucp_internal.cc index 5b37b5aff1d..944f4c1036a 100644 --- a/src/realm/ucx/ucp_internal.cc +++ b/src/realm/ucx/ucp_internal.cc @@ -24,6 +24,7 @@ #include "realm/logging.h" #include "unistd.h" #include +#include #ifdef REALM_USE_CUDA #include "realm/cuda/cuda_module.h" @@ -854,9 +855,14 @@ namespace Realm { size_t offset_size = sizeof(offset); uint64_t desired = ucc_comm->get_world_size(); bool success = false; - // Try this up to 100 times, if we don't succeed then - // we'll time out and fail to join - for(unsigned idx = 0; idx < 100; idx++) { + using clock = std::chrono::steady_clock; + using seconds = std::chrono::duration; + // Give this up to 10 seconds to succeed + constexpr seconds timeout{10.0}; + const auto start = clock::now(); + const auto deadline = start + timeout; + // Iterate until the timeout + while(clock::now() < deadline) { if(runtime->key_value_store_cas(key.data(), key.size(), &offset, &offset_size, &desired, sizeof(desired))) { success = true; @@ -869,7 +875,8 @@ namespace Realm { offset += ucc_comm->get_world_size(); } if(!success) { - log_ucp.error() << "UCP timed out trying to join Realm"; + log_ucp.error() << "UCP timed out after " << timeout + << " seconds trying to join Realm"; return false; } } From 07e2d7df7f9cb6ef16422b89772cdf0de0612f52 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 15 Jan 2026 19:56:55 -0800 Subject: [PATCH 26/26] realm: fix compilation error for std::chrono::duration --- src/realm/ucx/ucp_internal.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/ucx/ucp_internal.cc b/src/realm/ucx/ucp_internal.cc index 944f4c1036a..e66276b35f4 100644 --- a/src/realm/ucx/ucp_internal.cc +++ b/src/realm/ucx/ucp_internal.cc @@ -875,7 +875,7 @@ namespace Realm { offset += ucc_comm->get_world_size(); } if(!success) { - log_ucp.error() << "UCP timed out after " << timeout + log_ucp.error() << "UCP timed out after " << timeout.count() << " seconds trying to join Realm"; return false; }