--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to the Google Groups "v8-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to v8-users+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Thanks, Michael, this gives me a better idea of what to look for.I'm a little surprised because I had always assumed that tracing was triggered during allocation. When else does it run?
Also, what counts as allocation? Does allocating a new persistent handle in itself count, or is it strictly allocating objects?
I've designed my code such that when a reference to an object exists on the C++ stack, I call ClearWeak() on the persistent handle. Once all C++ stack references go away, I call SetWeak() again. Could this possibly confuse the tracer if a trace is in-progress? Should I, for example, be calling RegisterExternalReference() after SetWeak()?
- The ClearWeak part should work if there's a final GC pause interrupting whatever is done with C++ stack.
- The SetWeak after being done may or may not require a manual registering call, depending on how EmbedderHeapTracer is implemented.
In Blink we implemented regular traced garbage collection for the EmbedderHeapTracer. This means that the object containing such an interesting references may have been processed by the EmbedderHeapTracer already. If the reference is then just marked as weak with SetWeak() the GC misses out on it as it never sees the containing object again. Blink emits a RegisterExternalReference() call for such objects. The general concept for solving mutation in the graph while a garbage collector is running is called (write barrier).
I'm still working on this off-and-on. The issue is not as urgent as it sounds because in the case that a wrapper object is collected prematurely, we simply remake it as needed. Still, this could cause issues if scripts add JS properties on native objects and expect them to stay there, but the issues seems to be affecting only a handful of specific scripts on our platform and none of them happen to do that... Still, we do need to fix the issue for future scripts.
On Wed, Jan 16, 2019 at 2:23 AM Michael Lippautz <mlip...@chromium.org> wrote:- The ClearWeak part should work if there's a final GC pause interrupting whatever is done with C++ stack.Sorry, I'm a GC noob. What happens in the "final GC pause", exactly? What does it mean for it to "interrupt whatever is done with the C++ stack"?
Does ClearWeak() implicitly mark the object, if tracing is already in-progress? Or is that what happens in the final GC pause? Does that object get traced?
Looking at the code, it seems like ClearWeak() does not mark the object. So I guess I should probably RegisterExternalReference() after ClearWeak()? Normally a strong reference would be a root and so would be marked at the beginning of the trace cycle, but if ClearWeak() happens mid-cycle it seems like there's an issue.
However, this doesn't seem to fit the pattern of the problems I'm seeing in production.- The SetWeak after being done may or may not require a manual registering call, depending on how EmbedderHeapTracer is implemented.In Blink we implemented regular traced garbage collection for the EmbedderHeapTracer. This means that the object containing such an interesting references may have been processed by the EmbedderHeapTracer already. If the reference is then just marked as weak with SetWeak() the GC misses out on it as it never sees the containing object again. Blink emits a RegisterExternalReference() call for such objects. The general concept for solving mutation in the graph while a garbage collector is running is called (write barrier).Not sure I follow. RegisterExternalReference() is only meaningful on objects that are currently white, right? But before SetWeak(), the handle was strong, making it a root. Roots should have been marked gray at the start of the tracer cycle?
In my case, I think the objects are frequently newly-allocated at the time SetWeak() is called. But a newly-allocated object should be marked black at allocation, right?
Thanks again for the help! Hopefully we'll be able to open source this glue library so others can reuse this work...
--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to a topic in the Google Groups "v8-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/v8-users/EeIPnAmNa4g/unsubscribe.
To unsubscribe from this group and all its topics, send an email to v8-users+u...@googlegroups.com.
Hi Michael,Thanks, I think I now see the problem: I had assumed that newly-allocated objects were always marked immediately on allocation, since they are clearly reachable at that time. But, now that I think about it, I suppose that would make it hard to quickly collect short-lived objects.
I think realistically the current embedding API requires an understanding of implementation details to use correctly. Since there is no written guide, the only way I've been able to understand how the pieces fit together is by understanding implementation details. In particular I think the concept of write barriers and when to use them is not very intuitive to those who haven't studied garbage collection. Once I get this sorted out I may try to write a beginner's guide from that perspective.
------The TracedGlobal API looks like a good addition to make embedders' lives easier, but it might turn out it doesn't quite fit with the model we've settled on in our code. Specifically, we want to allocate JS wrapper objects for a C++ object lazily, on the first time the object is directly referenced from JS. This makes it tricky to use V8 handles to link C++ objects together, because there may not be a JS object yet for the handle to point to. Instead, we reference the C++ object directly, and the C++ object holds a handle to its own JS wrapper which is allocated lazily. If each reference from another C++ object required its own TracedHandle, then we'd have to keep a reverse mapping of all the references pointing to an object so that we could initialize all of the TracedHandles when the wrapper is lazily allocated.
A further problem is that the parent object may itself not have any wrapper allocated, but may have references from C++ which we considered to be strong references. Consider e.g. the case of a Request object containing a Headers object. Imagine the Headers object has a JS wrapper but the Request object does not, and is only referenced from C++. In this case, the reference from Request -> Headers will never be traced, and so we need to treat the Headers wrapper as having a strong reference. Later on, a wrapper may be allocated for the Request. Once that happens, then we only need a strong reference on the Request wrapper itself, and we can rely on tracing to find the Headers wrapper.
So, the problem here is that over the lifetime of a reference between two C++ objects, the reference's nature can change between being C++-only (no V8 handle), being a strong V8 handle, and being a traced V8 handle. So it doesn't seem like we can simply drop in a TracedGlobal here, unfortunately.That said, if you would prefer that we move towards using weak handles strictly for registering the destructor callback, and strictly use TracedGlobal for traced handles, then I think we can work with that by having a scheme where each object potentially holds three different handles to its own wrapper:- A weak independent handle, to get the weak callback.
- A strong handle, when any non-traced references exist from C++, to keep the wrapper alive.
- A TracedGlobal, when any traced references exist from C++, used to implement tracing.Let me know if you think it would be a good idea for me to implement that instead of relying on one handle and doing SetWeak()/ClearWeak().
------Also, another technical question clarifying something you said: If an object is first discovered and marked during "final pause" (e.g. because it has a strong persistent handle at that point which was never seen before), it still gets traced, right? I guess that means that the "final pause" could end up arbitrarily long, if some deep object tree managed to slip by the tracer until then?
One more low-level question (where it seems like implementation details matter to embedders):Based on what you've said, an object may or may not be "black" on allocation. It seems, though, that I need to do different things depending on the color. If it is allocated black, then I need to arrange to trace the object myself to register all its outgoing references. If it is white, then I need to call RegisterExternalReference(), and V8 will trace it later. If it's gray, I don't need to do anything.But if I don't know the color, it seems I need to *both* call RegisterExternalReference() and initiate my own trace to cover all bases. But in the case that it's not already-black, this will result in a redundant trace later on.
Is it best to live with this potential redundant trace, or is there some way I can detect the color after allocation?
Well, barriers are only needed for incremental (and concurrent) collection. From an embedder perspective all references reported by V8 can be traced after EmbedderHeapTracer::EnterFinalPause() is called which would mean that there's no incremental collection and barriers are unnecessary.
We suggest using actually tracing for C++ to solve this problem. This way objects are traced through until they reach entry points into V8.It's more engineering effort but simple to reason about.
The weak callback is only for the destructor, right? How do you differentiate between a C++ object without JS wrapper (lazy creation) and a C++ object that should be destructed?
- A strong handle, when any non-traced references exist from C++, to keep the wrapper alive.These should usually be temporary (ideally Local<T>) and looks fine to me.
The redundant trace can be avoided by having a color marker on the C++ object itself so that it only traces through it once.
Ultimately, solving all those issues leads to having a fully trace-based system with marking colors on the C++ side too. We went that way because we need a system to collect reference cycles and the easiest way to do so is by basing the whole system on tracing.