diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc.hpp index 21d6cf79b0..596e6fb76a 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc.hpp @@ -41,7 +41,7 @@ class eosvmoc_runtime : public eosio::chain::wasm_runtime_interface { // Defined in eos-vm-oc.cpp. Used for non-main thread in multi-threaded execution thread_local static std::unique_ptr exec_thread_local; - thread_local static eosvmoc::memory mem_thread_local; + thread_local static std::unique_ptr mem_thread_local; }; /** diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp index cce12a2703..2197878d30 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp @@ -48,6 +48,10 @@ class memory { static constexpr uintptr_t first_intrinsic_offset = cb_offset + 8u; // The maximum amount of data that PIC code can include in the prologue static constexpr uintptr_t max_prologue_size = mutable_global_size + table_size; + // Number of slices for read-only threads. + // Use a small number to save upfront virtual memory consumption. + // Memory uses beyond this limit will be handled by mprotect. + static constexpr uint32_t sliced_pages_for_ro_thread = 10; // Changed from -cb_offset == EOS_VM_OC_CONTROL_BLOCK_OFFSET to get around // of compile warning about comparing integers of different signedness diff --git a/libraries/chain/wasm_interface_collection.cpp b/libraries/chain/wasm_interface_collection.cpp index eace6f6517..0ea68397bf 100644 --- a/libraries/chain/wasm_interface_collection.cpp +++ b/libraries/chain/wasm_interface_collection.cpp @@ -9,26 +9,29 @@ namespace eosio::chain { #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED struct eosvmoc_tier { + // Called from main thread eosvmoc_tier(const std::filesystem::path& d, const eosvmoc::config& c, const chainbase::database& db) : cc(d, c, db) { - // construct exec for the main thread - init_thread_local_data(); + // Construct exec and mem for the main thread + exec = std::make_unique(cc); + mem = std::make_unique(wasm_constraints::maximum_linear_memory/wasm_constraints::wasm_page_size); } - // Support multi-threaded execution. + // Called from read-only threads void init_thread_local_data() { exec = std::make_unique(cc); + mem = std::make_unique(eosvmoc::memory::sliced_pages_for_ro_thread); } eosvmoc::code_cache_async cc; // Each thread requires its own exec and mem. thread_local static std::unique_ptr exec; - thread_local static eosvmoc::memory mem; + thread_local static std::unique_ptr mem; }; thread_local std::unique_ptr eosvmoc_tier::exec{}; -thread_local eosvmoc::memory eosvmoc_tier::mem{wasm_constraints::maximum_linear_memory / wasm_constraints::wasm_page_size}; +thread_local std::unique_ptr eosvmoc_tier::mem{}; #endif wasm_interface_collection::wasm_interface_collection(wasm_interface::vm_type vm, wasm_interface::vm_oc_enable eosvmoc_tierup, @@ -71,7 +74,7 @@ void wasm_interface_collection::apply(const digest_type& code_hash, const uint8_ if (cd) { if (!context.is_applying_block()) // read_only_trx_test.py looks for this log statement tlog("${a} speculatively executing ${h} with eos vm oc", ("a", context.get_receiver())("h", code_hash)); - eosvmoc->exec->execute(*cd, eosvmoc->mem, context); + eosvmoc->exec->execute(*cd, *eosvmoc->mem, context); return; } } diff --git a/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp index 9c7fd4ca21..3a20d83e7c 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp @@ -34,7 +34,7 @@ class eosvmoc_instantiated_module : public wasm_instantiated_module_interface { if ( is_main_thread() ) _eosvmoc_runtime.exec.execute(*cd, _eosvmoc_runtime.mem, context); else - _eosvmoc_runtime.exec_thread_local->execute(*cd, _eosvmoc_runtime.mem_thread_local, context); + _eosvmoc_runtime.exec_thread_local->execute(*cd, *_eosvmoc_runtime.mem_thread_local, context); } const digest_type _code_hash; @@ -57,12 +57,10 @@ std::unique_ptr eosvmoc_runtime::instantiate void eosvmoc_runtime::init_thread_local_data() { exec_thread_local = std::make_unique(cc); + mem_thread_local = std::make_unique(eosvmoc::memory::sliced_pages_for_ro_thread); } -thread_local std::unique_ptr eosvmoc_runtime::exec_thread_local {}; -// Set sliced_pages_for_ro_thread to a small number to save upfront virtual memory -// consumption. Usage beyond this limit will be handled by mprotect. -constexpr uint32_t sliced_pages_for_ro_thread = 10; -thread_local eosvmoc::memory eosvmoc_runtime::mem_thread_local{sliced_pages_for_ro_thread}; +thread_local std::unique_ptr eosvmoc_runtime::exec_thread_local{}; +thread_local std::unique_ptr eosvmoc_runtime::mem_thread_local{}; }}}} diff --git a/tests/read_only_trx_test.py b/tests/read_only_trx_test.py index 752f9775b7..661c3fded8 100755 --- a/tests/read_only_trx_test.py +++ b/tests/read_only_trx_test.py @@ -4,6 +4,8 @@ import time import signal import threading +import os +import platform from TestHarness import Account, Cluster, ReturnType, TestHelper, Utils, WalletMgr from TestHarness.TestHelper import AppArgs @@ -109,6 +111,9 @@ def startCluster(): specificExtraNodeosArgs[pnodes]+=" --read-only-threads " specificExtraNodeosArgs[pnodes]+=str(args.read_only_threads) if args.eos_vm_oc_enable: + if platform.system() != "Linux": + Print("OC not run on Linux. Skip the test") + exit(True) # Do not fail the test specificExtraNodeosArgs[pnodes]+=" --eos-vm-oc-enable " specificExtraNodeosArgs[pnodes]+=args.eos_vm_oc_enable if args.wasm_runtime: @@ -132,6 +137,29 @@ def startCluster(): found = producerNode.findInLog(f"executing {eosioCodeHash} with eos vm oc") assert( found or (noOC and not found) ) + if args.eos_vm_oc_enable: + verifyOcVirtualMemory() + +def verifyOcVirtualMemory(): + try: + with open(f"/proc/{apiNode.pid}/statm") as f: + data = f.read().split() + vmPages = int(data[0]) + pageSize = os.sysconf("SC_PAGESIZE") + actualVmSize = vmPages * pageSize + + # When OC tierup is enabled, virtual memory used by IC is around + # 529 slices * 8GB (for main thread) + numReadOnlyThreads * 11 slices * 8GB + # This test verifies virtual memory taken by one read-only thread + # is not in the order of 1TB. + otherGB = 1000 # add 1TB for virtual memory used by others + expectedVmSize = ((529 * 8) + (args.read_only_threads * 88) + otherGB) * 1024 * 1024 * 1024 + Utils.Print(f"pid: {apiNode.pid}, actualVmSize: {actualVmSize}, expectedVmSize: {expectedVmSize}") + assert(actualVmSize < expectedVmSize) + except FileNotFoundError: + Utils.Print(f"/proc/{apiNode.pid}/statm not found") + assert(False) + def deployTestContracts(): Utils.Print("create test accounts") testAccount = Account(testAccountName) @@ -348,4 +376,4 @@ def runEverythingParallel(): TestHelper.shutdown(cluster, walletMgr, testSuccessful, dumpErrorDetails) errorCode = 0 if testSuccessful else 1 -exit(errorCode) \ No newline at end of file +exit(errorCode)