-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Fix memory leak in instantiation_cache #2701
Conversation
In order to break the module cache check, eosio.token need a small hack, place the following code in eosio.token.cpp, testing code will replace "hello,world" with other characters in order to workaround the module cache check.
Run the following testing code under build/contracts
Expecting to see no dramatically memory increase during the running of testing code. |
Keeping all the versions of code was an explicit choice at the time. Calling @wanderingbort what do you think? |
@@ -19,6 +19,12 @@ using namespace Runtime; | |||
|
|||
namespace eosio { namespace chain { | |||
|
|||
struct account_module { | |||
digest_type digest; | |||
std::shared_ptr<wasm_instantiated_module_interface> module_interface; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to be used anymore, meaning the map<account_name, account_module> account_module_map
can probably become a map<account_name, digest_type>
That would remove the need to change the std::unique_ptr
to a std::shared_ptr
as well which is nicer.
tests/tests/misc_tests.cpp
Outdated
|
||
auto wasm = ::eosio::chain::wast_to_wasm(code); | ||
|
||
auto wasm_code = shared_vector<char>(alloc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shared_vector is just a vector that has a different allocator. For the purposes of your test it could just be a vector<char>
which would make the inclusion of bip::managed_mapped_file seg(bip::open_or_create,"./test.db", 1024*1024);
and all the related code to derive alloc
unnecessary
tests/tests/misc_tests.cpp
Outdated
code += "(call $test (call $i64_trunc_u_f64 (f64.const 1)))\n"; | ||
code += "))"; | ||
|
||
wasm_interface_impl wasm_imp(wasm_interface::vm_type::binaryen); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs to be the runtime that the unit tests are targeting
module_interface in account_module is used for reference counting, so when no account reference to the code anymore,
we can delete the code from instantiation_cache safely.
//check code reference
if (oldcode_replaced) {
auto _it = instantiation_cache.find(old_digest);
if (_it != instantiation_cache.end()) {
//no account reference to this instantiated module anymore, release it
if (_it->second.use_count() == 1) {
instantiation_cache.erase(_it);
}
}
}
This prevent instantiation_cache from memory leak.
… On May 4, 2018, at 4:55 AM, wanderingbort ***@***.***> wrote:
@wanderingbort requested changes on this pull request.
In libraries/chain/include/eosio/chain/wasm_interface_private.hpp <#2701 (comment)>:
> @@ -19,6 +19,12 @@ using namespace Runtime;
namespace eosio { namespace chain {
+ struct account_module {
+ digest_type digest;
+ std::shared_ptr<wasm_instantiated_module_interface> module_interface;
This doesn't seem to be used anymore, meaning the map<account_name, account_module> account_module_map can probably become a map<account_name, digest_type>
That would remove the need to change the std::unique_ptr to a std::shared_ptr as well which is nicer.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <#2701 (review)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/Ad6ZPogrFNe5t-eOVwLEwKwFhiS3yRr6ks5tu27OgaJpZM4Tu_l3>.
|
wasm_interface_impl::get_instantiated_module has shared_vector<char> as it’s parameter,
I can not find a way about converting vector<char> to shared_vector directly.
So managed_mapped_file is just for the purpose of keep the code in wasm_interface_private.hpp unchanged, but still it can be tested.
… On May 4, 2018, at 4:59 AM, wanderingbort ***@***.***> wrote:
@wanderingbort commented on this pull request.
In tests/tests/misc_tests.cpp <#2701 (comment)>:
> + (memory $0 1)
+ (export "apply" (func $apply))
+ (func $i64_trunc_u_f64 (param $0 f64) (result i64) (i64.trunc_u/f64 (get_local $0)))
+ (func $test (param $0 i64))
+ (func $apply (param $0 i64)(param $1 i64)(param $2 i64)
+ )=====";
+
+ std::string code = wast_code;
+ code += "(call $test (call $i64_trunc_u_f64 (f64.const 1)))\n";
+ code += "))";
+
+ wasm_interface_impl wasm_imp(wasm_interface::vm_type::binaryen);
+
+ auto wasm = ::eosio::chain::wast_to_wasm(code);
+
+ auto wasm_code = shared_vector<char>(alloc);
shared_vector is just a vector that has a different allocator. For the purposes of your test it could just be a vector<char> which would make the inclusion of bip::managed_mapped_file seg(bip::open_or_create,"./test.db", 1024*1024); and all the related code to derive alloc unnecessary
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <#2701 (review)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/Ad6ZPuD2D9yUfRhDtJWRPM6YQVCprKLCks5tu2_DgaJpZM4Tu_l3>.
|
Or can someone tell me how to convert vector<char> to shared_vector<char> nice and easy, so I can remove the ugly code :).
… On May 4, 2018, at 8:31 AM, john rok ***@***.***> wrote:
wasm_interface_impl::get_instantiated_module has shared_vector<char> as it’s parameter,
there is no way you can convert vector<char> to shared_vector directly.
So managed_mapped_file is just for the purpose of keep the code in wasm_interface_private.hpp unchanged, but still it can be tested.
> On May 4, 2018, at 4:59 AM, wanderingbort ***@***.*** ***@***.***>> wrote:
>
> @wanderingbort commented on this pull request.
>
> In tests/tests/misc_tests.cpp <#2701 (comment)>:
>
> > + (memory $0 1)
> + (export "apply" (func $apply))
> + (func $i64_trunc_u_f64 (param $0 f64) (result i64) (i64.trunc_u/f64 (get_local $0)))
> + (func $test (param $0 i64))
> + (func $apply (param $0 i64)(param $1 i64)(param $2 i64)
> + )=====";
> +
> + std::string code = wast_code;
> + code += "(call $test (call $i64_trunc_u_f64 (f64.const 1)))\n";
> + code += "))";
> +
> + wasm_interface_impl wasm_imp(wasm_interface::vm_type::binaryen);
> +
> + auto wasm = ::eosio::chain::wast_to_wasm(code);
> +
> + auto wasm_code = shared_vector<char>(alloc);
> shared_vector is just a vector that has a different allocator. For the purposes of your test it could just be a vector<char> which would make the inclusion of bip::managed_mapped_file seg(bip::open_or_create,"./test.db", 1024*1024); and all the related code to derive alloc unnecessary
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub <#2701 (review)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/Ad6ZPuD2D9yUfRhDtJWRPM6YQVCprKLCks5tu2_DgaJpZM4Tu_l3>.
>
|
This is true however, you never access I can only find an access for Basically, the mechanism is sound but I don't see a reason to share ownership of |
Good catch, I did not realize that. I think its cleaner to have the storage semantics defined elsewhere. In the past, we've other places we've passed If you want to take that approach, I would approve of it. It adds a small amount of complexity to the caller and removes a large amount of complexity from your test code and all the called code. |
@wanderingbort, I think you understand why I use share_ptr, My purpose is to tracking the reference of code used by accounts since the same code may be used by more than one account and once no account reference to the code in instantiation_cache anymore, the code need to be release to prevent instantiation_cache from memory leak. Since accounts can update their code with no times limit, this bug may be used by malicious attack and paralyses the network in a few hours.
Can you show me a piece of code on how to tracking the reference of code with map<account_name, digest_type>?
I can only find one way: iterate through the entire map every time updating the code in account, and I think that's inefficient.
I will accept your suggestion if I can understand it’s a better solution.
… On May 4, 2018, at 8:43 PM, wanderingbort ***@***.***> wrote:
module_interface in account_module is used for reference counting, so when no account reference to the code anymore,
This is true however, you never access account_module::module_instance from an iterator or entry in the account_module_map as far as I can tell.
I can only find an access for account_module::digest which is used to find the entry instantiation_cache, decrement the reference count and potentially erase the entry.
Basically, the mechanism is sound but I don't see a reason to share ownership of wasm_instantiated_module_interface between the two maps. Only one is ever used to access that data and therefore it seems like this mechanism would still work as designed with instantiation_cache having the only ownership and using std::unique_ptr
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <#2701 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/Ad6ZPj0sHWGhbstm_0-E-ydJWCW7zbDNks5tvEzxgaJpZM4Tu_l3>.
|
What I missed was that you were using I would recommend making the fact that you are reference counting explicit since you don't actually need shared memory ownership. consider this patch: https://gist.github.com/wanderingbort/117d0d21b0affab98d9216f33174ac18 That said, if you integrate this patch I would make an explicit struct instead of using |
@wanderingbort Nice work, it’s better than I thought. Patch done. I got stuck in my own mind. Also return a reference to shared_ptr is actually unsafe.
… On May 5, 2018, at 12:25 AM, wanderingbort ***@***.***> wrote:
What I missed was that you were using std::shared_ptr as a basic reference count. This is a very opaque way of achieving what you want and has the downside of implying that there is shared ownership of the module when there is not.
I would recommend making the fact that you are reference counting explicit since you don't actually need shared memory ownership.
consider this patch: https://gist.github.com/wanderingbort/117d0d21b0affab98d9216f33174ac18 <https://gist.github.com/wanderingbort/117d0d21b0affab98d9216f33174ac18>
This is an example using std::pair that passes your test and makes the mechanism clearer in my opinion without granting shared ownership over the lifecycle of the allocated memory.
That said, if you integrate this patch I would make an explicit struct instead of using std::pair but I wanted to make my point as quickly as possible.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <#2701 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/Ad6ZPuEHagTo6lVCF5Ijovqwqk7crEDlks5tvIDrgaJpZM4Tu_l3>.
|
I think after some internal discussion a couple days ago we decided this was not what we wanted to do. Two reasons: 1) forks could throb cache invalidation more than desired and 2) we actually need the cache to additionally be aware of the code's "injection version" |
Hmm, it’s the core part, good to hear that you guys have a better thought about improvement.
… On May 10, 2018, at 3:08 AM, Matt Witherspoon ***@***.***> wrote:
I think after some internal discussion a couple days we decided this was not what we wanted to do. Two reasons: 1) hard forks could throb cache invalidation more than desired and 2) we actually need the cache to additionally be aware of the code's "injection version"
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <#2701 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/Ad6ZPppfSgTHNRep61kswD5kxaJ-3PZeks5twz6mgaJpZM4Tu_l3>.
|
@spoonincode should we close this PR? |
I would like to revisit this issue. Fork thrashing could be resolved by expiring cache on IRB rather than immediately upon changing of code. Lets resolve the merge conflicts. |
I wonder if the garbage collector in WAVM would need to be fired for the WAVM modules to actually be freed. |
Sorry, I deleted my eos fork accidentally, Hope this issue will be solved soon. |
No description provided.