From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id gHK7EDbh517DZQAA0tVLHw (envelope-from ) for ; Mon, 15 Jun 2020 20:59:34 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id kFm0DDbh516rbgAAB5/wlQ (envelope-from ) for ; Mon, 15 Jun 2020 20:59:34 +0000 Received: from arlo.cworth.org (arlo.cworth.org [50.126.95.6]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id CC4EE94005D for ; Mon, 15 Jun 2020 20:59:33 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by arlo.cworth.org (Postfix) with ESMTP id 3A7E86DE0A42; Mon, 15 Jun 2020 13:59:32 -0700 (PDT) X-Virus-Scanned: Debian amavisd-new at cworth.org Received: from arlo.cworth.org ([127.0.0.1]) by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id j33cH0mcQLR0; Mon, 15 Jun 2020 13:59:31 -0700 (PDT) Received: from arlo.cworth.org (localhost [IPv6:::1]) by arlo.cworth.org (Postfix) with ESMTP id E6E816DE0A77; Mon, 15 Jun 2020 13:59:30 -0700 (PDT) Received: from localhost (localhost [127.0.0.1]) by arlo.cworth.org (Postfix) with ESMTP id 8E2076DE0A43 for ; Mon, 15 Jun 2020 13:59:29 -0700 (PDT) X-Virus-Scanned: Debian amavisd-new at cworth.org Received: from arlo.cworth.org ([127.0.0.1]) by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id JL9bVZUEmbLR for ; Mon, 15 Jun 2020 13:59:28 -0700 (PDT) Received: from mail-ej1-f42.google.com (mail-ej1-f42.google.com [209.85.218.42]) by arlo.cworth.org (Postfix) with ESMTPS id 66DA56DE0A77 for ; Mon, 15 Jun 2020 13:59:28 -0700 (PDT) Received: by mail-ej1-f42.google.com with SMTP id p20so18917627ejd.13 for ; Mon, 15 Jun 2020 13:59:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=IeE6zZ9hXoabubijp/ZMFCJ/lg+SDFmMWu/ewFb5Mwo=; b=I39YwW0DOEXD+vi9ATdcREvLBChe1rc6iNkHrqrwvBu+TQxTHkOcd1prk40CAj5MY6 fCE9GvxBV8Mn/zJRYgqLgaUDmfSQygxWxjG736sVVn7AxStgPSun55azbGTOrvMnO2Ts FeIeNkHLKsvx0IgI0B/iJFUIMYGgblnqCNfusTujxy8edXEmlSMnJOtQz0ookr3JSrZR ozJenrMks1Wgi9lYPDu0CU5iYG7poZsccFPqkeuA51h6UEHVczOLMRuRj5PKV/Sa6o2Q kJju3EdTCOfatD9G9DCALiR5zTdi2N0qK5HysCeySW92sWP0fRrOkrYfAsHn+QwE5aeY nNFw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references:mime-version:content-transfer-encoding; bh=IeE6zZ9hXoabubijp/ZMFCJ/lg+SDFmMWu/ewFb5Mwo=; b=ZzcDW1ulzMPZ8sm7+bPsver8QYlxGdFB2P4oDd0j2+cE7ZrSPlAjDprYixhuSw9mWL EqRO8Yy80ezgcM3zJ9Q5dtr87ttxODr/z4OuVddiX2OCSjTVt7z97RxTEBxyfSawsG8L UoiPlXv/Peb7jQqXt9YuhZHKZ89eul1Efrcyp3SePyimKZCgW1vFJAwpRcQk1Tox/uqQ kCmS6xcznyMmWe+1dikSrQPW67aSVOeIgimnIGYpO53nJJ3XVX8bu2QMUCLGQaktItbj IP99Ez0ZdzfxvRhrBuWqfG+IhBUjL1QQBLjwDfPVnsrLjF/MZPAzaJ+1fLeZNJwg0UK4 JRwg== X-Gm-Message-State: AOAM533omkmJJV8nTk8nid2QvHWwp02+cCHhkZM+XdvaRXJI0XvQQOmP 3mMly9nzNUoex7cEIylD7G9ujsWP X-Google-Smtp-Source: ABdhPJwfqh1V583xjGSTXDAoBG9UtgOWQHpbtBwCnzJpt5taXeQ5qlsyGOe/OfDHOUJFedoHx34afQ== X-Received: by 2002:a17:906:4ec1:: with SMTP id i1mr26518508ejv.152.1592254766498; Mon, 15 Jun 2020 13:59:26 -0700 (PDT) Received: from powell.devork.be (2a02-8388-8480-1180-4c18-fc69-8d8c-22b5.cable.dynamic.v6.surfer.at. [2a02:8388:8480:1180:4c18:fc69:8d8c:22b5]) by smtp.gmail.com with ESMTPSA id fw16sm9696849ejb.55.2020.06.15.13.59.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 15 Jun 2020 13:59:25 -0700 (PDT) Received: (nullmailer pid 210691 invoked by uid 1000); Mon, 15 Jun 2020 20:59:24 -0000 From: Floris Bruynooghe To: notmuch@notmuchmail.org Subject: [PATCH 2/2] Make messages returned by Thread objects owned Date: Mon, 15 Jun 2020 22:58:50 +0200 Message-Id: <20200615205850.210480-3-flub@devork.be> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20200615205850.210480-1-flub@devork.be> References: <20200615205850.210480-1-flub@devork.be> MIME-Version: 1.0 X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: notmuch-bounces@notmuchmail.org Sender: "notmuch" X-Scanner: scn0 Authentication-Results: aspmx1.migadu.com; dkim=fail (body hash did not verify) header.d=gmail.com header.s=20161025 header.b=I39YwW0D; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 50.126.95.6 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Spam-Score: 2.49 X-TUID: /5erPyBuBO7K This reverses the logic of StandaloneMessage to instead create a OwnedMessage. Only the Thread class allows retrieving messages more then once so it can explicitly create such messages. The added test fails with SIGABRT without the fix for the message re-use in threads being present. --- bindings/python-cffi/notmuch2/_database.py | 6 +-- bindings/python-cffi/notmuch2/_message.py | 55 ++++++++++++--------- bindings/python-cffi/notmuch2/_thread.py | 8 ++- bindings/python-cffi/tests/test_database.py | 11 +++++ 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index f14eac78..95f59ca0 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -399,7 +399,7 @@ class Database(base.NotmuchObject): capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: raise errors.NotmuchError(ret) - msg = message.StandaloneMessage(self, msg_pp[0], db=self) + msg = message.Message(self, msg_pp[0], db=self) if sync_flags: msg.tags.from_maildir_flags() return self.AddedMessage( @@ -468,7 +468,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError - msg = message.StandaloneMessage(self, msg_p, db=self) + msg = message.Message(self, msg_p, db=self) return msg def get(self, filename): @@ -501,7 +501,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError - msg = message.StandaloneMessage(self, msg_p, db=self) + msg = message.Message(self, msg_p, db=self) return msg @property diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index 416ce7ca..02de50ad 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -47,9 +47,7 @@ class Message(base.NotmuchObject): :type db: Database :param msg_p: The C pointer to the ``notmuch_message_t``. :type msg_p: - :param dup: Whether the message was a duplicate on insertion. - :type dup: None or bool """ _msg_p = base.MemoryPointer() @@ -61,10 +59,22 @@ class Message(base.NotmuchObject): @property def alive(self): - return self._parent.alive + if not self._parent.alive: + return False + try: + self._msg_p + except errors.ObjectDestroyedError: + return False + else: + return True + + def __del__(self): + self._destroy() def _destroy(self): - pass + if self.alive: + capi.lib.notmuch_message_destroy(self._msg_p) + self._msg_p = None @property def messageid(self): @@ -363,30 +373,26 @@ class Message(base.NotmuchObject): if isinstance(other, self.__class__): return self.messageid == other.messageid -class StandaloneMessage(Message): - """An email message stored in the notmuch database. - This subclass of Message is used for messages that are retrieved from the - database directly and are not owned by a query. +class OwnedMessage(Message): + """An email message owned by parent thread object. + + This subclass of Message is used for messages that are retrieved + from the notmuch database via a parent :class:`notmuch2.Thread` + object, which "owns" this message. This means that when this + message object is destroyed, by calling :func:`del` or + :meth:`_destroy` directly or indirectly, the message is not freed + in the notmuch API and the parent :class:`notmuch2.Thread` object + can return the same object again when needed. """ + @property def alive(self): - if not self._parent.alive: - return False - try: - self._msg_p - except errors.ObjectDestroyedError: - return False - else: - return True - - def __del__(self): - self._destroy() + return self._parent.alive def _destroy(self): - if self.alive: - capi.lib.notmuch_message_destroy(self._msg_p) - self._msg_p = None + pass + class FilenamesIter(base.NotmuchIter): """Iterator for binary filenames objects.""" @@ -690,8 +696,9 @@ collections.abc.ValuesView.register(PropertiesValuesView) class MessageIter(base.NotmuchIter): - def __init__(self, parent, msgs_p, *, db): + def __init__(self, parent, msgs_p, *, db, msg_cls=Message): self._db = db + self._msg_cls = msg_cls super().__init__(parent, msgs_p, fn_destroy=capi.lib.notmuch_messages_destroy, fn_valid=capi.lib.notmuch_messages_valid, @@ -700,4 +707,4 @@ class MessageIter(base.NotmuchIter): def __next__(self): msg_p = super().__next__() - return Message(self, msg_p, db=self._db) + return self._msg_cls(self, msg_p, db=self._db) diff --git a/bindings/python-cffi/notmuch2/_thread.py b/bindings/python-cffi/notmuch2/_thread.py index bb76f2dc..e883f308 100644 --- a/bindings/python-cffi/notmuch2/_thread.py +++ b/bindings/python-cffi/notmuch2/_thread.py @@ -62,7 +62,9 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :raises ObjectDestroyedError: if used after destroyed. """ msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p) - return message.MessageIter(self, msgs_p, db=self._db) + return message.MessageIter(self, msgs_p, + db=self._db, + msg_cls=message.OwnedMessage) def __iter__(self): """Return an iterator over all the messages in the thread. @@ -72,7 +74,9 @@ class Thread(base.NotmuchObject, collections.abc.Iterable): :raises ObjectDestroyedError: if used after destroyed. """ msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p) - return message.MessageIter(self, msgs_p, db=self._db) + return message.MessageIter(self, msgs_p, + db=self._db, + msg_cls=message.OwnedMessage) @property def matched(self): diff --git a/bindings/python-cffi/tests/test_database.py b/bindings/python-cffi/tests/test_database.py index e3a8344d..55069b5e 100644 --- a/bindings/python-cffi/tests/test_database.py +++ b/bindings/python-cffi/tests/test_database.py @@ -324,3 +324,14 @@ class TestQuery: threads = db.threads('*') thread = next(threads) assert isinstance(thread, notmuch2.Thread) + + def test_use_threaded_message_twice(self, db): + thread = next(db.threads('*')) + for msg in thread.toplevel(): + assert isinstance(msg, notmuch2.Message) + assert msg.alive + del msg + for msg in thread: + assert isinstance(msg, notmuch2.Message) + assert msg.alive + del msg -- 2.27.0