[Libreoffice-commits] online.git: 2 commits - Makefile.am test/helpers.hpp tools/Replay.hpp tools/Stress.cpp wsd/LOOLWSD.cpp wsd/LOOLWSD.hpp wsd/TraceFile.hpp
Jan Holesovsky
kendy at collabora.com
Wed Feb 8 07:41:11 UTC 2017
Makefile.am | 2
test/helpers.hpp | 5 +
tools/Replay.hpp | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
tools/Stress.cpp | 198 +------------------------------------------
wsd/LOOLWSD.cpp | 48 ++++++++++
wsd/LOOLWSD.hpp | 1
wsd/TraceFile.hpp | 6 +
7 files changed, 309 insertions(+), 196 deletions(-)
New commits:
commit 6dd581f63532c81375196b2e85e0dd244480d2b7
Author: Jan Holesovsky <kendy at collabora.com>
Date: Tue Feb 7 21:00:23 2017 +0100
fuzzer; Added --fuzz param that takes a looltrace file as input.
To perform one fuzzing iteration, use something like:
./loolwsd_fuzzer --config-file=loolwsd.xml --o:lo_template_path="/opt/libreoffice/instdir" --o:storage.filesystem[@allow]=true --fuzz=/tmp/looltrace
Change-Id: I27210d55a65f75e7d51e05c2f5f999acb758c4b1
diff --git a/Makefile.am b/Makefile.am
index e4a2af1..651057b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,7 +32,7 @@ AM_LDFLAGS = -pthread -Wl,-E
loolforkit_LDFLAGS = -pthread -Wl,-E,-rpath,/snap/loolwsd/current/usr/lib
loolmount_LDFLAGS = -pthread -Wl,-E,-rpath,/snap/loolwsd/current/usr/lib
-loolwsd_fuzzer_CPPFLAGS = -DKIT_IN_PROCESS=1 $(AM_CPPFLAGS)
+loolwsd_fuzzer_CPPFLAGS = -DKIT_IN_PROCESS=1 -DFUZZER=1 -DTDOC=\"$(abs_top_srcdir)/test/data\" $(AM_CPPFLAGS)
AM_ETAGSFLAGS = --c++-kinds=+p --fields=+iaS --extra=+q -R --totals=yes *
AM_CTAGSFLAGS = $(AM_ETAGSFLAGS)
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 5d7dd4e..12f91c0 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -121,6 +121,10 @@
#include <Kit.hpp>
#endif
+#ifdef FUZZER
+#include <tools/Replay.hpp>
+#endif
+
#include "common/SigUtil.hpp"
using namespace LOOLProtocol;
@@ -152,6 +156,7 @@ using Poco::ProcessHandle;
using Poco::StreamCopier;
using Poco::StringTokenizer;
using Poco::TemporaryFile;
+using Poco::Thread;
using Poco::ThreadPool;
using Poco::URI;
using Poco::Util::Application;
@@ -1697,6 +1702,9 @@ std::atomic<int> LOOLWSD::ForKitWritePipe(-1);
std::atomic<int> LOOLWSD::ForKitProcId(-1);
bool LOOLWSD::NoCapsForKit = false;
#endif
+#ifdef FUZZER
+std::string LOOLWSD::FuzzFileName = "";
+#endif
std::string LOOLWSD::Cache = LOOLWSD_CACHEDIR;
std::string LOOLWSD::SysTemplate;
std::string LOOLWSD::LoTemplate;
@@ -2049,7 +2057,7 @@ void LOOLWSD::defineOptions(OptionSet& optionSet)
" must not be " + std::to_string(MasterPortNumber) + ".")
.required(false)
.repeatable(false)
- .argument("port number"));
+ .argument("port_number"));
optionSet.addOption(Option("disable-ssl", "", "Disable SSL security layer.")
.required(false)
@@ -2080,6 +2088,13 @@ void LOOLWSD::defineOptions(OptionSet& optionSet)
.repeatable(false)
.argument("seconds"));
#endif
+
+#ifdef FUZZER
+ optionSet.addOption(Option("fuzz", "", "Read input from the specified file for fuzzing.")
+ .required(false)
+ .repeatable(false)
+ .argument("trace_file_name"));
+#endif
}
void LOOLWSD::handleOption(const std::string& optionName,
@@ -2125,6 +2140,11 @@ void LOOLWSD::handleOption(const std::string& optionName,
if (masterPort)
MasterPortNumber = std::stoi(masterPort);
#endif
+
+#ifdef FUZZER
+ if (optionName == "fuzz")
+ FuzzFileName = value;
+#endif
}
void LOOLWSD::displayHelp()
@@ -2282,6 +2302,10 @@ bool LOOLWSD::createForKit()
#endif
}
+#ifdef FUZZER
+std::mutex Connection::Mutex;
+#endif
+
int LOOLWSD::main(const std::vector<std::string>& /*args*/)
{
SigUtil::setFatalSignals();
@@ -2432,7 +2456,27 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
// Make sure we have sufficient reserves.
if (prespawnChildren())
{
- // Nothing more to do this round.
+ // Nothing more to do this round, unless we are fuzzing
+#if FUZZER
+ if (!FuzzFileName.empty())
+ {
+ std::unique_ptr<Replay> replay(new Replay(
+#if ENABLE_SSL
+ "https://127.0.0.1:" + std::to_string(ClientPortNumber),
+#else
+ "http://127.0.0.1:" + std::to_string(ClientPortNumber),
+#endif
+ FuzzFileName));
+
+ std::unique_ptr<Thread> replayThread(new Thread());
+ replayThread->start(*replay);
+
+ // block until the replay finishes
+ replayThread->join();
+
+ TerminationFlag = true;
+ }
+#endif
}
else if (!std::getenv("LOOL_NO_AUTOSAVE") &&
std::chrono::duration_cast<std::chrono::seconds>
diff --git a/wsd/LOOLWSD.hpp b/wsd/LOOLWSD.hpp
index ab0b479..7438c80 100644
--- a/wsd/LOOLWSD.hpp
+++ b/wsd/LOOLWSD.hpp
@@ -38,6 +38,7 @@ public:
static bool NoCapsForKit;
static std::atomic<int> ForKitWritePipe;
static std::atomic<int> ForKitProcId;
+ static std::string FuzzFileName;
static std::string Cache;
static std::string ConfigFile;
static std::string SysTemplate;
commit 964ae25ccf038e92454ead354ee8484d493b738a
Author: Jan Holesovsky <kendy at collabora.com>
Date: Tue Feb 7 17:16:59 2017 +0100
fuzzer: Factor out the replay functionality to a separate file.
Change-Id: Ief946b1703ef1ca0b17de3467dce66b4c3da2601
diff --git a/test/helpers.hpp b/test/helpers.hpp
index cc8205e..5b9b498 100644
--- a/test/helpers.hpp
+++ b/test/helpers.hpp
@@ -7,6 +7,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+#ifndef INCLUDED_TEST_HELPERS_HPP
+#define INCLUDED_TEST_HELPERS_HPP
+
#include "config.h"
#include <algorithm>
@@ -570,4 +573,6 @@ inline void sendText(std::shared_ptr<LOOLWebSocket>& socket, const std::string&
}
+#endif
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/Replay.hpp b/tools/Replay.hpp
new file mode 100644
index 0000000..d5ed00b
--- /dev/null
+++ b/tools/Replay.hpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_REPLAY_HPP
+#define INCLUDED_REPLAY_HPP
+
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPResponse.h>
+
+#include <LOOLWebSocket.hpp>
+
+#include "TraceFile.hpp"
+#include <test/helpers.hpp>
+
+/// Connection class with WSD.
+class Connection
+{
+public:
+ static
+ std::shared_ptr<Connection> create(const std::string& serverURI, const std::string& documentURL, const std::string& sessionId)
+ {
+ Poco::URI uri(serverURI);
+
+ std::unique_lock<std::mutex> lock(Mutex);
+
+ // Load a document and get its status.
+ std::cout << "NewSession [" << sessionId << "]: " << uri.toString() << "... ";
+
+ std::string encodedUri;
+ Poco::URI::encode(documentURL, ":/?", encodedUri);
+ Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, "/lool/" + encodedUri + "/ws");
+ Poco::Net::HTTPResponse response;
+ auto ws = helpers::connectLOKit(uri, request, response, sessionId + ' ');
+ std::cout << "Connected.\n";
+ return std::shared_ptr<Connection>(new Connection(documentURL, sessionId, ws));
+ }
+
+ const std::string& getName() const { return _name; }
+ std::shared_ptr<LOOLWebSocket> getWS() const { return _ws; };
+
+ /// Send a command to the server.
+ void send(const std::string& data) const
+ {
+ helpers::sendTextFrame(_ws, data, _name);
+ }
+
+ /// Poll socket until expected prefix is fetched, or timeout.
+ std::vector<char> recv(const std::string& prefix)
+ {
+ return helpers::getResponseMessage(_ws, prefix, _name);
+ }
+
+ /// Request loading the document and wait for completion.
+ bool load()
+ {
+ send("load url=" + _documentURL);
+ return helpers::isDocumentLoaded(_ws, _name);
+ }
+
+private:
+ Connection(const std::string& documentURL, const std::string& sessionId, std::shared_ptr<LOOLWebSocket>& ws) :
+ _documentURL(documentURL),
+ _sessionId(sessionId),
+ _name(sessionId + ' '),
+ _ws(ws)
+ {
+ }
+
+private:
+ const std::string _documentURL;
+ const std::string _sessionId;
+ const std::string _name;
+ std::shared_ptr<LOOLWebSocket> _ws;
+ static std::mutex Mutex;
+};
+
+/// Main thread class to replay a trace file.
+class Replay : public Poco::Runnable
+{
+public:
+
+ Replay(const std::string& serverUri, const std::string& uri, bool ignoreTiming = true) :
+ _serverUri(serverUri),
+ _uri(uri),
+ _ignoreTiming(ignoreTiming)
+ {
+ }
+
+ void run() override
+ {
+ try
+ {
+ replay();
+ }
+ catch (const Poco::Exception &e)
+ {
+ std::cout << "Error: " << e.name() << ' ' << e.message() << std::endl;
+ }
+ catch (const std::exception &e)
+ {
+ std::cout << "Error: " << e.what() << std::endl;
+ }
+ }
+
+protected:
+
+ void replay()
+ {
+ TraceFileReader traceFile(_uri);
+
+ auto epochFile(traceFile.getEpoch());
+ auto epochCurrent(std::chrono::steady_clock::now());
+
+ for (;;)
+ {
+ const auto rec = traceFile.getNextRecord();
+ if (rec.Dir == TraceFileRecord::Direction::Invalid)
+ {
+ // End of trace file.
+ break;
+ }
+
+ const auto deltaCurrent = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - epochCurrent).count();
+ const auto deltaFile = rec.TimestampNs - epochFile;
+ const auto delay = (_ignoreTiming ? 0 : deltaFile - deltaCurrent);
+ if (delay > 0)
+ {
+ if (delay > 1e6)
+ {
+ std::cout << "Sleeping for " << delay / 1000 << " ms.\n";
+ }
+
+ std::this_thread::sleep_for(std::chrono::microseconds(delay));
+ }
+
+ if (rec.Dir == TraceFileRecord::Direction::Event)
+ {
+ // Meta info about about an event.
+ static const std::string NewSession("NewSession: ");
+ static const std::string EndSession("EndSession: ");
+
+ if (rec.Payload.find(NewSession) == 0)
+ {
+ const auto uriOrig = rec.Payload.substr(NewSession.size());
+ std::string uri;
+ Poco::URI::decode(uriOrig, uri);
+ auto it = _sessions.find(uri);
+ if (it != _sessions.end())
+ {
+ // Add a new session.
+ if (it->second.find(rec.SessionId) != it->second.end())
+ {
+ std::cout << "ERROR: session [" << rec.SessionId << "] already exists on doc [" << uri << "]\n";
+ }
+ else
+ {
+ it->second.emplace(rec.SessionId, Connection::create(_serverUri, uri, rec.SessionId));
+ }
+ }
+ else
+ {
+ std::cout << "New Document: " << uri << "\n";
+ _childToDoc.emplace(rec.Pid, uri);
+ _sessions[uri].emplace(rec.SessionId, Connection::create(_serverUri, uri, rec.SessionId));
+ }
+ }
+ else if (rec.Payload.find(EndSession) == 0)
+ {
+ const auto uriOrig = rec.Payload.substr(EndSession.size());
+ std::string uri;
+ Poco::URI::decode(uriOrig, uri);
+ auto it = _sessions.find(uri);
+ if (it != _sessions.end())
+ {
+ std::cout << "EndSession [" << rec.SessionId << "]: " << uri << "\n";
+
+ it->second.erase(rec.SessionId);
+ if (it->second.empty())
+ {
+ std::cout << "End Doc [" << uri << "].\n";
+ _sessions.erase(it);
+ _childToDoc.erase(rec.Pid);
+ }
+ }
+ else
+ {
+ std::cout << "ERROR: Doc [" << uri << "] does not exist.\n";
+ }
+ }
+ }
+ else if (rec.Dir == TraceFileRecord::Direction::Incoming)
+ {
+ auto docIt = _childToDoc.find(rec.Pid);
+ if (docIt != _childToDoc.end())
+ {
+ const auto& uri = docIt->second;
+ auto it = _sessions.find(uri);
+ if (it != _sessions.end())
+ {
+ const auto sessionIt = it->second.find(rec.SessionId);
+ if (sessionIt != it->second.end())
+ {
+ // Send the command.
+ sessionIt->second->send(rec.Payload);
+ }
+ }
+ else
+ {
+ std::cout << "ERROR: Doc [" << uri << "] does not exist.\n";
+ }
+ }
+ else
+ {
+ std::cout << "ERROR: Unknown PID [" << rec.Pid << "] maps to no active document.\n";
+ }
+ }
+
+ epochCurrent = std::chrono::steady_clock::now();
+ epochFile = rec.TimestampNs;
+ }
+ }
+
+protected:
+ const std::string _serverUri;
+ const std::string _uri;
+
+ /// Should we ignore timing that is saved in the trace file?
+ bool _ignoreTiming;
+
+ /// LOK child process PID to Doc URI map.
+ std::map<unsigned, std::string> _childToDoc;
+
+ /// Doc URI to _sessions map. _sessions are maps of SessionID to Connection.
+ std::map<std::string, std::map<std::string, std::shared_ptr<Connection>>> _sessions;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/Stress.cpp b/tools/Stress.cpp
index a7e90d1..33a2243 100644
--- a/tools/Stress.cpp
+++ b/tools/Stress.cpp
@@ -27,6 +27,7 @@
#include <Poco/Util/Option.h>
#include <Poco/Util/OptionSet.h>
+#include "Replay.hpp"
#include "TraceFile.hpp"
#include "test/helpers.hpp"
@@ -78,68 +79,6 @@ long percentile(std::vector<long>& v, const double percentile)
return v[k - 1] + d * (v[k] - v[k - 1]);
}
-/// Connection class with WSD.
-class Connection
-{
-public:
- static
- std::shared_ptr<Connection> create(const std::string& serverURI, const std::string& documentURL, const std::string& sessionId)
- {
- Poco::URI uri(serverURI);
-
- std::unique_lock<std::mutex> lock(Mutex);
-
- // Load a document and get its status.
- std::cout << "NewSession [" << sessionId << "]: " << uri.toString() << "... ";
-
- std::string encodedUri;
- Poco::URI::encode(documentURL, ":/?", encodedUri);
- Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, "/lool/" + encodedUri + "/ws");
- Poco::Net::HTTPResponse response;
- auto ws = helpers::connectLOKit(uri, request, response, sessionId + ' ');
- std::cout << "Connected.\n";
- return std::shared_ptr<Connection>(new Connection(documentURL, sessionId, ws));
- }
-
- const std::string& getName() const { return _name; }
- std::shared_ptr<LOOLWebSocket> getWS() const { return _ws; };
-
- /// Send a command to the server.
- void send(const std::string& data) const
- {
- helpers::sendTextFrame(_ws, data, _name);
- }
-
- /// Poll socket until expected prefix is fetched, or timeout.
- std::vector<char> recv(const std::string& prefix)
- {
- return helpers::getResponseMessage(_ws, prefix, _name);
- }
-
- /// Request loading the document and wait for completion.
- bool load()
- {
- send("load url=" + _documentURL);
- return helpers::isDocumentLoaded(_ws, _name);
- }
-
-private:
- Connection(const std::string& documentURL, const std::string& sessionId, std::shared_ptr<LOOLWebSocket>& ws) :
- _documentURL(documentURL),
- _sessionId(sessionId),
- _name(sessionId + ' '),
- _ws(ws)
- {
- }
-
-private:
- const std::string _documentURL;
- const std::string _sessionId;
- const std::string _name;
- std::shared_ptr<LOOLWebSocket> _ws;
- static std::mutex Mutex;
-};
-
std::mutex Connection::Mutex;
//static constexpr auto FIRST_ROW_TILES = "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840";
@@ -147,13 +86,11 @@ static constexpr auto FIRST_PAGE_TILES = "tilecombine part=0 width=256 height=25
static constexpr auto FIRST_PAGE_TILE_COUNT = 16;
/// Main thread class to replay a trace file.
-class Worker: public Runnable
+class Worker: public Replay
{
public:
- Worker(Stress& app, const std::string& uri) :
- _app(app),
- _uri(uri)
+ Worker(const std::string& serverUri, const std::string& uri) : Replay(serverUri, uri, Stress::NoDelay)
{
}
@@ -256,7 +193,7 @@ private:
static std::atomic<unsigned> SessionId;
const auto sessionId = ++SessionId;
- auto connection = Connection::create(_app._serverURI, _uri, std::to_string(sessionId));
+ auto connection = Connection::create(_serverUri, _uri, std::to_string(sessionId));
connection->load();
@@ -268,132 +205,7 @@ private:
}
}
- void replay()
- {
- TraceFileReader traceFile(_uri);
-
- auto epochFile(traceFile.getEpoch());
- auto epochCurrent(std::chrono::steady_clock::now());
-
- for (;;)
- {
- const auto rec = traceFile.getNextRecord();
- if (rec.Dir == TraceFileRecord::Direction::Invalid)
- {
- // End of trace file.
- break;
- }
-
- const auto deltaCurrent = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - epochCurrent).count();
- const auto deltaFile = rec.TimestampNs - epochFile;
- const auto delay = (Stress::NoDelay ? 0 : deltaFile - deltaCurrent);
- if (delay > 0)
- {
- if (delay > 1e6)
- {
- std::cout << "Sleeping for " << delay / 1000 << " ms.\n";
- }
-
- std::this_thread::sleep_for(std::chrono::microseconds(delay));
- }
-
- if (rec.Dir == TraceFileRecord::Direction::Event)
- {
- // Meta info about about an event.
- static const std::string NewSession("NewSession: ");
- static const std::string EndSession("EndSession: ");
-
- if (rec.Payload.find(NewSession) == 0)
- {
- const auto uriOrig = rec.Payload.substr(NewSession.size());
- std::string uri;
- Poco::URI::decode(uriOrig, uri);
- auto it = _sessions.find(uri);
- if (it != _sessions.end())
- {
- // Add a new session.
- if (it->second.find(rec.SessionId) != it->second.end())
- {
- std::cout << "ERROR: session [" << rec.SessionId << "] already exists on doc [" << uri << "]\n";
- }
- else
- {
- it->second.emplace(rec.SessionId, Connection::create(_app._serverURI, uri, rec.SessionId));
- }
- }
- else
- {
- std::cout << "New Document: " << uri << "\n";
- _childToDoc.emplace(rec.Pid, uri);
- _sessions[uri].emplace(rec.SessionId, Connection::create(_app._serverURI, uri, rec.SessionId));
- }
- }
- else if (rec.Payload.find(EndSession) == 0)
- {
- const auto uriOrig = rec.Payload.substr(EndSession.size());
- std::string uri;
- Poco::URI::decode(uriOrig, uri);
- auto it = _sessions.find(uri);
- if (it != _sessions.end())
- {
- std::cout << "EndSession [" << rec.SessionId << "]: " << uri << "\n";
-
- it->second.erase(rec.SessionId);
- if (it->second.empty())
- {
- std::cout << "End Doc [" << uri << "].\n";
- _sessions.erase(it);
- _childToDoc.erase(rec.Pid);
- }
- }
- else
- {
- std::cout << "ERROR: Doc [" << uri << "] does not exist.\n";
- }
- }
- }
- else if (rec.Dir == TraceFileRecord::Direction::Incoming)
- {
- auto docIt = _childToDoc.find(rec.Pid);
- if (docIt != _childToDoc.end())
- {
- const auto& uri = docIt->second;
- auto it = _sessions.find(uri);
- if (it != _sessions.end())
- {
- const auto sessionIt = it->second.find(rec.SessionId);
- if (sessionIt != it->second.end())
- {
- // Send the command.
- sessionIt->second->send(rec.Payload);
- }
- }
- else
- {
- std::cout << "ERROR: Doc [" << uri << "] does not exist.\n";
- }
- }
- else
- {
- std::cout << "ERROR: Unknown PID [" << rec.Pid << "] maps to no active document.\n";
- }
- }
-
- epochCurrent = std::chrono::steady_clock::now();
- epochFile = rec.TimestampNs;
- }
- }
-
private:
- Stress& _app;
- const std::string _uri;
-
- /// LOK child process PID to Doc URI map.
- std::map<unsigned, std::string> _childToDoc;
-
- /// Doc URI to _sessions map. _sessions are maps of SessionID to Connection.
- std::map<std::string, std::map<std::string, std::shared_ptr<Connection>>> _sessions;
-
std::vector<long> _latencyStats;
std::vector<long> _renderingStats;
std::vector<long> _cacheStats;
@@ -486,7 +298,7 @@ int Stress::main(const std::vector<std::string>& args)
std::cout << "Arg: " << args[i] << std::endl;
for (unsigned j = 0; j < _numClients; ++j, ++index)
{
- workers.emplace_back(new Worker(*this, args[i]));
+ workers.emplace_back(new Worker(_serverURI, args[i]));
clients[index].reset(new Thread());
clients[index]->start(*workers[workers.size() - 1]);
}
diff --git a/wsd/TraceFile.hpp b/wsd/TraceFile.hpp
index 8f308f1..544adac 100644
--- a/wsd/TraceFile.hpp
+++ b/wsd/TraceFile.hpp
@@ -7,6 +7,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+#ifndef INCLUDED_TRACEFILE_HPP
+#define INCLUDED_TRACEFILE_HPP
+
#include <fstream>
#include <mutex>
#include <sstream>
@@ -17,6 +20,7 @@
#include <Poco/DateTimeFormatter.h>
#include <Poco/DeflatingStream.h>
#include <Poco/InflatingStream.h>
+#include <Poco/URI.h>
#include "Protocol.hpp"
#include "Log.hpp"
@@ -463,4 +467,6 @@ private:
unsigned _indexOut;
};
+#endif
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
More information about the Libreoffice-commits
mailing list