Deleting a text mark from buffer: what happens really?

The docu for gtk_text_buffer_delete_mark is quite ambiguous: “Removes the reference the buffer holds to the mark, so if you haven’t called g_object_ref() on the mark, it will be freed. Even if the mark isn’t freed, most operations on mark become invalid, until it gets added to a buffer again with gtk_text_buffer_add_mark().”

Experimentally, I watch the following behavior:

GtkTextMark *mark = gtk_text_mark_new(NULL, FALSE);
...
while(...){ // this symbolizes repeating tasks
	
	gtk_text_buffer_add_mark(buffer, mark, &iter);
		
	// use mark
			
	g_object_ref(mark);	// ???
		
	gtk_text_buffer_delete_mark(buffer, mark);
}

// the reference count was increased and contains the number of tasks + 1

while(G_IS_OBJECT(mark)) g_object_unref(mark);	// the right cleanup

It seems that calling g_object_ref before deleting a mark is not necessary. Even more: this is bad. Even more: for the right cleanup you need additionally to unreference an already deleted mark even if g_object_ref was not called before. Is this all correct?

So, nobody has disagreed. This means that my conclusions are correct and the documentation is wrong, which raises the question how reliable documentation is in general. Finding a memory leak is a challenge. It is no coincidence that there is a rule or a joke shared by Linux developers: the best method for cleanup is restarting the application. I propose the following two methods against potential memory leaks that take into account the low quality of the API documentation:

(1) typically by getter functions, the documentation says “do not free the returned pointer”; you should test this notice and try to free the pointer nevertheless; if the program crashes, or an error message/warning is emitted, everything is OK; if not, what then? then, post a question on this forum :slight_smile:

(2) all objects created by the application that are finished with a delete/remove/destroy API function should be unreferenced like follows:

gtk_widget_destroy(widget);

if(G_IS_OBJECT(widget)) g_object_unref(widget);

What would you say about this approach, is it a reasonable one, or, rather a paranoid one? Or both? It reminds me a little of MISRA rules.

Or nobody has answered your question. Remember: this is a volunteer driven project, and you are not owed a reply.

It is not necessary.

The documentation is saying, albeit awkwardly, is that the text buffer acquires a reference to the mark, and removing the mark will release that reference. If you want to keep a mark instance around, you can hold on to a reference yourself.

There’s really no need to do that. Getters are annotated with an ownership transfer rule:

  • “transfer none”: the ownership of the returned data is not transferred to the caller
  • “transfer container”: the ownership of the returned data container, but not of its contents, is transferred to the caller
  • “transfer full”: the ownership of the returned data is transferred to the caller

If no transfer rule is specified, then “transfer none” is the default.

No, this is wrong.

You should only ever call g_object_unref() on a widget if you called g_object_ref() yourself, e.g.

GtkWidget *label = gtk_label_new ();

g_object_ref_sink (label);

gtk_box_append (box, label);

// at the end of the process

g_object_unref (label);

The ownership of a widget is transferred to its parent with gtk_widget_set_parent() (and every container widget wrapper around it); once a parent container calls gtk_widget_unparent() on its children, each child drops its reference and unless somebody is holding an additional reference, the widget is collected and its resources freed.

Debugging memory leaks is easy: run your application under Valgrind. Debugging reference counting issues is more complicated; there are tools, like systemtap, but they can be finicky.

In any case, when the GTK documentation is not clear, feel free to open an issue.

Thank you, Emmanuele, for an – as usual – comprehensive answer. However, the situation with deleting text marks is more complicated.

The quoted documentation says in other words: while deleting a text mark, sometimes the mark is freed and sometimes is not. This is absolutely correct. However, the documentation does not disclose “when”. Here is the answer:

In short, it depends on how the mark has been created and added to the text buffer. If an already existing mark is added by using gtk_text_buffer_add_mark, the reference count of the mark is increased by 1. Because the initial minimal value is 1, it is now 2 (or more). If the mark is created and added by gtk_text_buffer_create_mark, the mark is created with gtk_text_mark_new but its reference count stays untouched and contains value of 1. By deleting the mark with gtk_text_buffer_delete_mark the reference count is decreased by 1. If it was 1, it gets value of 0 and the object is freed; if it was 2 (or more), it gets back the initial value which is not 0. In this case you additionally need to unreference the mark, otherwise its stays in the memory, which effectively means a memory leak.

I am not sure that this (badly documented) variability in the result of applying of gtk_text_buffer_delete_mark is a case of good design. Because similar things can occur by other pairs of adding/deleting(removing, destroying) functions, my idea about unreferencing “deleted” objects just in case, seems to be not quite mad. Unless you are absolutely sure, that GtkTextMark is unique in this context.

You just described reference counting, which applies to every object.

No, it does not say that.

It says that the mark object is removed from the buffer; if you’re holding a reference on the mark, then the instance is still available—that’s what “acquiring a reference” means. The resources associated to any GObject instance are freed when the last reference held on it is released.

I partially disagree.

If a freshly-created text mark had a floating reference then it were treated differently by the “adder”-function, the reference count were kept unchanged (due to the call to g_object_ref_sink).

For this reason, the rule “no call to g_object_unref without a previous (explicit) call to g_object_ref” is valid only for widgets in narrow sense and other descendants of GInitiallyUnowned.

This rule is not valid for text marks and other “normal” objects.

There is another point. Look at the code:

GtkTextMark *mark = gtk_text_mark_new(NULL, FALSE);
		
g_object_force_floating((GObject *)mark);
		
g_object_is_floating(mark);	// the function returns TRUE
		
gtk_text_buffer_add_mark(buffer, mark, &start);
		
gtk_text_buffer_delete_mark(buffer, mark);
		
g_object_is_floating(mark);	// the function still returns TRUE

The adder-function takes it for granted that the mark is a normal object. The case of a floating object is ignored. At the same time, the creation and adding of text marks are separated steps. This means, that something can happen inbetween, e.g. the (allowed) transformation of the reference type, and API functions should take this into account.

This code is entirely incorrect. You cannot go around marking random object instances as floating, and then expect the API to cope with that. That’s not how anything works. If you do that, then you’re on your own, and you get to keep both the pieces when things break horribly.

GtkTextMark is a GObject. Anything using it expects a GObject. This means GtkTextBuffer will acquire a plain reference to any mark added to it, and then release the reference when the mark is deleted. Deleting a mark will detach the mark from the buffer, and if the mark is entirely owned by the buffer, the mark instance will be collected. That’s all the documentation guarantees to the consumers of the API.

You are just constructing a strawman and then setting fire to it—good for you, but it has no relevance on the documentation, the API, or its guarantees.

I think you’re making things needlessly complicated in your mental concept around an unfortunate wording in the documentation (which should not have said “[unless you have] called g_object_ref() on the mark” in those words, but probably rather “unless you are holding a reference to the mark” or something).

As Emmanuele says, it’s a very straightforward instance of plain reference counting, nothing bizarre or convoluted here: add_mark() adds a reference, and delete_mark() drops it. You mention create_mark() seemingly as a source of confusion on the reference count after a delete_mark() call, but when you see create_mark() doesn’t take an object, it’s not far fetched to accept it not adding an extra reference (while internally it could just release the reference after having called add_mark()).

The “expected” handing on your side when using add_mark() is for you to release your reference to it when you don’t want to hold one yourself, and let the buffer keep (or not) its own reference as it needs.
So basically, either:

  • you have a mark you want to reuse and keep a long-lived reference to it,
  • you want to create a mark and “hand it over” to the buffer, and then you call unref() right after add_mark() so the buffer has the only reference to it.

In the first case, remove_mark() will not destroy the object as you still have a reference to it, otherwise you already released the reference and it’ll be dropping the last one, thus collecting the object.

GtkTextMark *mark = gtk_text_mark_new(NULL, FALSE);
while (…) {
  gtk_text_buffer_add_mark(buffer, mark, &iter); // adds a reference to mark
  // …
  gtk_text_buffer_delete_mark(buffer, mark); // removes a reference to mark
}
g_object_unref(mark); // releases the last reference created by new()

/* or */

while (…) {
  GtkTextMark *mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, FALSE); // creates a mark and own the reference to it, just returning an unowned pointer (transfer: none)
  // …
  gtk_text_buffer_delete_mark(buffer, mark); // releases the last reference to the mark
}

The problem I think only arises from the documentation mentioning ref(), which is only really relevant if the mark was created with create_mark(). This said, it’s often more convenient just use create_mark() than GtkTextMark::new() followed by add_mark() (and ref the retrurned mark if you really want to hold on to it).

Yes, it covers only this case, but there are no hints of this limitation.

I’m using both approaches and meanwhile I have learned how to distinguish between them. In fact, the documentation was not very helpful thereby. However the strange wording motivated me to examine the issue.

Why not? Saying “floating objects are treated in the manner x” could mean the presence of an underlying mechanism that does it for all relevant functions. Of course, it can be simply a hint for contributors who must then implement it manually for every single function.

This was a proposal (not very serious one, I must confess). If you don’t agree, you can write simply that. Many existing things once had “no relevance”.

===

The only thing that I don’t understand is why g_object_force_floating expects as argument GObject * and not GInitiallyUnowned * ?

In the latter case the illegality of the application of this function on text marks and other pure GObjects would be obvious.

Because that’s not supported by the API. Of course, you can call g_object_force_floating() on an instance, but objects that will acquire a reference do not expect floating reference.

In general, floating references are a bad idea to begin with; they exist purely as a C convenience, to allow things like:

foo_container_add_child (container, foo_child_new ());

except we have ways to explicitly mark ownership transfer, these days, and it’s perfectly safe to write API that works that way.

The reason why the GObject floating reference API exists in its current form is that it was bolted on top of GObject itself in order to seamlessly migrate GTK from GtkObject to GObject. It’s a transitional API that cannot be removed because of API stability considerations, but it’s not meant to be used that way.

The proper way in the GType type system to design objects to have an initial floating reference is to derive from GInitiallyUnowned, and then ensure that any ownership transfer API calls g_object_ref_sink() instead of g_object_ref().

It’s not just a matter of “not agreeing”: it’s a matter of fundamentally misunderstanding how the API works. Since people might end up on this topic in the future, I need to be explicit—especially because you keep trying to renegotiate facts in the face of one of the maintainers of the library you’re using telling you that you’re wrong, and you’re using the API in the wrong way.

Instead of trying to get the last word in, I would have appreciated you submitting a change to the documentation, to clarify it for newcomers.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.