annotate vamp-client/qt/ProcessQtTransport.h @ 218:ea8994465322

Rebuild these for capnp v0.6. But it would probably be better at this point not to commit them, as the main reason they are in the repo is because the compiler wasn't available for Visual Studio builds, and that's no longer the case.
author Chris Cannam <cannam@all-day-breakfast.com>
date Tue, 09 May 2017 11:46:23 +0100
parents 590b1a1fd955
children ce42f0bebae3
rev   line source
cannam@150 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@150 2 /*
cannam@150 3 Piper C++
cannam@150 4
cannam@150 5 An API for audio analysis and feature extraction plugins.
cannam@150 6
cannam@150 7 Centre for Digital Music, Queen Mary, University of London.
cannam@150 8 Copyright 2006-2016 Chris Cannam and QMUL.
cannam@150 9
cannam@150 10 Permission is hereby granted, free of charge, to any person
cannam@150 11 obtaining a copy of this software and associated documentation
cannam@150 12 files (the "Software"), to deal in the Software without
cannam@150 13 restriction, including without limitation the rights to use, copy,
cannam@150 14 modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@150 15 of the Software, and to permit persons to whom the Software is
cannam@150 16 furnished to do so, subject to the following conditions:
cannam@150 17
cannam@150 18 The above copyright notice and this permission notice shall be
cannam@150 19 included in all copies or substantial portions of the Software.
cannam@150 20
cannam@150 21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@150 22 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@150 23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@150 24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@150 25 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@150 26 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@150 27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@150 28
cannam@150 29 Except as contained in this notice, the names of the Centre for
cannam@150 30 Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@150 31 shall not be used in advertising or otherwise to promote the sale,
cannam@150 32 use or other dealings in this Software without prior written
cannam@150 33 authorization.
cannam@150 34 */
cannam@150 35
cannam@150 36 #ifndef PIPER_PROCESS_QT_TRANSPORT_H
cannam@150 37 #define PIPER_PROCESS_QT_TRANSPORT_H
cannam@150 38
cannam@152 39 #include "../SynchronousTransport.h"
cannam@170 40 #include "../Exceptions.h"
cannam@150 41
cannam@150 42 #include <QProcess>
cannam@150 43 #include <QString>
cannam@150 44 #include <QMutex>
cannam@150 45 #include <QTime>
cannam@150 46
cannam@150 47 #include <iostream>
cannam@150 48
cannam@150 49 //#define DEBUG_TRANSPORT 1
cannam@150 50
cannam@150 51 namespace piper_vamp {
cannam@150 52 namespace client {
cannam@150 53
cannam@150 54 /**
cannam@150 55 * A SynchronousTransport implementation that spawns a sub-process
cannam@150 56 * using Qt's QProcess abstraction and talks to it via stdin/stdout
cannam@150 57 * channels. Calls are completely serialized; the protocol only
cannam@150 58 * supports one call in process at a time, and therefore the transport
cannam@150 59 * only allows one at a time.
cannam@150 60 *
cannam@150 61 * This class is thread-safe, but in practice you can only use it from
cannam@150 62 * within a single thread, because the underlying QProcess does not
cannam@150 63 * support switching threads.
cannam@150 64 */
cannam@150 65 class ProcessQtTransport : public SynchronousTransport
cannam@150 66 {
cannam@150 67 public:
cannam@150 68 ProcessQtTransport(std::string processName,
cannam@150 69 std::string formatArg,
cannam@150 70 LogCallback *logger) : // logger may be nullptr for cerr
cannam@150 71 m_logger(logger),
cannam@150 72 m_completenessChecker(0),
cannam@150 73 m_crashed(false) {
cannam@150 74
cannam@150 75 m_process = new QProcess();
cannam@150 76 m_process->setReadChannel(QProcess::StandardOutput);
cannam@150 77 m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
cannam@150 78
cannam@150 79 m_process->start(QString::fromStdString(processName),
cannam@150 80 { QString::fromStdString(formatArg) });
cannam@150 81
cannam@150 82 if (!m_process->waitForStarted()) {
cannam@150 83 if (m_process->state() == QProcess::NotRunning) {
cannam@150 84 QProcess::ProcessError err = m_process->error();
cannam@150 85 if (err == QProcess::FailedToStart) {
cannam@150 86 log("Unable to start server process " + processName);
cannam@150 87 } else if (err == QProcess::Crashed) {
cannam@150 88 log("Server process " + processName + " crashed on startup");
cannam@150 89 } else {
cannam@150 90 QString e = QString("%1").arg(err);
cannam@150 91 log("Server process " + processName +
cannam@150 92 " failed on startup with error code " + e.toStdString());
cannam@150 93 }
cannam@150 94 delete m_process;
cannam@150 95 m_process = nullptr;
cannam@150 96 }
cannam@150 97 }
cannam@150 98
cannam@150 99 if (m_process) {
cannam@150 100 log("Server process " + processName + " started OK");
cannam@150 101 }
cannam@150 102 }
cannam@150 103
cannam@150 104 ~ProcessQtTransport() {
cannam@150 105 if (m_process) {
cannam@150 106 if (m_process->state() != QProcess::NotRunning) {
cannam@150 107 m_process->closeWriteChannel();
cannam@150 108 m_process->waitForFinished(200);
cannam@150 109 m_process->close();
cannam@150 110 m_process->waitForFinished();
cannam@150 111 log("Server process exited normally");
cannam@150 112 }
cannam@150 113 delete m_process;
cannam@150 114 }
cannam@150 115 }
cannam@150 116
cannam@150 117 void
cannam@150 118 setCompletenessChecker(MessageCompletenessChecker *checker) override {
cannam@150 119 m_completenessChecker = checker;
cannam@150 120 }
cannam@150 121
cannam@150 122 bool
cannam@150 123 isOK() const override {
cannam@150 124 return (m_process != nullptr) && !m_crashed;
cannam@150 125 }
cannam@150 126
cannam@150 127 std::vector<char>
cannam@150 128 call(const char *ptr, size_t size, std::string type, bool slow) override {
cannam@150 129
cannam@150 130 QMutexLocker locker(&m_mutex);
cannam@150 131
cannam@150 132 if (!m_completenessChecker) {
cannam@150 133 log("call: No completeness checker set on transport");
cannam@150 134 throw std::logic_error("No completeness checker set on transport");
cannam@150 135 }
cannam@150 136 if (!isOK()) {
cannam@150 137 log("call: Transport is not OK");
cannam@150 138 throw std::logic_error("Transport is not OK");
cannam@150 139 }
cannam@150 140
cannam@150 141 #ifdef DEBUG_TRANSPORT
cannam@150 142 std::cerr << "writing " << size << " bytes to server" << std::endl;
cannam@150 143 #endif
cannam@150 144 m_process->write(ptr, size);
cannam@150 145 m_process->waitForBytesWritten();
cannam@150 146
cannam@150 147 std::vector<char> buffer;
cannam@150 148 bool complete = false;
cannam@150 149
cannam@150 150 QTime t;
cannam@150 151 t.start();
cannam@150 152
cannam@150 153 // We don't like to timeout at all while waiting for a
cannam@150 154 // response -- we'd like to wait as long as the server
cannam@150 155 // continues running.
cannam@150 156 //
cannam@150 157 int beforeResponseTimeout = 0; // ms, 0 = no timeout
cannam@150 158
cannam@150 159 // But if the call is marked as fast (i.e. just retrieving
cannam@150 160 // info rather than calculating something) we will time out
cannam@150 161 // after a bit.
cannam@150 162 //
cannam@150 163 if (!slow) beforeResponseTimeout = 10000; // ms, 0 = no timeout
cannam@150 164
cannam@150 165 // But we do timeout if the server sends part of a reply and
cannam@150 166 // then gets stuck. It's reasonable to assume that a server
cannam@150 167 // that's already prepared its message and started sending has
cannam@150 168 // finished doing any real work. In each case the timeout is
cannam@150 169 // measured since data was last read.
cannam@150 170 //
cannam@150 171 int duringResponseTimeout = 5000; // ms, 0 = no timeout
cannam@150 172
cannam@150 173 while (!complete) {
cannam@150 174
cannam@150 175 bool responseStarted = !buffer.empty(); // already have something
cannam@150 176 int ms = t.elapsed(); // time since start or since last read
cannam@150 177
cannam@150 178 qint64 byteCount = m_process->bytesAvailable();
cannam@150 179
cannam@150 180 if (!byteCount) {
cannam@150 181
cannam@150 182 if (responseStarted) {
cannam@150 183 if (duringResponseTimeout > 0 && ms > duringResponseTimeout) {
cannam@150 184 log("Server timed out during response");
cannam@170 185 m_crashed = true;
cannam@170 186 throw RequestTimedOut();
cannam@150 187 }
cannam@150 188 } else {
cannam@150 189 if (beforeResponseTimeout > 0 && ms > beforeResponseTimeout) {
cannam@150 190 log("Server timed out before response");
cannam@170 191 m_crashed = true;
cannam@170 192 throw RequestTimedOut();
cannam@150 193 }
cannam@150 194 }
cannam@150 195
cannam@150 196 #ifdef DEBUG_TRANSPORT
cannam@150 197 std::cerr << "waiting for data from server (slow = " << slow << ")..." << std::endl;
cannam@150 198 #endif
cannam@150 199 if (slow) {
cannam@150 200 m_process->waitForReadyRead(1000);
cannam@150 201 } else {
cannam@150 202 #ifdef _WIN32
cannam@150 203 // This is most unsatisfactory -- if we give a non-zero
cannam@150 204 // arg here, then we end up sleeping way beyond the arrival
cannam@150 205 // of the data to read -- can end up using less than 10%
cannam@150 206 // CPU during processing which is crazy. So for Windows
cannam@150 207 // only, we busy-wait during "fast" calls. It works out
cannam@150 208 // much faster in the end. Could do with a simpler native
cannam@150 209 // blocking API really.
cannam@150 210 m_process->waitForReadyRead(0);
cannam@150 211 #else
cannam@150 212 m_process->waitForReadyRead(100);
cannam@150 213 #endif
cannam@150 214 }
cannam@150 215 if (m_process->state() == QProcess::NotRunning &&
cannam@150 216 // don't give up until we've read all that's been buffered!
cannam@150 217 !m_process->bytesAvailable()) {
cannam@150 218 QProcess::ProcessError err = m_process->error();
cannam@150 219 if (err == QProcess::Crashed) {
cannam@150 220 log("Server crashed during " + type + " request");
cannam@150 221 } else {
cannam@150 222 QString e = QString("%1").arg(err);
cannam@150 223 log("Server failed during " + type
cannam@150 224 + " request with error code " + e.toStdString());
cannam@150 225 }
cannam@150 226 m_crashed = true;
cannam@150 227 throw ServerCrashed();
cannam@150 228 }
cannam@150 229 } else {
cannam@150 230 size_t formerSize = buffer.size();
cannam@150 231 buffer.resize(formerSize + byteCount);
cannam@150 232 m_process->read(buffer.data() + formerSize, byteCount);
cannam@150 233 switch (m_completenessChecker->check(buffer)) {
cannam@150 234 case MessageCompletenessChecker::Complete: complete = true; break;
cannam@150 235 case MessageCompletenessChecker::Incomplete: break;
cannam@170 236 case MessageCompletenessChecker::Invalid: throw ProtocolError();
cannam@150 237 }
cannam@150 238 (void)t.restart(); // reset timeout when we read anything
cannam@150 239 }
cannam@150 240 }
cannam@150 241
cannam@150 242 return buffer;
cannam@150 243 }
cannam@150 244
cannam@150 245 private:
cannam@150 246 LogCallback *m_logger;
cannam@150 247 MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently)
cannam@150 248 QProcess *m_process; // I own this
cannam@150 249 QMutex m_mutex;
cannam@150 250 bool m_crashed;
cannam@150 251
cannam@150 252 void log(std::string message) const {
cannam@150 253 if (m_logger) m_logger->log(message);
cannam@150 254 else std::cerr << message << std::endl;
cannam@150 255 }
cannam@150 256 };
cannam@150 257
cannam@150 258 }
cannam@150 259 }
cannam@150 260
cannam@150 261 #endif