annotate vamp-client/CapnpRRClient.h @ 288:c9f63ca1914d

Fix overlooked constructor call
author Chris Cannam <cannam@all-day-breakfast.com>
date Tue, 07 Apr 2020 18:04:53 +0100
parents 3db4c7998faf
children 26027c3a99a0
rev   line source
cannam@111 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@118 2 /*
c@118 3 Piper C++
c@118 4
c@118 5 An API for audio analysis and feature extraction plugins.
c@118 6
c@118 7 Centre for Digital Music, Queen Mary, University of London.
c@118 8 Copyright 2006-2016 Chris Cannam and QMUL.
c@118 9
c@118 10 Permission is hereby granted, free of charge, to any person
c@118 11 obtaining a copy of this software and associated documentation
c@118 12 files (the "Software"), to deal in the Software without
c@118 13 restriction, including without limitation the rights to use, copy,
c@118 14 modify, merge, publish, distribute, sublicense, and/or sell copies
c@118 15 of the Software, and to permit persons to whom the Software is
c@118 16 furnished to do so, subject to the following conditions:
c@118 17
c@118 18 The above copyright notice and this permission notice shall be
c@118 19 included in all copies or substantial portions of the Software.
c@118 20
c@118 21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
c@118 22 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
c@118 23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
c@118 24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
c@118 25 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
c@118 26 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
c@118 27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
c@118 28
c@118 29 Except as contained in this notice, the names of the Centre for
c@118 30 Digital Music; Queen Mary, University of London; and Chris Cannam
c@118 31 shall not be used in advertising or otherwise to promote the sale,
c@118 32 use or other dealings in this Software without prior written
c@118 33 authorization.
c@118 34 */
c@96 35
c@96 36 #ifndef PIPER_CAPNP_CLIENT_H
c@96 37 #define PIPER_CAPNP_CLIENT_H
c@96 38
c@96 39 #include "Loader.h"
c@96 40 #include "PluginClient.h"
cannam@208 41 #include "PiperVampPlugin.h"
c@96 42 #include "SynchronousTransport.h"
c@96 43
c@96 44 #include "vamp-support/AssignedPluginHandleMapper.h"
c@96 45 #include "vamp-capnp/VampnProto.h"
c@96 46
c@134 47 #include <sstream>
c@134 48
c@96 49 #include <capnp/serialize.h>
c@96 50
c@144 51 //#define LOG_ENTRYPOINTS 1
c@142 52
c@142 53 #ifdef LOG_ENTRYPOINTS
c@142 54 #define LOG_E(x) log(x)
c@142 55 #else
c@142 56 #define LOG_E(x)
c@142 57 #endif
c@142 58
c@97 59 namespace piper_vamp {
c@97 60 namespace client {
c@96 61
c@100 62 /**
c@100 63 * Client for a request-response Piper server, i.e. using the
c@100 64 * RpcRequest/RpcResponse structures with a single process call rather
c@100 65 * than having individual RPC methods, with a synchronous transport
c@100 66 * such as a subprocess pipe arrangement. Only one request can be
c@100 67 * handled at a time. This class is thread-safe if and only if it is
c@100 68 * constructed with a thread-safe SynchronousTransport implementation.
cannam@187 69 *
cannam@187 70 * This class takes Vamp-like structures (Plugin and the classes in
cannam@187 71 * vamp-support) and uses them to communicate with a Piper server
cannam@187 72 * using the Cap'n Proto serialisation of the Piper API. The transport
cannam@187 73 * layer (and thus the nature of the server) is defined by the
cannam@187 74 * SynchronousTransport passed to the constructor.
cannam@187 75 *
cannam@187 76 * This class implements both the Loader interface (which constructs
cannam@187 77 * PluginStub objects) and the PluginClient (which accepts PluginStubs
cannam@187 78 * and maps them into Piper handles).
c@100 79 */
c@96 80 class CapnpRRClient : public PluginClient,
c@118 81 public Loader
c@96 82 {
c@96 83 // unsigned to avoid undefined behaviour on possible wrap
c@96 84 typedef uint32_t ReqId;
c@96 85
c@96 86 class CompletenessChecker : public MessageCompletenessChecker {
c@96 87 public:
c@146 88 State check(const std::vector<char> &message) const override {
c@146 89
c@147 90 if (message.size() < sizeof(capnp::word)) {
c@147 91 return Incomplete;
c@147 92 }
c@147 93
c@96 94 auto karr = toKJArray(message);
c@96 95 size_t words = karr.size();
c@96 96 size_t expected = capnp::expectedSizeInWordsFromPrefix(karr);
c@146 97
c@146 98 // Lacking a way to definitively check whether a message
c@146 99 // is valid or not, we would still like to trap obvious
c@146 100 // cases where a programming mistake results in garbage
c@146 101 // being returned from the server. We impose a limit on
c@146 102 // message size and, if a prefix is projected to exceed
c@146 103 // that limit, call it invalid. If an extractor wants to
c@146 104 // return a feature set greater than a gigaword in size,
c@146 105 // it'll just have to do it across multiple process calls.
c@146 106 size_t limit = size_t(1) << 30;
c@146 107
c@145 108 // cerr << "CompletenessChecker: message.size() = " << message.size()
c@146 109 // << ", words = " << words << ", limit = " << limit << ", expected = " << expected << endl;
c@146 110
c@96 111 if (words > expected) {
c@96 112 std::cerr << "WARNING: obtained more data than expected ("
c@96 113 << words << " " << sizeof(capnp::word)
c@96 114 << "-byte words, expected "
c@96 115 << expected << ")" << std::endl;
c@146 116 return Complete;
c@146 117 } else if (words == expected) {
c@146 118 return Complete;
c@146 119 } else if (expected > limit) {
c@147 120 std::cerr << "WARNING: apparently invalid message prefix: have "
c@147 121 << words << " words in prefix, projected message size is "
c@147 122 << expected << " against limit of " << limit << std::endl;
c@146 123 return Invalid;
c@146 124 } else {
c@146 125 return Incomplete;
c@96 126 }
c@96 127 }
c@96 128 };
c@96 129
c@96 130 public:
c@134 131 CapnpRRClient(SynchronousTransport *transport, //!!! ownership? shared ptr?
c@134 132 LogCallback *logger) : // logger may be nullptr for cerr
c@134 133 m_logger(logger),
c@96 134 m_transport(transport),
c@96 135 m_completenessChecker(new CompletenessChecker) {
c@96 136 transport->setCompletenessChecker(m_completenessChecker);
c@96 137 }
c@96 138
c@96 139 ~CapnpRRClient() {
c@96 140 delete m_completenessChecker;
c@96 141 }
c@96 142
c@96 143 //!!! obviously, factor out all repetitive guff
c@96 144
c@96 145 //!!! list and load are supposed to be called by application code,
c@96 146 //!!! but the rest are only supposed to be called by the plugin --
c@96 147 //!!! sort out the api here
c@96 148
c@96 149 // Loader methods:
c@96 150
c@97 151 ListResponse
cannam@207 152 list(const ListRequest &req) override {
c@96 153
c@142 154 LOG_E("CapnpRRClient::listPluginData called");
c@142 155
cannam@170 156 checkServerOK();
cannam@170 157
c@96 158 capnp::MallocMessageBuilder message;
c@118 159 piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@131 160 VampnProto::buildRpcRequest_List(builder, req);
c@96 161 ReqId id = getId();
c@96 162 builder.getId().setNumber(id);
c@96 163
c@134 164 auto karr = call(message, "list", true);
c@96 165
c@96 166 capnp::FlatArrayMessageReader responseMessage(karr);
c@97 167 piper::RpcResponse::Reader reader = responseMessage.getRoot<piper::RpcResponse>();
c@96 168
c@97 169 checkResponseType(reader, piper::RpcResponse::Response::Which::LIST, id);
c@96 170
c@97 171 ListResponse lr;
c@96 172 VampnProto::readListResponse(lr, reader.getResponse().getList());
c@142 173
c@142 174 LOG_E("CapnpRRClient::listPluginData returning");
c@142 175
c@96 176 return lr;
c@96 177 }
c@96 178
c@97 179 LoadResponse
cannam@207 180 load(const LoadRequest &req) override {
c@96 181
c@142 182 LOG_E("CapnpRRClient::loadPlugin called");
c@142 183
cannam@170 184 checkServerOK();
cannam@170 185
c@97 186 LoadResponse resp;
c@96 187 PluginHandleMapper::Handle handle = serverLoad(req.pluginKey,
c@96 188 req.inputSampleRate,
c@96 189 req.adapterFlags,
c@96 190 resp.staticData,
c@96 191 resp.defaultConfiguration);
c@96 192
cannam@208 193 Vamp::Plugin *plugin = new PiperVampPlugin(this,
cannam@208 194 req.pluginKey,
cannam@208 195 req.inputSampleRate,
cannam@208 196 req.adapterFlags,
cannam@208 197 resp.staticData,
cannam@288 198 resp.defaultConfiguration,
cannam@288 199 resp.programParameters);
c@96 200
c@96 201 m_mapper.addPlugin(handle, plugin);
c@96 202
c@96 203 resp.plugin = plugin;
c@142 204
c@142 205 LOG_E("CapnpRRClient::loadPlugin returning");
c@142 206
c@96 207 return resp;
c@96 208 }
c@96 209
c@96 210 // PluginClient methods:
c@96 211
c@96 212 virtual
cannam@185 213 ConfigurationResponse
cannam@208 214 configure(PiperVampPlugin *plugin,
c@97 215 PluginConfiguration config) override {
c@96 216
c@142 217 LOG_E("CapnpRRClient::configure called");
cannam@170 218
cannam@170 219 checkServerOK();
c@142 220
c@97 221 ConfigurationRequest request;
c@96 222 request.plugin = plugin;
c@96 223 request.configuration = config;
c@96 224
c@96 225 capnp::MallocMessageBuilder message;
c@97 226 piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@96 227
c@96 228 VampnProto::buildRpcRequest_Configure(builder, request, m_mapper);
c@96 229 ReqId id = getId();
c@96 230 builder.getId().setNumber(id);
c@96 231
c@134 232 auto karr = call(message, "configure", true);
c@96 233
c@96 234 capnp::FlatArrayMessageReader responseMessage(karr);
c@97 235 piper::RpcResponse::Reader reader = responseMessage.getRoot<piper::RpcResponse>();
c@96 236
c@97 237 checkResponseType(reader, piper::RpcResponse::Response::Which::CONFIGURE, id);
c@96 238
c@97 239 ConfigurationResponse cr;
c@96 240 VampnProto::readConfigurationResponse(cr,
c@96 241 reader.getResponse().getConfigure(),
c@96 242 m_mapper);
c@96 243
c@142 244 LOG_E("CapnpRRClient::configure returning");
c@142 245
cannam@185 246 return cr;
c@96 247 };
c@96 248
c@96 249 virtual
c@96 250 Vamp::Plugin::FeatureSet
cannam@208 251 process(PiperVampPlugin *plugin,
c@96 252 std::vector<std::vector<float> > inputBuffers,
c@96 253 Vamp::RealTime timestamp) override {
c@96 254
c@142 255 LOG_E("CapnpRRClient::process called");
c@142 256
cannam@170 257 checkServerOK();
cannam@170 258
c@97 259 ProcessRequest request;
c@96 260 request.plugin = plugin;
c@96 261 request.inputBuffers = inputBuffers;
c@96 262 request.timestamp = timestamp;
c@96 263
c@96 264 capnp::MallocMessageBuilder message;
c@97 265 piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@96 266 VampnProto::buildRpcRequest_Process(builder, request, m_mapper);
c@118 267 ReqId id = getId();
c@96 268 builder.getId().setNumber(id);
c@96 269
c@134 270 auto karr = call(message, "process", false);
c@96 271
c@96 272 capnp::FlatArrayMessageReader responseMessage(karr);
c@97 273 piper::RpcResponse::Reader reader = responseMessage.getRoot<piper::RpcResponse>();
c@96 274
c@97 275 checkResponseType(reader, piper::RpcResponse::Response::Which::PROCESS, id);
c@96 276
c@97 277 ProcessResponse pr;
c@96 278 VampnProto::readProcessResponse(pr,
c@96 279 reader.getResponse().getProcess(),
c@96 280 m_mapper);
c@96 281
c@142 282 LOG_E("CapnpRRClient::process returning");
c@142 283
c@96 284 return pr.features;
c@96 285 }
c@96 286
c@96 287 virtual Vamp::Plugin::FeatureSet
cannam@208 288 finish(PiperVampPlugin *plugin) override {
c@96 289
c@142 290 LOG_E("CapnpRRClient::finish called");
c@142 291
cannam@170 292 checkServerOK();
cannam@170 293
c@97 294 FinishRequest request;
c@96 295 request.plugin = plugin;
c@96 296
c@96 297 capnp::MallocMessageBuilder message;
c@97 298 piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@96 299
c@96 300 VampnProto::buildRpcRequest_Finish(builder, request, m_mapper);
c@96 301 ReqId id = getId();
c@96 302 builder.getId().setNumber(id);
c@96 303
c@134 304 auto karr = call(message, "finish", true);
c@96 305
c@96 306 capnp::FlatArrayMessageReader responseMessage(karr);
c@97 307 piper::RpcResponse::Reader reader = responseMessage.getRoot<piper::RpcResponse>();
c@96 308
c@97 309 checkResponseType(reader, piper::RpcResponse::Response::Which::FINISH, id);
c@96 310
c@97 311 FinishResponse pr;
c@96 312 VampnProto::readFinishResponse(pr,
c@96 313 reader.getResponse().getFinish(),
c@96 314 m_mapper);
c@96 315
c@96 316 m_mapper.removePlugin(m_mapper.pluginToHandle(plugin));
c@96 317
c@118 318 // Don't delete the plugin. It's the plugin that is supposed
c@118 319 // to be calling us here
c@96 320
c@142 321 LOG_E("CapnpRRClient::finish returning");
c@142 322
c@96 323 return pr.features;
c@96 324 }
c@96 325
c@96 326 virtual void
cannam@208 327 reset(PiperVampPlugin *plugin,
c@97 328 PluginConfiguration config) override {
c@96 329
c@96 330 // Reload the plugin on the server side, and configure it as requested
c@134 331
c@134 332 log("CapnpRRClient: reset() called, plugin will be closed and reloaded");
c@96 333
cannam@170 334 checkServerOK();
cannam@170 335
c@96 336 if (m_mapper.havePlugin(plugin)) {
c@96 337 (void)finish(plugin); // server-side unload
c@96 338 }
c@96 339
c@97 340 PluginStaticData psd;
c@97 341 PluginConfiguration defaultConfig;
c@96 342 PluginHandleMapper::Handle handle =
c@96 343 serverLoad(plugin->getPluginKey(),
c@96 344 plugin->getInputSampleRate(),
c@96 345 plugin->getAdapterFlags(),
c@96 346 psd, defaultConfig);
c@96 347
c@96 348 m_mapper.addPlugin(handle, plugin);
c@96 349
c@96 350 (void)configure(plugin, config);
c@96 351 }
c@96 352
c@96 353 private:
c@96 354 AssignedPluginHandleMapper m_mapper;
c@96 355 ReqId getId() {
c@96 356 //!!! todo: mutex
c@96 357 static ReqId m_nextId = 0;
c@96 358 return m_nextId++;
c@96 359 }
c@96 360
c@96 361 static
c@96 362 kj::Array<capnp::word>
c@96 363 toKJArray(const std::vector<char> &buffer) {
c@118 364 // We could do this whole thing with fewer copies, but let's
c@118 365 // see whether it matters first
c@96 366 size_t wordSize = sizeof(capnp::word);
c@118 367 size_t words = buffer.size() / wordSize;
c@118 368 kj::Array<capnp::word> karr(kj::heapArray<capnp::word>(words));
cannam@260 369 memcpy(reinterpret_cast<char *>(karr.begin()),
cannam@260 370 buffer.data(),
cannam@260 371 words * wordSize);
c@118 372 return karr;
c@96 373 }
c@96 374
c@96 375 void
cannam@170 376 checkServerOK() {
cannam@170 377 if (!m_transport->isOK()) {
cannam@170 378 log("Piper server crashed or failed to start (caller should have checked this)");
cannam@170 379 throw ServerCrashed();
cannam@170 380 }
cannam@170 381 }
cannam@170 382
cannam@170 383 /**
cannam@170 384 * Check (i) that the response has the same id as supplied (which
cannam@170 385 * presumably is the corresponding request id) and (ii) that the
cannam@170 386 * response has the expected type.
cannam@170 387 *
cannam@170 388 * Return only if both of these things are the case.
cannam@170 389 *
cannam@170 390 * If the response has the right id but is an error response,
cannam@170 391 * throw a ServiceError exception with the error response's
cannam@170 392 * message in it.
cannam@170 393 *
cannam@170 394 * If the response has the wrong id, or if it has the wrong type
cannam@170 395 * and is not an error response, throw ProtocolError. (i.e. for
cannam@170 396 * cases having errors that are not conveyed through our official
cannam@170 397 * error response.)
cannam@170 398 */
cannam@170 399 void
c@97 400 checkResponseType(const piper::RpcResponse::Reader &r,
c@97 401 piper::RpcResponse::Response::Which type,
c@96 402 ReqId id) {
c@96 403
c@96 404 if (ReqId(r.getId().getNumber()) != id) {
c@134 405 std::ostringstream s;
c@134 406 s << "checkResponseType: wrong response id (received "
c@134 407 << r.getId().getNumber() << ", expected " << id << ")";
c@134 408 log(s.str());
cannam@170 409 throw ProtocolError("Wrong response id");
cannam@170 410 }
cannam@170 411 if (r.getResponse().which() != type) {
cannam@170 412 if (r.getResponse().which() == piper::RpcResponse::Response::Which::ERROR) {
cannam@170 413 int code;
cannam@170 414 std::string message;
cannam@170 415 VampnProto::readRpcResponse_Error(code, message, r);
cannam@170 416 std::ostringstream s;
cannam@170 417 s << "checkResponseType: received an error with message: "
cannam@170 418 << message;
cannam@170 419 log(s.str());
cannam@170 420 throw ServiceError(message);
cannam@170 421 } else {
cannam@170 422 std::ostringstream s;
cannam@170 423 s << "checkResponseType: wrong response type (received "
cannam@170 424 << int(r.getResponse().which()) << ", expected " << int(type) << ")";
cannam@170 425 log(s.str());
cannam@170 426 throw ProtocolError("Wrong response type");
cannam@170 427 }
c@96 428 }
c@96 429 }
c@96 430
c@96 431 kj::Array<capnp::word>
c@134 432 call(capnp::MallocMessageBuilder &message, std::string type, bool slow) {
c@96 433 auto arr = capnp::messageToFlatArray(message);
c@96 434 auto responseBuffer = m_transport->call(arr.asChars().begin(),
c@126 435 arr.asChars().size(),
c@134 436 type,
c@126 437 slow);
c@118 438 return toKJArray(responseBuffer);
c@96 439 }
c@96 440
c@96 441 PluginHandleMapper::Handle
c@96 442 serverLoad(std::string key, float inputSampleRate, int adapterFlags,
c@97 443 PluginStaticData &psd,
c@97 444 PluginConfiguration &defaultConfig) {
c@96 445
c@97 446 LoadRequest request;
c@96 447 request.pluginKey = key;
c@96 448 request.inputSampleRate = inputSampleRate;
c@96 449 request.adapterFlags = adapterFlags;
c@96 450
c@96 451 capnp::MallocMessageBuilder message;
c@97 452 piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@96 453
c@96 454 VampnProto::buildRpcRequest_Load(builder, request);
c@96 455 ReqId id = getId();
c@96 456 builder.getId().setNumber(id);
c@96 457
c@134 458 auto karr = call(message, "load", false);
c@96 459
c@96 460 capnp::FlatArrayMessageReader responseMessage(karr);
c@97 461 piper::RpcResponse::Reader reader = responseMessage.getRoot<piper::RpcResponse>();
c@96 462
c@97 463 checkResponseType(reader, piper::RpcResponse::Response::Which::LOAD, id);
c@96 464
c@97 465 const piper::LoadResponse::Reader &lr = reader.getResponse().getLoad();
c@96 466 VampnProto::readExtractorStaticData(psd, lr.getStaticData());
c@96 467 VampnProto::readConfiguration(defaultConfig, lr.getDefaultConfiguration());
c@96 468 return lr.getHandle();
c@96 469 };
c@96 470
c@96 471 private:
c@134 472 LogCallback *m_logger;
c@96 473 SynchronousTransport *m_transport; //!!! I don't own this, but should I?
c@96 474 CompletenessChecker *m_completenessChecker; // I own this
c@134 475
c@134 476 void log(std::string message) const {
c@134 477 if (m_logger) m_logger->log(message);
c@134 478 else std::cerr << message << std::endl;
c@134 479 }
c@96 480 };
c@96 481
c@96 482 }
c@96 483 }
c@96 484
c@96 485 #endif