Re: [PATCH 3/7] go: Allow notmuch objects to be garbage collected
authorAdrien Bustany <adrien@bustany.org>
Thu, 19 Jul 2012 18:25:10 +0000 (21:25 +0300)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:48:24 +0000 (09:48 -0800)
ec/5ca22893220fb8ec4f9746d2e88c50cdc698f8 [new file with mode: 0644]

diff --git a/ec/5ca22893220fb8ec4f9746d2e88c50cdc698f8 b/ec/5ca22893220fb8ec4f9746d2e88c50cdc698f8
new file mode 100644 (file)
index 0000000..dc3691b
--- /dev/null
@@ -0,0 +1,414 @@
+Return-Path: <adrien@bustany.org>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id D2640431FB6\r
+       for <notmuch@notmuchmail.org>; Thu, 19 Jul 2012 11:25:16 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
+       autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id zP9Rpi5XX19Q for <notmuch@notmuchmail.org>;\r
+       Thu, 19 Jul 2012 11:25:15 -0700 (PDT)\r
+Received: from mail.bustany.org (bustany.org [176.31.244.208])\r
+       by olra.theworths.org (Postfix) with ESMTP id 022F7431FAE\r
+       for <notmuch@notmuchmail.org>; Thu, 19 Jul 2012 11:25:14 -0700 (PDT)\r
+Received: from [192.168.1.147] (91-158-2-79.elisa-laajakaista.fi\r
+ [91.158.2.79])        by mail.bustany.org (Postfix) with ESMTPSA id 80077140090;\r
+       Thu, 19 Jul 2012 20:27:41 +0200 (CEST)\r
+Message-ID: <50085106.8040804@bustany.org>\r
+Date: Thu, 19 Jul 2012 21:25:10 +0300\r
+From: Adrien Bustany <adrien@bustany.org>\r
+User-Agent: Mozilla/5.0 (X11; Linux x86_64;\r
+       rv:13.0) Gecko/20120615 Thunderbird/13.0.1\r
+MIME-Version: 1.0\r
+To: Austin Clements <amdragon@MIT.EDU>\r
+Subject: Re: [PATCH 3/7] go: Allow notmuch objects to be garbage collected\r
+References: <1342636475-16057-1-git-send-email-adrien@bustany.org>\r
+       <1342636475-16057-4-git-send-email-adrien@bustany.org>\r
+       <20120718204001.GT31670@mit.edu>\r
+In-Reply-To: <20120718204001.GT31670@mit.edu>\r
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed\r
+Content-Transfer-Encoding: 8bit\r
+Cc: notmuch@notmuchmail.org\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Thu, 19 Jul 2012 18:25:17 -0000\r
+\r
+Le 18/07/2012 23:40, Austin Clements a écrit :\r
+> This is subtle enough that I think it deserves a comment in the source\r
+> code explaining that tracking the talloc owner reference, combined\r
+> with the fact that Go finalizers are run in dependency order, ensures\r
+> that the C objects will always be destroyed from the talloc leaves up.\r
+\r
+Definitely, I tend to comment in the commit message and forget about the \r
+code...\r
+\r
+>\r
+> Just one inline comment below.  Otherwise, I think this is all\r
+> correct.\r
+\r
+Agree with the comment, the Database should be the parent. I guess I \r
+wasn't sure of the talloc parenting.\r
+\r
+>\r
+> Is reproducing the talloc hierarchy in all of the bindings really the\r
+> right approach?  I feel like there has to be a better way (or that the\r
+> way we use talloc in the library is slightly broken).  What if the\r
+> bindings created an additional talloc reference to each managed\r
+> object, just to keep the object alive, and used talloc_unlink instead\r
+> of the destroy functions?\r
+\r
+Reproducing the hierarchy is probably error prone, and not that simple \r
+indeed :/\r
+I haven't checked at all the way you suggest, but if we use \r
+talloc_reference/unlink, we get the same issue no?\r
+- If we do for each new wrapped object talloc_reference(NULL, \r
+wrapped_object), the the object will be kept alive until we \r
+talloc_unlink(NULL, wrapped_object), but what about its parents? For \r
+example will doing that on a notmuch_message_t keep the \r
+notmuch_messages_t alive?\r
+- If we do talloc_reference(parent, wrapped), then we reproduce the \r
+hierarchy again?\r
+\r
+Note that I have 0 experience with talloc, so I might as well be getting \r
+things wrong here.\r
+\r
+>\r
+> Quoth Adrien Bustany on Jul 18 at  9:34 pm:\r
+>> This makes notmuch appropriately free the underlying notmuch C objects\r
+>> when garbage collecting their Go wrappers. To make sure we don't break\r
+>> the underlying links between objects (for example, a notmuch_messages_t\r
+>> being GC'ed before a notmuch_message_t belonging to it), we add for each\r
+>> wraper struct a pointer to the owner object (Go objects with a reference\r
+>> pointing to them don't get garbage collected).\r
+>> ---\r
+>>   bindings/go/src/notmuch/notmuch.go |  153 +++++++++++++++++++++++++++++++-----\r
+>>   1 files changed, 134 insertions(+), 19 deletions(-)\r
+>>\r
+>> diff --git a/bindings/go/src/notmuch/notmuch.go b/bindings/go/src/notmuch/notmuch.go\r
+>> index 1d77fd2..3f436a0 100644\r
+>> --- a/bindings/go/src/notmuch/notmuch.go\r
+>> +++ b/bindings/go/src/notmuch/notmuch.go\r
+>> @@ -11,6 +11,7 @@ package notmuch\r
+>>   #include "notmuch.h"\r
+>>   */\r
+>>   import "C"\r
+>> +import "runtime"\r
+>>   import "unsafe"\r
+>>\r
+>>   // Status codes used for the return values of most functions\r
+>> @@ -47,40 +48,152 @@ func (self Status) String() string {\r
+>>   /* Various opaque data types. For each notmuch_<foo>_t see the various\r
+>>    * notmuch_<foo> functions below. */\r
+>>\r
+>> +type Object interface {}\r
+>> +\r
+>>   type Database struct {\r
+>>     db *C.notmuch_database_t\r
+>>   }\r
+>>\r
+>> +func createDatabase(db *C.notmuch_database_t) *Database {\r
+>> +   self := &Database{db: db}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Database) {\r
+>> +           if (x.db != nil) {\r
+>> +                   C.notmuch_database_destroy(x.db)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>> +}\r
+>> +\r
+>>   type Query struct {\r
+>>     query *C.notmuch_query_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createQuery(query *C.notmuch_query_t, owner Object) *Query {\r
+>> +   self := &Query{query: query, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Query) {\r
+>> +           if (x.query != nil) {\r
+>> +                   C.notmuch_query_destroy(x.query)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Threads struct {\r
+>>     threads *C.notmuch_threads_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createThreads(threads *C.notmuch_threads_t, owner Object) *Threads {\r
+>> +   self := &Threads{threads: threads, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Threads) {\r
+>> +           if (x.threads != nil) {\r
+>> +                   C.notmuch_threads_destroy(x.threads)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Thread struct {\r
+>>     thread *C.notmuch_thread_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createThread(thread *C.notmuch_thread_t, owner Object) *Thread {\r
+>> +   self := &Thread{thread: thread, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Thread) {\r
+>> +           if (x.thread != nil) {\r
+>> +                   C.notmuch_thread_destroy(x.thread)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Messages struct {\r
+>>     messages *C.notmuch_messages_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createMessages(messages *C.notmuch_messages_t, owner Object) *Messages {\r
+>> +   self := &Messages{messages: messages, owner: owner}\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Message struct {\r
+>>     message *C.notmuch_message_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createMessage(message *C.notmuch_message_t, owner Object) *Message {\r
+>> +   self := &Message{message: message, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Message) {\r
+>> +           if (x.message != nil) {\r
+>> +                   C.notmuch_message_destroy(x.message)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Tags struct {\r
+>>     tags *C.notmuch_tags_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createTags(tags *C.notmuch_tags_t, owner Object) *Tags {\r
+>> +   self := &Tags{tags: tags, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Tags) {\r
+>> +           if (x.tags != nil) {\r
+>> +                   C.notmuch_tags_destroy(x.tags)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Directory struct {\r
+>>     dir *C.notmuch_directory_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createDirectory(directory *C.notmuch_directory_t, owner Object) *Directory {\r
+>> +   self := &Directory{dir: directory, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Directory) {\r
+>> +           if (x.dir != nil) {\r
+>> +                   C.notmuch_directory_destroy(x.dir)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type Filenames struct {\r
+>>     fnames *C.notmuch_filenames_t\r
+>> +   owner Object\r
+>> +}\r
+>> +\r
+>> +func createFilenames(filenames *C.notmuch_filenames_t, owner Object) *Filenames {\r
+>> +   self := &Filenames{fnames: filenames, owner: owner}\r
+>> +\r
+>> +   runtime.SetFinalizer(self, func(x *Filenames) {\r
+>> +           if (x.fnames != nil) {\r
+>> +                   C.notmuch_filenames_destroy(x.fnames)\r
+>> +           }\r
+>> +   })\r
+>> +\r
+>> +   return self\r
+>>   }\r
+>>\r
+>>   type DatabaseMode C.notmuch_database_mode_t\r
+>> @@ -100,12 +213,13 @@ func NewDatabase(path string) (*Database, Status) {\r
+>>             return nil, STATUS_OUT_OF_MEMORY\r
+>>     }\r
+>>\r
+>> -   self := &Database{db: nil}\r
+>> -   st := Status(C.notmuch_database_create(c_path, &self.db))\r
+>> +   var db *C.notmuch_database_t;\r
+>> +   st := Status(C.notmuch_database_create(c_path, &db))\r
+>>     if st != STATUS_SUCCESS {\r
+>>             return nil, st\r
+>>     }\r
+>> -   return self, st\r
+>> +\r
+>> +   return createDatabase(db), st\r
+>>   }\r
+>>\r
+>>   /* Open an existing notmuch database located at 'path'.\r
+>> @@ -134,12 +248,13 @@ func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) {\r
+>>             return nil, STATUS_OUT_OF_MEMORY\r
+>>     }\r
+>>\r
+>> -   self := &Database{db: nil}\r
+>> -   st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db))\r
+>> +   var db *C.notmuch_database_t;\r
+>> +   st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &db))\r
+>>     if st != STATUS_SUCCESS {\r
+>>             return nil, st\r
+>>     }\r
+>> -   return self, st\r
+>> +\r
+>> +   return createDatabase(db), st\r
+>>   }\r
+>>\r
+>>   /* Close the given notmuch database, freeing all associated\r
+>> @@ -204,7 +319,7 @@ func (self *Database) GetDirectory(path string) (*Directory, Status) {\r
+>>     if st != STATUS_SUCCESS || c_dir == nil {\r
+>>             return nil, st\r
+>>     }\r
+>> -   return &Directory{dir: c_dir}, st\r
+>> +   return createDirectory(c_dir, nil), st\r
+>\r
+> It looks like you have a nil owner for anything whose talloc parent is\r
+> the database.  Is this intentional?  Shouldn't the owner be self in\r
+> these cases, too?\r
+>\r
+>>   }\r
+>>\r
+>>   /* Add a new message to the given notmuch database.\r
+>> @@ -258,7 +373,7 @@ func (self *Database) AddMessage(fname string) (*Message, Status) {\r
+>>     var c_msg *C.notmuch_message_t = new(C.notmuch_message_t)\r
+>>     st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg))\r
+>>\r
+>> -   return &Message{message: c_msg}, st\r
+>> +   return createMessage(c_msg, nil), st\r
+>>   }\r
+>>\r
+>>   /* Remove a message from the given notmuch database.\r
+>> @@ -319,12 +434,12 @@ func (self *Database) FindMessage(message_id string) (*Message, Status) {\r
+>>             return nil, STATUS_OUT_OF_MEMORY\r
+>>     }\r
+>>\r
+>> -   msg := &Message{message: nil}\r
+>> -   st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message))\r
+>> +   var msg *C.notmuch_message_t\r
+>> +   st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg))\r
+>>     if st != STATUS_SUCCESS {\r
+>>             return nil, st\r
+>>     }\r
+>> -   return msg, st\r
+>> +   return createMessage(msg, nil), st\r
+>>   }\r
+>>\r
+>>   /* Return a list of all tags found in the database.\r
+>> @@ -339,7 +454,7 @@ func (self *Database) GetAllTags() *Tags {\r
+>>     if tags == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Tags{tags: tags}\r
+>> +   return createTags(tags, nil)\r
+>>   }\r
+>>\r
+>>   /* Create a new query for 'database'.\r
+>> @@ -379,7 +494,7 @@ func (self *Database) CreateQuery(query string) *Query {\r
+>>     if q == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Query{query: q}\r
+>> +   return createQuery(q, nil)\r
+>>   }\r
+>>\r
+>>   /* Sort values for notmuch_query_set_sort */\r
+>> @@ -459,7 +574,7 @@ func (self *Query) SearchThreads() *Threads {\r
+>>     if threads == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Threads{threads: threads}\r
+>> +   return createThreads(threads, self)\r
+>>   }\r
+>>\r
+>>   /* Execute a query for messages, returning a notmuch_messages_t object\r
+>> @@ -505,7 +620,7 @@ func (self *Query) SearchMessages() *Messages {\r
+>>     if msgs == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Messages{messages: msgs}\r
+>> +   return createMessages(msgs, self)\r
+>>   }\r
+>>\r
+>>   /* Destroy a notmuch_query_t along with any associated resources.\r
+>> @@ -607,7 +722,7 @@ func (self *Messages) Get() *Message {\r
+>>     if msg == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Message{message: msg}\r
+>> +   return createMessage(msg, self)\r
+>>   }\r
+>>\r
+>>   /* Move the 'messages' iterator to the next message.\r
+>> @@ -659,7 +774,7 @@ func (self *Messages) CollectTags() *Tags {\r
+>>     if tags == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Tags{tags: tags}\r
+>> +   return createTags(tags, self)\r
+>>   }\r
+>>\r
+>>   /* Get the message ID of 'message'.\r
+>> @@ -739,7 +854,7 @@ func (self *Message) GetReplies() *Messages {\r
+>>     if msgs == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Messages{messages: msgs}\r
+>> +   return createMessages(msgs, self)\r
+>>   }\r
+>>\r
+>>   /* Get a filename for the email corresponding to 'message'.\r
+>> @@ -871,7 +986,7 @@ func (self *Message) GetTags() *Tags {\r
+>>     if tags == nil {\r
+>>             return nil\r
+>>     }\r
+>> -   return &Tags{tags: tags}\r
+>> +   return createTags(tags, self)\r
+>>   }\r
+>>\r
+>>   /* The longest possible tag value. */\r
+\r
+\r