diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 18e3350310..499ac13fc4 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1483,29 +1483,31 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia plugin_config_exception, "read-only-read-window-time-us (${read}) must be at least greater than ${min} us", ("read", _ro_read_window_time_us)("min", _ro_read_window_minimum_time_us)); - _ro_read_window_effective_time_us = _ro_read_window_time_us - _ro_read_window_minimum_time_us; - + _ro_read_window_effective_time_us = _ro_read_window_time_us; ilog("read-only-write-window-time-us: ${ww} us, read-only-read-window-time-us: ${rw} us, effective read window time to be used: ${w} us", ("ww", _ro_write_window_time_us)("rw", _ro_read_window_time_us)("w", _ro_read_window_effective_time_us)); - } - app().executor().init_read_threads(_ro_thread_pool_size); + // Make sure _ro_max_trx_time_us is always set. + // Make sure a read-only transaction can finish within the read + // window if scheduled at the very beginning of the window. + if (_max_transaction_time_ms.load() > 0) { + _ro_max_trx_time_us = fc::milliseconds(_max_transaction_time_ms.load()); + } else { + // max-transaction-time can be set to negative for unlimited time + _ro_max_trx_time_us = fc::microseconds::maximum(); + } + // Factor _ro_read_window_minimum_time_us into _ro_max_trx_time_us + // such that a transaction which runs less than or equal to _ro_max_trx_time_us + // can fit in effective read-only window + assert(_ro_read_window_effective_time_us > _ro_read_window_minimum_time_us); + if (_ro_max_trx_time_us > _ro_read_window_effective_time_us - _ro_read_window_minimum_time_us) { + _ro_max_trx_time_us = _ro_read_window_effective_time_us - _ro_read_window_minimum_time_us; + } + ilog("Read-only max transaction time ${rot}us set to fit in the effective read-only window ${row}us.", + ("rot", _ro_max_trx_time_us)("row", _ro_read_window_effective_time_us)); + ilog("read-only-threads ${s}, max read-only trx time to be enforced: ${t} us", ("s", _ro_thread_pool_size)("t", _ro_max_trx_time_us)); - // Make sure _ro_max_trx_time_us is always set. - // Make sure a read-only transaction can finish within the read - // window if scheduled at the very beginning of the window. - // Add _ro_read_window_minimum_time_us for safety margin. - if (_max_transaction_time_ms.load() > 0) { - _ro_max_trx_time_us = fc::milliseconds(_max_transaction_time_ms.load()); - } else { - // max-transaction-time can be set to negative for unlimited time - _ro_max_trx_time_us = fc::microseconds::maximum(); - } - if (_ro_max_trx_time_us > _ro_read_window_effective_time_us) { - _ro_max_trx_time_us = _ro_read_window_effective_time_us; + app().executor().init_read_threads(_ro_thread_pool_size); } - ilog("Read-only max transaction time ${rot}us set to fit in the effective read-only window ${row}us.", - ("rot", _ro_max_trx_time_us)("row", _ro_read_window_effective_time_us)); - ilog("read-only-threads ${s}, max read-only trx time to be enforced: ${t} us", ("s", _ro_thread_pool_size)("t", _ro_max_trx_time_us)); _incoming_block_sync_provider = app().get_method().register_provider( [this](const signed_block_ptr& block, const block_id_type& block_id, const std::optional& obt) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6194c13f1..1039be1763 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -206,13 +206,13 @@ add_test(NAME compute_transaction_test COMMAND tests/compute_transaction_test.py set_property(TEST compute_transaction_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME read-only-trx-basic-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 1 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-basic-test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 3 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 2 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-parallel-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME read-only-trx-basic-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 1 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-basic-if-test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME read-only-trx-parallel-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 3 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME read-only-trx-parallel-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 2 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-parallel-if-test PROPERTY LABELS nonparallelizable_tests) -add_test(NAME read-only-trx-parallel-if-eos-vm-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable all --read-only-threads 16 --num-test-runs 3 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME read-only-trx-parallel-if-eos-vm-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable all --read-only-threads 16 --num-test-runs 2 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-parallel-if-eos-vm-oc-test PROPERTY LABELS nonparallelizable_tests) add_test(NAME read-only-trx-parallel-no-oc-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --eos-vm-oc-enable none --read-only-threads 6 --num-test-runs 2 --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST read-only-trx-parallel-no-oc-if-test PROPERTY LABELS nonparallelizable_tests) diff --git a/tests/read_only_trx_test.py b/tests/read_only_trx_test.py index eeb9a73046..57fae05fc8 100755 --- a/tests/read_only_trx_test.py +++ b/tests/read_only_trx_test.py @@ -210,6 +210,9 @@ def sendReadOnlyPayloadless(): def sendReadOnlySlowPayloadless(): return sendTransaction('payloadless', action='doitslow', data={}, auth=[], opts='--read') +def sendReadOnlyForeverPayloadless(): + return sendTransaction('payloadless', action='doitforever', data={}, auth=[], opts='--read') + # Send read-only trxs from mutltiple threads to bump load def sendReadOnlyTrxOnThread(startId, numTrxs): Print("start sendReadOnlyTrxOnThread") @@ -283,7 +286,7 @@ def runReadOnlyTrxAndRpcInParallel(resource, command, fieldIn=None, expectedValu def mixedOpsTest(opt=None): Print("mixedOpsTest -- opt = ", opt) - numRuns = 200 + numRuns = 100 readOnlyThread = threading.Thread(target = sendReadOnlyTrxOnThread, args = (0, numRuns )) readOnlyThread.start() sendTrxThread = threading.Thread(target = sendTrxsOnThread, args = (numRuns, numRuns, opt)) @@ -373,6 +376,60 @@ def runEverythingParallel(): for thr in threadList: thr.join() +def fastTransactions(): + Print("fastTransactions") + for i in range(1000): + result = sendReadOnlyPayloadless() + assert(result[0]) + +def slowTransactions(): + Print("slowTransactions") + for i in range(100): # run fewer number than regular Payloadless so total running time is close + result = sendReadOnlySlowPayloadless() + assert(result[0]) + +def foreverTransactions(): + Print("foreverTransactions") + for i in range(5): # run fewer number than slowPayloadless so total running time is close + result = sendReadOnlyForeverPayloadless() + assert(result[0] == False) # should fail + +def timeoutTest(): + Print("timeoutTest") + + # Send a forever readonly transaction. It should timeout + Print("Sending a forever read only transaction") + results = sendReadOnlyForeverPayloadless() + # Results look like + ''' + ( False, + {'processed': + { + ... + 'except': {'code': 3080004, 'name': 'tx_cpu_usage_exceeded', 'message': 'Transaction exceeded the current CPU usage limit imposed on the transaction' ...} + ... + } + } + ) + ''' + assert(results[0] == False) + assert('except' in results[1]['processed']) + assert(results[1]['processed']['except']['code'] == 3080004) + assert(results[1]['processed']['except']['name'] == "tx_cpu_usage_exceeded") + + # Send multiple different speeds of read only transactions simutaneously + # to trigger forever transactions are exhausted in read window but RETRYING + # at next round. + threadList = [] + threadList.append(threading.Thread(target = fastTransactions)) + threadList.append(threading.Thread(target = slowTransactions)) + threadList.append(threading.Thread(target = foreverTransactions)) + Print("Sending different speeds of read only transactions simutaneously") + for thr in threadList: + thr.start() + for thr in threadList: + thr.join() + try: startCluster() deployTestContracts() @@ -387,6 +444,9 @@ def runEverythingParallel(): mixedOpsTest() runEverythingParallel() + # should be running under multiple threads but no need to run multiple times + timeoutTest() + testSuccessful = True finally: TestHelper.shutdown(cluster, walletMgr, testSuccessful, dumpErrorDetails) diff --git a/unittests/test-contracts/payloadless/payloadless.abi b/unittests/test-contracts/payloadless/payloadless.abi index aafa35c171..f78c5c42f0 100644 --- a/unittests/test-contracts/payloadless/payloadless.abi +++ b/unittests/test-contracts/payloadless/payloadless.abi @@ -8,6 +8,11 @@ "base": "", "fields": [] }, + { + "name": "doitforever", + "base": "", + "fields": [] + }, { "name": "doitslow", "base": "", @@ -20,6 +25,11 @@ "type": "doit", "ricardian_contract": "" }, + { + "name": "doitforever", + "type": "doitforever", + "ricardian_contract": "" + }, { "name": "doitslow", "type": "doitslow", diff --git a/unittests/test-contracts/payloadless/payloadless.cpp b/unittests/test-contracts/payloadless/payloadless.cpp index e9556ca5ed..d7dcc99d4d 100644 --- a/unittests/test-contracts/payloadless/payloadless.cpp +++ b/unittests/test-contracts/payloadless/payloadless.cpp @@ -50,3 +50,17 @@ void payloadless::doitslow() { } } +void payloadless::doitforever() { + print("Im a payloadless forever action"); + constexpr size_t max_cpu_prime = std::numeric_limits::max(); + + while (true) { + for (size_t p = 2; p <= max_cpu_prime; p += 1) { + if (is_prime(p) && is_mersenne_prime(p)) { + // We need to keep an eye on this to make sure it doesn't get optimized out. So far so good. + //eosio::print_f(" %u", p); + } + } + } +} + diff --git a/unittests/test-contracts/payloadless/payloadless.hpp b/unittests/test-contracts/payloadless/payloadless.hpp index 0fea87a29b..10f696fded 100644 --- a/unittests/test-contracts/payloadless/payloadless.hpp +++ b/unittests/test-contracts/payloadless/payloadless.hpp @@ -11,4 +11,7 @@ class [[eosio::contract]] payloadless : public eosio::contract { [[eosio::action]] void doitslow(); + + [[eosio::action]] + void doitforever(); }; diff --git a/unittests/test-contracts/payloadless/payloadless.wasm b/unittests/test-contracts/payloadless/payloadless.wasm index 53cc07f560..6c8396afb6 100644 Binary files a/unittests/test-contracts/payloadless/payloadless.wasm and b/unittests/test-contracts/payloadless/payloadless.wasm differ