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