Communicate JS memory pressure or GC to .NET WASM #92434
-
Currently I am using Uno.Wasm.Bootstrap for supporting handles to JS objects from .NET using JSObjectHandle/IJSobject, which basically maintains an array of JS objects that each C# object indexes into. Once a C# object is garbage collected, then it removes the corresponding JS instance from the array making it in turn available for GC in javascript. I would guess .NET's System.Runtime JSObject will behave similarly in terms of preventing the JS object from being GC'd as long as the .NET JSObject is alive, but I have not worked with it a great deal yet. I assume that is the reason it implements IDisposable. Consider this scenario:
Obviously this falls under an "unmanaged resource" category and what really needs to happen is JSObject must be managed with Managing IDisposable types imposes alot of restrictions on how code can be structured, impedes the general expressiveness of code, additional work, and it is always a potential pitfall. Because both systems conceptually have similar concepts of memory management, it seems like there's a real opportunity to free devs of these concerns and eliminate pitfalls by having the systems exchange information to facilitate coordinated GC. If JS could signal to .NET (perhaps by exposing a GC event) that it has memory pressure and is about to do a JS GC, then .NET could first perform its own GC, which would ensure any unreferenced JSObject's are GC'd and allow the corresponding JS object to be freed in JS's subsequent GC. This would allow developers to be freed from managing deterministic finalization for these types. I'm not sure if having JS GC "wait" for a .NET GC would be a concern though. Alternatively, exposing memory pressure metadata from JS to .NET might allow .NET to preemptively GC in anticipation of a JS GC without requiring the JS engine to hook up to .NET GC explicitly or wait for it. Another possibility is for JSObject to track metadata about the JS size of the JS object, and this could be used by .NET to aggregate estimates of JS memory usage for its JSObject's, and use that as a heuristic to influence when it needs to do a GC. Some of these approaches I think could be implemented without explicit support of the WASM spec, but obviously some of them would require that. I admittedly can't really grok alot of what is in the spec to speak intelligently about it or make concrete suggestions. Hopefully you'll keep options to simplify memory management on your radar. Thank you. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Yes, the JSObject and also As you could guess having 2 GCs in the same process is quite challenging and that's reason why I think about those proxies as expensive resource.
Regarding memory pressure: We do register proxies on the JS side into But I'm not sure that's reason enough to trigger (expensive) Mono GC for all users. By the spec, My 2c :) @kg any thoughts ? |
Beta Was this translation helpful? Give feedback.
-
Realistically speaking the solution to this is more robust cross-language GC integration. If you want to communicate memory pressure to .NET you need to know "how much" to communicate, and now you are reliant on .NET to not change its pressure thresholds/logic for GCs to happen when you want them. If they happen at the wrong times your app will feel unresponsive and freeze. For your scenario it may be most appropriate to just trigger a managed GC periodically on a timer to free unused resources. In the long run we may be able to utilize WASM GC integration to hold onto JS objects directly somehow in a way that would alleviate this problem, but I'm not sure when/if that will happen due to the constraints of WASM and the status of the related specs. |
Beta Was this translation helpful? Give feedback.
-
I understand the caution, especially around generating unnecessaries GC's. Responding to a couple points from each of you.
Yes, taking that mindset addresses the current as-is implementation. REducing number of allocations, and fencing them with Dispose patterns avoids the problem since strictly they are unmanaged resources. Given current implementation this is what is appropriate. I would like to encourage a more optimistic mindset, and not commit long term to the idea that it's a expensive in terms of a handle to a resource(obviously interop calls are another story), because that requires a high level of "carefulness" we must handle these with, elevated to the same level as say a connection or file handle where those patterns are a necessity.
Yep, if it's something that is kept on the horizon, because I really think there's probably an elegant solution somewhere that can be cautiously reserved to avoid excessive GC's.
I could imagine a mechanism that queues a deferred .NET GC from JS in cases where JS is approaching memory pressure, such that the GC request is only processed by .NET when there's an opportunity of little activity, to minimize visible impact. I think conceptually it's similar to GC Server Mode used to opportunistically garbage collect when it will have less visible impact. At the other end of the spectrum, an immediate .NET GC would only be requested only under certain extreme circumstances. Maybe only if there's a JS GC which wasn't sufficient to bring it below its desired memory threshold, and only then trigger an immediate .NET GC. Or maybe even more restrictive is only in the worst case scenario where a JS GC was triggered by an attempted allocation, but the GC didn't free up enough for the allocation, so it then requests a .NET GC. Consider if there was some such mechanism, and worst case scenario as a consuming dev you saw visible impact. Assuming the mechanism doesn't increase the number of GC's by an order of magnitude, then you probably already fall into a usage pattern approaching memory management problems. I.e. in the absence of this mechanism, you're usage pattern is getting close to visible issues with current GC implementation, and probably should already be thinking about changing approaches to how you handle some of your more allocation "churny" bits of code.
Certainly understood. I wanted to bring it up in case it had not been considered that there might be a happier solution somewhere over the horizon, rather than having to treat them as unmanaged resources eternally. Thanks to both of you for taking the time to think about this. And thanks for your hard work on the interop implementations in .NET 7.
I don't want to focus too much on this, but anyone doing this would be rolling the dice on whether I get the right combination of allocations between the interval and crashing. You would say the same of this if someone had an unmanaged resource they weren't disposing of properly, and rather than find the root problem, they just added a timed GC. |
Beta Was this translation helpful? Give feedback.
Realistically speaking the solution to this is more robust cross-language GC integration. If you want to communicate memory pressure to .NET you need to know "how much" to communicate, and now you are reliant on .NET to not change its pressure thresholds/logic for GCs to happen when you want them. If they happen at the wrong times your app will feel unresponsive and freeze. For your scenario it may be most appropriate to just trigger a managed GC periodically on a timer to free unused resources.
In the long run we may be able to utilize WASM GC integration to hold onto JS objects directly somehow in a way that would alleviate this problem, but I'm not sure when/if that will happen due to …