Hey, all,

I've been working on making it possible to cancel an export in the
background. I have it working at least part of the way. The problem is
most obvious with XHTML export of the math manual. When we are doing
that, we will have to generate images for some of the math. This
involves starting an external program and waiting for the result. But
when I call cancel() on the QFutureWatcher we have from QtConcurrent,
this seems to kill the external program, and then the background thread
just sits there and waits for it never to finish.

Somehow, then, I need to interrupt this process. I tried deleting the
PreviewLoader before calling cancel(), but this crashes because the
background processing is still happening, and we think we still need a
loader. Anyone have any ideas?

I'm attaching the work done so far if anyone wants to play with it. The
first two patches change from using QtConcurrent::run to
QtConcurrent::mapped, because run() does not allow cancelling for some
reason. The real action is in the third patch, and then the fourth one
is my failed attempt to work around the problem just described.

Richard


>From 0152558d8da5416097c68dc14d25bf02806a4233 Mon Sep 17 00:00:00 2001
From: Richard Heck <rgh...@lyx.org>
Date: Fri, 4 Nov 2011 15:35:44 -0400
Subject: [PATCH 1/4] Change the argument handling for background processing.
 This is a step towards making the background processing
 cancellable.

---
 src/frontends/qt4/GuiView.cpp |   51 +++++++++++++++++++++++-----------------
 1 files changed, 29 insertions(+), 22 deletions(-)

diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 6c258f7..6de334e 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -391,10 +391,19 @@ public:
 	DummyWatcher processing_thread_watcher_;
 #endif
 
+	class ArgumentStruct {
+	public:
+		ArgumentStruct(Buffer const * o, Buffer * c, string const & f) :
+		  orig(o), clone(c), format(f) {}
+		Buffer const * orig;
+		Buffer * clone;
+		string const format;
+	};
+
 	static QSet<Buffer const *> busyBuffers;
-	static Buffer::ExportStatus previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
-	static Buffer::ExportStatus exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
-	static Buffer::ExportStatus compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format);
+	static Buffer::ExportStatus previewAndDestroy(ArgumentStruct const & args);
+	static Buffer::ExportStatus exportAndDestroy(ArgumentStruct const & args);
+	static Buffer::ExportStatus compileAndDestroy(ArgumentStruct const & args);
 	static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
 
 	template<class T>
@@ -404,7 +413,7 @@ public:
 	bool asyncBufferProcessing(string const & argument,
 				   Buffer const * used_buffer,
 				   docstring const & msg,
-				   Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
+				   Buffer::ExportStatus (*asyncFunc)(ArgumentStruct const &),
 				   Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
 				   Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const);
 
@@ -3035,10 +3044,10 @@ bool GuiView::goToFileRow(string const & argument)
 	return true;
 }
 
-
 #if (QT_VERSION >= 0x040400)
 template<class T>
-Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * buffer, string const & format)
+Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(
+	const T& func, Buffer const * orig, Buffer * buffer, string const & format)
 {
 	Buffer::ExportStatus const status = func(format);
 
@@ -3051,24 +3060,24 @@ Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffe
 }
 
 
-Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format)
+Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(ArgumentStruct const & args)
 {
 	Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
-	return runAndDestroy(bind(mem_func, buffer, _1, true), orig, buffer, format);
+	return runAndDestroy(bind(mem_func, args.clone, _1, true), args.orig, args.clone, args.format);
 }
 
 
-Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format)
+Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(ArgumentStruct const & args)
 {
 	Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
-	return runAndDestroy(bind(mem_func, buffer, _1, false), orig, buffer, format);
+	return runAndDestroy(bind(mem_func, args.clone, _1, false), args.orig, args.clone, args.format);
 }
 
 
-Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format)
+Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(ArgumentStruct const & args)
 {
 	Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
-	return runAndDestroy(bind(mem_func, buffer, _1), orig, buffer, format);
+	return runAndDestroy(bind(mem_func, args.clone, _1), args.orig, args.clone, args.format);
 }
 
 #else
@@ -3076,21 +3085,21 @@ Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * o
 // not used, but the linker needs them
 
 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
-		Buffer const *, Buffer *, string const &)
+		ArgumentStruct const &)
 {
 	return Buffer::ExportSuccess;
 }
 
 
 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
-		Buffer const *, Buffer *, string const &)
+		ArgumentStruct const &)
 {
 	return Buffer::ExportSuccess;
 }
 
 
 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
-		Buffer const *, Buffer *, string const &)
+		ArgumentStruct const &)
 {
 	return Buffer::ExportSuccess;
 }
@@ -3102,7 +3111,7 @@ bool GuiView::GuiViewPrivate::asyncBufferProcessing(
 			   string const & argument,
 			   Buffer const * used_buffer,
 			   docstring const & msg,
-			   Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
+			   Buffer::ExportStatus (*asyncFunc)(ArgumentStruct const &),
 			   Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
 			   Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
 {
@@ -3119,11 +3128,8 @@ bool GuiView::GuiViewPrivate::asyncBufferProcessing(
 		gv_->message(msg);
 	}
 	GuiViewPrivate::busyBuffers.insert(used_buffer);
-	QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
-				asyncFunc,
-				used_buffer,
-				used_buffer->clone(),
-				format);
+	QFuture<Buffer::ExportStatus> f = QtConcurrent::run(asyncFunc, 
+		ArgumentStruct(used_buffer, used_buffer->clone(), format));
 	setPreviewFuture(f);
 	last_export_format = used_buffer->params().bufferFormat();
 	(void) syncFunc;
@@ -3278,7 +3284,8 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 						(doc_buffer ? doc_buffer->masterBuffer() : 0),
 						docstring(),
 						&GuiViewPrivate::previewAndDestroy,
-						0, &Buffer::preview);
+						0, 
+						&Buffer::preview);
 			break;
 		}
 		case LFUN_BUFFER_SWITCH: {
-- 
1.7.4.4

>From 4ee568e5d96f64ab9926599f4c4399c77bb6c143 Mon Sep 17 00:00:00 2001
From: Richard Heck <rgh...@lyx.org>
Date: Fri, 4 Nov 2011 16:12:08 -0400
Subject: [PATCH 2/4] Switch to using QtConcurrent::mapped, because it can be
 cancelled.

---
 src/frontends/qt4/GuiView.cpp |   17 +++++++++++------
 1 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 6de334e..62e7022 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -118,6 +118,7 @@
 #include <QFuture>
 #include <QFutureWatcher>
 #include <QtConcurrentRun>
+#include <QtConcurrentMap>
 #endif
 
 #include "support/bind.h"
@@ -400,6 +401,8 @@ public:
 		string const format;
 	};
 
+	typedef QList<ArgumentStruct> ArgumentList;
+
 	static QSet<Buffer const *> busyBuffers;
 	static Buffer::ExportStatus previewAndDestroy(ArgumentStruct const & args);
 	static Buffer::ExportStatus exportAndDestroy(ArgumentStruct const & args);
@@ -578,7 +581,7 @@ void GuiView::processingThreadFinished()
 	QFutureWatcher<Buffer::ExportStatus> const * watcher =
 		static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
 
-	Buffer::ExportStatus const status = watcher->result();
+	Buffer::ExportStatus const status = *(watcher->future().constBegin());
 	handleExportStatus(this, status, d.processing_format);
 	
 	updateToolbars();
@@ -3085,21 +3088,21 @@ Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(ArgumentStruct c
 // not used, but the linker needs them
 
 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
-		ArgumentStruct const &)
+		ArgumentList::const_iterator)
 {
 	return Buffer::ExportSuccess;
 }
 
 
 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
-		ArgumentStruct const &)
+		ArgumentList::const_iterator)
 {
 	return Buffer::ExportSuccess;
 }
 
 
 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
-		ArgumentStruct const &)
+		ArgumentList::const_iterator)
 {
 	return Buffer::ExportSuccess;
 }
@@ -3128,8 +3131,10 @@ bool GuiView::GuiViewPrivate::asyncBufferProcessing(
 		gv_->message(msg);
 	}
 	GuiViewPrivate::busyBuffers.insert(used_buffer);
-	QFuture<Buffer::ExportStatus> f = QtConcurrent::run(asyncFunc, 
-		ArgumentStruct(used_buffer, used_buffer->clone(), format));
+	ArgumentList arglist;
+	ArgumentStruct args(used_buffer, used_buffer->clone(), format);
+	arglist.append(args);
+	QFuture<Buffer::ExportStatus> f = QtConcurrent::mapped(arglist, asyncFunc);
 	setPreviewFuture(f);
 	last_export_format = used_buffer->params().bufferFormat();
 	(void) syncFunc;
-- 
1.7.4.4

>From 283eccb228fc512b394fa914e4d07d8d742c467d Mon Sep 17 00:00:00 2001
From: Richard Heck <rgh...@lyx.org>
Date: Fri, 4 Nov 2011 16:22:50 -0400
Subject: [PATCH 3/4] Allow cancellation of background processing.

---
 lib/ui/stdmenus.inc           |    1 +
 lib/ui/stdtoolbars.inc        |    1 +
 src/FuncCode.h                |    1 +
 src/LyXAction.cpp             |    9 +++++++++
 src/frontends/qt4/GuiView.cpp |   16 ++++++++++++++++
 5 files changed, 28 insertions(+), 0 deletions(-)

diff --git a/lib/ui/stdmenus.inc b/lib/ui/stdmenus.inc
index b764046..7a13cf6 100644
--- a/lib/ui/stdmenus.inc
+++ b/lib/ui/stdmenus.inc
@@ -327,6 +327,7 @@ Menuset
 		UpdateFormats
 		OptItem "View Master Document|M" "master-buffer-view"
 		OptItem "Update Master Document|a" "master-buffer-update"
+		OptItem "Cancel Export Process" "export-cancel"
 		Separator
 		Item "Split View Into Left and Right Half|i" "split-view horizontal"
 		Item "Split View Into Upper and Lower Half|e" "split-view vertical"
diff --git a/lib/ui/stdtoolbars.inc b/lib/ui/stdtoolbars.inc
index 449aac0..9102af9 100644
--- a/lib/ui/stdtoolbars.inc
+++ b/lib/ui/stdtoolbars.inc
@@ -102,6 +102,7 @@ ToolbarSet
 		Item "Update" "buffer-update"
 		Item "View master document" "master-buffer-view"
 		Item "Update master document" "master-buffer-update"
+#		Item "Cancel export" "export-cancel"
 		Item "Enable Forward/Reverse Search" "buffer-toggle-output-sync"
 		Separator
 		StickyPopupMenu "view-others" "View other formats"
diff --git a/src/FuncCode.h b/src/FuncCode.h
index dd60d33..dedaf47 100644
--- a/src/FuncCode.h
+++ b/src/FuncCode.h
@@ -451,6 +451,7 @@ enum FuncCode
 	LFUN_BUFFER_EXPORT_AS,          // tommaso 20111006
 	// 350
 	LFUN_CLIPBOARD_PASTE_SIMPLE,	// tommaso, 20111028
+	LFUN_EXPORT_CANCEL,             // rgh, 20111102
 
 	LFUN_LASTACTION                 // end of the table
 };
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index a171cd1..5e2538b 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -2995,6 +2995,15 @@ void LyXAction::init()
  */
 		{ LFUN_BUFFER_EXPORT_AS, "buffer-export-as", ReadOnly, Buffer },
 /*!
+ * \var lyx::FuncCode lyx::LFUN_EXPORT_CANCEL
+ * \li Action: Cancels a background export process
+ * \li Syntax: export-cancel
+ * \li Origin: rgh, 2 November 2011
+ * \endvar
+ */
+		{ LFUN_EXPORT_CANCEL, "export-cancel", ReadOnly, Buffer },
+
+/*!
  * \var lyx::FuncCode lyx::LFUN_BUFFER_PRINT
  * \li Action: Prints the current document.
  * \li Notion: Many settings can be given via the preferences dialog.
diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 62e7022..537a887 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -485,6 +485,8 @@ GuiView::GuiView(int id)
 		busylabel, SLOT(show()));
 	connect(&d.processing_thread_watcher_, SIGNAL(finished()), 
 		busylabel, SLOT(hide()));
+	connect(&d.processing_thread_watcher_, SIGNAL(canceled()),
+		busylabel, SLOT(hide()));
 
 	statusBar()->setSizeGripEnabled(true);
 	updateStatusBar();
@@ -578,6 +580,10 @@ void GuiView::processingThreadStarted()
 
 void GuiView::processingThreadFinished()
 {
+	if (d.processing_thread_watcher_.isCanceled()) {
+		Alert::warning(_("Processing Canceled"), _("Background processing successfully canceled."));
+		return;
+	}
 	QFutureWatcher<Buffer::ExportStatus> const * watcher =
 		static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
 
@@ -1689,6 +1695,10 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
 		enable = doc_buffer;
 		break;
 
+	case LFUN_EXPORT_CANCEL:
+		enable = d.processing_thread_watcher_.isRunning();
+		break;
+
 	case LFUN_BUFFER_CLOSE:
 		enable = doc_buffer;
 		break;
@@ -3293,6 +3303,12 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 						&Buffer::preview);
 			break;
 		}
+		case LFUN_EXPORT_CANCEL: {
+#if QT_VERSION >= 0x040400
+			d.processing_thread_watcher_.cancel();
+#endif
+			break;
+		}
 		case LFUN_BUFFER_SWITCH: {
 			string const file_name = to_utf8(cmd.argument());
 			if (!FileName::isAbsolute(file_name)) {
-- 
1.7.4.4

>From db1b648fdf11141ea10dea3c5254af17dbc01522 Mon Sep 17 00:00:00 2001
From: Richard Heck <rgh...@lyx.org>
Date: Fri, 4 Nov 2011 17:37:45 -0400
Subject: [PATCH 4/4] A try at canceling the background image processing.
 Unfortunately, this version crashes.

---
 src/Buffer.cpp                |   17 +++++++++++++++--
 src/Buffer.h                  |    2 ++
 src/frontends/qt4/GuiView.cpp |   11 ++++++++++-
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 625c0ff..10ddfeb 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -303,6 +303,8 @@ public:
 	Buffer const * cloned_buffer_;
 	/// are we in the process of exporting this buffer?
 	mutable bool doing_export;
+	///
+	mutable bool export_canceled;
 	
 private:
 	/// So we can force access via the accessors.
@@ -338,7 +340,7 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_,
 	  wa_(0), gui_(0), undo_(*owner), bibinfo_cache_valid_(false), 
 	  bibfile_cache_valid_(false), cite_labels_valid_(false), 
 	  preview_loader_(0), cloned_buffer_(cloned_buffer),
-	  doing_export(false), parent_buffer(0)
+	  doing_export(false), export_canceled(false), parent_buffer(0)
 {
 	if (!cloned_buffer_) {
 		temppath = createBufferTmpDir();
@@ -955,6 +957,8 @@ PreviewLoader * Buffer::loader() const
 {
 	if (!isExporting() && lyxrc.preview == LyXRC::PREVIEW_OFF)
 		return 0;
+	if (d->export_canceled)
+		return 0;
 	if (!d->preview_loader_)
 		d->preview_loader_ = new PreviewLoader(*this);
 	return d->preview_loader_;
@@ -3498,11 +3502,14 @@ private:
 void Buffer::setExportStatus(bool e) const
 {
 	d->doing_export = e;	
+	d->export_canceled = false;
 	ListOfBuffers clist = getDescendents();
 	ListOfBuffers::const_iterator cit = clist.begin();
 	ListOfBuffers::const_iterator const cen = clist.end();
-	for (; cit != cen; ++cit)
+	for (; cit != cen; ++cit) {
 		(*cit)->d->doing_export = e;
+		(*cit)->d->export_canceled = false;
+	}
 }
 
 
@@ -3512,6 +3519,12 @@ bool Buffer::isExporting() const
 }
 
 
+void Buffer::cancelExport() 
+{
+	d->export_canceled = true;
+}
+
+
 Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir)
 	const
 {
diff --git a/src/Buffer.h b/src/Buffer.h
index bf2a9ea..5a86ed1 100644
--- a/src/Buffer.h
+++ b/src/Buffer.h
@@ -641,6 +641,8 @@ private:
 public:
 	///
 	bool isExporting() const;
+	///
+	void cancelExport();
 
 	///
 	typedef std::vector<std::pair<Inset *, ParIterator> > References;
diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 537a887..7e1ba4a 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -64,6 +64,8 @@
 #include "Toolbars.h"
 #include "version.h"
 
+#include "graphics/PreviewLoader.h"
+
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/ExceptionMessage.h"
@@ -387,6 +389,7 @@ public:
 	///
 	string last_export_format;
 	string processing_format;
+	Buffer * cloned_buffer;
 #else
 	struct DummyWatcher { bool isRunning(){return false;} };
 	DummyWatcher processing_thread_watcher_;
@@ -580,6 +583,7 @@ void GuiView::processingThreadStarted()
 
 void GuiView::processingThreadFinished()
 {
+	d.cloned_buffer = 0;
 	if (d.processing_thread_watcher_.isCanceled()) {
 		Alert::warning(_("Processing Canceled"), _("Background processing successfully canceled."));
 		return;
@@ -3141,8 +3145,9 @@ bool GuiView::GuiViewPrivate::asyncBufferProcessing(
 		gv_->message(msg);
 	}
 	GuiViewPrivate::busyBuffers.insert(used_buffer);
+	cloned_buffer = used_buffer->clone();
 	ArgumentList arglist;
-	ArgumentStruct args(used_buffer, used_buffer->clone(), format);
+	ArgumentStruct args(used_buffer, cloned_buffer, format);
 	arglist.append(args);
 	QFuture<Buffer::ExportStatus> f = QtConcurrent::mapped(arglist, asyncFunc);
 	setPreviewFuture(f);
@@ -3305,6 +3310,10 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 		}
 		case LFUN_EXPORT_CANCEL: {
 #if QT_VERSION >= 0x040400
+			LASSERT(d.cloned_buffer, /* */);
+			d.cloned_buffer->cancelExport();
+			graphics::PreviewLoader * loader = d.cloned_buffer->loader();
+			delete loader;
 			d.processing_thread_watcher_.cancel();
 #endif
 			break;
-- 
1.7.4.4

Reply via email to