[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-1-9' - 57 commits - loleaflet/dist loleaflet/po loleaflet/src loolwsd/Admin.cpp loolwsd/AdminModel.cpp loolwsd/ChildSession.cpp loolwsd/ChildSession.hpp loolwsd/ClientSession.cpp loolwsd/configure.ac loolwsd/DocumentBroker.cpp loolwsd/DocumentBroker.hpp loolwsd/Exceptions.hpp loolwsd/FileServer.cpp loolwsd/IoUtil.cpp loolwsd/LibreOfficeKit.hpp loolwsd/LOOLForKit.cpp loolwsd/LOOLKit.cpp loolwsd/LOOLProtocol.cpp loolwsd/LOOLProtocol.hpp loolwsd/LOOLSession.cpp loolwsd/LOOLSession.hpp loolwsd/LOOLWSD.cpp loolwsd/LOOLWSD.hpp loolwsd/Makefile.am loolwsd/protocol.txt loolwsd/Storage.cpp loolwsd/Storage.hpp loolwsd/test loolwsd/TileCache.cpp loolwsd/TileCache.hpp loolwsd/Unit.cpp loolwsd/Unit.hpp loolwsd/UnitHTTP.cpp loolwsd/UnitHTTP.hpp loolwsd/Util.cpp loolwsd/Util.hpp scripts/downloadpootle.sh

Ashod Nakashian ashod.nakashian at collabora.co.uk
Tue Oct 25 13:00:34 UTC 2016


 loleaflet/dist/errormessages.js               |    2 
 loleaflet/dist/l10n/uno/af.json               |    2 
 loleaflet/dist/l10n/uno/am.json               |    2 
 loleaflet/dist/l10n/uno/an.json               |    2 
 loleaflet/dist/l10n/uno/ar.json               |    2 
 loleaflet/dist/l10n/uno/as.json               |    2 
 loleaflet/dist/l10n/uno/ast.json              |    2 
 loleaflet/dist/l10n/uno/be.json               |    2 
 loleaflet/dist/l10n/uno/bg.json               |    2 
 loleaflet/dist/l10n/uno/bn-IN.json            |    2 
 loleaflet/dist/l10n/uno/bn.json               |    2 
 loleaflet/dist/l10n/uno/bo.json               |    2 
 loleaflet/dist/l10n/uno/br.json               |    2 
 loleaflet/dist/l10n/uno/brx.json              |    2 
 loleaflet/dist/l10n/uno/bs.json               |    2 
 loleaflet/dist/l10n/uno/ca-valencia.json      |    2 
 loleaflet/dist/l10n/uno/ca.json               |    2 
 loleaflet/dist/l10n/uno/cs.json               |    2 
 loleaflet/dist/l10n/uno/cy.json               |    2 
 loleaflet/dist/l10n/uno/da.json               |    2 
 loleaflet/dist/l10n/uno/de.json               |    2 
 loleaflet/dist/l10n/uno/dgo.json              |    2 
 loleaflet/dist/l10n/uno/dz.json               |    2 
 loleaflet/dist/l10n/uno/el.json               |    2 
 loleaflet/dist/l10n/uno/en-GB.json            |    2 
 loleaflet/dist/l10n/uno/en-ZA.json            |    2 
 loleaflet/dist/l10n/uno/eo.json               |    2 
 loleaflet/dist/l10n/uno/es.json               |    2 
 loleaflet/dist/l10n/uno/et.json               |    2 
 loleaflet/dist/l10n/uno/eu.json               |    2 
 loleaflet/dist/l10n/uno/fa.json               |    2 
 loleaflet/dist/l10n/uno/fi.json               |    2 
 loleaflet/dist/l10n/uno/fr.json               |    2 
 loleaflet/dist/l10n/uno/ga.json               |    2 
 loleaflet/dist/l10n/uno/gd.json               |    2 
 loleaflet/dist/l10n/uno/gl.json               |    2 
 loleaflet/dist/l10n/uno/gu.json               |    2 
 loleaflet/dist/l10n/uno/gug.json              |    2 
 loleaflet/dist/l10n/uno/he.json               |    2 
 loleaflet/dist/l10n/uno/hi.json               |    2 
 loleaflet/dist/l10n/uno/hr.json               |    2 
 loleaflet/dist/l10n/uno/hu.json               |    2 
 loleaflet/dist/l10n/uno/id.json               |    2 
 loleaflet/dist/l10n/uno/is.json               |    2 
 loleaflet/dist/l10n/uno/it.json               |    2 
 loleaflet/dist/l10n/uno/ja.json               |    2 
 loleaflet/dist/l10n/uno/ka.json               |    2 
 loleaflet/dist/l10n/uno/kk.json               |    2 
 loleaflet/dist/l10n/uno/km.json               |    2 
 loleaflet/dist/l10n/uno/kmr-Latn.json         |    2 
 loleaflet/dist/l10n/uno/kn.json               |    2 
 loleaflet/dist/l10n/uno/ko.json               |    2 
 loleaflet/dist/l10n/uno/kok.json              |    2 
 loleaflet/dist/l10n/uno/ks.json               |    2 
 loleaflet/dist/l10n/uno/lo.json               |    2 
 loleaflet/dist/l10n/uno/lt.json               |    2 
 loleaflet/dist/l10n/uno/lv.json               |    2 
 loleaflet/dist/l10n/uno/mai.json              |    2 
 loleaflet/dist/l10n/uno/mk.json               |    2 
 loleaflet/dist/l10n/uno/ml.json               |    2 
 loleaflet/dist/l10n/uno/mn.json               |    2 
 loleaflet/dist/l10n/uno/mni.json              |    2 
 loleaflet/dist/l10n/uno/mr.json               |    2 
 loleaflet/dist/l10n/uno/my.json               |    2 
 loleaflet/dist/l10n/uno/nb.json               |    2 
 loleaflet/dist/l10n/uno/ne.json               |    2 
 loleaflet/dist/l10n/uno/nl.json               |    2 
 loleaflet/dist/l10n/uno/nn.json               |    2 
 loleaflet/dist/l10n/uno/nr.json               |    2 
 loleaflet/dist/l10n/uno/nso.json              |    2 
 loleaflet/dist/l10n/uno/oc.json               |    2 
 loleaflet/dist/l10n/uno/om.json               |    2 
 loleaflet/dist/l10n/uno/or.json               |    2 
 loleaflet/dist/l10n/uno/pa-IN.json            |    2 
 loleaflet/dist/l10n/uno/pl.json               |    2 
 loleaflet/dist/l10n/uno/pt-BR.json            |    2 
 loleaflet/dist/l10n/uno/pt.json               |    2 
 loleaflet/dist/l10n/uno/ro.json               |    2 
 loleaflet/dist/l10n/uno/ru.json               |    2 
 loleaflet/dist/l10n/uno/rw.json               |    2 
 loleaflet/dist/l10n/uno/sa-IN.json            |    2 
 loleaflet/dist/l10n/uno/sat.json              |    2 
 loleaflet/dist/l10n/uno/sd.json               |    2 
 loleaflet/dist/l10n/uno/si.json               |    2 
 loleaflet/dist/l10n/uno/sid.json              |    2 
 loleaflet/dist/l10n/uno/sk.json               |    2 
 loleaflet/dist/l10n/uno/sl.json               |    2 
 loleaflet/dist/l10n/uno/sq.json               |    2 
 loleaflet/dist/l10n/uno/sr-Latn.json          |    2 
 loleaflet/dist/l10n/uno/sr.json               |    2 
 loleaflet/dist/l10n/uno/ss.json               |    2 
 loleaflet/dist/l10n/uno/st.json               |    2 
 loleaflet/dist/l10n/uno/sv.json               |    2 
 loleaflet/dist/l10n/uno/sw-TZ.json            |    2 
 loleaflet/dist/l10n/uno/ta.json               |    2 
 loleaflet/dist/l10n/uno/te.json               |    2 
 loleaflet/dist/l10n/uno/tg.json               |    2 
 loleaflet/dist/l10n/uno/th.json               |    2 
 loleaflet/dist/l10n/uno/tn.json               |    2 
 loleaflet/dist/l10n/uno/tr.json               |    2 
 loleaflet/dist/l10n/uno/ts.json               |    2 
 loleaflet/dist/l10n/uno/ug.json               |    2 
 loleaflet/dist/l10n/uno/uk.json               |    2 
 loleaflet/dist/l10n/uno/uz.json               |    2 
 loleaflet/dist/l10n/uno/ve.json               |    2 
 loleaflet/dist/l10n/uno/vi.json               |    2 
 loleaflet/dist/l10n/uno/xh.json               |    2 
 loleaflet/dist/l10n/uno/zh-CN.json            |    2 
 loleaflet/dist/l10n/uno/zh-TW.json            |    2 
 loleaflet/dist/l10n/uno/zu.json               |    2 
 loleaflet/po/templates/loleaflet-ui.pot       |    2 
 loleaflet/po/ui-sl.po                         |  831 ++++++++++++++++++++------
 loleaflet/src/control/Control.ColumnHeader.js |   40 +
 loleaflet/src/control/Control.ContextMenu.js  |   10 
 loleaflet/src/control/Control.RowHeader.js    |   40 +
 loleaflet/src/control/Control.Tabs.js         |    3 
 loleaflet/src/layer/tile/TileLayer.js         |   13 
 loolwsd/Admin.cpp                             |    1 
 loolwsd/AdminModel.cpp                        |   97 +--
 loolwsd/ChildSession.cpp                      |    6 
 loolwsd/ChildSession.hpp                      |    2 
 loolwsd/ClientSession.cpp                     |    2 
 loolwsd/DocumentBroker.cpp                    |   76 +-
 loolwsd/DocumentBroker.hpp                    |   23 
 loolwsd/Exceptions.hpp                        |    1 
 loolwsd/FileServer.cpp                        |    9 
 loolwsd/IoUtil.cpp                            |    1 
 loolwsd/LOOLForKit.cpp                        |   24 
 loolwsd/LOOLKit.cpp                           |   13 
 loolwsd/LOOLProtocol.cpp                      |    6 
 loolwsd/LOOLProtocol.hpp                      |   32 -
 loolwsd/LOOLSession.cpp                       |   16 
 loolwsd/LOOLSession.hpp                       |    9 
 loolwsd/LOOLWSD.cpp                           |  315 ++++++---
 loolwsd/LOOLWSD.hpp                           |    1 
 loolwsd/LibreOfficeKit.hpp                    |    8 
 loolwsd/Makefile.am                           |    1 
 loolwsd/Storage.cpp                           |   27 
 loolwsd/Storage.hpp                           |   13 
 loolwsd/TileCache.cpp                         |   54 +
 loolwsd/TileCache.hpp                         |    2 
 loolwsd/Unit.cpp                              |   20 
 loolwsd/Unit.hpp                              |   13 
 loolwsd/UnitHTTP.cpp                          |   35 +
 loolwsd/UnitHTTP.hpp                          |   21 
 loolwsd/Util.cpp                              |    2 
 loolwsd/Util.hpp                              |   11 
 loolwsd/configure.ac                          |    5 
 loolwsd/protocol.txt                          |    2 
 loolwsd/test/Makefile.am                      |    8 
 loolwsd/test/TileCacheTests.cpp               |  164 +++++
 loolwsd/test/UnitAdmin.cpp                    |   25 
 loolwsd/test/UnitPrefork.cpp                  |   61 -
 loolwsd/test/UnitStorage.cpp                  |   63 +
 loolwsd/test/UnitTileCache.cpp                |   37 -
 loolwsd/test/helpers.hpp                      |   43 -
 loolwsd/test/httpcrashtest.cpp                |   31 
 loolwsd/test/httpwserror.cpp                  |   14 
 loolwsd/test/httpwstest.cpp                   |  139 +---
 loolwsd/test/run_unit.sh.in                   |   10 
 scripts/downloadpootle.sh                     |    2 
 161 files changed, 1798 insertions(+), 806 deletions(-)

New commits:
commit e420c891a2f00a06156ba1878c0afaf1609542b4
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Mon Oct 24 19:57:37 2016 -0400

    loolwsd: mark all tiles but the first to have come from the cache
    
    While this has some overhead, it makes debugging of tile
    rendering easier.
    
    Change-Id: I0430015f41fd044e4be1099a5d61a23c0ef88176
    Reviewed-on: https://gerrit.libreoffice.org/30245
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit a2ef70c71ce48e81864de6d20382e8ef156bf64d)

diff --git a/loolwsd/TileCache.cpp b/loolwsd/TileCache.cpp
index 5decc9e..e8ebdd1 100644
--- a/loolwsd/TileCache.cpp
+++ b/loolwsd/TileCache.cpp
@@ -167,20 +167,37 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const
         {
             std::string response = tile.serialize("tile:");
             Log::debug("Sending tile message to subscribers: " + response);
-            response += '\n';
 
-            std::vector<char> output;
-            output.reserve(static_cast<size_t>(4) * tile.getWidth() * tile.getHeight());
-            output.resize(response.size());
+            std::vector<char> output(256 + size);
+            output.resize(response.size() + 1 + size);
+
             std::memcpy(output.data(), response.data(), response.size());
+            output[response.size()] = '\n';
+            std::memcpy(output.data() + response.size() + 1, data, size);
 
-            const auto pos = output.size();
-            output.resize(pos + size);
-            std::memcpy(output.data() + pos, data, size);
+            // Send to first subscriber as-is (without cache marker).
+            auto firstSubscriber = tileBeingRendered->_subscribers[0].lock();
+            if (firstSubscriber)
+            {
+                try
+                {
+                    firstSubscriber->sendBinaryFrame(output.data(), output.size());
+                }
+                catch (const std::exception& ex)
+                {
+                    Log::warn("Failed to send tile to " + firstSubscriber->getName() + ": " + ex.what());
+                }
+            }
+
+            // All others must get served from the cache.
+            response += " renderid=cached\n";
+            output.resize(response.size() + size);
+            std::memcpy(output.data(), response.data(), response.size());
+            std::memcpy(output.data() + response.size(), data, size);
 
-            for (const auto& i: tileBeingRendered->_subscribers)
+            for (size_t i = 1; i < tileBeingRendered->_subscribers.size(); ++i)
             {
-                auto subscriber = i.lock();
+                auto subscriber = tileBeingRendered->_subscribers[i].lock();
                 if (subscriber)
                 {
                     try
commit cd47726bfac7f05ef7aa9a929ad9a156ad3086f5
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Mon Oct 24 08:54:37 2016 -0400

    loolwsd: lookupRendering -> lookupCachedFile
    
    Change-Id: I668e1e11aaf9a88b40084ac23bcd574cf258262f
    Reviewed-on: https://gerrit.libreoffice.org/30244
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit cdf6beee864843078b66806200f7e45c2fdd9aa9)

diff --git a/loolwsd/ClientSession.cpp b/loolwsd/ClientSession.cpp
index ee5510b..2faf494 100644
--- a/loolwsd/ClientSession.cpp
+++ b/loolwsd/ClientSession.cpp
@@ -300,7 +300,7 @@ bool ClientSession::sendFontRendering(const char *buffer, int length, StringToke
     output.resize(response.size());
     std::memcpy(output.data(), response.data(), response.size());
 
-    std::unique_ptr<std::fstream> cachedRendering = docBroker->tileCache().lookupRendering(font, "font");
+    std::unique_ptr<std::fstream> cachedRendering = docBroker->tileCache().lookupCachedFile(font, "font");
     if (cachedRendering && cachedRendering->is_open())
     {
         cachedRendering->seekg(0, std::ios_base::end);
diff --git a/loolwsd/TileCache.cpp b/loolwsd/TileCache.cpp
index f3cb817..5decc9e 100644
--- a/loolwsd/TileCache.cpp
+++ b/loolwsd/TileCache.cpp
@@ -280,7 +280,7 @@ void TileCache::saveRendering(const std::string& name, const std::string& dir, c
     Util::saveDataToFileSafely(fileName, data, size);
 }
 
-std::unique_ptr<std::fstream> TileCache::lookupRendering(const std::string& name, const std::string& dir)
+std::unique_ptr<std::fstream> TileCache::lookupCachedFile(const std::string& name, const std::string& dir)
 {
     const std::string dirName = _cacheDir + "/" + dir;
     const std::string fileName = dirName + "/" + name;
diff --git a/loolwsd/TileCache.hpp b/loolwsd/TileCache.hpp
index fc0210b..fd44cd0 100644
--- a/loolwsd/TileCache.hpp
+++ b/loolwsd/TileCache.hpp
@@ -61,7 +61,7 @@ public:
     // The dir parameter should be the type of rendering, like "font", "style", etc
     void saveRendering(const std::string& name, const std::string& dir, const char *data, size_t size);
 
-    std::unique_ptr<std::fstream> lookupRendering(const std::string& name, const std::string& dir);
+    std::unique_ptr<std::fstream> lookupCachedFile(const std::string& name, const std::string& dir);
 
     // The tiles parameter is an invalidatetiles: message as sent by the child process
     void invalidateTiles(const std::string& tiles);
commit 79d3fbac258e624b7b93ea227f390945b89c9de6
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Mon Oct 24 08:47:54 2016 -0400

    loolwsd: use a document with comments to test tile rendering
    
    Change-Id: I694bee6523d0830af9f8496071f0f3a6f023736a
    Reviewed-on: https://gerrit.libreoffice.org/30243
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit ff7f325794bcad0365c27b03075798cfba3515d1)

diff --git a/loolwsd/test/TileCacheTests.cpp b/loolwsd/test/TileCacheTests.cpp
index 33bf2b1..3618f19 100644
--- a/loolwsd/test/TileCacheTests.cpp
+++ b/loolwsd/test/TileCacheTests.cpp
@@ -385,7 +385,7 @@ void TileCacheTests::testTilesRenderedJustOnce()
 {
     const auto testname = "tilesRenderdJustOnce ";
 
-    auto socket = *loadDocAndGetSocket("empty.odt", _uri, testname);
+    auto socket = *loadDocAndGetSocket("with_comment.odt", _uri, testname);
 
     assertResponseString(socket, "statechanged: .uno:AcceptTrackedChange=", testname);
 
@@ -450,7 +450,7 @@ void TileCacheTests::testTilesRenderedJustOnceMultiClient()
     const auto testname4 = testname + "-4 ";
 
     std::string documentPath, documentURL;
-    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+    getDocumentPathAndURL("with_comment.odt", documentPath, documentURL);
 
     std::cerr << "Connecting first client." << std::endl;
     auto socket = *loadDocAndGetSocket(_uri, documentURL, testname1);
commit b1a9032e8021e74c9e1b50449342b54802c8fd0b
Author: Andras Timar <andras.timar at collabora.com>
Date:   Mon Oct 24 22:18:00 2016 +0200

    loleaflet: spelling noun vs. verb: checkout -> check out
    
    (cherry picked from commit f79546853f7e646f581ecbece123b9f2bf306cdf)

diff --git a/loleaflet/dist/errormessages.js b/loleaflet/dist/errormessages.js
index 9ccfaf8..348c6c1 100644
--- a/loleaflet/dist/errormessages.js
+++ b/loleaflet/dist/errormessages.js
@@ -1,6 +1,6 @@
 exports.diskfull = _('No disk space left on server, please contact the server administrator to continue.');
 exports.emptyhosturl = _('The host URL is empty. The loolwsd server is probably misconfigured, please contact the administrator.');
-exports.limitreached = _('This development build is limited to %0 documents, and %1 connections - to avoid the impression that it is suitable for deployment in large enterprises. To find out more about deploying and scaling %2 checkout: <br/><a href=\"%3\">%3</a>.');
+exports.limitreached = _('This development build is limited to %0 documents, and %1 connections - to avoid the impression that it is suitable for deployment in large enterprises. To find out more about deploying and scaling %2 check out: <br/><a href=\"%3\">%3</a>.');
 exports.serviceunavailable = _('Service is unavailable. Please try again later and report to your administrator if the issue persists.');
 exports.unauthorized = _('Unauthorized WOPI host. Please try again later and report to your administrator if the issue persists.');
 exports.wrongwopisrc = _('Wrong WOPISrc, usage: WOPISrc=valid encoded URI, or file_path, usage: file_path=/path/to/doc/');
diff --git a/loleaflet/po/templates/loleaflet-ui.pot b/loleaflet/po/templates/loleaflet-ui.pot
index e12b9c9..08ba209 100644
--- a/loleaflet/po/templates/loleaflet-ui.pot
+++ b/loleaflet/po/templates/loleaflet-ui.pot
@@ -211,7 +211,7 @@ msgstr ""
 msgid ""
 "This development build is limited to %0 documents, and %1 connections - to "
 "avoid the impression that it is suitable for deployment in large "
-"enterprises. To find out more about deploying and scaling %2 checkout: <br/"
+"enterprises. To find out more about deploying and scaling %2 check out: <br/"
 "><a href=\"%3\">%3</a>."
 msgstr ""
 
diff --git a/loleaflet/po/ui-sl.po b/loleaflet/po/ui-sl.po
index f9c0793..d8a0bf0 100644
--- a/loleaflet/po/ui-sl.po
+++ b/loleaflet/po/ui-sl.po
@@ -216,7 +216,7 @@ msgstr ""
 msgid ""
 "This development build is limited to %0 documents, and %1 connections - to "
 "avoid the impression that it is suitable for deployment in large "
-"enterprises. To find out more about deploying and scaling %2 checkout: <br/"
+"enterprises. To find out more about deploying and scaling %2 check out: <br/"
 "><a href=\"%3\">%3</a>."
 msgstr ""
 "Ta razvojna gradnja je omejena na %0 dokumentov in %1 povezav - da bi se "
commit e38b3eb27f93f02a98da3b3552ddac0ea7479893
Author: Andras Timar <andras.timar at collabora.com>
Date:   Mon Oct 24 20:54:55 2016 +0200

    loleaflet: add loleaflet-font class to Calc tab context menu
    
    (cherry picked from commit ae0b3a6cac374f7cfdfd310b7a2fce6f95971032)

diff --git a/loleaflet/src/control/Control.Tabs.js b/loleaflet/src/control/Control.Tabs.js
index 4dd43cb..d1e6bf9 100644
--- a/loleaflet/src/control/Control.Tabs.js
+++ b/loleaflet/src/control/Control.Tabs.js
@@ -32,6 +32,7 @@ L.Control.Tabs = L.Control.extend({
 
 		$.contextMenu({
 			selector: '.spreadsheet-context-menu',
+			className: 'loleaflet-font',
 			callback: function(key, options) {
 				var nPos = parseInt(options.$trigger.attr('id').split('spreadsheet-tab')[1]);
 
commit 42eb5a69276bc5700648907e64673be8c2518af7
Author: Andras Timar <andras.timar at collabora.com>
Date:   Mon Oct 24 20:52:52 2016 +0200

    loleaflet: typo: initalized -> initialized
    
    (cherry picked from commit 67d21d682d273e134ceae41dc4d7d6a51ee994e5)

diff --git a/loleaflet/src/control/Control.Tabs.js b/loleaflet/src/control/Control.Tabs.js
index 7e4bd46..4dd43cb 100644
--- a/loleaflet/src/control/Control.Tabs.js
+++ b/loleaflet/src/control/Control.Tabs.js
@@ -6,7 +6,7 @@
 L.Control.Tabs = L.Control.extend({
 	onAdd: function(map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
-		this._initalized = false;
+		this._initialized = false;
 	},
 
 	_onUpdatePermission: function(e) {
commit 9a85add9ca529fcc1d645a5acf92ed961d5e3c24
Author: Andras Timar <andras.timar at collabora.com>
Date:   Mon Oct 24 16:37:56 2016 +0200

    loolwsd: remove unused defines
    
    (cherry picked from commit e3029937918f9f65bcfb4db22896e8be78785fbf)

diff --git a/loolwsd/FileServer.cpp b/loolwsd/FileServer.cpp
index b140cb1..d56824f 100644
--- a/loolwsd/FileServer.cpp
+++ b/loolwsd/FileServer.cpp
@@ -39,15 +39,6 @@
 #include "FileServer.hpp"
 #include "LOOLWSD.hpp"
 
-/* CODE */
-#define LOOLWSD_CODE "This development build is limited to %d documents, and %d connections - to avoid the impression that it is suitable for deployment in large enterprises. To find out more about deploying and scaling %s checkout - <a href=\"%s\">%s</a>."
-
-/* PRODUCT */
-#define LOOLWSD_PRODUCT "LibreOffice Online"
-
-/* PRODUCT URL */
-#define LOOLWSD_URL "https://wiki.documentfoundation.org/Development/LibreOffice_Online"
-
 using Poco::FileInputStream;
 using Poco::Net::HTMLForm;
 using Poco::Net::HTTPRequest;
commit 0b88beb9a55814a5c27881bbe7f96732d96a1b33
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Mon Oct 24 16:26:35 2016 +0530

    loleaflet: Fix missing comma, semi-colons etc
    
    Change-Id: Ic47e6e44a83c43ff7aea840fb6b04d1ad7c4e4a2
    (cherry picked from commit d50d3f5b775fe72879aebb623ade2ea7b326e9c9)

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index cbea6e6..90dcc34 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1920,8 +1920,8 @@ L.TileLayer = L.GridLayer.extend({
 			var overlayMaps = {
 				'Tile overlays': this._debugInfo,
 				'Screen overlays': this._debugInfo2,
-				'Typing': this._debugTyper,
-			}
+				'Typing': this._debugTyper
+			};
 			L.control.layers({}, overlayMaps, {collapsed: false}).addTo(map);
 
 			this._map.on('layeradd', function(e) {
@@ -2025,7 +2025,7 @@ L.TileLayer = L.GridLayer.extend({
 					rect.setStyle({fillOpacity: opac - 0.04});
 				}
 			}
-			this._debugTimeoutId = setTimeout(function () { map._docLayer._debugTimeout() }, 50);
+			this._debugTimeoutId = setTimeout(function () { map._docLayer._debugTimeout(); }, 50);
 		}
 	},
 
@@ -2038,7 +2038,7 @@ L.TileLayer = L.GridLayer.extend({
 			this._postKeyboardEvent('input', this._debugLorem.charCodeAt(this._debugLoremPos % this._debugLorem.length), 0);
 		}
 		this._debugLoremPos++;
-		this._debugTypeTimeoutId = setTimeout(function () { map._docLayer._debugTypeTimeout() }, 50);
+		this._debugTypeTimeoutId = setTimeout(function () { map._docLayer._debugTypeTimeout(); }, 50);
 	}
 
 });
commit 57b9dcc34dd64ddf3caf18e1dd79b1aa273fe72f
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Sun Oct 23 17:35:46 2016 +0530

    loolwsd: Separate WOPI load duration and check fileinfo duration
    
    Add both of them and send them as wopiloadduration to the client,
    if storage is WOPI.
    
    Change-Id: I5652d346d70f473624f03536469acf45470db742
    (cherry picked from commit 36fece8b07097d8e155f632abdf6c0f907550502)

diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index eeb3fd7..3a5e1be 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -237,19 +237,29 @@ bool DocumentBroker::load(const std::string& sessionId, const std::string& jailI
         }
 
         // Lets load the document now
-        if (_storage->isLoaded())
+        const bool loaded = _storage->isLoaded();
+        if (!loaded)
         {
-            // Already loaded. Nothing to do.
-            return true;
-        }
+            const auto localPath = _storage->loadStorageFileToLocal();
+            _uriJailed = Poco::URI(Poco::URI("file://"), localPath);
+            _filename = fileInfo._filename;
 
-        const auto localPath = _storage->loadStorageFileToLocal();
-        _uriJailed = Poco::URI(Poco::URI("file://"), localPath);
-        _filename = fileInfo._filename;
+            // Use the local temp file's timestamp.
+            _lastFileModifiedTime = Poco::File(_storage->getLocalRootPath()).getLastModified();
+            _tileCache.reset(new TileCache(_uriPublic.toString(), _lastFileModifiedTime, _cacheRoot));
+        }
 
-        // Use the local temp file's timestamp.
-        _lastFileModifiedTime = Poco::File(_storage->getLocalRootPath()).getLastModified();
-        _tileCache.reset(new TileCache(_uriPublic.toString(), _lastFileModifiedTime, _cacheRoot));
+        // If its WOPI storage, send the duration that it took for WOPI calls
+        if (dynamic_cast<WopiStorage*>(_storage.get()) != nullptr)
+        {
+            // Get the time taken to load the file from storage
+            auto callDuration = dynamic_cast<WopiStorage*>(_storage.get())->getWopiLoadDuration();
+            // Add the time taken to check file info
+            callDuration += fileInfo._callDuration;
+            const std::string msg = "stats: wopiloadduration " + std::to_string(callDuration.count());
+            Log::trace("Sending to Client [" + msg + "].");
+            it->second->sendTextFrame(msg);
+        }
 
         return true;
     }
@@ -856,16 +866,6 @@ bool DocumentBroker::forwardToClient(const std::string& prefix, const std::vecto
     return false;
 }
 
-const std::chrono::duration<double> DocumentBroker::getStorageLoadDuration() const
-{
-    if (dynamic_cast<WopiStorage*>(_storage.get()) != nullptr)
-    {
-        return dynamic_cast<WopiStorage*>(_storage.get())->getWopiLoadDuration();
-    }
-
-    return std::chrono::duration<double>::zero();
-}
-
 void DocumentBroker::childSocketTerminated()
 {
     if (!_childProcess->isAlive())
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 10b4e2f..3eefd6d 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -881,15 +881,6 @@ private:
             auto sessionsCount = docBroker->addSession(session);
             Log::trace(docKey + ", ws_sessions++: " + std::to_string(sessionsCount));
 
-            // If its a WOPI host, return time taken to make calls to it
-            const auto storageCallDuration = docBroker->getStorageLoadDuration();
-            if (storageCallDuration != std::chrono::duration<double>::zero())
-            {
-                status = "stats: wopiloadduration " + std::to_string(storageCallDuration.count());
-                Log::trace("Sending to Client [" + status + "].");
-                ws->sendFrame(status.data(), (int) status.size());
-            }
-
             LOOLWSD::dumpEventTrace(docBroker->getJailId(), id, "NewSession: " + uri);
 
             // Let messages flow.
diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp
index a20691d..3a00066 100644
--- a/loolwsd/Storage.cpp
+++ b/loolwsd/Storage.cpp
@@ -306,9 +306,8 @@ StorageBase::FileInfo WopiStorage::getFileInfo(const Poco::URI& uriPublic)
     Poco::StreamCopier::copyToString(rs, resMsg);
 
     const auto endTime = std::chrono::steady_clock::now();
-    const std::chrono::duration<double> diff = (endTime - startTime);
-    _wopiLoadDuration += diff;
-    Log::debug("WOPI::CheckFileInfo returned: " + resMsg + ". Call duration: " + std::to_string(diff.count()) + "s");
+    const std::chrono::duration<double> callDuration = (endTime - startTime);
+    Log::debug("WOPI::CheckFileInfo returned: " + resMsg + ". Call duration: " + std::to_string(callDuration.count()) + "s");
     const auto index = resMsg.find_first_of('{');
     if (index != std::string::npos)
     {
@@ -331,6 +330,7 @@ StorageBase::FileInfo WopiStorage::getFileInfo(const Poco::URI& uriPublic)
 
     // WOPI doesn't support file last modified time.
     _fileInfo = FileInfo({filename, Poco::Timestamp(), size, userId, userName, canWrite});
+    _fileInfo._callDuration = callDuration;
     return _fileInfo;
 }
 
diff --git a/loolwsd/Storage.hpp b/loolwsd/Storage.hpp
index 163443c..6238a57 100644
--- a/loolwsd/Storage.hpp
+++ b/loolwsd/Storage.hpp
@@ -42,7 +42,8 @@ public:
               _size(size),
               _userId(userId),
               _userName(userName),
-              _canWrite(canWrite)
+              _canWrite(canWrite),
+              _callDuration(0)
         {
         }
 
@@ -57,6 +58,10 @@ public:
         std::string _userId;
         std::string _userName;
         bool _canWrite;
+
+        // Time it took to fetch fileinfo from storage
+        // Matters in case of external storage such as WOPI
+        std::chrono::duration<double> _callDuration;
     };
 
     /// localStorePath the absolute root path of the chroot.
@@ -164,11 +169,11 @@ public:
 
     bool saveLocalFileToStorage(const Poco::URI& uriPublic) override;
 
-    /// Total time taken for making WOPI calls during load (includes GetFileInfo calls)
-    const std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; }
+    /// Total time taken for making WOPI calls during load
+    std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; }
 
 private:
-    // Time spend in waiting for WOPI host during document load
+    // Time spend in loading the file from storage
     std::chrono::duration<double> _wopiLoadDuration;
 };
 
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index eb40f7a..560208f 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -334,7 +334,7 @@ redlinetablechanged:
 
 stats: <key> <value>
 
-    Contains statistical data. Eg: 'stats: wopiduration 5' means that
+    Contains statistical data. Eg: 'stats: wopiloadduration 5' means that
     wopi calls made in loading of document took 5 seconds.
 
 perm: <permission>
commit f99c330c52ee2de8b2f82b55c3364b93731719f5
Author: Pranav Kant <pranavk at collabora.co.uk>
Date:   Sun Oct 23 17:06:32 2016 +0530

    loolwsd: Remove this comment - this is fixed now
    
    Change-Id: I032c7e4a1609b68882dba6cc48ebd3fb2d59b8f5
    (cherry picked from commit 050bf4c65ec6901749da484cd568b175e98aa0ef)

diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 850d570..eeb3fd7 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -222,8 +222,6 @@ bool DocumentBroker::load(const std::string& sessionId, const std::string& jailI
 
     if (_storage)
     {
-        // Set the username for the session
-        // TODO: security: Set the permission (readonly etc.) of the session here also
         const auto fileInfo = _storage->getFileInfo(uriPublic);
         if (!fileInfo.isValid())
         {
commit 576e721b3d3bd7393bcc554517b4daea0af5d2a3
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 21:14:30 2016 -0400

    loleaflet: prevent the zoomlevel from changing on connect/disconnect
    
    When a client connects or disconnects ViewInfo message is sent.
    The handler for this updates the number of users via addView
    and removeView. Unfortunately when the toolbar control is
    updated, it resets the zoomlevel to 100% (the initial value).
    
    This is an ugly hack to change it back to the correct
    value, since nothing really visually changes. It certainly
    could be improved by only correcting the toolbar.
    
    Change-Id: I37294da2d9d1bc84e8cb3b7f634aadcfd80d6497
    Reviewed-on: https://gerrit.libreoffice.org/30218
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit b26f2f2738c4c60c8b3212f222336d07465744da)

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index c26dd47..cbea6e6 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -810,6 +810,11 @@ L.TileLayer = L.GridLayer.extend({
 				this._removeView(parseInt(viewInfoIdx));
 			}
 		}
+
+		//FIXME: Ugly hack to prevent the toolbar from reseting the zoomlevel to 100%.
+		var center = this._map.getCenter();
+		this._map.setView(center, this._map._zoom-1, {reset: false, animate: false});
+		this._map.setView(center, this._map._zoom+1, {reset: true, animate: false});
 	},
 
 	_onPartPageRectanglesMsg: function (textMsg) {
commit 707078430949adad5169742ae284f3d201b9a536
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 16:11:22 2016 -0400

    loolwsd: log the invalid/dead PID in exception
    
    Change-Id: Ib523e34de3b354944c53ce0e6b5a7cab8fdb8cf3
    Reviewed-on: https://gerrit.libreoffice.org/30215
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit b78e224f8dc1c88ec99cff2d6b8f636bd4735d90)

diff --git a/loolwsd/Util.cpp b/loolwsd/Util.cpp
index 1f53d7e..ffcbf03 100644
--- a/loolwsd/Util.cpp
+++ b/loolwsd/Util.cpp
@@ -510,7 +510,7 @@ namespace Util
         }
         catch(const std::exception&)
         {
-            Log::warn() << "Trying to find memory of invalid/dead PID" << Log::end;
+            Log::warn() << "Trying to find memory of invalid/dead PID " << nPid << Log::end;
         }
 
         return nMem;
commit 98c584487a76e001f00f6f68e95d04a50f71e2c2
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 16:46:05 2016 -0400

    loolwsd: show disconnected usernames in the document repair
    
    By retaining the view-ID to username mapping of disconnected
    users we are able to show a disconnected username in the
    document repair dialog.
    
    Change-Id: Id08790de31f92653381b6a1525caf044bd875479
    Reviewed-on: https://gerrit.libreoffice.org/30216
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 5802d7e8a1b6ada4963ef02403f7835ebbd2d38c)

diff --git a/loolwsd/ChildSession.cpp b/loolwsd/ChildSession.cpp
index 7ce377b..4e1243f 100644
--- a/loolwsd/ChildSession.cpp
+++ b/loolwsd/ChildSession.cpp
@@ -466,7 +466,7 @@ bool ChildSession::getCommandValues(const char* /*buffer*/, int /*length*/, Stri
                                         std::string(pValues == nullptr ? "" : pValues),
                                         std::string(pUndo == nullptr ? "" : pUndo));
         // json only contains view IDs, insert matching user names.
-        std::map<int, std::string> viewInfo =_docManager.getViewInfo();
+        std::map<int, std::string> viewInfo = _docManager.getViewInfo();
         insertUserNames(viewInfo, json);
         success = sendTextFrame("commandvalues: " + json);
         std::free(pValues);
diff --git a/loolwsd/ChildSession.hpp b/loolwsd/ChildSession.hpp
index c901cf0..ccd0720 100644
--- a/loolwsd/ChildSession.hpp
+++ b/loolwsd/ChildSession.hpp
@@ -75,7 +75,7 @@ public:
     bool getPartPageRectangles(const char *buffer, int length);
     int getViewId() const { return _viewId; }
     void setViewId(const int viewId) { _viewId = viewId; }
-    const std::string getViewUserName() const { return _userName; }
+    const std::string& getViewUserName() const { return _userName; }
 
     void loKitCallback(const int nType, const std::string& rPayload);
 
diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp
index 0ddcac5..b88126a 100644
--- a/loolwsd/LOOLKit.cpp
+++ b/loolwsd/LOOLKit.cpp
@@ -851,14 +851,13 @@ private:
 
         for (auto& pair : _sessions)
         {
-            const auto session = pair.second;
-            if (!session->isCloseFrame())
-            {
-                const auto viewId = session->getViewId();
-                viewInfo[viewId] = session->getViewUserName();
-            }
+            const auto& session = pair.second;
+            const auto viewId = session->getViewId();
+            viewInfo[viewId] = session->getViewUserName();
         }
 
+        viewInfo.insert(_oldSessionIds.begin(), _oldSessionIds.end());
+
         return viewInfo;
     }
 
@@ -1133,6 +1132,7 @@ private:
                 if (message == "disconnect")
                 {
                     Log::debug("Removing ChildSession " + viewId);
+                    _oldSessionIds[it->second->getViewId()] = it->second->getViewUserName();
                     _sessions.erase(it);
                     return true;
                 }
@@ -1271,6 +1271,7 @@ private:
     std::atomic_size_t _isLoading;
     std::map<int, std::unique_ptr<CallbackDescriptor>> _viewIdToCallbackDescr;
     std::map<std::string, std::shared_ptr<ChildSession>> _sessions;
+    std::map<int, std::string> _oldSessionIds;
     Poco::Thread _callbackThread;
     std::atomic_size_t _clientViews;
 };
commit 6f9d275b3e006a218b3cff9264aa88e5dcffe8c6
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 16:11:01 2016 -0400

    loolwsd: don't wait after autosaving to rebalance children
    
    Change-Id: Ifb44d0395e8f416e418711b09deed3eb7f57effa
    Reviewed-on: https://gerrit.libreoffice.org/30214
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit cecb87dfe31669fc0c3802f160030856f21a3db0)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index e1c07cb..10b4e2f 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -2025,29 +2025,30 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
         }
         else // pid == 0, no children have died
         {
-            if (!std::getenv("LOOL_NO_AUTOSAVE"))
+            if (!std::getenv("LOOL_NO_AUTOSAVE") &&
+                (time(nullptr) >= last30SecCheck + 30))
             {
-                if (time(nullptr) >= last30SecCheck + 30)
+                try
                 {
-                    try
-                    {
-                        std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
-                        cleanupDocBrokers();
-                        for (auto& brokerIt : DocBrokers)
-                        {
-                            brokerIt.second->autoSave(false, 0);
-                        }
-                    }
-                    catch (const std::exception& exc)
+                    std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
+                    cleanupDocBrokers();
+                    for (auto& brokerIt : DocBrokers)
                     {
-                        Log::error("Exception: " + std::string(exc.what()));
+                        brokerIt.second->autoSave(false, 0);
                     }
-
-                    last30SecCheck = time(nullptr);
                 }
-            }
+                catch (const std::exception& exc)
+                {
+                    Log::error("Exception: " + std::string(exc.what()));
+                }
 
-            std::this_thread::sleep_for(std::chrono::milliseconds(CHILD_REBALANCE_INTERVAL_MS));
+                last30SecCheck = time(nullptr);
+            }
+            else
+            {
+                // Don't wait if we had been saving, which takes a while anyway.
+                std::this_thread::sleep_for(std::chrono::milliseconds(CHILD_REBALANCE_INTERVAL_MS));
+            }
 
             // Make sure we have sufficient reserves.
             prespawnChildren();
commit 7c749089ddf63a85cab60544429fd8c6cdb6a90b
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 16:00:32 2016 -0400

    loolwsd: spawn new children when no forks are outstanding
    
    This avoids always waiting until child spawning times
    out by being smarter about whether or not there are
    any children spawning to wait for.
    
    Change-Id: I96a16ac35f90f70219d4153db9862cf2ee5b6a76
    Reviewed-on: https://gerrit.libreoffice.org/30213
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit f4b4037f9ed73501159efb359702df2bbb161d25)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index a287320..e1c07cb 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -167,6 +167,7 @@ static std::vector<std::shared_ptr<ChildProcess>> NewChildren;
 static std::mutex NewChildrenMutex;
 static std::condition_variable NewChildrenCV;
 static std::chrono::steady_clock::time_point LastForkRequestTime = std::chrono::steady_clock::now();
+static std::atomic<int> OutstandingForks(1); // Forkit always spawns 1.
 static std::map<std::string, std::shared_ptr<DocumentBroker>> DocBrokers;
 static std::mutex DocBrokersMutex;
 
@@ -257,6 +258,8 @@ static void forkChildren(const int number)
         Util::checkDiskSpaceOnRegisteredFileSystems();
         const std::string aMessage = "spawn " + std::to_string(number) + "\n";
         Log::debug("MasterToForKit: " + aMessage.substr(0, aMessage.length() - 1));
+
+        ++OutstandingForks;
         IoUtil::writeToPipe(LOOLWSD::ForKitWritePipe, aMessage);
         LastForkRequestTime = std::chrono::steady_clock::now();
     }
@@ -315,27 +318,29 @@ static void prespawnChildren()
     // Do the cleanup first.
     const bool rebalance = cleanupChildren();
 
-    int balance = LOOLWSD::NumPreSpawnedChildren;
-    balance -= NewChildren.size();
-    if (balance <= 0)
+    const auto duration = (std::chrono::steady_clock::now() - LastForkRequestTime);
+    const auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
+    if (durationMs >= CHILD_TIMEOUT_MS)
     {
-        return;
+        // Children taking too long to spawn.
+        // Forget we had requested any, and request anew.
+        OutstandingForks = 0;
     }
 
-    const auto duration = (std::chrono::steady_clock::now() - LastForkRequestTime);
-    if (!rebalance &&
-        std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() <= CHILD_TIMEOUT_MS)
+    int balance = LOOLWSD::NumPreSpawnedChildren;
+    balance -= NewChildren.size();
+    balance -= OutstandingForks;
+
+    if (rebalance || durationMs >= CHILD_TIMEOUT_MS)
     {
-        // Not enough time passed to balance children.
-        return;
+        forkChildren(balance);
     }
-
-    forkChildren(balance);
 }
 
 static size_t addNewChild(const std::shared_ptr<ChildProcess>& child)
 {
     std::unique_lock<std::mutex> lock(NewChildrenMutex);
+    --OutstandingForks;
     NewChildren.emplace_back(child);
     const auto count = NewChildren.size();
     Log::info() << "Have " << count << " "
commit fda77a8981e233f17f400b00a22dbc4de0659e93
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 15:59:22 2016 -0400

    loolwsd: remove dead DocumentBrokers
    
    Change-Id: If9b24ac1e45454e21222699a00defa70acc4ed80
    Reviewed-on: https://gerrit.libreoffice.org/30212
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 70690f6dabffd1ffb5aff8e01349371700b940e1)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index a9aa59d..a287320 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -224,6 +224,30 @@ void shutdownLimitReached(WebSocket& ws)
 
 }
 
+/// Remove dead DocBrokers.
+/// Returns true if at least one is removed.
+bool cleanupDocBrokers()
+{
+    Util::assertIsLocked(DocBrokersMutex);
+
+    const auto count = DocBrokers.size();
+    for (auto it = DocBrokers.begin(); it != DocBrokers.end(); )
+    {
+        // Cleanup used and dead entries.
+        if (it->second->isLoaded() && !it->second->isAlive())
+        {
+            Log::debug("Removing dead DocBroker [" + it->first + "].");
+            it = DocBrokers.erase(it);
+        }
+        else
+        {
+            ++it;
+        }
+    }
+
+    return (count != DocBrokers.size());
+}
+
 static void forkChildren(const int number)
 {
     Util::assertIsLocked(NewChildrenMutex);
@@ -273,6 +297,14 @@ static void preForkChildren()
 /// to load documents with alacrity.
 static void prespawnChildren()
 {
+    // First remove dead DocBrokers, if possible.
+    std::unique_lock<std::mutex> docBrokersLock(DocBrokersMutex, std::defer_lock);
+    if (docBrokersLock.try_lock())
+    {
+        cleanupDocBrokers();
+        docBrokersLock.unlock();
+    }
+
     std::unique_lock<std::mutex> lock(NewChildrenMutex, std::defer_lock);
     if (!lock.try_lock())
     {
@@ -468,6 +500,8 @@ private:
                     // In that case, we can use a pool and index by publicPath.
                     std::unique_lock<std::mutex> lock(DocBrokersMutex);
 
+                    cleanupDocBrokers();
+
                     //FIXME: What if the same document is already open? Need a fake dockey here?
                     Log::debug("New DocumentBroker for docKey [" + docKey + "].");
                     DocBrokers.emplace(docKey, docBroker);
@@ -683,6 +717,8 @@ private:
                 return;
             }
 
+            cleanupDocBrokers();
+
             // Lookup this document.
             auto it = DocBrokers.find(docKey);
             if (it != DocBrokers.end())
@@ -1991,6 +2027,7 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
                     try
                     {
                         std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
+                        cleanupDocBrokers();
                         for (auto& brokerIt : DocBrokers)
                         {
                             brokerIt.second->autoSave(false, 0);
commit 5582628058aafe320dde6551a5fda90398bc0b14
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 11:51:10 2016 -0400

    loolwsd: fix testMaxDocuments unittest
    
    Checking for document limit must be done before allocating
    a child process, otherwise the new child process will not
    be cleaned up or released, thereby failing the test.
    
    Change-Id: I99b1155bdacf2f0b7a24c7b7330d207e4c7beee8
    Reviewed-on: https://gerrit.libreoffice.org/30208
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit f68ece00377ef41e68996f78f2ac4cd868794930)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 6694f81..a9aa59d 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -192,17 +192,27 @@ void shutdownLimitReached(WebSocket& ws)
         int retries = 7;
         std::vector<char> buffer(READ_BUFFER_SIZE * 100);
 
-        // 5 seconds timeout
-        ws.setReceiveTimeout(5000000);
+        const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
         do
         {
-            // ignore loolclient, load and partpagerectangles
-            ws.receiveFrame(buffer.data(), buffer.capacity(), flags);
-            if (--retries == 4)
+            if (ws.poll(Poco::Timespan(0), Poco::Net::Socket::SelectMode::SELECT_ERROR))
             {
-                ws.sendFrame(error.data(), error.size());
-                ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
+                // Already disconnected, can't send 'close' frame.
+                ws.close();
+                return;
+            }
+
+            // Let the client know we are shutting down.
+            ws.sendFrame(error.data(), error.size());
+
+            // Ignore incoming messages.
+            if (ws.poll(waitTime, Poco::Net::Socket::SELECT_READ))
+            {
+                ws.receiveFrame(buffer.data(), buffer.capacity(), flags);
             }
+
+            // Shutdown.
+            ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
         }
         while (retries > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
     }
@@ -684,6 +694,17 @@ private:
             }
             else
             {
+                // New Document.
+#if MAX_DOCUMENTS > 0
+                if (DocBrokers.size() + 1 > MAX_DOCUMENTS)
+                {
+                    Log::error() << "Limit on maximum number of open documents of "
+                                 << MAX_DOCUMENTS << " reached." << Log::end;
+                    shutdownLimitReached(*ws);
+                    return;
+                }
+#endif
+
                 // Store a dummy (marked to destroy) document broker until we
                 // have the real one, so that the other requests block
                 Log::debug("Inserting a dummy DocumentBroker for docKey [" + docKey + "] temporarily.");
@@ -759,17 +780,6 @@ private:
                 throw WebSocketErrorMessageException(SERVICE_UNAVAILABLE_INTERNAL_ERROR);
             }
 
-#if MAX_DOCUMENTS > 0
-            std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
-            if (DocBrokers.size() + 1 > MAX_DOCUMENTS)
-            {
-                Log::error("Maximum number of open documents reached.");
-                shutdownLimitReached(*ws);
-                return;
-            }
-            DocBrokersLock.unlock();
-#endif
-
             // Set one we just created.
             Log::debug("New DocumentBroker for docKey [" + docKey + "].");
             docBroker = std::make_shared<DocumentBroker>(uriPublic, docKey, LOOLWSD::ChildRoot, child);
diff --git a/loolwsd/test/httpwserror.cpp b/loolwsd/test/httpwserror.cpp
index 011806b..1d686e4 100644
--- a/loolwsd/test/httpwserror.cpp
+++ b/loolwsd/test/httpwserror.cpp
@@ -103,11 +103,15 @@ void HTTPWSError::testMaxDocuments()
         // Load a document.
         std::vector<std::shared_ptr<Poco::Net::WebSocket>> docs;
 
+        std::cerr << "Loading max number of documents: " << MAX_DOCUMENTS << std::endl;
         for (int it = 1; it <= MAX_DOCUMENTS; ++it)
         {
             docs.emplace_back(loadDocAndGetSocket("empty.odt", _uri, testname));
+            std::cerr << "Loaded document #" << it << " of " << MAX_DOCUMENTS << std::endl;
         }
 
+        std::cerr << "Loading one more document beyond the limit." << std::endl;
+
         // try to open MAX_DOCUMENTS + 1
         std::string docPath;
         std::string docURL;
@@ -121,6 +125,8 @@ void HTTPWSError::testMaxDocuments()
         sendTextFrame(socket, "load ", testname);
         sendTextFrame(socket, "partpagerectangles ", testname);
 
+        assertResponseString(socket, "error:", testname);
+
         std::string message;
         const auto statusCode = getErrorCode(socket, message);
         CPPUNIT_ASSERT_EQUAL(static_cast<int>(Poco::Net::WebSocket::WS_POLICY_VIOLATION), statusCode);
commit f9368db9a20ad92fa7c1173c046d472b46f61a03
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 13:42:56 2016 -0400

    loolwsd: check for termination flag before loading new documents
    
    Change-Id: I4b7117ba255dd81f28ed1279814048e4311c2473
    Reviewed-on: https://gerrit.libreoffice.org/30211
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 30df646311db4a4a273486431f96caf0a18fd89a)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index df8a417..6694f81 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -667,6 +667,12 @@ private:
         {
             std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
 
+            if (TerminationFlag)
+            {
+                Log::error("Termination flag set. No loading new session [" + id + "]");
+                return;
+            }
+
             // Lookup this document.
             auto it = DocBrokers.find(docKey);
             if (it != DocBrokers.end())
@@ -719,6 +725,12 @@ private:
                     timedOut = false;
                     break;
                 }
+
+                if (TerminationFlag)
+                {
+                    Log::error("Termination flag set. No loading new session [" + id + "]");
+                    return;
+                }
             }
 
             if (timedOut)
@@ -728,6 +740,12 @@ private:
             }
         }
 
+        if (TerminationFlag)
+        {
+            Log::error("Termination flag set. No loading new session [" + id + "]");
+            return;
+        }
+
         bool newDoc = false;
         if (!docBroker)
         {
commit 843c02166a1ba504a20123c1580440d65636d2b5
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 13:42:20 2016 -0400

    loolwsd: logs around testMaxConnections
    
    Change-Id: I0f9dc957fb95d501626e321b4e15472d077c0301
    Reviewed-on: https://gerrit.libreoffice.org/30210
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 0287d7141df581805ecab863c719e27f9f60cab8)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index a526829..df8a417 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -979,7 +979,8 @@ public:
         if (++LOOLWSD::NumConnections > MAX_CONNECTIONS)
         {
             --LOOLWSD::NumConnections;
-            Log::error("Maximum number of connections reached.");
+            Log::error() << "Limit on maximum number of connections of "
+                         << MAX_CONNECTIONS << " reached." << Log::end;
             // accept hand shake
             WebSocket ws(request, response);
             shutdownLimitReached(ws);
diff --git a/loolwsd/test/httpwserror.cpp b/loolwsd/test/httpwserror.cpp
index 1e35593..011806b 100644
--- a/loolwsd/test/httpwserror.cpp
+++ b/loolwsd/test/httpwserror.cpp
@@ -140,22 +140,28 @@ void HTTPWSError::testMaxConnections()
     const auto testname = "maxConnections ";
     try
     {
+        std::cerr << "Opening max number of connections: " << MAX_CONNECTIONS << std::endl;
+
         // Load a document.
         std::string docPath;
         std::string docURL;
-        std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
 
         getDocumentPathAndURL("empty.odt", docPath, docURL);
         Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
         auto socket = loadDocAndGetSocket(_uri, docURL, testname);
+        std::cerr << "Opened connect #1 of " << MAX_CONNECTIONS << std::endl;
 
+        std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
         for(int it = 1; it < MAX_CONNECTIONS; it++)
         {
             std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(_uri));
             auto ws = std::make_shared<Poco::Net::WebSocket>(*session, request, _response);
             views.emplace_back(ws);
+            std::cerr << "Opened connect #" << (it+1) << " of " << MAX_CONNECTIONS << std::endl;
         }
 
+        std::cerr << "Opening one more connection beyond the limit." << std::endl;
+
         // try to connect MAX_CONNECTIONS + 1
         std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(_uri));
         Poco::Net::WebSocket socketN(*session, request, _response);
commit 1b4b9264635152cff1d5d949848731a52979e5af
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Oct 23 13:30:54 2016 -0400

    loolwsd: prevent configuring max_connections less than max_documents
    
    It would be nonsensical to allow less connections than documents
    since each document must, by definition, have at least a
    single connection.
    
    This prevents blocking new documents because of connection
    limit. If that were the intention, max_documents should be
    lowered to match max_connections.
    
    Change-Id: Ide07e977f548ed917c6e51a2ba88f3cc07947efe
    Reviewed-on: https://gerrit.libreoffice.org/30209
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 195936ffa1cb72078df722d4103fe476d2792a4f)

diff --git a/loolwsd/configure.ac b/loolwsd/configure.ac
index 0a3d242..ced0ffa 100644
--- a/loolwsd/configure.ac
+++ b/loolwsd/configure.ac
@@ -141,6 +141,11 @@ AS_IF([test -n "$with_max_connections"],
 AC_DEFINE_UNQUOTED([MAX_CONNECTIONS],[$MAX_CONNECTIONS],[Limit the maximum number of open connections])
 AC_SUBST(MAX_CONNECTIONS)
 
+if test $MAX_CONNECTIONS -lt $MAX_DOCUMENTS; then
+    AC_MSG_ERROR([Each document must have at least one connection, therefore, max_connections cannot be less than
+                  max_documents.])
+fi
+
 # Test for build environment
 
 CXXFLAGS="$CXXFLAGS -std=c++11"
commit 89cfd9d54a59f9dc38cde04c6b84f14847a0de85
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 19:10:07 2016 -0400

    loolwsd: improve ChildProcess cleanup
    
    Change-Id: I9852f04021097800d5ab0ce775f26fada5f3c8e7
    Reviewed-on: https://gerrit.libreoffice.org/30207
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit f0c3365f9f0ea454eccb9d26fd5ffcbdec3b2a19)

diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index e64dfff..850d570 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -60,8 +60,9 @@ void ChildProcess::socketProcessor()
 
     // Notify the broker that we're done.
     auto docBroker = _docBroker.lock();
-    if (docBroker)
+    if (docBroker && !_stop)
     {
+        // No need to notify if asked to stop.
         docBroker->childSocketTerminated();
     }
 }
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 170613c..39bc19a 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -55,11 +55,8 @@ public:
 
     ~ChildProcess()
     {
-        if (_pid > 0)
-        {
-            Log::info("~ChildProcess dtor [" + std::to_string(_pid) + "].");
-            close(false);
-        }
+        Log::debug("~ChildProcess dtor [" + std::to_string(_pid) + "].");
+        close(true);
     }
 
     void setDocumentBroker(const std::shared_ptr<DocumentBroker>& docBroker)
@@ -68,11 +65,18 @@ public:
         _docBroker = docBroker;
     }
 
+    void stop()
+    {
+        Log::debug("Stopping ChildProcess [" + std::to_string(_pid) + "]");
+        _stop = true;
+    }
+
     void close(const bool rude)
     {
         try
         {
-            _stop = true;
+            Log::debug("Closing ChildProcess [" + std::to_string(_pid) + "].");
+            stop();
             IoUtil::shutdownWebSocket(_ws);
             if (_thread.joinable())
             {
commit 32c716e0e56bd4fa5ca1efb90c8a97afaa19519d
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 19:04:07 2016 -0400

    loolwsd: avoid superflous noise where possible
    
    Change-Id: Idb9180d81e471da965152175aa3c327b83613ab1
    Reviewed-on: https://gerrit.libreoffice.org/30206
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 20b98906bd405d6b90e55c091366edad7bf69066)

diff --git a/loolwsd/LOOLForKit.cpp b/loolwsd/LOOLForKit.cpp
index c23141b..9272933 100644
--- a/loolwsd/LOOLForKit.cpp
+++ b/loolwsd/LOOLForKit.cpp
@@ -215,10 +215,14 @@ static int createLibreOfficeKit(const std::string& childRoot,
 
         if (std::getenv("SLEEPKITFORDEBUGGER"))
         {
-            std::cerr << "Sleeping " << std::getenv("SLEEPKITFORDEBUGGER")
-                      << " seconds to give you time to attach debugger to process "
-                      << Process::id() << std::endl;
-            Thread::sleep(std::stoul(std::getenv("SLEEPKITFORDEBUGGER")) * 1000);
+            const auto delaySecs = std::stoul(std::getenv("SLEEPKITFORDEBUGGER"));
+            if (delaySecs > 0)
+            {
+                std::cerr << "Sleeping " << delaySecs
+                          << " seconds to give you time to attach debugger to process "
+                          << Process::id() << std::endl;
+                Thread::sleep(delaySecs * 1000);
+            }
         }
 
         lokit_main(childRoot, sysTemplate, loTemplate, loSubPath, NoCapsForKit, queryVersion, DisplayVersion);
@@ -258,10 +262,14 @@ int main(int argc, char** argv)
 
     if (std::getenv("SLEEPFORDEBUGGER"))
     {
-        std::cerr << "Sleeping " << std::getenv("SLEEPFORDEBUGGER")
-                  << " seconds to give you time to attach debugger to process "
-                  << Process::id() << std::endl;
-        Thread::sleep(std::stoul(std::getenv("SLEEPFORDEBUGGER")) * 1000);
+        const auto delaySecs = std::stoul(std::getenv("SLEEPFORDEBUGGER"));
+        if (delaySecs > 0)
+        {
+            std::cerr << "Sleeping " << delaySecs
+                      << " seconds to give you time to attach debugger to process "
+                      << Process::id() << std::endl;
+            Thread::sleep(delaySecs * 1000);
+        }
     }
 
     // Initialization
commit 19c0a5489dc1a0a1cee9f325dbbab5a0244a4d2b
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 19:02:01 2016 -0400

    loolwsd: kill assertIsNotLocked
    
    It was intended to assert that the *same* thread
    hadn't locked, not any. As it stands, it's problematic
    and was decided to let go.
    
    Change-Id: Iddb76f0edd62b7cdca062c2aa924b08e3d7952ef
    Reviewed-on: https://gerrit.libreoffice.org/30205
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 47ed5e40dd743ded1bbe2ec66f4aa1e0885b9e5c)

diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index ca10d50..e64dfff 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -525,7 +525,6 @@ size_t DocumentBroker::removeSession(const std::string& id)
 
 void DocumentBroker::alertAllUsersOfDocument(const std::string& cmd, const std::string& kind)
 {
-    Util::assertIsNotLocked(_mutex);
     std::lock_guard<std::mutex> lock(_mutex);
 
     std::stringstream ss;
diff --git a/loolwsd/Util.hpp b/loolwsd/Util.hpp
index 21ef610..40f0793 100644
--- a/loolwsd/Util.hpp
+++ b/loolwsd/Util.hpp
@@ -106,13 +106,6 @@ namespace Util
         assert(!mtx.try_lock());
     }
 
-    inline
-    void assertIsNotLocked(std::mutex& mtx)
-    {
-        assert(mtx.try_lock());
-        mtx.unlock();
-    }
-
     /// Safely remove a file or directory.
     /// Supresses exception when the file is already removed.
     /// This can happen when there is a race (unavoidable) or when
commit dbf16ab1b762410d521e7de800c986832f7f1766
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 13:41:44 2016 -0400

    loolwsd: Unit-Prefork threading fixes and cleanup
    
    newChild is not thread safe and accessing _childSockets
    must be accessed under the lock. This fixes
    a segfault and merges getMemory with newChild.
    
    Change-Id: I523c3c31d465118f263f7cb09d84105946e19720
    Reviewed-on: https://gerrit.libreoffice.org/30204
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 0279fca436c219cb1ec3dd9eeea0d150dca2ac37)

diff --git a/loolwsd/test/UnitPrefork.cpp b/loolwsd/test/UnitPrefork.cpp
index b4213bd..3407e6b 100644
--- a/loolwsd/test/UnitPrefork.cpp
+++ b/loolwsd/test/UnitPrefork.cpp
@@ -62,14 +62,14 @@ public:
     virtual bool filterChildMessage(const std::vector<char>& payload) override
     {
         const std::string memory = LOOLProtocol::getFirstLine(payload);
-        if (!memory.compare(0,6,"error:"))
+        Poco::StringTokenizer tokens(memory, " ");
+        if (tokens[0] == "error:")
         {
             _failure = memory;
         }
         else
         {
             Log::info("Got memory stats [" + memory + "].");
-            Poco::StringTokenizer tokens(memory, " ");
             assert(tokens.count() == 2);
             _childPSS = atoi(tokens[0].c_str());
             _childDirty = atoi(tokens[1].c_str());
@@ -81,69 +81,53 @@ public:
         return true;
     }
 
-    bool getMemory(const std::shared_ptr<Poco::Net::WebSocket> &socket,
-                   size_t &childPSS, size_t &childDirty)
+    virtual void newChild(const std::shared_ptr<Poco::Net::WebSocket> &socket) override
     {
         std::unique_lock<std::mutex> lock(_mutex);
 
-        /// Fetch memory usage data from the last process ...
-        socket->sendFrame("unit-memdump: \n", sizeof("unit-memdump: \n"));
-
-        if (_cv.wait_for(lock, std::chrono::milliseconds(5 * 1000)) == std::cv_status::timeout)
-        {
-            _failure = "Timed out waiting for child to respond to unit-memdump.";
-            std::cerr << _failure << std::endl;
-            return false;
-        }
-
-        childPSS = _childPSS;
-        childDirty = _childDirty;
-        _childPSS = 0;
-        _childDirty = 0;
-        return true;
-    }
-
-    virtual void newChild(const std::shared_ptr<Poco::Net::WebSocket> &socket) override
-    {
         _childSockets.push_back(socket);
         if (_childSockets.size() >= NumToPrefork)
         {
             Poco::Timestamp::TimeDiff elapsed = _startTime.elapsed();
 
-            auto totalTime = (1000. * elapsed)/Poco::Timestamp::resolution();
+            const auto totalTime = (1000. * elapsed)/Poco::Timestamp::resolution();
             Log::info() << "Launched " << _childSockets.size() << " in "
                         << totalTime << Log::end;
             size_t totalPSSKb = 0;
             size_t totalDirtyKb = 0;
 
-            auto socketsCopy = _childSockets;
-
             // Skip the last one as it's not completely initialized yet.
-            for (size_t i = 0; i < socketsCopy.size() - 1; ++i)
+            for (size_t i = 0; i < _childSockets.size() - 1; ++i)
             {
-                Log::info() << "Getting memory of child #" << i + 1 << " of " << socketsCopy.size() << Log::end;
-                size_t childPSSKb = 0, childDirtyKb = 0;
-                if (!getMemory(socketsCopy[i], childPSSKb, childDirtyKb))
+                Log::info() << "Getting memory of child #" << i + 1 << " of " << _childSockets.size() << Log::end;
+
+                _childSockets[i]->sendFrame("unit-memdump: \n", sizeof("unit-memdump: \n"));
+                if (_cv.wait_for(lock, std::chrono::milliseconds(5 * 1000)) == std::cv_status::timeout)
                 {
+                    _failure = "Timed out waiting for child to respond to unit-memdump.";
+                    std::cerr << _failure << std::endl;
                     exitTest(TestResult::TEST_FAILED);
                     return;
                 }
-                std::cerr << "child # " << i + 1 << " pss: " << childPSSKb << " dirty: " << childDirtyKb << std::endl;
-                totalPSSKb += childPSSKb;
-                totalDirtyKb += childDirtyKb;
+
+                std::cerr << "child # " << i + 1 << " pss: " << _childPSS << " (totalPSS: " << (totalPSSKb + _childPSS)
+                          << "), dirty: " << _childDirty << " (totalDirty: " << (totalDirtyKb + _childDirty) << std::endl;
+                totalPSSKb += _childPSS;
+                _childPSS = 0;
+                totalDirtyKb += _childDirty;
+                _childDirty = 0;
             }
 
             std::cerr << "Memory use total   " << totalPSSKb << "k shared "
                         << totalDirtyKb << "k dirty" << std::endl;
 
-            totalPSSKb /= socketsCopy.size();
-            totalDirtyKb /= socketsCopy.size();
+            totalPSSKb /= _childSockets.size();
+            totalDirtyKb /= _childSockets.size();
             std::cerr << "Memory use average " << totalPSSKb << "k shared "
                         << totalDirtyKb << "k dirty" << std::endl;
 
             std::cerr << "Launch time total   " << totalTime << " ms" << std::endl;
-            totalTime /= socketsCopy.size();
-            std::cerr << "Launch time average " << totalTime << " ms" << std::endl;
+            std::cerr << "Launch time average " << (totalTime / _childSockets.size()) << " ms" << std::endl;
 
             if (!_failure.empty())
             {
@@ -159,7 +143,8 @@ public:
     }
 };
 
-namespace {
+namespace
+{
     std::vector<int> pids;
 
     const char *startsWith(const char *line, const char *tag)
commit 37ccfd834d08638921bc60e5d4ea5a1d27701de2
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 11:42:57 2016 -0400

    loolwsd: cleanup dead children before balancing
    
    Change-Id: I782080bb83973a795e2c967d91b152095678a93e
    Reviewed-on: https://gerrit.libreoffice.org/30203
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 785fd972f4ffb97b0d99b5cbe3d65e9e3f5ee7e2)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index c4f5368..a526829 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -228,6 +228,25 @@ static void forkChildren(const int number)
     }
 }
 
+/// Cleans up dead children.
+/// Returns true if removed at least one.
+static bool cleanupChildren()
+{
+    bool removed = false;
+    for (int i = NewChildren.size() - 1; i >= 0; --i)
+    {
+        if (!NewChildren[i]->isAlive())
+        {
+            Log::warn() << "Removing unused dead child [" << NewChildren[i]->getPid()
+                         << "]." << Log::end;
+            NewChildren.erase(NewChildren.begin() + i);
+            removed = true;
+        }
+    }
+
+    return removed;
+}
+
 /// Called on startup only.
 static void preForkChildren()
 {
@@ -240,6 +259,8 @@ static void preForkChildren()
     forkChildren(numPreSpawn);
 }
 
+/// Proatively spawn children processes
+/// to load documents with alacrity.
 static void prespawnChildren()
 {
     std::unique_lock<std::mutex> lock(NewChildrenMutex, std::defer_lock);
@@ -250,19 +271,7 @@ static void prespawnChildren()
     }
 
     // Do the cleanup first.
-    bool rebalance = false;
-    for (int i = NewChildren.size() - 1; i >= 0; --i)
-    {
-        if (!NewChildren[i]->isAlive())
-        {
-            Log::warn() << "Removing unused dead child [" << NewChildren[i]->getPid()
-                         << "]." << Log::end;
-            NewChildren.erase(NewChildren.begin() + i);
-
-            // Rebalance after cleanup.
-            rebalance = true;
-        }
-    }
+    const bool rebalance = cleanupChildren();
 
     int balance = LOOLWSD::NumPreSpawnedChildren;
     balance -= NewChildren.size();
@@ -303,6 +312,9 @@ static std::shared_ptr<ChildProcess> getNewChild()
     const auto startTime = chrono::steady_clock::now();
     do
     {
+        // Do the cleanup first.
+        cleanupChildren();
+
         const int available = NewChildren.size();
         int balance = LOOLWSD::NumPreSpawnedChildren;
         if (available == 0)
commit ff8573e437cf3fe27b93cd61433d8bb2b1efc4c4
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 11:41:54 2016 -0400

    loolwsd: don't do in catch what might have thrown in try
    
    Change-Id: I36d039f573529545c878d30e25f3884e2b4afeb2
    Reviewed-on: https://gerrit.libreoffice.org/30202
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit a69a67e81b367e36d80f0416c135d7bc4a4738bf)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 65a4153..c4f5368 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -206,11 +206,9 @@ void shutdownLimitReached(WebSocket& ws)
         }
         while (retries > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
     }
-    catch (const Exception&)
+    catch (const std::exception& ex)
     {
-        // FIXME: handle exceptions thrown from here ? ...
-        ws.sendFrame(error.data(), error.size());
-        ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
+        Log::error("Error while shuting down socket on reaching limit: " + std::string(ex.what()));
     }
 }
 
commit c6c8e29fb2d017e256ba3adc49dd1b17038e8bc4
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 11:35:22 2016 -0400

    loolwsd: cleanup testBarren
    
    Change-Id: I5affeb7938e3b1373043e86799c991ef84d9b191
    Reviewed-on: https://gerrit.libreoffice.org/30201
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 166b14a83a0d59b033152ac7e7696b07412acf22)

diff --git a/loolwsd/test/httpcrashtest.cpp b/loolwsd/test/httpcrashtest.cpp
index ab8f018..ecaf4af 100644
--- a/loolwsd/test/httpcrashtest.cpp
+++ b/loolwsd/test/httpcrashtest.cpp
@@ -119,6 +119,7 @@ void HTTPCrashTest::testNoExtraLoolKitsLeft()
 void HTTPCrashTest::testBarren()
 {
     // Kill all kit processes and try loading a document.
+    const auto testname = "barren ";
     try
     {
         killLoKitProcesses();
@@ -126,35 +127,11 @@ void HTTPCrashTest::testBarren()
         std::cerr << "Loading after kill." << std::endl;
 
         // Load a document and get its status.
-        std::string documentPath, documentURL;
-        getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+        auto socket = loadDocAndGetSocket("hello.odt", _uri, testname);
 
-        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
-        auto socket = connectLOKit(_uri, request, _response);
+        sendTextFrame(socket, "status", testname);
+        assertResponseString(socket, "status:", testname);
 
-        // First load should fail.
-        sendTextFrame(socket, "load url=" + documentURL);
-        SocketProcessor("Barren ", socket, [&](const std::string& msg)
-                {
-                    const std::string prefix = "status: ";
-                    if (msg.find(prefix) == 0)
-                    {
-                        const auto status = msg.substr(prefix.length());
-                        CPPUNIT_ASSERT_EQUAL(std::string("type=text parts=1 current=0 width=12808 height=16408 viewid=0"), status);
-                        return false;
-                    }
-                    else if (msg.find("Service") == 0)
-                    {
-                        // Service unavailable. Try again.
-                        auto socket2 = loadDocAndGetSocket(_uri, documentURL);
-                        sendTextFrame(socket2, "status");
-                        const auto status = getResponseString(socket2, "status");
-                        CPPUNIT_ASSERT_EQUAL(std::string("type=text parts=1 current=0 width=12808 height=16408"), status);
-                        return false;
-                    }
-
-                    return true;
-                });
     }
     catch (const Poco::Exception& exc)
     {
commit bd457be1ce8384429a43bd3e3092721cbd2f9d7c
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 11:09:18 2016 -0400

    loolwsd: use the container to track number of DocumentBrokers
    
    Change-Id: Ic2d88eb6265365f8ffc99c9117a2a4383018e519
    Reviewed-on: https://gerrit.libreoffice.org/30200
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 2644b39d5a686108bc0af3fc87615ebb298feaa0)

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index ef88a77..65a4153 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -176,19 +176,6 @@ static int careerSpanSeconds = 0;
 
 namespace {
 
-static void logNumDocBrokers(int lineNo)
-{
-    int size = 0;
-    int nonEmpty = 0;
-    for (auto& i : DocBrokers)
-    {
-        size++;
-        if (i.second->getPublicUri().toString() != "")
-            nonEmpty++;
-    }
-    Log::debug() << "line " << lineNo << ": NumDocBrokers=" << LOOLWSD::NumDocBrokers << " size: " << size << " of which non-empty: " << nonEmpty << Log::end;
-}
-
 static inline
 void shutdownLimitReached(WebSocket& ws)
 {
@@ -746,12 +733,9 @@ private:
 
 #if MAX_DOCUMENTS > 0
             std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
-            logNumDocBrokers(__LINE__);
-            if (++LOOLWSD::NumDocBrokers > MAX_DOCUMENTS)
+            if (DocBrokers.size() + 1 > MAX_DOCUMENTS)
             {
-                --LOOLWSD::NumDocBrokers;
                 Log::error("Maximum number of open documents reached.");
-                logNumDocBrokers(__LINE__);
                 shutdownLimitReached(*ws);
                 return;
             }
@@ -773,10 +757,6 @@ private:
                 // Remove.
                 std::unique_lock<std::mutex> lock(DocBrokersMutex);
                 DocBrokers.erase(docKey);
-#if MAX_DOCUMENTS > 0
-                --LOOLWSD::NumDocBrokers;
-                logNumDocBrokers(__LINE__);
-#endif
             }
 
             throw WebSocketErrorMessageException(SERVICE_UNAVAILABLE_INTERNAL_ERROR);
@@ -887,10 +867,6 @@ private:
                 std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
                 Log::debug("Removing DocumentBroker for docKey [" + docKey + "].");
                 DocBrokers.erase(docKey);
-#if MAX_DOCUMENTS > 0
-                --LOOLWSD::NumDocBrokers;
-                logNumDocBrokers(__LINE__);
-#endif
             }
 
             LOOLWSD::dumpEventTrace(docBroker->getJailId(), id, "EndSession: " + uri);
@@ -1367,7 +1343,6 @@ Util::RuntimeConstant<bool> LOOLWSD::SSLTermination;
 static std::string UnitTestLibrary;
 
 unsigned int LOOLWSD::NumPreSpawnedChildren = 0;
-std::atomic<unsigned> LOOLWSD::NumDocBrokers;
 std::atomic<unsigned> LOOLWSD::NumConnections;
 std::unique_ptr<TraceFileWriter> LOOLWSD::TraceDumper;
 
@@ -1543,7 +1518,6 @@ void LOOLWSD::initialize(Application& self)
     setenv ("SAL_DISABLE_OPENCL", "true", 1);
 
     // In Trial Versions we might want to set some limits.
-    LOOLWSD::NumDocBrokers = 0;
     LOOLWSD::NumConnections = 0;
     Log::info() << "Open Documents Limit: " << (MAX_DOCUMENTS > 0 ?
                                                 std::to_string(MAX_DOCUMENTS) :
diff --git a/loolwsd/LOOLWSD.hpp b/loolwsd/LOOLWSD.hpp
index 540dd8a..d1c0301 100644
--- a/loolwsd/LOOLWSD.hpp
+++ b/loolwsd/LOOLWSD.hpp
@@ -48,7 +48,6 @@ public:
     static std::string ServerName;
     static std::string FileServerRoot;
     static std::string LOKitVersion;
-    static std::atomic<unsigned> NumDocBrokers;
     static std::atomic<unsigned> NumConnections;
     static std::unique_ptr<TraceFileWriter> TraceDumper;
 
commit 453f0de2322a5ea35be741690fe50bd2e42417eb
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 10:25:57 2016 -0400

    loolwsd: move admin updates into DocumentBroker
    
    Change-Id: Ic35198a7e4457a775fac25954279501e37b94b42
    Reviewed-on: https://gerrit.libreoffice.org/30199
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 6c0be6d90dbb0808f667467bd67dc6b7582bcda0)

diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 61c1fc5..ca10d50 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -17,6 +17,7 @@
 #include <Poco/SHA1Engine.h>
 #include <Poco/StringTokenizer.h>
 
+#include "Admin.hpp"
 #include "ClientSession.hpp"
 #include "Exceptions.hpp"
 #include "LOOLProtocol.hpp"
@@ -162,6 +163,15 @@ DocumentBroker::DocumentBroker(const Poco::URI& uriPublic,
     Log::info("DocumentBroker [" + _uriPublic.toString() + "] created. DocKey: [" + _docKey + "]");
 }
 
+DocumentBroker::~DocumentBroker()
+{
+    Admin::instance().rmDoc(_docKey);
+
+    Log::info() << "~DocumentBroker [" << _uriPublic.toString()
+                << "] destroyed with " << getSessionsCount()
+                << " sessions left." << Log::end;
+}
+
 bool DocumentBroker::load(const std::string& sessionId, const std::string& jailId)
 {
     {
@@ -464,6 +474,9 @@ size_t DocumentBroker::addSession(std::shared_ptr<ClientSession>& session)
         throw;
     }
 
+    // Tell the admin console about this new doc
+    Admin::instance().addDoc(_docKey, getPid(), getFilename(), id);
+
     auto prisonerSession = std::make_shared<PrisonerSession>(id, shared_from_this());
 
     // Connect the prison session to the client.
@@ -504,6 +517,9 @@ size_t DocumentBroker::removeSession(const std::string& id)
         _childProcess->sendTextFrame(msg);
     }
 
+    // Lets remove this session from the admin console too
+    Admin::instance().rmDoc(_docKey, id);
+
     return _sessions.size();
 }
 
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 9472fb9..170613c 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -173,12 +173,7 @@ public:
                    const std::string& childRoot,
                    const std::shared_ptr<ChildProcess>& childProcess);
 
-    ~DocumentBroker()
-    {
-        Log::info() << "~DocumentBroker [" << _uriPublic.toString()
-                    << "] destroyed with " << getSessionsCount()
-                    << " sessions left." << Log::end;
-    }
+    ~DocumentBroker();
 
     /// Loads a document from the public URI into the jail.
     bool load(const std::string& sessionId, const std::string& jailId);
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index e0723e6..ef88a77 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -831,9 +831,6 @@ private:
                 ws->sendFrame(status.data(), (int) status.size());
             }
 
-            // Tell the admin console about this new doc
-            Admin::instance().addDoc(docKey, docBroker->getPid(), docBroker->getFilename(), id);
-
             LOOLWSD::dumpEventTrace(docBroker->getJailId(), id, "NewSession: " + uri);
 
             // Let messages flow.
@@ -883,9 +880,6 @@ private:
                     sessionsCount = docBroker->removeSession(id);
                     Log::trace(docKey + ", ws_sessions--: " + std::to_string(sessionsCount));
                 }
-
-                // Lets remove this session from the admin console too
-                Admin::instance().rmDoc(docKey, id);
             }
 
             if (sessionsCount == 0)
@@ -897,8 +891,6 @@ private:
                 --LOOLWSD::NumDocBrokers;
                 logNumDocBrokers(__LINE__);
 #endif
-                Log::info("Removing complete doc [" + docKey + "] from Admin.");
-                Admin::instance().rmDoc(docKey);
             }
 
             LOOLWSD::dumpEventTrace(docBroker->getJailId(), id, "EndSession: " + uri);
commit 444baabd154f4d90993f7a82a79b8465a723cac3
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Oct 22 10:24:27 2016 -0400

    loolwsd: admin cleanups
    
    Change-Id: I1ed9cdfe1c78665722ed517218ce45055c661b69
    Reviewed-on: https://gerrit.libreoffice.org/30198
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 2e9b3ae247879d199af80e52ef250bfe20038078)

diff --git a/loolwsd/Admin.cpp b/loolwsd/Admin.cpp
index 3ef735f..04d479b 100644
--- a/loolwsd/Admin.cpp
+++ b/loolwsd/Admin.cpp
@@ -317,6 +317,7 @@ void Admin::rmDoc(const std::string& docKey, const std::string& sessionId)
 void Admin::rmDoc(const std::string& docKey)
 {
     std::unique_lock<std::mutex> modelLock(_modelMutex);
+    Log::info("Removing complete doc [" + docKey + "] from Admin.");
     _model.removeDocument(docKey);
 }
 
diff --git a/loolwsd/AdminModel.cpp b/loolwsd/AdminModel.cpp
index a025400..45afd2a 100644
--- a/loolwsd/AdminModel.cpp
+++ b/loolwsd/AdminModel.cpp
@@ -20,6 +20,7 @@
 #include <Poco/StringTokenizer.h>
 #include <Poco/URI.h>
 
+#include "LOOLProtocol.hpp"
 #include "Log.hpp"
 #include "Unit.hpp"
 #include "Util.hpp"
@@ -56,64 +57,70 @@ int Document::expireView(const std::string& sessionId)
 
 bool Subscriber::notify(const std::string& message)
 {
-    StringTokenizer tokens(message, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
-    if (_subscriptions.find(tokens[0]) == _subscriptions.end())
+    if (_subscriptions.find(LOOLProtocol::getFirstToken(message)) == _subscriptions.end())
+    {
+        // No subscribers for the given message.
         return true;
+    }
 
     auto webSocket = _ws.lock();
     if (webSocket)
     {
-        UnitWSD::get().onAdminNotifyMessage(message);
-        webSocket->sendFrame(message.data(), message.length());
-        return true;
-    }
-    else
-    {
-        return false;
+        try
+        {
+            UnitWSD::get().onAdminNotifyMessage(message);
+            webSocket->sendFrame(message.data(), message.length());
+            return true;
+        }
+        catch (const std::exception& ex)
+        {
+            Log::error() << "Failed to notify Admin subscriber with message ["
+                         << message << "] due to [" << ex.what() << "]." << Log::end;
+        }
     }
+
+    return false;
 }
 
-bool  Subscriber::subscribe(const std::string& command)
+bool Subscriber::subscribe(const std::string& command)
 {
     auto ret = _subscriptions.insert(command);
     return ret.second;
 }
 
-void  Subscriber::unsubscribe(const std::string& command)
+void Subscriber::unsubscribe(const std::string& command)
 {
     _subscriptions.erase(command);
 }
 
 std::string AdminModel::query(const std::string& command)
 {
-    StringTokenizer tokens(command, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
-    if (tokens[0] == "documents")
+    const auto token = LOOLProtocol::getFirstToken(command);
+    if (token == "documents")
     {
         return getDocuments();
     }
-    else if (tokens[0] == "active_users_count")
+    else if (token == "active_users_count")
     {
         return std::to_string(getTotalActiveViews());
     }
-    else if (tokens[0] == "active_docs_count")
+    else if (token == "active_docs_count")
     {
         return std::to_string(_documents.size());
     }
-    else if (tokens[0] == "mem_stats")
+    else if (token == "mem_stats")
     {
         return getMemStats();
     }
-    else if (tokens[0] == "mem_stats_size")
+    else if (token == "mem_stats_size")
     {
         return std::to_string(_memStatsSize);
     }
-    else if (tokens[0] == "cpu_stats")
+    else if (token == "cpu_stats")
     {
         return getCpuStats();
     }
-    else if (tokens[0] == "cpu_stats_size")
+    else if (token == "cpu_stats_size")
     {
         return std::to_string(_cpuStatsSize);
     }
@@ -127,10 +134,14 @@ unsigned AdminModel::getTotalMemoryUsage()
     unsigned totalMem = 0;
     for (auto& it: _documents)
     {
-        if (it.second.isExpired())
-            continue;
-
-        totalMem += Util::getMemoryUsage(it.second.getPid());
+        if (!it.second.isExpired())
+        {
+            const int mem = Util::getMemoryUsage(it.second.getPid());
+            if (mem > 0)
+            {
+                totalMem += mem;
+            }
+        }
     }
 
     return totalMem;
@@ -148,19 +159,19 @@ void AdminModel::subscribe(int nSessionId, std::shared_ptr<Poco::Net::WebSocket>
 void AdminModel::subscribe(int nSessionId, const std::string& command)
 {
     auto subscriber = _subscribers.find(nSessionId);
-    if (subscriber == _subscribers.end() )
-        return;
-
-    subscriber->second.subscribe(command);
+    if (subscriber != _subscribers.end())
+    {
+        subscriber->second.subscribe(command);
+    }
 }
 
 void AdminModel::unsubscribe(int nSessionId, const std::string& command)
 {
     auto subscriber = _subscribers.find(nSessionId);
-    if (subscriber == _subscribers.end())
-        return;
-
-    subscriber->second.unsubscribe(command);
+    if (subscriber != _subscribers.end())
+    {
+        subscriber->second.unsubscribe(command);
+    }
 }
 
 void AdminModel::addMemStats(unsigned memUsage)
@@ -225,6 +236,7 @@ void AdminModel::setMemStatsSize(unsigned size)
 
 void AdminModel::notify(const std::string& message)
 {
+    Log::debug("Message to admin console: " + message);
     auto it = std::begin(_subscribers);
     while (it != std::end(_subscribers))
     {
@@ -234,7 +246,7 @@ void AdminModel::notify(const std::string& message)
         }
         else
         {
-            it++;
+            ++it;
         }
     }
 }
@@ -246,7 +258,7 @@ void AdminModel::addDocument(const std::string& docKey, Poco::Process::PID pid,
     ret.first->second.addView(sessionId);
 
     // Notify the subscribers
-    unsigned memUsage = Util::getMemoryUsage(pid);
+    const unsigned memUsage = Util::getMemoryUsage(pid);
     std::ostringstream oss;
     std::string encodedFilename;
     Poco::URI::encode(filename, " ", encodedFilename);
@@ -255,7 +267,6 @@ void AdminModel::addDocument(const std::string& docKey, Poco::Process::PID pid,
         << encodedFilename << " "
         << sessionId << " "
         << std::to_string(memUsage);
-    Log::info("Message to admin console: " + oss.str());
     notify(oss.str());
 }
 
@@ -269,7 +280,6 @@ void AdminModel::removeDocument(const std::string& docKey, const std::string& se
         oss << "rmdoc "
             << docIt->second.getPid() << " "
             << sessionId;
-        Log::info("Message to admin console: " + oss.str());
         notify(oss.str());
 
         // TODO: The idea is to only expire the document and keep the history
@@ -294,7 +304,6 @@ void AdminModel::removeDocument(const std::string& docKey)
             oss << "rmdoc "
                 << docIt->second.getPid() << " "
                 << pair.first;
-            Log::info("Message to admin console: " + oss.str());
             notify(oss.str());
         }
 
@@ -304,24 +313,24 @@ void AdminModel::removeDocument(const std::string& docKey)
 
 std::string AdminModel::getMemStats()
 {
-    std::string response;
+    std::ostringstream oss;
     for (auto& i: _memStats)
     {
-        response += std::to_string(i) + ",";
+        oss << i << ',';
     }
 
-    return response;
+    return oss.str();
 }
 
 std::string AdminModel::getCpuStats()
 {
-    std::string response;
+    std::ostringstream oss;
     for (auto& i: _cpuStats)
     {
-        response += std::to_string(i) + ",";
+        oss << i << ',';
     }
 
-    return response;
+    return oss.str();
 }
 
 unsigned AdminModel::getTotalActiveViews()
commit 67ddd7daa1b3af2e26811fb81c273a3292180a1c
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Fri Oct 21 22:10:43 2016 -0400

    loolwsd: two new tests for tile rendering
    
    Tiles are checked for correct count and whether or
    not they are serviced from cache or rendered.
    
    One test is done with a single view and another
    with four views.
    
    Change-Id: Ieadeef8547097d4a53fb1ce42c56c33ec16d849f
    Reviewed-on: https://gerrit.libreoffice.org/30197
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 05a967e82731fb49edceb3abcb426b8f14d36d3b)

diff --git a/loolwsd/test/TileCacheTests.cpp b/loolwsd/test/TileCacheTests.cpp
index f988db4..33bf2b1 100644
--- a/loolwsd/test/TileCacheTests.cpp
+++ b/loolwsd/test/TileCacheTests.cpp
@@ -58,6 +58,8 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST(testImpressTiles);
     CPPUNIT_TEST(testClientPartImpress);
     CPPUNIT_TEST(testClientPartCalc);
+    CPPUNIT_TEST(testTilesRenderedJustOnce);
+    CPPUNIT_TEST(testTilesRenderedJustOnceMultiClient);
 #if ENABLE_DEBUG
     CPPUNIT_TEST(testSimultaneousTilesRenderedJustOnce);
 #endif
@@ -79,6 +81,8 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture
     void testImpressTiles();
     void testClientPartImpress();
     void testClientPartCalc();
+    void testTilesRenderedJustOnce();
+    void testTilesRenderedJustOnceMultiClient();
     void testSimultaneousTilesRenderedJustOnce();
     void testLoad12ods();
     void testTileInvalidateWriter();
@@ -377,6 +381,162 @@ void TileCacheTests::testClientPartCalc()
     }
 }
 
+void TileCacheTests::testTilesRenderedJustOnce()
+{
+    const auto testname = "tilesRenderdJustOnce ";
+
+    auto socket = *loadDocAndGetSocket("empty.odt", _uri, testname);
+
+    assertResponseString(socket, "statechanged: .uno:AcceptTrackedChange=", testname);
+
+    for (int i = 0; i < 10; ++i)
+    {
+        // Get initial rendercount.
+        sendTextFrame(socket, "ping", testname);
+        const auto ping1 = assertResponseString(socket, "pong", testname);
+        int renderCount1 = 0;
+        CPPUNIT_ASSERT(LOOLProtocol::getTokenIntegerFromMessage(ping1, "rendercount", renderCount1));
+        CPPUNIT_ASSERT_EQUAL(i * 3, renderCount1);
+
+        // Modify.
+        sendText(socket, "a", testname);
+        assertResponseString(socket, "invalidatetiles:", testname);
+
+        // Get 3 tiles.
+        sendTextFrame(socket, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname);
+        assertResponseString(socket, "tile:", testname);
+        assertResponseString(socket, "tile:", testname);
+        assertResponseString(socket, "tile:", testname);
+
+        // Get new rendercount.
+        sendTextFrame(socket, "ping", testname);
+        const auto ping2 = assertResponseString(socket, "pong", testname);
+        int renderCount2 = 0;
+        CPPUNIT_ASSERT(LOOLProtocol::getTokenIntegerFromMessage(ping2, "rendercount", renderCount2));
+        CPPUNIT_ASSERT_EQUAL((i+1) * 3, renderCount2);
+
+        // Get same 3 tiles.
+        sendTextFrame(socket, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname);
+        const auto tile1 = assertResponseString(socket, "tile:", testname);
+        std::string renderId1;
+        LOOLProtocol::getTokenStringFromMessage(tile1, "renderid", renderId1);
+        CPPUNIT_ASSERT_EQUAL(std::string("cached"), renderId1);
+
+        const auto tile2 = assertResponseString(socket, "tile:", testname);
+        std::string renderId2;
+        LOOLProtocol::getTokenStringFromMessage(tile2, "renderid", renderId2);
+        CPPUNIT_ASSERT_EQUAL(std::string("cached"), renderId2);
+
+        const auto tile3 = assertResponseString(socket, "tile:", testname);
+        std::string renderId3;
+        LOOLProtocol::getTokenStringFromMessage(tile3, "renderid", renderId3);
+        CPPUNIT_ASSERT_EQUAL(std::string("cached"), renderId3);
+
+        // Get new rendercount.
+        sendTextFrame(socket, "ping", testname);
+        const auto ping3 = assertResponseString(socket, "pong", testname);
+        int renderCount3 = 0;
+        CPPUNIT_ASSERT(LOOLProtocol::getTokenIntegerFromMessage(ping3, "rendercount", renderCount3));
+        CPPUNIT_ASSERT_EQUAL(renderCount2, renderCount3);
+    }
+}
+
+void TileCacheTests::testTilesRenderedJustOnceMultiClient()
+{
+    const std::string testname = "tilesRenderdJustOnceMultiClient";
+    const auto testname1 = testname + "-1 ";
+    const auto testname2 = testname + "-2 ";
+    const auto testname3 = testname + "-3 ";
+    const auto testname4 = testname + "-4 ";
+
+    std::string documentPath, documentURL;
+    getDocumentPathAndURL("hello.odt", documentPath, documentURL);
+
+    std::cerr << "Connecting first client." << std::endl;
+    auto socket = *loadDocAndGetSocket(_uri, documentURL, testname1);
+    std::cerr << "Connecting second client." << std::endl;
+    auto socket2 = *loadDocAndGetSocket(_uri, documentURL, testname2);
+    std::cerr << "Connecting third client." << std::endl;
+    auto socket3 = *loadDocAndGetSocket(_uri, documentURL, testname3);
+    std::cerr << "Connecting fourth client." << std::endl;
+    auto socket4 = *loadDocAndGetSocket(_uri, documentURL, "tilesRenderdJustOnce-4 ");
+
+    for (int i = 0; i < 10; ++i)
+    {
+        // No tiles at this point.
+        assertNotInResponse(socket, "tile:", testname1);
+        assertNotInResponse(socket2, "tile:", testname2);
+        assertNotInResponse(socket3, "tile:", testname3);
+        assertNotInResponse(socket4, "tile:", testname4);
+
+        // Get initial rendercount.
+        sendTextFrame(socket, "ping", testname1);
+        const auto ping1 = assertResponseString(socket, "pong", testname1);
+        int renderCount1 = 0;
+        CPPUNIT_ASSERT(LOOLProtocol::getTokenIntegerFromMessage(ping1, "rendercount", renderCount1));
+        CPPUNIT_ASSERT_EQUAL(i * 3, renderCount1);
+
+        // Modify.
+        sendText(socket, "a", testname1);
+        assertResponseString(socket, "invalidatetiles:", testname1);
+
+        // Get 3 tiles.
+        sendTextFrame(socket, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname1);
+        assertResponseString(socket, "tile:", testname1);
+        assertResponseString(socket, "tile:", testname1);
+        assertResponseString(socket, "tile:", testname1);
+
+        assertResponseString(socket2, "invalidatetiles:", testname2);
+        sendTextFrame(socket2, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname2);
+        assertResponseString(socket2, "tile:", testname2);
+        assertResponseString(socket2, "tile:", testname2);
+        assertResponseString(socket2, "tile:", testname2);
+
+        assertResponseString(socket3, "invalidatetiles:", testname3);
+        sendTextFrame(socket3, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname3);
+        assertResponseString(socket3, "tile:", testname3);
+        assertResponseString(socket3, "tile:", testname3);
+        assertResponseString(socket3, "tile:", testname3);
+
+        assertResponseString(socket4, "invalidatetiles:", testname4);
+        sendTextFrame(socket4, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname4);
+        assertResponseString(socket4, "tile:", testname4);
+        assertResponseString(socket4, "tile:", testname4);
+        assertResponseString(socket4, "tile:", testname4);
+
+        // Get new rendercount.
+        sendTextFrame(socket, "ping", testname1);
+        const auto ping2 = assertResponseString(socket, "pong", testname1);
+        int renderCount2 = 0;
+        CPPUNIT_ASSERT(LOOLProtocol::getTokenIntegerFromMessage(ping2, "rendercount", renderCount2));
+        CPPUNIT_ASSERT_EQUAL((i+1) * 3, renderCount2);
+
+        // Get same 3 tiles.
+        sendTextFrame(socket, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840", testname1);
+        const auto tile1 = assertResponseString(socket, "tile:", testname1);
+        std::string renderId1;
+        LOOLProtocol::getTokenStringFromMessage(tile1, "renderid", renderId1);
+        CPPUNIT_ASSERT_EQUAL(std::string("cached"), renderId1);
+
+        const auto tile2 = assertResponseString(socket, "tile:", testname1);
+        std::string renderId2;
+        LOOLProtocol::getTokenStringFromMessage(tile2, "renderid", renderId2);
+        CPPUNIT_ASSERT_EQUAL(std::string("cached"), renderId2);
+
+        const auto tile3 = assertResponseString(socket, "tile:", testname1);
+        std::string renderId3;
+        LOOLProtocol::getTokenStringFromMessage(tile3, "renderid", renderId3);
+        CPPUNIT_ASSERT_EQUAL(std::string("cached"), renderId3);
+
+        // Get new rendercount.
+        sendTextFrame(socket, "ping", testname1);
+        const auto ping3 = assertResponseString(socket, "pong", testname1);
+        int renderCount3 = 0;
+        CPPUNIT_ASSERT(LOOLProtocol::getTokenIntegerFromMessage(ping3, "rendercount", renderCount3));
+        CPPUNIT_ASSERT_EQUAL(renderCount2, renderCount3);
+    }
+}
+
 void TileCacheTests::testSimultaneousTilesRenderedJustOnce()
 {
     std::string documentPath, documentURL;
commit deb2ba42034c2193bf2b0bb9d46137f83858f7a1
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Fri Oct 21 22:07:29 2016 -0400

    loolwsd: correct parsing of renderid in testSimultaneousTilesRenderedJustOnce
    
    Change-Id: I7c3610c43e5506f3548006b8724afb21e771342c
    Reviewed-on: https://gerrit.libreoffice.org/30196
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 5c7c825e943ef235e2c04de2fbc39c397f1aaa0a)

diff --git a/loolwsd/test/TileCacheTests.cpp b/loolwsd/test/TileCacheTests.cpp
index 0162464..f988db4 100644
--- a/loolwsd/test/TileCacheTests.cpp
+++ b/loolwsd/test/TileCacheTests.cpp
@@ -401,9 +401,9 @@ void TileCacheTests::testSimultaneousTilesRenderedJustOnce()
     if (!response1.empty() && !response2.empty())
     {
         std::string renderId1;
-        LOOLProtocol::getTokenString(response1, "renderid", renderId1);
+        LOOLProtocol::getTokenStringFromMessage(response1, "renderid", renderId1);
         std::string renderId2;
-        LOOLProtocol::getTokenString(response2, "renderid", renderId2);
+        LOOLProtocol::getTokenStringFromMessage(response2, "renderid", renderId2);
 
         CPPUNIT_ASSERT(renderId1 == renderId2 ||
                        (renderId1 == "cached" && renderId2 != "cached") ||
commit 841702b2f61e9f0448a9c88477cfff78b8537307
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Fri Oct 21 22:06:42 2016 -0400

    loolwsd: cleanup testEachView
    
    Change-Id: I5f2b6bf0f437b27c536fee9d1020b616a4ea215f
    Reviewed-on: https://gerrit.libreoffice.org/30195
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>
    (cherry picked from commit 6486977ca7d712e303477b371f550123fd9bd589)

diff --git a/loolwsd/test/httpwstest.cpp b/loolwsd/test/httpwstest.cpp
index bcd8169..b5aefcc 100644
--- a/loolwsd/test/httpwstest.cpp
+++ b/loolwsd/test/httpwstest.cpp
@@ -1857,113 +1857,94 @@ void HTTPWSTest::testOptimalResize()
     }
 }
 
-void HTTPWSTest::testEachView(const std::string& doc, const std::string& type, const std::string& protocol, const std::string& protocolView, const std::string& testname)
+void HTTPWSTest::testEachView(const std::string& doc, const std::string& type,
+                              const std::string& protocol, const std::string& protocolView,
+                              const std::string& testname)
 {
-    int docPart = -1;
-    int docParts = 0;
-    int docHeight = 0;
-    int docWidth = 0;
-    int docViewId = -1;
-    int itView = 0;
-
-    // 0..N Views
-    std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
     const std::string view = testname + "view %d -> ";
     const std::string load = testname + "view %d, cannot load the document ";
     const std::string error = testname + "view %d, did not receive a %s message as expected";
 
-    // Load a document
-    std::string documentPath, documentURL, response, text;
-    getDocumentPathAndURL(doc, documentPath, documentURL);
-
-    auto socket = *loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView));
-
-    // Check document size
-    sendTextFrame(socket, "status", Poco::format(view, itView));
-    response = getResponseString(socket, "status:", Poco::format(view, itView));
-    CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, std::string("status:")), !response.empty());
-    parseDocSize(response.substr(7), type, docPart, docParts, docWidth, docHeight, docViewId);
-
-    // Send click message
-    Poco::format(text, "mouse type=%s x=%d y=%d count=1 buttons=1 modifier=0", std::string("buttondown"), docWidth/2, docHeight/6);
-    sendTextFrame(socket, text, Poco::format(view, itView));
-    text.clear();
-
-    Poco::format(text, "mouse type=%s x=%d y=%d count=1 buttons=1 modifier=0", std::string("buttonup"), docWidth/2, docHeight/6);
-    sendTextFrame(socket, text, Poco::format(view, itView));
-    response = getResponseString(socket, protocol, Poco::format(view, itView));
-    CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocol), !response.empty());
+    try
+    {
+        // Load a document
+        std::string documentPath, documentURL;
+        getDocumentPathAndURL(doc, documentPath, documentURL);
+
+        int itView = 0;
+        auto socket = *loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView));
+
+        // Check document size
+        sendTextFrame(socket, "status", Poco::format(view, itView));
+        auto response = getResponseString(socket, "status:", Poco::format(view, itView));
+        CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, std::string("status:")), !response.empty());
+        int docPart = -1;
+        int docParts = 0;
+        int docHeight = 0;
+        int docWidth = 0;
+        int docViewId = -1;
+        parseDocSize(response.substr(7), type, docPart, docParts, docWidth, docHeight, docViewId);
 
-    // Connect and load 0..N Views, where N=10
+        // Send click message
+        std::string text;
+        Poco::format(text, "mouse type=%s x=%d y=%d count=1 buttons=1 modifier=0", std::string("buttondown"), docWidth/2, docHeight/6);
+        sendTextFrame(socket, text, Poco::format(view, itView));
+        text.clear();
+
+        Poco::format(text, "mouse type=%s x=%d y=%d count=1 buttons=1 modifier=0", std::string("buttonup"), docWidth/2, docHeight/6);
+        sendTextFrame(socket, text, Poco::format(view, itView));
+        response = getResponseString(socket, protocol, Poco::format(view, itView));
+        CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocol), !response.empty());
+
+        // Connect and load 0..N Views, where N<=limit
+        std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
 #if MAX_DOCUMENTS > 0
-    const auto limit = std::min(10, MAX_DOCUMENTS - 1); // +1 connection above
+        const auto limit = std::min(5, MAX_DOCUMENTS - 1); // +1 connection above
 #else
-    constexpr auto limit = 10;
+        constexpr auto limit = 5;
 #endif
-    for (itView = 0; itView < limit; ++itView)
-    {
-        views.emplace_back(loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView)));
-    }
+        for (itView = 0; itView < limit; ++itView)
+        {
+            views.emplace_back(loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView)));
+        }
 
-    // main view should receive response each view
-    itView = 0;
-    for (auto socketView : views)
+        // main view should receive response each view
+        itView = 0;
+        for (auto socketView : views)
+        {
+            getResponseString(socket, protocolView, Poco::format(view, itView));
+            CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocolView), !response.empty());
+            ++itView;
+        }
+    }
+    catch (const Poco::Exception& exc)
     {
-        getResponseString(socket, protocolView, Poco::format(view, itView));
-        CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocolView), !response.empty());
-        ++itView;
+        CPPUNIT_FAIL(exc.displayText());
     }
+
+    testNoExtraLoolKitsLeft();
 }
 
 void HTTPWSTest::testInvalidateViewCursor()
 {
-    try
-    {
-        testEachView("viewcursor.odp", "presentation", "invalidatecursor:", "invalidateviewcursor:", "invalidateViewCursor ");
-    }
-    catch (const Poco::Exception& exc)
-    {
-        CPPUNIT_FAIL(exc.displayText());
-    }
+    testEachView("viewcursor.odp", "presentation", "invalidatecursor:", "invalidateviewcursor:", "invalidateViewCursor ");
 }
 
 void HTTPWSTest::testViewCursorVisible()
 {
-    try
-    {
-        testEachView("viewcursor.odp", "presentation", "cursorvisible:", "viewcursorvisible:", "viewCursorVisible ");
-
-    }
-    catch (const Poco::Exception& exc)
-    {
-        CPPUNIT_FAIL(exc.displayText());
-    }
+    testEachView("viewcursor.odp", "presentation", "cursorvisible:", "viewcursorvisible:", "viewCursorVisible ");
 }
 
 void HTTPWSTest::testCellViewCursor()
 {
-    try
-    {
-        testEachView("empty.ods", "spreadsheet", "cellcursor:", "cellviewcursor:", "cellViewCursor");
-    }
-    catch (const Poco::Exception& exc)
-    {
-        CPPUNIT_FAIL(exc.displayText());
-    }
+    testEachView("empty.ods", "spreadsheet", "cellcursor:", "cellviewcursor:", "cellViewCursor");
 }
 
 void HTTPWSTest::testGraphicViewSelection()
 {
-    try
-    {
-        testEachView("graphicviewselection.odp", "presentation", "graphicselection:", "graphicviewselection:", "graphicViewSelection-odp ");
-        testEachView("graphicviewselection.odt", "text", "graphicselection:", "graphicviewselection:", "graphicViewSelection-odt ");
-        testEachView("graphicviewselection.ods", "spreadsheet", "graphicselection:", "graphicviewselection:", "graphicViewSelection-ods ");
-    }
-    catch (const Poco::Exception& exc)
-    {
-        CPPUNIT_FAIL(exc.displayText());
-    }
+    testEachView("graphicviewselection.odp", "presentation", "graphicselection:", "graphicviewselection:", "graphicViewSelection-odp ");

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list