Pavel reported a regression crash in branch in this thread:
    http://marc.info/?t=123552919800002&r=1&w=2&n=21
The problem turned out to be multiple deletions of cloned child buffers.
In this commit:
    http://www.lyx.org/trac/changeset/40205
I tried to fix the crash by having something kind of like a BufferList
to keep track of the cloned buffers and I then added an assertion in
this commit:
    http://www.lyx.org/trac/changeset/40208
to catch attempts to reuse the cloned_buffer_list_, as the code assumed
we'd have only one set of clones at a time.

Unfortunately, Kornel reported here:
    http://marc.info/?l=lyx-devel&m=132172531628079&w=2
that the assertion was being triggered at autosave, and the problem
turned out to be that, while we do make sure we only do one export at a
time, more than one autosave thread can be active at a time.

So, what to do? I see a couple options for branch:

1) Another version of the patch, which is here:
    http://marc.info/?l=lyx-devel&m=132148645632711&w=2
keeps the cloned_bufer_list in the master buffer of the cloned document
set, so there's no danger of a conflict. I worried, however, that, since
this is being accessed during the destruction process, we might run into
a crash due to deletion of the master before the child. The code is not
structured that way, so maybe it's not a serious worry, so perhaps this
is a decent option.

2) The assertion that's being triggered tests to make sure
cloned_buffer_list is empty, which is also a kind of check that all the
clones have been deleted. But this could simply be eliminated:

diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index d01ff7b..a9743a6 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -450,7 +450,6 @@ Buffer * Buffer::clone() const
        Buffer * cloned_buffer = bit->second;
 
        // record the list of cloned buffers
-       cloned_buffer_list.clear();
        BufferMap::iterator it = bufmap.begin();
        BufferMap::iterator en = bufmap.end();
        for (; it != en; ++it)

There is, in principle, a kind of race condition that could happen here.
Suppose the same Buffer is included twice in some master. We delete it,
and delete it from this list, but then autosave triggers again, and a
new Buffer with the same memory location as the deleted child is
created. Now when we try to delete the second child, it will seem to be
there, and we will actually delete the unrelated Buffer from the second
autosave. This seems extremely unlikely, but it could happen.

3) As Vincent essentially noted, it's anyway wrong to clone all the
children, etc, when we are just doing autosave. The attached patch
addresses that issue. So with it, we won't see the assertion that Kornel
reported. My worry here is that this patch seems intrusive, and it's
kind of late in the day to commit it to 2.0.2. Longer term, though, we
definitely want this patch.

Overall, then, (1) looks safest for 2.0.2, so long as my concerns about
it are misplaced. If not, then I guess we should go with (2), for 2.0.2,
since the crash I described is extremely unlikely ever to happen. For
trunk, we should certainly do (3) and commit the same patch to 2.0.3svn
as soon as it is open. That would allow for the option of going back to
the global variable as we have it now, for the reasons mentioned.

Comments?

Richard

>From c8d9378ab5efc650e5e7d1fcc1a098ecf08e9c75 Mon Sep 17 00:00:00 2001
From: Richard Heck <rgh...@lyx.org>
Date: Tue, 15 Nov 2011 11:08:50 -0500
Subject: [PATCH] Don't clone all the children on autosave.

---
 src/Buffer.cpp                |   16 ++++++++++++----
 src/Buffer.h                  |    9 ++++++---
 src/frontends/qt4/GuiView.cpp |    4 ++--
 3 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 0badaae..123ef35 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -448,10 +448,10 @@ Buffer::~Buffer()
 }
 
 
-Buffer * Buffer::clone() const
+Buffer * Buffer::cloneFromMaster() const
 {
 	BufferMap bufmap;
-	masterBuffer()->clone(bufmap);
+  masterBuffer()->cloneWithChildren(bufmap);
 
 	// make sure we got cloned
 	BufferMap::const_iterator bit = bufmap.find(this);
@@ -472,7 +472,7 @@ Buffer * Buffer::clone() const
 }
 
 
-void Buffer::clone(BufferMap & bufmap) const
+void Buffer::cloneWithChildren(BufferMap & bufmap) const
 {
 	// have we already been cloned?
 	if (bufmap.find(this) != bufmap.end())
@@ -493,7 +493,7 @@ void Buffer::clone(BufferMap & bufmap) const
 		dit.setBuffer(buffer_clone);
 		Buffer * child = const_cast<Buffer *>(it->second.second);
 
-		child->clone(bufmap);
+		child->cloneWithChildren(bufmap);
 		BufferMap::iterator const bit = bufmap.find(child);
 		LASSERT(bit != bufmap.end(), continue);
 		Buffer * child_clone = bit->second;
@@ -511,6 +511,14 @@ void Buffer::clone(BufferMap & bufmap) const
 }
 
 
+Buffer * Buffer::cloneBufferOnly() const {
+	Buffer * buffer_clone = new Buffer(fileName().absFileName(), false, this);
+	// we won't be cloning the children
+	buffer_clone->d->children_positions.clear();
+	return buffer_clone;
+}
+
+
 bool Buffer::isClone() const
 {
 	return d->cloned_buffer_;
diff --git a/src/Buffer.h b/src/Buffer.h
index bf2a9ea..463ff66 100644
--- a/src/Buffer.h
+++ b/src/Buffer.h
@@ -166,8 +166,11 @@ public:
 	/// Destructor
 	~Buffer();
 
-	///
-	Buffer * clone() const;
+	/// Clones the entire structure of which this Buffer is part, starting
+	/// with the master and cloning all the children, too.
+	Buffer * cloneFromMaster() const;
+	/// Just clones this single Buffer. For autosave.
+	Buffer * cloneBufferOnly() const;
 	///
 	bool isClone() const;
 
@@ -230,7 +233,7 @@ private:
 	///
 	typedef std::map<Buffer const *, Buffer *> BufferMap;
 	///
-	void clone(BufferMap &) const;
+	void cloneWithChildren(BufferMap &) const;
 	/// save timestamp and checksum of the given file.
 	void saveCheckSum() const;	
 	/// read a new file
diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 2409823..48bbc05 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -1575,7 +1575,7 @@ void GuiView::autoSave()
 #if (QT_VERSION >= 0x040400)
 	GuiViewPrivate::busyBuffers.insert(buffer);
 	QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
-		buffer, buffer->clone());
+		buffer, buffer->cloneBufferOnly());
 	d.autosave_watcher_.setFuture(f);
 #else
 	buffer->autoSave();
@@ -3122,7 +3122,7 @@ bool GuiView::GuiViewPrivate::asyncBufferProcessing(
 	QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
 				asyncFunc,
 				used_buffer,
-				used_buffer->clone(),
+				used_buffer->cloneFromMaster(),
 				format);
 	setPreviewFuture(f);
 	last_export_format = used_buffer->params().bufferFormat();
-- 
1.7.4.4

Reply via email to